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.
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.
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?
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
.