mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-17 09:42:39 -04:00
Merge pull request #72 from tmickel/feature/execute-with-args
Refactor for block execution
This commit is contained in:
commit
07354cddbc
8 changed files with 322 additions and 206 deletions
|
@ -15,14 +15,16 @@ Scratch3ControlBlocks.prototype.getPrimitives = function() {
|
||||||
'control_repeat': this.repeat,
|
'control_repeat': this.repeat,
|
||||||
'control_forever': this.forever,
|
'control_forever': this.forever,
|
||||||
'control_wait': this.wait,
|
'control_wait': this.wait,
|
||||||
|
'control_if': this.if,
|
||||||
|
'control_if_else': this.ifElse,
|
||||||
'control_stop': this.stop
|
'control_stop': this.stop
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.repeat = function(argValues, util) {
|
Scratch3ControlBlocks.prototype.repeat = function(args, util) {
|
||||||
// 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(args.TIMES);
|
||||||
}
|
}
|
||||||
// Decrease counter
|
// Decrease counter
|
||||||
util.stackFrame.loopCounter--;
|
util.stackFrame.loopCounter--;
|
||||||
|
@ -32,15 +34,39 @@ Scratch3ControlBlocks.prototype.repeat = function(argValues, util) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.forever = function(argValues, util) {
|
Scratch3ControlBlocks.prototype.forever = function(args, util) {
|
||||||
util.startSubstack();
|
util.startSubstack();
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.wait = function(argValues, util) {
|
Scratch3ControlBlocks.prototype.wait = function(args, util) {
|
||||||
util.yield();
|
util.yield();
|
||||||
util.timeout(function() {
|
util.timeout(function() {
|
||||||
util.done();
|
util.done();
|
||||||
}, 1000 * parseFloat(argValues[0]));
|
}, 1000 * args.DURATION);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3ControlBlocks.prototype.if = function(args, util) {
|
||||||
|
// Only execute one time. `if` will be returned to
|
||||||
|
// when the substack finishes, but it shouldn't execute again.
|
||||||
|
if (util.stackFrame.executed === undefined) {
|
||||||
|
util.stackFrame.executed = true;
|
||||||
|
if (args.CONDITION) {
|
||||||
|
util.startSubstack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3ControlBlocks.prototype.ifElse = function(args, util) {
|
||||||
|
// Only execute one time. `ifElse` will be returned to
|
||||||
|
// when the substack finishes, but it shouldn't execute again.
|
||||||
|
if (util.stackFrame.executed === undefined) {
|
||||||
|
util.stackFrame.executed = true;
|
||||||
|
if (args.CONDITION) {
|
||||||
|
util.startSubstack(1);
|
||||||
|
} else {
|
||||||
|
util.startSubstack(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3ControlBlocks.prototype.stop = function() {
|
Scratch3ControlBlocks.prototype.stop = function() {
|
||||||
|
|
|
@ -27,17 +27,8 @@ Scratch3EventBlocks.prototype.whenBroadcastReceived = function() {
|
||||||
// No-op
|
// No-op
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3EventBlocks.prototype.broadcast = function(argValues, util) {
|
Scratch3EventBlocks.prototype.broadcast = function() {
|
||||||
util.startHats(function(hat) {
|
// @todo
|
||||||
if (hat.opcode === 'event_whenbroadcastreceived') {
|
|
||||||
var shadows = hat.fields.CHOICE.blocks;
|
|
||||||
for (var sb in shadows) {
|
|
||||||
var shadowblock = shadows[sb];
|
|
||||||
return shadowblock.fields.CHOICE.value === argValues[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Scratch3EventBlocks;
|
module.exports = Scratch3EventBlocks;
|
||||||
|
|
38
src/blocks/scratch3_operators.js
Normal file
38
src/blocks/scratch3_operators.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
function Scratch3OperatorsBlocks(runtime) {
|
||||||
|
/**
|
||||||
|
* The runtime instantiating this block package.
|
||||||
|
* @type {Runtime}
|
||||||
|
*/
|
||||||
|
this.runtime = runtime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the block primitives implemented by this package.
|
||||||
|
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||||
|
*/
|
||||||
|
Scratch3OperatorsBlocks.prototype.getPrimitives = function() {
|
||||||
|
return {
|
||||||
|
'math_number': this.number,
|
||||||
|
'text': this.text,
|
||||||
|
'math_add': this.add,
|
||||||
|
'logic_equals': this.equals
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3OperatorsBlocks.prototype.number = function (args) {
|
||||||
|
return Number(args.NUM);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3OperatorsBlocks.prototype.text = function (args) {
|
||||||
|
return String(args.TEXT);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3OperatorsBlocks.prototype.add = function (args) {
|
||||||
|
return args.NUM1 + args.NUM2;
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3OperatorsBlocks.prototype.equals = function (args) {
|
||||||
|
return args.VALUE1 == args.VALUE2;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Scratch3OperatorsBlocks;
|
|
@ -22,6 +22,13 @@ function Blocks () {
|
||||||
this._stacks = [];
|
this._stacks = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blockly inputs that represent statements/substacks
|
||||||
|
* are prefixed with this string.
|
||||||
|
* @const{string}
|
||||||
|
*/
|
||||||
|
Blocks.SUBSTACK_INPUT_PREFIX = 'SUBSTACK';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide an object with metadata for the requested block ID.
|
* Provide an object with metadata for the requested block ID.
|
||||||
* @param {!string} blockId ID of block we have stored.
|
* @param {!string} blockId ID of block we have stored.
|
||||||
|
@ -60,7 +67,7 @@ Blocks.prototype.getSubstack = function (id, substackNum) {
|
||||||
if (typeof block === 'undefined') return null;
|
if (typeof block === 'undefined') return null;
|
||||||
if (!substackNum) substackNum = 1;
|
if (!substackNum) substackNum = 1;
|
||||||
|
|
||||||
var inputName = 'SUBSTACK';
|
var inputName = Blocks.SUBSTACK_INPUT_PREFIX;
|
||||||
if (substackNum > 1) {
|
if (substackNum > 1) {
|
||||||
inputName += substackNum;
|
inputName += substackNum;
|
||||||
}
|
}
|
||||||
|
@ -80,6 +87,34 @@ Blocks.prototype.getOpcode = function (id) {
|
||||||
return this._blocks[id].opcode;
|
return this._blocks[id].opcode;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all fields and their values for a block.
|
||||||
|
* @param {?string} id ID of block to query.
|
||||||
|
* @return {!Object} All fields and their values.
|
||||||
|
*/
|
||||||
|
Blocks.prototype.getFields = function (id) {
|
||||||
|
if (typeof this._blocks[id] === 'undefined') return null;
|
||||||
|
return this._blocks[id].fields;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all non-substack inputs for a block.
|
||||||
|
* @param {?string} id ID of block to query.
|
||||||
|
* @return {!Object} All non-substack inputs and their associated blocks.
|
||||||
|
*/
|
||||||
|
Blocks.prototype.getInputs = function (id) {
|
||||||
|
if (typeof this._blocks[id] === 'undefined') return null;
|
||||||
|
var inputs = {};
|
||||||
|
for (var input in this._blocks[id].inputs) {
|
||||||
|
// Ignore blocks prefixed with substack prefix.
|
||||||
|
if (input.substring(0, Blocks.SUBSTACK_INPUT_PREFIX.length)
|
||||||
|
!= Blocks.SUBSTACK_INPUT_PREFIX) {
|
||||||
|
inputs[input] = this._blocks[id].inputs[input];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inputs;
|
||||||
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
94
src/engine/execute.js
Normal file
94
src/engine/execute.js
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
var YieldTimers = require('../util/yieldtimers.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, block calls, args, and return values will be logged to the console.
|
||||||
|
* @const {boolean}
|
||||||
|
*/
|
||||||
|
var DEBUG_BLOCK_CALLS = true;
|
||||||
|
|
||||||
|
var execute = function (sequencer, thread) {
|
||||||
|
var runtime = sequencer.runtime;
|
||||||
|
|
||||||
|
// Current block to execute is the one on the top of the stack.
|
||||||
|
var currentBlockId = thread.peekStack();
|
||||||
|
var currentStackFrame = thread.peekStackFrame();
|
||||||
|
|
||||||
|
// Save the yield timer ID, in case a primitive makes a new one
|
||||||
|
// @todo hack - perhaps patch this to allow more than one timer per
|
||||||
|
// primitive, for example...
|
||||||
|
var oldYieldTimerId = YieldTimers.timerId;
|
||||||
|
|
||||||
|
var opcode = runtime.blocks.getOpcode(currentBlockId);
|
||||||
|
|
||||||
|
// Generate values for arguments (inputs).
|
||||||
|
var argValues = {};
|
||||||
|
|
||||||
|
// Add all fields on this block to the argValues.
|
||||||
|
var fields = runtime.blocks.getFields(currentBlockId);
|
||||||
|
for (var fieldName in fields) {
|
||||||
|
argValues[fieldName] = fields[fieldName].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively evaluate input blocks.
|
||||||
|
var inputs = runtime.blocks.getInputs(currentBlockId);
|
||||||
|
for (var inputName in inputs) {
|
||||||
|
var input = inputs[inputName];
|
||||||
|
var inputBlockId = input.block;
|
||||||
|
// Push to the stack to evaluate this input.
|
||||||
|
thread.pushStack(inputBlockId);
|
||||||
|
var result = execute(sequencer, thread);
|
||||||
|
thread.popStack();
|
||||||
|
argValues[input.name] = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opcode) {
|
||||||
|
console.warn('Could not get opcode for block: ' + currentBlockId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockFunction = runtime.getOpcodeFunction(opcode);
|
||||||
|
if (!blockFunction) {
|
||||||
|
console.warn('Could not get implementation for opcode: ' + opcode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_BLOCK_CALLS) {
|
||||||
|
console.groupCollapsed('Executing: ' + opcode);
|
||||||
|
console.log('with arguments: ', argValues);
|
||||||
|
console.log('and stack frame: ', currentStackFrame);
|
||||||
|
}
|
||||||
|
var primitiveReturnValue = null;
|
||||||
|
try {
|
||||||
|
// @todo deal with the return value
|
||||||
|
primitiveReturnValue = blockFunction(argValues, {
|
||||||
|
yield: thread.yield.bind(thread),
|
||||||
|
done: function() {
|
||||||
|
sequencer.proceedThread(thread);
|
||||||
|
},
|
||||||
|
timeout: YieldTimers.timeout,
|
||||||
|
stackFrame: currentStackFrame,
|
||||||
|
startSubstack: function (substackNum) {
|
||||||
|
sequencer.stepToSubstack(thread, substackNum);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
console.error(
|
||||||
|
'Exception calling block function for opcode: ' +
|
||||||
|
opcode + '\n' + e);
|
||||||
|
} finally {
|
||||||
|
// Update if the thread has set a yield timer ID
|
||||||
|
// @todo hack
|
||||||
|
if (YieldTimers.timerId > oldYieldTimerId) {
|
||||||
|
thread.yieldTimerId = YieldTimers.timerId;
|
||||||
|
}
|
||||||
|
if (DEBUG_BLOCK_CALLS) {
|
||||||
|
console.log('ending stack frame: ', currentStackFrame);
|
||||||
|
console.log('returned: ', primitiveReturnValue);
|
||||||
|
console.groupEnd();
|
||||||
|
}
|
||||||
|
return primitiveReturnValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = execute;
|
|
@ -6,6 +6,7 @@ var util = require('util');
|
||||||
var defaultBlockPackages = {
|
var defaultBlockPackages = {
|
||||||
'scratch3_control': require('../blocks/scratch3_control'),
|
'scratch3_control': require('../blocks/scratch3_control'),
|
||||||
'scratch3_event': require('../blocks/scratch3_event'),
|
'scratch3_event': require('../blocks/scratch3_event'),
|
||||||
|
'scratch3_operators': require('../blocks/scratch3_operators'),
|
||||||
'wedo2': require('../blocks/wedo2')
|
'wedo2': require('../blocks/wedo2')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -121,6 +122,7 @@ Runtime.prototype.getOpcodeFunction = function (opcode) {
|
||||||
Runtime.prototype._pushThread = function (id) {
|
Runtime.prototype._pushThread = function (id) {
|
||||||
this.emit(Runtime.STACK_GLOW_ON, id);
|
this.emit(Runtime.STACK_GLOW_ON, id);
|
||||||
var thread = new Thread(id);
|
var thread = new Thread(id);
|
||||||
|
thread.pushStack(id);
|
||||||
this.threads.push(thread);
|
this.threads.push(thread);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -231,6 +233,9 @@ Runtime.prototype._step = function () {
|
||||||
* @param {boolean} isGlowing True to turn on glow; false to turn off.
|
* @param {boolean} isGlowing True to turn on glow; false to turn off.
|
||||||
*/
|
*/
|
||||||
Runtime.prototype.glowBlock = function (blockId, isGlowing) {
|
Runtime.prototype.glowBlock = function (blockId, isGlowing) {
|
||||||
|
if (!this.blocks.getBlock(blockId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isGlowing) {
|
if (isGlowing) {
|
||||||
this.emit(Runtime.BLOCK_GLOW_ON, blockId);
|
this.emit(Runtime.BLOCK_GLOW_ON, blockId);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
var Timer = require('../util/timer');
|
var Timer = require('../util/timer');
|
||||||
var Thread = require('./thread');
|
var Thread = require('./thread');
|
||||||
var YieldTimers = require('../util/yieldtimers.js');
|
var YieldTimers = require('../util/yieldtimers.js');
|
||||||
|
var execute = require('./execute.js');
|
||||||
|
|
||||||
function Sequencer (runtime) {
|
function Sequencer (runtime) {
|
||||||
/**
|
/**
|
||||||
|
@ -24,12 +25,6 @@ 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.
|
||||||
|
@ -49,33 +44,27 @@ Sequencer.prototype.stepThreads = function (threads) {
|
||||||
this.timer.timeElapsed() < Sequencer.WORK_TIME) {
|
this.timer.timeElapsed() < Sequencer.WORK_TIME) {
|
||||||
// New threads at the end of the iteration.
|
// New threads at the end of the iteration.
|
||||||
var newThreads = [];
|
var newThreads = [];
|
||||||
|
// Reset yielding thread count.
|
||||||
|
numYieldingThreads = 0;
|
||||||
// Attempt to run each thread one time
|
// Attempt to run each thread one time
|
||||||
for (var i = 0; i < threads.length; i++) {
|
for (var i = 0; i < threads.length; i++) {
|
||||||
var activeThread = threads[i];
|
var activeThread = threads[i];
|
||||||
if (activeThread.status === Thread.STATUS_RUNNING) {
|
if (activeThread.status === Thread.STATUS_RUNNING) {
|
||||||
// Normal-mode thread: step.
|
// Normal-mode thread: step.
|
||||||
this.stepThread(activeThread);
|
this.startThread(activeThread);
|
||||||
} else if (activeThread.status === Thread.STATUS_YIELD) {
|
} else if (activeThread.status === Thread.STATUS_YIELD) {
|
||||||
// Yield-mode thread: check if the time has passed.
|
// Yield-mode thread: check if the time has passed.
|
||||||
YieldTimers.resolve(activeThread.yieldTimerId);
|
if (!YieldTimers.resolve(activeThread.yieldTimerId)) {
|
||||||
numYieldingThreads++;
|
// Thread is still yielding
|
||||||
|
// if YieldTimers.resolve returns false.
|
||||||
|
numYieldingThreads++;
|
||||||
|
}
|
||||||
} else if (activeThread.status === Thread.STATUS_DONE) {
|
} else if (activeThread.status === Thread.STATUS_DONE) {
|
||||||
// Moved to a done state - finish up
|
// Moved to a done state - finish up
|
||||||
activeThread.status = Thread.STATUS_RUNNING;
|
activeThread.status = Thread.STATUS_RUNNING;
|
||||||
// @todo Deal with the return value
|
// @todo Deal with the return value
|
||||||
}
|
}
|
||||||
// First attempt to pop from the stack
|
if (activeThread.stack.length === 0 &&
|
||||||
if (activeThread.stack.length > 0 &&
|
|
||||||
activeThread.nextBlock === null &&
|
|
||||||
activeThread.status === Thread.STATUS_DONE) {
|
|
||||||
activeThread.nextBlock = activeThread.stack.pop();
|
|
||||||
// Don't pop stack frame - we need the data.
|
|
||||||
// A new one won't be created when we execute.
|
|
||||||
if (activeThread.nextBlock !== null) {
|
|
||||||
activeThread.status === Thread.STATUS_RUNNING;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (activeThread.nextBlock === null &&
|
|
||||||
activeThread.status === Thread.STATUS_DONE) {
|
activeThread.status === Thread.STATUS_DONE) {
|
||||||
// Finished with this thread - tell runtime to clean it up.
|
// Finished with this thread - tell runtime to clean it up.
|
||||||
inactiveThreads.push(activeThread);
|
inactiveThreads.push(activeThread);
|
||||||
|
@ -94,175 +83,71 @@ Sequencer.prototype.stepThreads = function (threads) {
|
||||||
* Step the requested thread
|
* Step the requested thread
|
||||||
* @param {!Thread} thread Thread object to step
|
* @param {!Thread} thread Thread object to step
|
||||||
*/
|
*/
|
||||||
Sequencer.prototype.stepThread = function (thread) {
|
Sequencer.prototype.startThread = function (thread) {
|
||||||
// Save the yield timer ID, in case a primitive makes a new one
|
var currentBlockId = thread.peekStack();
|
||||||
// @todo hack - perhaps patch this to allow more than one timer per
|
if (!currentBlockId) {
|
||||||
// primitive, for example...
|
// A "null block" - empty substack. Pop the stack.
|
||||||
var oldYieldTimerId = YieldTimers.timerId;
|
thread.popStack();
|
||||||
|
|
||||||
// Save the current block and set the nextBlock.
|
|
||||||
// If the primitive would like to do control flow,
|
|
||||||
// it can overwrite nextBlock.
|
|
||||||
var currentBlock = thread.nextBlock;
|
|
||||||
if (!currentBlock || !this.runtime.blocks.getBlock(currentBlock)) {
|
|
||||||
thread.status = Thread.STATUS_DONE;
|
thread.status = Thread.STATUS_DONE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
thread.nextBlock = this.runtime.blocks.getNextBlock(currentBlock);
|
|
||||||
|
|
||||||
var opcode = this.runtime.blocks.getOpcode(currentBlock);
|
|
||||||
|
|
||||||
// Push the current block to the stack
|
|
||||||
thread.stack.push(currentBlock);
|
|
||||||
// Push an empty stack frame, if we need one.
|
|
||||||
// Might not, if we just popped the stack.
|
|
||||||
if (thread.stack.length > thread.stackFrames.length) {
|
|
||||||
thread.stackFrames.push({});
|
|
||||||
}
|
|
||||||
var currentStackFrame = thread.stackFrames[thread.stackFrames.length - 1];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback for the primitive to indicate its thread should yield.
|
|
||||||
* @type {Function}
|
|
||||||
*/
|
|
||||||
var threadYieldCallback = function () {
|
|
||||||
thread.status = Thread.STATUS_YIELD;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback for the primitive to indicate its thread is finished
|
|
||||||
* @type {Function}
|
|
||||||
*/
|
|
||||||
var instance = this;
|
|
||||||
var threadDoneCallback = function () {
|
|
||||||
thread.status = Thread.STATUS_DONE;
|
|
||||||
// Refresh nextBlock in case it has changed during a yield.
|
|
||||||
thread.nextBlock = instance.runtime.blocks.getNextBlock(currentBlock);
|
|
||||||
// Pop the stack and stack frame
|
|
||||||
thread.stack.pop();
|
|
||||||
thread.stackFrames.pop();
|
|
||||||
// Stop showing run feedback in the editor.
|
|
||||||
instance.runtime.glowBlock(currentBlock, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A callback for the primitive to start hats.
|
|
||||||
* @todo very hacked...
|
|
||||||
* Provide a callback that is passed in a block and returns true
|
|
||||||
* if it is a hat that should be triggered.
|
|
||||||
* @param {Function} callback Provided callback.
|
|
||||||
*/
|
|
||||||
var startHats = function(callback) {
|
|
||||||
var stacks = instance.runtime.blocks.getStacks();
|
|
||||||
for (var i = 0; i < stacks.length; i++) {
|
|
||||||
var stack = stacks[i];
|
|
||||||
var stackBlock = instance.runtime.blocks.getBlock(stack);
|
|
||||||
var result = callback(stackBlock);
|
|
||||||
if (result) {
|
|
||||||
// Check if the stack is already running
|
|
||||||
var stackRunning = false;
|
|
||||||
|
|
||||||
for (var j = 0; j < instance.runtime.threads.length; j++) {
|
|
||||||
if (instance.runtime.threads[j].topBlock == stack) {
|
|
||||||
stackRunning = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!stackRunning) {
|
|
||||||
instance.runtime._pushThread(stack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Record whether we have switched stack,
|
|
||||||
* to avoid proceeding the thread automatically.
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
var switchedStack = false;
|
|
||||||
/**
|
|
||||||
* A callback for a primitive to start a substack.
|
|
||||||
* @type {Function}
|
|
||||||
*/
|
|
||||||
var threadStartSubstack = function () {
|
|
||||||
// Set nextBlock to the start of the substack
|
|
||||||
var substack = instance.runtime.blocks.getSubstack(currentBlock);
|
|
||||||
if (substack && substack.value) {
|
|
||||||
thread.nextBlock = substack.value;
|
|
||||||
} else {
|
|
||||||
thread.nextBlock = null;
|
|
||||||
}
|
|
||||||
switchedStack = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// @todo extreme hack to get the single argument value for prototype
|
|
||||||
var argValues = [];
|
|
||||||
var blockInputs = this.runtime.blocks.getBlock(currentBlock).fields;
|
|
||||||
for (var bi in blockInputs) {
|
|
||||||
var outer = blockInputs[bi];
|
|
||||||
for (var b in outer.blocks) {
|
|
||||||
var block = outer.blocks[b];
|
|
||||||
var fields = block.fields;
|
|
||||||
for (var f in fields) {
|
|
||||||
var field = fields[f];
|
|
||||||
argValues.push(field.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start showing run feedback in the editor.
|
// Start showing run feedback in the editor.
|
||||||
this.runtime.glowBlock(currentBlock, true);
|
this.runtime.glowBlock(currentBlockId, true);
|
||||||
|
|
||||||
if (!opcode) {
|
// Execute the current block
|
||||||
console.warn('Could not get opcode for block: ' + currentBlock);
|
execute(this, thread);
|
||||||
}
|
|
||||||
else {
|
|
||||||
var blockFunction = this.runtime.getOpcodeFunction(opcode);
|
|
||||||
if (!blockFunction) {
|
|
||||||
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
|
|
||||||
blockFunctionReturnValue = blockFunction(argValues, {
|
|
||||||
yield: threadYieldCallback,
|
|
||||||
done: threadDoneCallback,
|
|
||||||
timeout: YieldTimers.timeout,
|
|
||||||
stackFrame: currentStackFrame,
|
|
||||||
startSubstack: threadStartSubstack,
|
|
||||||
startHats: startHats
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch(e) {
|
|
||||||
console.error(
|
|
||||||
'Exception calling block function for opcode: ' +
|
|
||||||
opcode + '\n' + e);
|
|
||||||
} finally {
|
|
||||||
// Update if the thread has set a yield timer ID
|
|
||||||
// @todo hack
|
|
||||||
if (YieldTimers.timerId > oldYieldTimerId) {
|
|
||||||
thread.yieldTimerId = YieldTimers.timerId;
|
|
||||||
}
|
|
||||||
if (thread.status === Thread.STATUS_RUNNING && !switchedStack) {
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// If the block executed without yielding and without doing control flow,
|
||||||
|
// move to done.
|
||||||
|
if (thread.status === Thread.STATUS_RUNNING &&
|
||||||
|
thread.peekStack() === currentBlockId) {
|
||||||
|
this.proceedThread(thread, currentBlockId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step a thread into a block's substack.
|
||||||
|
* @param {!Thread} thread Thread object to step to substack.
|
||||||
|
* @param {Number} substackNum Which substack to step to (i.e., 1, 2).
|
||||||
|
*/
|
||||||
|
Sequencer.prototype.stepToSubstack = function (thread, substackNum) {
|
||||||
|
if (!substackNum) {
|
||||||
|
substackNum = 1;
|
||||||
|
}
|
||||||
|
var currentBlockId = thread.peekStack();
|
||||||
|
var substackId = this.runtime.blocks.getSubstack(
|
||||||
|
currentBlockId,
|
||||||
|
substackNum
|
||||||
|
);
|
||||||
|
if (substackId) {
|
||||||
|
// Push substack ID to the thread's stack.
|
||||||
|
thread.pushStack(substackId);
|
||||||
|
} else {
|
||||||
|
// Push null, so we come back to the current block.
|
||||||
|
thread.pushStack(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish stepping a thread and proceed it to the next block.
|
||||||
|
* @param {!Thread} thread Thread object to proceed.
|
||||||
|
*/
|
||||||
|
Sequencer.prototype.proceedThread = function (thread) {
|
||||||
|
var currentBlockId = thread.peekStack();
|
||||||
|
// Mark the status as done and proceed to the next block.
|
||||||
|
this.runtime.glowBlock(currentBlockId, false);
|
||||||
|
thread.status = Thread.STATUS_DONE;
|
||||||
|
// Pop from the stack - finished this level of execution.
|
||||||
|
thread.popStack();
|
||||||
|
// Push next connected block, if there is one.
|
||||||
|
var nextBlockId = this.runtime.blocks.getNextBlock(currentBlockId);
|
||||||
|
if (nextBlockId) {
|
||||||
|
thread.pushStack(nextBlockId);
|
||||||
|
}
|
||||||
|
// Pop from the stack until we have a next block.
|
||||||
|
while (thread.peekStack() === null && thread.stack.length > 0) {
|
||||||
|
thread.popStack();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Sequencer;
|
module.exports = Sequencer;
|
||||||
|
|
|
@ -9,11 +9,7 @@ function Thread (firstBlock) {
|
||||||
* @type {!string}
|
* @type {!string}
|
||||||
*/
|
*/
|
||||||
this.topBlock = firstBlock;
|
this.topBlock = firstBlock;
|
||||||
/**
|
|
||||||
* ID of next block that the thread will execute, or null if none.
|
|
||||||
* @type {?string}
|
|
||||||
*/
|
|
||||||
this.nextBlock = firstBlock;
|
|
||||||
/**
|
/**
|
||||||
* Stack for the thread. When the sequencer enters a control structure,
|
* Stack for the thread. When the sequencer enters a control structure,
|
||||||
* the block is pushed onto the stack so we know where to exit.
|
* the block is pushed onto the stack so we know where to exit.
|
||||||
|
@ -62,4 +58,50 @@ Thread.STATUS_YIELD = 1;
|
||||||
*/
|
*/
|
||||||
Thread.STATUS_DONE = 2;
|
Thread.STATUS_DONE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push stack and update stack frames appropriately.
|
||||||
|
* @param {string} blockId Block ID to push to stack.
|
||||||
|
*/
|
||||||
|
Thread.prototype.pushStack = function (blockId) {
|
||||||
|
this.stack.push(blockId);
|
||||||
|
// Push an empty stack frame, if we need one.
|
||||||
|
// Might not, if we just popped the stack.
|
||||||
|
if (this.stack.length > this.stackFrames.length) {
|
||||||
|
this.stackFrames.push({});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop last block on the stack and its stack frame.
|
||||||
|
* @return {string} Block ID popped from the stack.
|
||||||
|
*/
|
||||||
|
Thread.prototype.popStack = function () {
|
||||||
|
this.stackFrames.pop();
|
||||||
|
return this.stack.pop();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get top stack item.
|
||||||
|
* @return {?string} Block ID on top of stack.
|
||||||
|
*/
|
||||||
|
Thread.prototype.peekStack = function () {
|
||||||
|
return this.stack[this.stack.length - 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get top stack frame.
|
||||||
|
* @return {?Object} Last stack frame stored on this thread.
|
||||||
|
*/
|
||||||
|
Thread.prototype.peekStackFrame = function () {
|
||||||
|
return this.stackFrames[this.stackFrames.length - 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Yields the thread.
|
||||||
|
*/
|
||||||
|
Thread.prototype.yield = function () {
|
||||||
|
this.status = Thread.STATUS_YIELD;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Thread;
|
module.exports = Thread;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue