mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-09 20:43:59 -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_forever': this.forever,
|
||||
'control_wait': this.wait,
|
||||
'control_if': this.if,
|
||||
'control_if_else': this.ifElse,
|
||||
'control_stop': this.stop
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.repeat = function(argValues, util) {
|
||||
Scratch3ControlBlocks.prototype.repeat = function(args, util) {
|
||||
// Initialize loop
|
||||
if (util.stackFrame.loopCounter === undefined) {
|
||||
util.stackFrame.loopCounter = parseInt(argValues[0]); // @todo arg
|
||||
util.stackFrame.loopCounter = parseInt(args.TIMES);
|
||||
}
|
||||
// Decrease counter
|
||||
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();
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.wait = function(argValues, util) {
|
||||
Scratch3ControlBlocks.prototype.wait = function(args, util) {
|
||||
util.yield();
|
||||
util.timeout(function() {
|
||||
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() {
|
||||
|
|
|
@ -27,17 +27,8 @@ Scratch3EventBlocks.prototype.whenBroadcastReceived = function() {
|
|||
// No-op
|
||||
};
|
||||
|
||||
Scratch3EventBlocks.prototype.broadcast = function(argValues, util) {
|
||||
util.startHats(function(hat) {
|
||||
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;
|
||||
});
|
||||
Scratch3EventBlocks.prototype.broadcast = function() {
|
||||
// @todo
|
||||
};
|
||||
|
||||
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 = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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 (!substackNum) substackNum = 1;
|
||||
|
||||
var inputName = 'SUBSTACK';
|
||||
var inputName = Blocks.SUBSTACK_INPUT_PREFIX;
|
||||
if (substackNum > 1) {
|
||||
inputName += substackNum;
|
||||
}
|
||||
|
@ -80,6 +87,34 @@ Blocks.prototype.getOpcode = function (id) {
|
|||
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 = {
|
||||
'scratch3_control': require('../blocks/scratch3_control'),
|
||||
'scratch3_event': require('../blocks/scratch3_event'),
|
||||
'scratch3_operators': require('../blocks/scratch3_operators'),
|
||||
'wedo2': require('../blocks/wedo2')
|
||||
};
|
||||
|
||||
|
@ -121,6 +122,7 @@ Runtime.prototype.getOpcodeFunction = function (opcode) {
|
|||
Runtime.prototype._pushThread = function (id) {
|
||||
this.emit(Runtime.STACK_GLOW_ON, id);
|
||||
var thread = new Thread(id);
|
||||
thread.pushStack(id);
|
||||
this.threads.push(thread);
|
||||
};
|
||||
|
||||
|
@ -231,6 +233,9 @@ Runtime.prototype._step = function () {
|
|||
* @param {boolean} isGlowing True to turn on glow; false to turn off.
|
||||
*/
|
||||
Runtime.prototype.glowBlock = function (blockId, isGlowing) {
|
||||
if (!this.blocks.getBlock(blockId)) {
|
||||
return;
|
||||
}
|
||||
if (isGlowing) {
|
||||
this.emit(Runtime.BLOCK_GLOW_ON, blockId);
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
var Timer = require('../util/timer');
|
||||
var Thread = require('./thread');
|
||||
var YieldTimers = require('../util/yieldtimers.js');
|
||||
var execute = require('./execute.js');
|
||||
|
||||
function Sequencer (runtime) {
|
||||
/**
|
||||
|
@ -24,12 +25,6 @@ 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.
|
||||
* @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) {
|
||||
// New threads at the end of the iteration.
|
||||
var newThreads = [];
|
||||
// Reset yielding thread count.
|
||||
numYieldingThreads = 0;
|
||||
// Attempt to run each thread one time
|
||||
for (var i = 0; i < threads.length; i++) {
|
||||
var activeThread = threads[i];
|
||||
if (activeThread.status === Thread.STATUS_RUNNING) {
|
||||
// Normal-mode thread: step.
|
||||
this.stepThread(activeThread);
|
||||
this.startThread(activeThread);
|
||||
} else if (activeThread.status === Thread.STATUS_YIELD) {
|
||||
// Yield-mode thread: check if the time has passed.
|
||||
YieldTimers.resolve(activeThread.yieldTimerId);
|
||||
numYieldingThreads++;
|
||||
if (!YieldTimers.resolve(activeThread.yieldTimerId)) {
|
||||
// Thread is still yielding
|
||||
// if YieldTimers.resolve returns false.
|
||||
numYieldingThreads++;
|
||||
}
|
||||
} else if (activeThread.status === Thread.STATUS_DONE) {
|
||||
// Moved to a done state - finish up
|
||||
activeThread.status = Thread.STATUS_RUNNING;
|
||||
// @todo Deal with the return value
|
||||
}
|
||||
// First attempt to pop from the stack
|
||||
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 &&
|
||||
if (activeThread.stack.length === 0 &&
|
||||
activeThread.status === Thread.STATUS_DONE) {
|
||||
// Finished with this thread - tell runtime to clean it up.
|
||||
inactiveThreads.push(activeThread);
|
||||
|
@ -94,175 +83,71 @@ Sequencer.prototype.stepThreads = function (threads) {
|
|||
* Step the requested thread
|
||||
* @param {!Thread} thread Thread object to step
|
||||
*/
|
||||
Sequencer.prototype.stepThread = function (thread) {
|
||||
// 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;
|
||||
|
||||
// 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)) {
|
||||
Sequencer.prototype.startThread = function (thread) {
|
||||
var currentBlockId = thread.peekStack();
|
||||
if (!currentBlockId) {
|
||||
// A "null block" - empty substack. Pop the stack.
|
||||
thread.popStack();
|
||||
thread.status = Thread.STATUS_DONE;
|
||||
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.
|
||||
this.runtime.glowBlock(currentBlock, true);
|
||||
this.runtime.glowBlock(currentBlockId, true);
|
||||
|
||||
if (!opcode) {
|
||||
console.warn('Could not get opcode for block: ' + currentBlock);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Execute the current block
|
||||
execute(this, thread);
|
||||
|
||||
// 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;
|
||||
|
|
|
@ -9,11 +9,7 @@ function Thread (firstBlock) {
|
|||
* @type {!string}
|
||||
*/
|
||||
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,
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue