diff --git a/.eslintrc b/.eslintrc index 0d67347a8..aacaef356 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,7 @@ "max-len": [2, 80, 4], "semi": [2, "always"], "strict": [2, "never"], - "no-console": [2, {"allow": ["log", "warn", "error"]}] + "no-console": [2, {"allow": ["log", "warn", "error", "groupCollapsed", "groupEnd"]}] }, "env": { "node": true, diff --git a/playground/index.html b/playground/index.html index 87cef30b3..3d10a22d0 100644 --- a/playground/index.html +++ b/playground/index.html @@ -12,8 +12,16 @@ <h2>Scratch VM Playground</h2> <button id="greenflag">Green flag</button> <button id="stopall">Stop</button> + <p> + <a id="threadexplorer-link" href="#">VM Threads</a><br /> + <a id="blockexplorer-link" href="#">VM Block Representation</a> + </p> + <div id="tab-threadexplorer"> + Thread explorer + <pre id="threadexplorer"></pre> + </div> <div id="tab-blockexplorer"> - <h3>VM Block Representation</h3> + Block explorer <pre id="blockexplorer"></pre> </div> </div> diff --git a/playground/playground.css b/playground/playground.css index b20b2bd91..451a7cad5 100644 --- a/playground/playground.css +++ b/playground/playground.css @@ -1,6 +1,9 @@ body { background: rgb(36,36,36); } +a { + color: rgb(217,217,217); +} #blocks { position: absolute; left: 40%; @@ -17,7 +20,7 @@ body { bottom: 0; width: 35%; } -#blockexplorer { +#blockexplorer, #threadexplorer { position: absolute; width: 100%; height: 75%; @@ -28,3 +31,7 @@ body { font-family: monospace; font-size: 10pt; } + +#tab-blockexplorer { + display: none; +} diff --git a/playground/playground.js b/playground/playground.js index 3926ecb90..5669212d7 100644 --- a/playground/playground.js +++ b/playground/playground.js @@ -7,6 +7,11 @@ window.onload = function() { var workspace = window.Blockly.inject('blocks', { toolbox: toolbox, media: '../node_modules/scratch-blocks/media/', + zoom: { + controls: true, + wheel: true, + startScale: 0.75 + }, colours: { workspace: '#334771', flyout: '#283856', @@ -25,20 +30,40 @@ window.onload = function() { var flyoutWorkspace = workspace.toolbox_.flyout_.workspace_; flyoutWorkspace.addChangeListener(vm.flyoutBlockListener); - var explorer = document.getElementById('blockexplorer'); + var blockexplorer = document.getElementById('blockexplorer'); workspace.addChangeListener(function() { // On a change, update the block explorer. - explorer.innerHTML = JSON.stringify(vm.runtime.blocks, null, 2); - window.hljs.highlightBlock(explorer); + blockexplorer.innerHTML = JSON.stringify(vm.runtime.blocks, null, 2); + window.hljs.highlightBlock(blockexplorer); }); - // Feedback for stacks running. + var threadexplorer = document.getElementById('threadexplorer'); + var cachedThreadJSON = ''; + var updateThreadExplorer = function () { + var newJSON = JSON.stringify(vm.runtime.threads, null, 2); + if (newJSON != cachedThreadJSON) { + cachedThreadJSON = newJSON; + threadexplorer.innerHTML = cachedThreadJSON; + window.hljs.highlightBlock(threadexplorer); + } + window.requestAnimationFrame(updateThreadExplorer); + }; + updateThreadExplorer(); + + // Feedback for stacks and blocks running. vm.runtime.on('STACK_GLOW_ON', function(blockId) { workspace.glowStack(blockId, true); }); vm.runtime.on('STACK_GLOW_OFF', function(blockId) { workspace.glowStack(blockId, false); }); + vm.runtime.on('BLOCK_GLOW_ON', function(blockId) { + workspace.glowBlock(blockId, true); + }); + vm.runtime.on('BLOCK_GLOW_OFF', function(blockId) { + workspace.glowBlock(blockId, false); + }); + // Run threads vm.runtime.start(); @@ -50,4 +75,19 @@ window.onload = function() { document.getElementById('stopall').addEventListener('click', function() { vm.runtime.stopAll(); }); + + var tabBlockExplorer = document.getElementById('tab-blockexplorer'); + var tabThreadExplorer = document.getElementById('tab-threadexplorer'); + + // Handlers to show different explorers. + document.getElementById('threadexplorer-link').addEventListener('click', + function () { + tabBlockExplorer.style.display = 'none'; + tabThreadExplorer.style.display = 'block'; + }); + document.getElementById('blockexplorer-link').addEventListener('click', + function () { + tabBlockExplorer.style.display = 'block'; + tabThreadExplorer.style.display = 'none'; + }); }; diff --git a/src/blocks/scratch3.js b/src/blocks/scratch3.js index 6bf327642..a2f132532 100644 --- a/src/blocks/scratch3.js +++ b/src/blocks/scratch3.js @@ -23,7 +23,6 @@ Scratch3Blocks.prototype.getPrimitives = function() { }; Scratch3Blocks.prototype.repeat = function(argValues, util) { - console.log('Running: control_repeat'); // Initialize loop if (util.stackFrame.loopCounter === undefined) { util.stackFrame.loopCounter = parseInt(argValues[0]); // @todo arg @@ -37,12 +36,10 @@ Scratch3Blocks.prototype.repeat = function(argValues, util) { }; Scratch3Blocks.prototype.forever = function(argValues, util) { - console.log('Running: control_forever'); util.startSubstack(); }; Scratch3Blocks.prototype.wait = function(argValues, util) { - console.log('Running: control_wait'); util.yield(); util.timeout(function() { util.done(); @@ -50,23 +47,19 @@ Scratch3Blocks.prototype.wait = function(argValues, util) { }; Scratch3Blocks.prototype.stop = function() { - console.log('Running: control_stop'); // @todo - don't use this.runtime this.runtime.stopAll(); }; Scratch3Blocks.prototype.whenFlagClicked = function() { - console.log('Running: event_whenflagclicked'); // No-op }; Scratch3Blocks.prototype.whenBroadcastReceived = function() { - console.log('Running: event_whenbroadcastreceived'); // No-op }; Scratch3Blocks.prototype.broadcast = function(argValues, util) { - console.log('Running: event_broadcast'); util.startHats(function(hat) { if (hat.opcode === 'event_whenbroadcastreceived') { var shadows = hat.fields.CHOICE.blocks; diff --git a/src/blocks/wedo2.js b/src/blocks/wedo2.js index 86c73de07..5074b26a1 100644 --- a/src/blocks/wedo2.js +++ b/src/blocks/wedo2.js @@ -144,11 +144,9 @@ WeDo2Blocks.prototype.setColor = function(argValues, util) { }; WeDo2Blocks.prototype.whenDistanceClose = function() { - console.log('Running: wedo_whendistanceclose'); }; WeDo2Blocks.prototype.whenTilt = function() { - console.log('Running: wedo_whentilt'); }; module.exports = WeDo2Blocks; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 9531eadc7..c4aa06e49 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -199,7 +199,13 @@ Runtime.prototype.startDistanceSensors = function () { Runtime.prototype.stopAll = function () { var threadsCopy = this.threads.slice(); while (threadsCopy.length > 0) { - this._removeThread(threadsCopy.pop()); + var poppedThread = threadsCopy.pop(); + // Unglow any blocks on this thread's stack. + for (var i = 0; i < poppedThread.stack.length; i++) { + this.glowBlock(poppedThread.stack[i], false); + } + // Actually remove the thread. + this._removeThread(poppedThread); } // @todo call stop function in all extensions/packages/WeDo stub if (window.native) { diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 95a28f7bf..a1572c3b3 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -24,6 +24,12 @@ function Sequencer (runtime) { */ Sequencer.WORK_TIME = 10; +/** + * If set, block calls, args, and return values will be logged to the console. + * @const {boolean} + */ +Sequencer.DEBUG_BLOCK_CALLS = true; + /** * Step through all threads in `this.threads`, running them in order. * @return {Array.<Thread>} All threads which have finished in this iteration. @@ -134,6 +140,8 @@ Sequencer.prototype.stepThread = function (thread) { // Pop the stack and stack frame thread.stack.pop(); thread.stackFrames.pop(); + // Stop showing run feedback in the editor. + instance.runtime.glowBlock(currentBlock, false); }; /** @@ -199,6 +207,9 @@ Sequencer.prototype.stepThread = function (thread) { } } + // Start showing run feedback in the editor. + this.runtime.glowBlock(currentBlock, true); + if (!opcode) { console.warn('Could not get opcode for block: ' + currentBlock); } @@ -208,9 +219,15 @@ Sequencer.prototype.stepThread = function (thread) { console.warn('Could not get implementation for opcode: ' + opcode); } else { + if (Sequencer.DEBUG_BLOCK_CALLS) { + console.groupCollapsed('Executing: ' + opcode); + console.log('with arguments: ', argValues); + console.log('and stack frame: ', currentStackFrame); + } + var blockFunctionReturnValue = null; try { // @todo deal with the return value - blockFunction(argValues, { + blockFunctionReturnValue = blockFunction(argValues, { yield: threadYieldCallback, done: threadDoneCallback, timeout: YieldTimers.timeout, @@ -233,6 +250,11 @@ Sequencer.prototype.stepThread = function (thread) { // Thread executed without yielding - move to done threadDoneCallback(); } + if (Sequencer.DEBUG_BLOCK_CALLS) { + console.log('ending stack frame: ', currentStackFrame); + console.log('returned: ', blockFunctionReturnValue); + console.groupEnd(); + } } } }