mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-05-16 00:12:55 -04:00
Merge pull request #60 from tmickel/feature/vm-playground-execution
Instrument VM and playground for better execution debugging
This commit is contained in:
commit
d17ffbb99b
8 changed files with 92 additions and 18 deletions
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue