Merge branch 'develop' into feature/eslint-jsdoc

# Conflicts:
#	.eslintrc
#	src/index.js
This commit is contained in:
Tim Mickel 2016-06-08 17:23:41 -04:00
commit 42d03db28b
11 changed files with 178 additions and 95 deletions

11
.editorconfig Normal file
View file

@ -0,0 +1,11 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_size = 4
trim_trailing_whitespace = true
[*.{js,html}]
indent_style = space

View file

@ -8,7 +8,7 @@
"max-len": [2, 80, 4], "max-len": [2, 80, 4],
"semi": [2, "always"], "semi": [2, "always"],
"strict": [2, "never"], "strict": [2, "never"],
"no-console": [2, {"allow": ["log", "warn", "error"]}], "no-console": [2, {"allow": ["log", "warn", "error", "groupCollapsed", "groupEnd"]}],
"valid-jsdoc": ["error", {"requireReturn": false}] "valid-jsdoc": ["error", {"requireReturn": false}]
}, },
"env": { "env": {

View file

@ -12,8 +12,16 @@
<h2>Scratch VM Playground</h2> <h2>Scratch VM Playground</h2>
<button id="greenflag">Green flag</button> <button id="greenflag">Green flag</button>
<button id="stopall">Stop</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"> <div id="tab-blockexplorer">
<h3>VM Block Representation</h3> Block explorer
<pre id="blockexplorer"></pre> <pre id="blockexplorer"></pre>
</div> </div>
</div> </div>

View file

@ -1,6 +1,9 @@
body { body {
background: rgb(36,36,36); background: rgb(36,36,36);
} }
a {
color: rgb(217,217,217);
}
#blocks { #blocks {
position: absolute; position: absolute;
left: 40%; left: 40%;
@ -17,7 +20,7 @@ body {
bottom: 0; bottom: 0;
width: 35%; width: 35%;
} }
#blockexplorer { #blockexplorer, #threadexplorer {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 75%; height: 75%;
@ -28,3 +31,7 @@ body {
font-family: monospace; font-family: monospace;
font-size: 10pt; font-size: 10pt;
} }
#tab-blockexplorer {
display: none;
}

View file

@ -7,6 +7,11 @@ window.onload = function() {
var workspace = window.Blockly.inject('blocks', { var workspace = window.Blockly.inject('blocks', {
toolbox: toolbox, toolbox: toolbox,
media: '../node_modules/scratch-blocks/media/', media: '../node_modules/scratch-blocks/media/',
zoom: {
controls: true,
wheel: true,
startScale: 0.75
},
colours: { colours: {
workspace: '#334771', workspace: '#334771',
flyout: '#283856', flyout: '#283856',
@ -20,24 +25,45 @@ window.onload = function() {
}); });
window.workspace = workspace; window.workspace = workspace;
// @todo: Also bind to flyout events.
// Block events. // Block events.
workspace.addChangeListener(vm.blockListener); workspace.addChangeListener(vm.blockListener);
var flyoutWorkspace = workspace.toolbox_.flyout_.workspace_;
flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
var explorer = document.getElementById('blockexplorer'); var blockexplorer = document.getElementById('blockexplorer');
workspace.addChangeListener(function() { workspace.addChangeListener(function() {
// On a change, update the block explorer. // On a change, update the block explorer.
explorer.innerHTML = JSON.stringify(vm.runtime.blocks, null, 2); blockexplorer.innerHTML = JSON.stringify(vm.runtime.blocks, null, 2);
window.hljs.highlightBlock(explorer); 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) { vm.runtime.on('STACK_GLOW_ON', function(blockId) {
workspace.glowStack(blockId, true); workspace.glowStack(blockId, true);
}); });
vm.runtime.on('STACK_GLOW_OFF', function(blockId) { vm.runtime.on('STACK_GLOW_OFF', function(blockId) {
workspace.glowStack(blockId, false); 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 // Run threads
vm.runtime.start(); vm.runtime.start();
@ -49,4 +75,19 @@ window.onload = function() {
document.getElementById('stopall').addEventListener('click', function() { document.getElementById('stopall').addEventListener('click', function() {
vm.runtime.stopAll(); 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';
});
}; };

View file

@ -23,7 +23,6 @@ Scratch3Blocks.prototype.getPrimitives = function() {
}; };
Scratch3Blocks.prototype.repeat = function(argValues, util) { Scratch3Blocks.prototype.repeat = function(argValues, util) {
console.log('Running: control_repeat');
// Initialize loop // Initialize loop
if (util.stackFrame.loopCounter === undefined) { if (util.stackFrame.loopCounter === undefined) {
util.stackFrame.loopCounter = parseInt(argValues[0]); // @todo arg util.stackFrame.loopCounter = parseInt(argValues[0]); // @todo arg
@ -37,12 +36,10 @@ Scratch3Blocks.prototype.repeat = function(argValues, util) {
}; };
Scratch3Blocks.prototype.forever = function(argValues, util) { Scratch3Blocks.prototype.forever = function(argValues, util) {
console.log('Running: control_forever');
util.startSubstack(); util.startSubstack();
}; };
Scratch3Blocks.prototype.wait = function(argValues, util) { Scratch3Blocks.prototype.wait = function(argValues, util) {
console.log('Running: control_wait');
util.yield(); util.yield();
util.timeout(function() { util.timeout(function() {
util.done(); util.done();
@ -50,23 +47,19 @@ Scratch3Blocks.prototype.wait = function(argValues, util) {
}; };
Scratch3Blocks.prototype.stop = function() { Scratch3Blocks.prototype.stop = function() {
console.log('Running: control_stop');
// @todo - don't use this.runtime // @todo - don't use this.runtime
this.runtime.stopAll(); this.runtime.stopAll();
}; };
Scratch3Blocks.prototype.whenFlagClicked = function() { Scratch3Blocks.prototype.whenFlagClicked = function() {
console.log('Running: event_whenflagclicked');
// No-op // No-op
}; };
Scratch3Blocks.prototype.whenBroadcastReceived = function() { Scratch3Blocks.prototype.whenBroadcastReceived = function() {
console.log('Running: event_whenbroadcastreceived');
// No-op // No-op
}; };
Scratch3Blocks.prototype.broadcast = function(argValues, util) { Scratch3Blocks.prototype.broadcast = function(argValues, util) {
console.log('Running: event_broadcast');
util.startHats(function(hat) { util.startHats(function(hat) {
if (hat.opcode === 'event_whenbroadcastreceived') { if (hat.opcode === 'event_whenbroadcastreceived') {
var shadows = hat.fields.CHOICE.blocks; var shadows = hat.fields.CHOICE.blocks;

View file

@ -144,11 +144,9 @@ WeDo2Blocks.prototype.setColor = function(argValues, util) {
}; };
WeDo2Blocks.prototype.whenDistanceClose = function() { WeDo2Blocks.prototype.whenDistanceClose = function() {
console.log('Running: wedo_whendistanceclose');
}; };
WeDo2Blocks.prototype.whenTilt = function() { WeDo2Blocks.prototype.whenTilt = function() {
console.log('Running: wedo_whentilt');
}; };
module.exports = WeDo2Blocks; module.exports = WeDo2Blocks;

View file

@ -1,3 +1,5 @@
var adapter = require('./adapter');
/** /**
* @fileoverview * @fileoverview
* Store and mutate the VM block representation, * Store and mutate the VM block representation,
@ -80,6 +82,70 @@ Blocks.prototype.getOpcode = function (id) {
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
/**
* Create event listener for blocks. Handles validation and serves as a generic
* adapter between the blocks and the runtime interface.
* @param {boolean} isFlyout If true, create a listener for flyout events.
* @param {?Runtime} opt_runtime Optional runtime to forward click events to.
* @return {Function} A generated listener to attach to Blockly instance.
*/
Blocks.prototype.generateBlockListener = function (isFlyout, opt_runtime) {
/**
* The actual generated block listener.
* @param {Object} Blockly "block" event
*/
var instance = this;
return function (e) {
// Validate event
if (typeof e !== 'object') return;
if (typeof e.blockId !== 'string') return;
// UI event: clicked stacks toggle in the runtime.
if (e.element === 'stackclick') {
if (opt_runtime) {
opt_runtime.toggleStack(e.blockId);
}
return;
}
// Block create/update/destroy
switch (e.type) {
case 'create':
var newBlocks = adapter(e);
// A create event can create many blocks. Add them all.
for (var i = 0; i < newBlocks.length; i++) {
instance.createBlock(newBlocks[i], isFlyout);
}
break;
case 'change':
instance.changeBlock({
id: e.blockId,
element: e.element,
name: e.name,
value: e.newValue
});
break;
case 'move':
instance.moveBlock({
id: e.blockId,
oldParent: e.oldParentId,
oldInput: e.oldInputName,
newParent: e.newParentId,
newInput: e.newInputName
});
break;
case 'delete':
instance.deleteBlock({
id: e.blockId
});
break;
}
};
};
// ---------------------------------------------------------------------
/** /**
* Block management: create blocks and stacks from a `create` event * Block management: create blocks and stacks from a `create` event
* @param {!Object} block Blockly create event to be processed * @param {!Object} block Blockly create event to be processed

View file

@ -199,7 +199,13 @@ Runtime.prototype.startDistanceSensors = function () {
Runtime.prototype.stopAll = function () { Runtime.prototype.stopAll = function () {
var threadsCopy = this.threads.slice(); var threadsCopy = this.threads.slice();
while (threadsCopy.length > 0) { 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 // @todo call stop function in all extensions/packages/WeDo stub
if (window.native) { if (window.native) {

View file

@ -24,6 +24,12 @@ function Sequencer (runtime) {
*/ */
Sequencer.WORK_TIME = 10; 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. * Step through all threads in `this.threads`, running them in order.
* @param {Array.<Thread>} threads List of which threads to step. * @param {Array.<Thread>} threads List of which threads to step.
@ -135,6 +141,8 @@ Sequencer.prototype.stepThread = function (thread) {
// Pop the stack and stack frame // Pop the stack and stack frame
thread.stack.pop(); thread.stack.pop();
thread.stackFrames.pop(); thread.stackFrames.pop();
// Stop showing run feedback in the editor.
instance.runtime.glowBlock(currentBlock, false);
}; };
/** /**
@ -203,6 +211,9 @@ Sequencer.prototype.stepThread = function (thread) {
} }
} }
// Start showing run feedback in the editor.
this.runtime.glowBlock(currentBlock, true);
if (!opcode) { if (!opcode) {
console.warn('Could not get opcode for block: ' + currentBlock); console.warn('Could not get opcode for block: ' + currentBlock);
} }
@ -212,9 +223,15 @@ Sequencer.prototype.stepThread = function (thread) {
console.warn('Could not get implementation for opcode: ' + opcode); console.warn('Could not get implementation for opcode: ' + opcode);
} }
else { 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 { try {
// @todo deal with the return value // @todo deal with the return value
blockFunction(argValues, { blockFunctionReturnValue = blockFunction(argValues, {
yield: threadYieldCallback, yield: threadYieldCallback,
done: threadDoneCallback, done: threadDoneCallback,
timeout: YieldTimers.timeout, timeout: YieldTimers.timeout,
@ -237,6 +254,11 @@ Sequencer.prototype.stepThread = function (thread) {
// Thread executed without yielding - move to done // Thread executed without yielding - move to done
threadDoneCallback(); threadDoneCallback();
} }
if (Sequencer.DEBUG_BLOCK_CALLS) {
console.log('ending stack frame: ', currentStackFrame);
console.log('returned: ', blockFunctionReturnValue);
console.groupEnd();
}
} }
} }
} }

View file

@ -3,7 +3,6 @@ var util = require('util');
var Blocks = require('./engine/blocks'); var Blocks = require('./engine/blocks');
var Runtime = require('./engine/runtime'); var Runtime = require('./engine/runtime');
var adapter = require('./engine/adapter');
/** /**
* Handles connections between blocks, stage, and extensions. * Handles connections between blocks, stage, and extensions.
@ -20,83 +19,15 @@ function VirtualMachine () {
instance.runtime = new Runtime(instance.blocks); instance.runtime = new Runtime(instance.blocks);
/** /**
* Event listener for blocks. Handles validation and serves as a generic * Event listeners for scratch-blocks.
* adapter between the blocks and the runtime interface.
*
* @param {Object} e Blockly "block" event
*/ */
instance.blockListener = function (e) { instance.blockListener = (
// Validate event instance.blocks.generateBlockListener(false, instance.runtime)
if (typeof e !== 'object') return; );
if (typeof e.blockId !== 'string') return;
// UI event: clicked stacks toggle in the runtime. instance.flyoutBlockListener = (
if (e.element === 'stackclick') { instance.blocks.generateBlockListener(true, instance.runtime)
instance.runtime.toggleStack(e.blockId); );
return;
}
// Block create/update/destroy
switch (e.type) {
case 'create':
var newBlocks = adapter(e);
// A create event can create many blocks. Add them all.
for (var i = 0; i < newBlocks.length; i++) {
instance.blocks.createBlock(newBlocks[i], false);
}
break;
case 'change':
instance.blocks.changeBlock({
id: e.blockId,
element: e.element,
name: e.name,
value: e.newValue
});
break;
case 'move':
instance.blocks.moveBlock({
id: e.blockId,
oldParent: e.oldParentId,
oldInput: e.oldInputName,
newParent: e.newParentId,
newInput: e.newInputName
});
break;
case 'delete':
instance.blocks.deleteBlock({
id: e.blockId
});
break;
}
};
instance.flyoutBlockListener = function (e) {
switch (e.type) {
case 'create':
var newBlocks = adapter(e);
// A create event can create many blocks. Add them all.
for (var i = 0; i < newBlocks.length; i++) {
instance.blocks.createBlock(newBlocks[i], true);
}
break;
case 'change':
instance.blocks.changeBlock({
id: e.blockId,
element: e.element,
name: e.name,
value: e.newValue
});
break;
case 'delete':
instance.blocks.deleteBlock({
id: e.blockId
});
break;
case 'stackclick':
instance.runtime.toggleStack(e.blockId);
break;
}
};
} }
/** /**