Search code examples
javascriptblockly

JS wait/pause in generated functions


What I am doing

I am in the middle of building a turtle graphics app using Blockly. The user can build a code from blocks, then the Blockly engine generates JS code, which draws to a canvas.

What my problem is

The Blockly engine generates the JS code, but returns it as a string, which I have to eval() to draw to the canvas.

I can change the code of the blocks to generate different output, but it's important to keep it as simple as possible, because the users can read the actual code behind the block input. So I would like not to mess it up.

What I would like to do

I have full control over the atomic operations (go, turn, etc.), so I would like to insert a small piece of code to the beginning of the functions, which delays the execution of the rest of the bodies of the functions. Something like:

function go(dir, dist) {
  // wait here a little

  // do the drawing
}

I think it should be something synchronous, which keeps the delay in the flow of the execution. I've tried to use setTimeout (async, fail), a promise (fail), timestamp checks in a loop (fail).

Is it even possible in JS?


Solution

  • You must not make the code wait synchronously. The only thing you will get is a frozen browser window.

    What you need is to use the js interpreter instead of eval. This way you can pause the execution, play animations, highlight currently executing blocks, etc... The tutorial has many examples that will help you get started. Here is a working code, based on the JS interpreter example:

    var workspace = Blockly.inject("editor-div", {
      toolbox: document.getElementById('toolbox')
    });
    
    Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
    Blockly.JavaScript.addReservedWords('highlightBlock');
    
    Blockly.JavaScript['text_print'] = function(block) {
      var argument0 = Blockly.JavaScript.valueToCode(
        block, 'TEXT',
        Blockly.JavaScript.ORDER_FUNCTION_CALL
      ) || '\'\'';
      return "print(" + argument0 + ');\n';
    };
    
    function run() {
      var code = Blockly.JavaScript.workspaceToCode(workspace);
      var running = false;
    
      workspace.traceOn(true);
      workspace.highlightBlock(null);
    
      var lastBlockToHighlight = null;
      var myInterpreter = new Interpreter(code, (interpreter, scope) => {
        interpreter.setProperty(
          scope, 'highlightBlock',
          interpreter.createNativeFunction(id => {
            id = id ? id.toString() : '';
            running = false;
            workspace.highlightBlock(lastBlockToHighlight);
            lastBlockToHighlight = id;
          })
        );
        interpreter.setProperty(
          scope, 'print',
          interpreter.createNativeFunction(val => {
            val = val ? val.toString() : '';
            console.log(val);
          })
        );
      });
    
      var intervalId = setInterval(() => {
        running = true;
        while (running) {
          if (!myInterpreter.step()) {
            workspace.highlightBlock(lastBlockToHighlight);
            clearInterval(intervalId);
            return;
          }
        }
      }, 500);
    }
    #editor-div {
      width: 500px;
      height: 150px;
    }
    <script src="https://rawgit.com/google/blockly/master/blockly_compressed.js"></script>
    <script src="https://rawgit.com/google/blockly/master/blocks_compressed.js"></script>
    <script src="https://rawgit.com/google/blockly/master/javascript_compressed.js"></script>
    <script src="https://rawgit.com/google/blockly/master/msg/js/en.js"></script>
    <script src="https://rawgit.com/NeilFraser/JS-Interpreter/master/acorn_interpreter.js"></script>
    
    <xml id="toolbox" style="display: none">
      <block type="text"></block>
      <block type="text_print"></block>
      <block type="controls_repeat_ext"></block>
     <block type="math_number"></block>
    </xml>
    
    <div>
      <button id="run-code" onclick="run()">run</button>
    </div>
    <div id="editor-div"></div>

    EDIT

    Added variable running to control the interpreter. Now it steps over until the running variable is set to false, so the running = false statement inside the highlightBlock function essentially works as a breakpoint.

    EDIT

    Introduced lastBlockToHighlight variable to delay the highlighting, so the latest run statement is highlighted, not the next one. Unfortunately the JavaScript code generator doesn't have a STATEMENT_SUFFIX config similar to STATEMENT_PREFIX.