JS在生成的函数中等待/暂停

时间:2016-08-21 22:21:46

标签: javascript blockly

我在做什么

我正在使用Blockly构建一个海龟图形应用程序。用户可以从块构建代码,然后Blockly引擎生成JS代码,该代码将绘制到画布上。

我的问题是什么

Blockly引擎生成JS代码,但将其作为字符串返回,我必须eval()绘制到画布。

我可以更改块的代码以生成不同的输出,但保持它尽可能简单非常重要,因为用户可以读取块输入后面的实际代码。所以我不想搞砸它。

我想做什么

我可以完全控制原子操作(goturn等),所以我想在函数的开头插入一小段代码,这会延迟执行功能的其余部分。类似的东西:

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

  // do the drawing
}

我认为它应该是同步的东西,它可以保持执行流程的延迟。我尝试使用setTimeout(异步,失败),promise(失败),时间戳检查循环(失败)。

甚至可以在JS中使用吗?

3 个答案:

答案 0 :(得分:3)

您不能让代码同步等待。你唯一能得到的就是一个冻结的浏览器窗口。

您需要的是使用js interpreter而不是eval。通过这种方式,您可以暂停执行,播放动画,突出显示当前正在执行的块等...本教程有许多示例可帮助您入门。这是一个基于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>
&#13;
&#13;
&#13;

修改

添加了变量running来控制解释器。现在它逐步执行,直到running变量设置为false,因此highlightBlock函数内的running = false语句基本上用作断点。

修改

引入lastBlockToHighlight变量以延迟突出显示,因此突出显示最新的运行语句,而不是下一个。很遗憾,JavaScript代码生成器没有类似于STATEMENT_SUFFIX的{​​{1}}配置。

答案 1 :(得分:2)

最近,我发布了一个库,允许您异步进行块式交互,我为此类游戏设计了该库。 实际上,在文档中,您可以找到一个maze游戏翻版的游戏演示。 该库名为 gamepad.js?,我希望它就是您想要的。


这是演示的gif图像。

Demo


工作原理

与常规使用分组方式相比,这是一种不同且简化的方法

首先,您必须定义 (请参见如何在documentation中定义它们)
您不必定义any code generator,所有与代码生成有关的事情都由库执行。

enter image description here


每个块都会生成一个请求

// the request
{ method: 'TURN', args: ['RIGHT'] }


当执行块时,相应的请求将传递到您的游戏

class Game{
    manageRequests(request){
        // requests are passed here
        if(request.method == 'TURN')
            // animate your sprite
            turn(request.args)
    }
}


您可以根据情况使用 promise (承诺)
来管理异步动画

class Game{
    async manageRequests(request){
        if(request.method == 'TURN')
            await turn(request.args)
    }
}


方块与游戏之间的链接由游戏手柄管理。

let gamepad = new Blockly.Gamepad(),
    game = new Game()

// requests will be passed here
gamepad.setGame(game, game.manageRequest)


游戏手柄提供了一些方法来管理块执行,并因此请求生成

// load the code from the blocks in the workspace
gamepad.load()
// reset the code loaded previously
gamepad.reset()

// the blocks are executed one after the other
gamepad.play() 
// play in reverse
gamepad.play(true)
// the blocks execution is paused
gamepad.pause()
// toggle play
gamepad.togglePlay()

// load the next request 
gamepad.forward()
// load the prior request
gamepad.backward()

// use a block as a breakpoint and play until it is reached
gamepad.debug(id)

您可以阅读完整的文档here

答案 2 :(得分:1)

如果我理解你的话!

您可以构建一个新类来处理 go(dir,dist) 函数的执行,并覆盖go函数以创建新的 执行者中的

function GoExecutor(){

    var executeArray = [];     // Store go methods that waiting for execute
    var isRunning = false;     // Handle looper function

    // start runner function
    var run = function(){
        if(isRunning)
            return;
        isRunning = true;
        runner();
    }

    // looper for executeArray
    var runner = function(){
        if(executeArray.length == 0){
            isRunning = false;
            return;
        }

        // pop the first inserted params 
        var currentExec = executeArray.shift(0);

        // wait delay miliseconds
        setTimeout(function(){
            // execute the original go function
            originalGoFunction(currentExec.dir, currentExec.dist);

            // after finish drawing loop on the next execute method
            runner();
        }, currentExec.delay);

    }
    this.push = function(dir, dist){
        executeArray.push([dir,dist]);
        run();
    }
}

// GoExecutor instance
var goExec = new GoExecutor();

// Override go function
var originalGoFunction = go;
var go = function (dir, dist, delay){
    goExec.push({"dir":dir, "dist":dist, "delay":delay});
}

编辑1:

现在你必须用你的函数和参数调用 callWithDelay , 执行程序将通过将params应用于指定的函数来处理此调用。

function GoExecutor(){

    var executeArray = [];     // Store go methods that waiting for execute
    var isRunning = false;     // Handle looper function

    // start runner function
    var run = function(){
        if(isRunning)
            return;
        isRunning = true;
        runner();
    }

    // looper for executeArray
    var runner = function(){
        if(executeArray.length == 0){
            isRunning = false;
            return;
        }

        // pop the first inserted params 
        var currentExec = executeArray.shift(0);

        // wait delay miliseconds
        setTimeout(function(){
            // execute the original go function
            currentExec.funcNam.apply(currentExec.funcNam, currentExec.arrayParams);

            // after finish drawing loop on the next execute method
            runner();
        }, currentExec.delay);

    }
    this.push = function(dir, dist){
        executeArray.push([dir,dist]);
        run();
    }
}

// GoExecutor instance
var goExec = new GoExecutor();

var callWithDelay = function (func, arrayParams, delay){
    goExec.push({"func": func, "arrayParams":arrayParams, "delay":delay});
}