mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-05-18 09:21:04 -04:00
Merge pull request #83 from tmickel/feature/yielding-reporters
Yielding reporters, blocking yields
This commit is contained in:
commit
1a48e75341
11 changed files with 193 additions and 328 deletions
|
@ -8,7 +8,7 @@
|
|||
"max-len": [2, 80, 4],
|
||||
"semi": [2, "always"],
|
||||
"strict": [2, "never"],
|
||||
"no-console": [2, {"allow": ["log", "warn", "error", "groupCollapsed", "groupEnd"]}],
|
||||
"no-console": [2, {"allow": ["log", "warn", "error", "groupCollapsed", "groupEnd", "time", "timeEnd"]}],
|
||||
"valid-jsdoc": ["error", {"requireReturn": false}]
|
||||
},
|
||||
"env": {
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"htmlparser2": "3.9.0",
|
||||
"memoizee": "0.3.10"
|
||||
"memoizee": "0.3.10",
|
||||
"promise": "7.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "2.7.0",
|
||||
|
|
|
@ -79,12 +79,6 @@
|
|||
</block>
|
||||
<block type="control_delete_this_clone"></block>
|
||||
</category>
|
||||
<category name="Wedo">
|
||||
<block type="wedo_setcolor"></block>
|
||||
<block type="wedo_motorspeed"></block>
|
||||
<block type="wedo_whentilt"></block>
|
||||
<block type="wedo_whendistanceclose"></block>
|
||||
</category>
|
||||
<category name="Operators">
|
||||
<block type="operator_add">
|
||||
<value name="NUM1">
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
var Promise = require('promise');
|
||||
|
||||
function Scratch3ControlBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
|
@ -38,11 +40,12 @@ Scratch3ControlBlocks.prototype.forever = function(args, util) {
|
|||
util.startSubstack();
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.wait = function(args, util) {
|
||||
util.yield();
|
||||
util.timeout(function() {
|
||||
util.done();
|
||||
}, 1000 * args.DURATION);
|
||||
Scratch3ControlBlocks.prototype.wait = function(args) {
|
||||
return new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
resolve();
|
||||
}, 1000 * args.DURATION);
|
||||
});
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.if = function(args, util) {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
var Promise = require('promise');
|
||||
|
||||
function Scratch3OperatorsBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
|
@ -15,7 +17,16 @@ Scratch3OperatorsBlocks.prototype.getPrimitives = function() {
|
|||
'math_number': this.number,
|
||||
'text': this.text,
|
||||
'operator_add': this.add,
|
||||
'operator_equals': this.equals
|
||||
'operator_subtract': this.subtract,
|
||||
'operator_multiply': this.multiply,
|
||||
'operator_divide': this.divide,
|
||||
'operator_lt': this.lt,
|
||||
'operator_equals': this.equals,
|
||||
'operator_gt': this.gt,
|
||||
'operator_and': this.and,
|
||||
'operator_or': this.or,
|
||||
'operator_not': this.not,
|
||||
'operator_random': this.random
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -31,8 +42,53 @@ Scratch3OperatorsBlocks.prototype.add = function (args) {
|
|||
return args.NUM1 + args.NUM2;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.subtract = function (args) {
|
||||
return args.NUM1 - args.NUM2;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.multiply = function (args) {
|
||||
return args.NUM1 * args.NUM2;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.divide = function (args) {
|
||||
return args.NUM1 / args.NUM2;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.lt = function (args) {
|
||||
return Boolean(args.OPERAND1 < args.OPERAND2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.equals = function (args) {
|
||||
return args.OPERAND1 == args.OPERAND2;
|
||||
return Boolean(args.OPERAND1 == args.OPERAND2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.gt = function (args) {
|
||||
return Boolean(args.OPERAND1 > args.OPERAND2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.and = function (args) {
|
||||
return Boolean(args.OPERAND1 && args.OPERAND2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.or = function (args) {
|
||||
return Boolean(args.OPERAND1 || args.OPERAND2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.not = function (args) {
|
||||
return Boolean(!args.OPERAND);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.random = function (args) {
|
||||
// As a demo, this implementation of random returns after 1 second of yield.
|
||||
// @todo Match Scratch 2.0 implementation with int-truncation.
|
||||
// See: http://bit.ly/1Qc0GzC
|
||||
var examplePromise = new Promise(function(resolve) {
|
||||
setTimeout(function() {
|
||||
var res = (Math.random() * (args.TO - args.FROM)) + args.FROM;
|
||||
resolve(res);
|
||||
}, 1000);
|
||||
});
|
||||
return examplePromise;
|
||||
};
|
||||
|
||||
module.exports = Scratch3OperatorsBlocks;
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
|
||||
var YieldTimers = require('../util/yieldtimers.js');
|
||||
|
||||
function WeDo2Blocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
|
||||
/**
|
||||
* Current motor speed, as a percentage (100 = full speed).
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this._motorSpeed = 100;
|
||||
|
||||
/**
|
||||
* The timeout ID for a pending motor action.
|
||||
* @type {?int}
|
||||
* @private
|
||||
*/
|
||||
this._motorTimeout = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
WeDo2Blocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'wedo_motorclockwise': this.motorClockwise,
|
||||
'wedo_motorcounterclockwise': this.motorCounterClockwise,
|
||||
'wedo_motorspeed': this.motorSpeed,
|
||||
'wedo_setcolor': this.setColor,
|
||||
'wedo_whendistanceclose': this.whenDistanceClose,
|
||||
'wedo_whentilt': this.whenTilt
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Clamp a value between a minimum and maximum value.
|
||||
* @todo move this to a common utility class.
|
||||
* @param {number} val The value to clamp.
|
||||
* @param {number} min The minimum return value.
|
||||
* @param {number} max The maximum return value.
|
||||
* @returns {number} The clamped value.
|
||||
* @private
|
||||
*/
|
||||
WeDo2Blocks.prototype._clamp = function(val, min, max) {
|
||||
return Math.max(min, Math.min(val, max));
|
||||
};
|
||||
|
||||
/**
|
||||
* Common implementation for motor blocks.
|
||||
* @param {string} direction The direction to turn ('left' or 'right').
|
||||
* @param {number} durationSeconds The number of seconds to run.
|
||||
* @param {Object} util The util instance to use for yielding and finishing.
|
||||
* @private
|
||||
*/
|
||||
WeDo2Blocks.prototype._motorOnFor = function(direction, durationSeconds, util) {
|
||||
if (this._motorTimeout > 0) {
|
||||
// @todo maybe this should go through util
|
||||
YieldTimers.resolve(this._motorTimeout);
|
||||
this._motorTimeout = null;
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.native) {
|
||||
window.native.motorRun(direction, this._motorSpeed);
|
||||
}
|
||||
|
||||
var instance = this;
|
||||
var myTimeout = this._motorTimeout = util.timeout(function() {
|
||||
if (instance._motorTimeout == myTimeout) {
|
||||
instance._motorTimeout = null;
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.native) {
|
||||
window.native.motorStop();
|
||||
}
|
||||
util.done();
|
||||
}, 1000 * durationSeconds);
|
||||
|
||||
util.yield();
|
||||
};
|
||||
|
||||
WeDo2Blocks.prototype.motorClockwise = function(argValues, util) {
|
||||
this._motorOnFor('right', parseFloat(argValues[0]), util);
|
||||
};
|
||||
|
||||
WeDo2Blocks.prototype.motorCounterClockwise = function(argValues, util) {
|
||||
this._motorOnFor('left', parseFloat(argValues[0]), util);
|
||||
};
|
||||
|
||||
WeDo2Blocks.prototype.motorSpeed = function(argValues) {
|
||||
var speed = argValues[0];
|
||||
switch (speed) {
|
||||
case 'slow':
|
||||
this._motorSpeed = 20;
|
||||
break;
|
||||
case 'medium':
|
||||
this._motorSpeed = 50;
|
||||
break;
|
||||
case 'fast':
|
||||
this._motorSpeed = 100;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a color name to a WeDo color index.
|
||||
* Supports 'mystery' for a random hue.
|
||||
* @param {string} colorName The color to retrieve.
|
||||
* @returns {number} The WeDo color index.
|
||||
* @private
|
||||
*/
|
||||
WeDo2Blocks.prototype._getColor = function(colorName) {
|
||||
var colors = {
|
||||
'yellow': 7,
|
||||
'orange': 8,
|
||||
'coral': 9,
|
||||
'magenta': 1,
|
||||
'purple': 2,
|
||||
'blue': 3,
|
||||
'green': 6,
|
||||
'white': 10
|
||||
};
|
||||
|
||||
if (colorName == 'mystery') {
|
||||
return Math.floor((Math.random() * 10) + 1);
|
||||
}
|
||||
|
||||
return colors[colorName];
|
||||
};
|
||||
|
||||
WeDo2Blocks.prototype.setColor = function(argValues, util) {
|
||||
if (typeof window !== 'undefined' && window.native) {
|
||||
var colorIndex = this._getColor(argValues[0]);
|
||||
window.native.setLedColor(colorIndex);
|
||||
}
|
||||
// Pause for quarter second
|
||||
util.yield();
|
||||
util.timeout(function() {
|
||||
util.done();
|
||||
}, 250);
|
||||
};
|
||||
|
||||
WeDo2Blocks.prototype.whenDistanceClose = function() {
|
||||
};
|
||||
|
||||
WeDo2Blocks.prototype.whenTilt = function() {
|
||||
};
|
||||
|
||||
module.exports = WeDo2Blocks;
|
|
@ -1,11 +1,10 @@
|
|||
var YieldTimers = require('../util/yieldtimers.js');
|
||||
var Thread = require('./thread');
|
||||
|
||||
/**
|
||||
* If set, block calls, args, and return values will be logged to the console.
|
||||
* @const {boolean}
|
||||
* Execute a block.
|
||||
* @param {!Sequencer} sequencer Which sequencer is executing.
|
||||
* @param {!Thread} thread Thread which to read and execute.
|
||||
*/
|
||||
var DEBUG_BLOCK_CALLS = true;
|
||||
|
||||
var execute = function (sequencer, thread) {
|
||||
var runtime = sequencer.runtime;
|
||||
|
||||
|
@ -13,13 +12,19 @@ var execute = function (sequencer, thread) {
|
|||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Generate values for arguments (inputs).
|
||||
var argValues = {};
|
||||
|
||||
|
@ -34,53 +39,65 @@ var execute = function (sequencer, thread) {
|
|||
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;
|
||||
// Is there no value for this input waiting in the stack frame?
|
||||
if (typeof currentStackFrame.reported[inputName] === 'undefined') {
|
||||
// If there's not, we need to evaluate the block.
|
||||
var reporterYielded = (
|
||||
sequencer.stepToReporter(thread, inputBlockId, inputName)
|
||||
);
|
||||
// If the reporter yielded, return immediately;
|
||||
// it needs time to finish and report its value.
|
||||
if (reporterYielded) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
argValues[inputName] = currentStackFrame.reported[inputName];
|
||||
}
|
||||
|
||||
if (!opcode) {
|
||||
console.warn('Could not get opcode for block: ' + currentBlockId);
|
||||
return;
|
||||
}
|
||||
// If we've gotten this far, all of the input blocks are evaluated,
|
||||
// and `argValues` is fully populated. So, execute the block primitive.
|
||||
// First, clear `currentStackFrame.reported`, so any subsequent execution
|
||||
// (e.g., on return from a substack) gets fresh inputs.
|
||||
currentStackFrame.reported = {};
|
||||
|
||||
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;
|
||||
// @todo deal with the return value
|
||||
primitiveReturnValue = blockFunction(argValues, {
|
||||
var primitiveReportedValue = null;
|
||||
primitiveReportedValue = blockFunction(argValues, {
|
||||
yield: thread.yield.bind(thread),
|
||||
done: function() {
|
||||
sequencer.proceedThread(thread);
|
||||
},
|
||||
timeout: YieldTimers.timeout,
|
||||
stackFrame: currentStackFrame,
|
||||
stackFrame: currentStackFrame.executionContext,
|
||||
startSubstack: function (substackNum) {
|
||||
sequencer.stepToSubstack(thread, substackNum);
|
||||
}
|
||||
});
|
||||
// Update if the thread has set a yield timer ID
|
||||
// @todo hack
|
||||
if (YieldTimers.timerId > oldYieldTimerId) {
|
||||
thread.yieldTimerId = YieldTimers.timerId;
|
||||
|
||||
// Deal with any reported value.
|
||||
// If it's a promise, wait until promise resolves.
|
||||
var isPromise = (
|
||||
primitiveReportedValue &&
|
||||
primitiveReportedValue.then &&
|
||||
typeof primitiveReportedValue.then === 'function'
|
||||
);
|
||||
if (isPromise) {
|
||||
if (thread.status === Thread.STATUS_RUNNING) {
|
||||
// Primitive returned a promise; automatically yield thread.
|
||||
thread.status = Thread.STATUS_YIELD;
|
||||
}
|
||||
// Promise handlers
|
||||
primitiveReportedValue.then(function(resolvedValue) {
|
||||
// Promise resolved: the primitive reported a value.
|
||||
thread.pushReportedValue(resolvedValue);
|
||||
sequencer.proceedThread(thread);
|
||||
}, function(rejectionReason) {
|
||||
// Promise rejected: the primitive had some error.
|
||||
// Log it and proceed.
|
||||
console.warn('Primitive rejected promise: ', rejectionReason);
|
||||
sequencer.proceedThread(thread);
|
||||
});
|
||||
} else if (thread.status === Thread.STATUS_RUNNING) {
|
||||
thread.pushReportedValue(primitiveReportedValue);
|
||||
}
|
||||
if (DEBUG_BLOCK_CALLS) {
|
||||
console.log('ending stack frame: ', currentStackFrame);
|
||||
console.log('returned: ', primitiveReturnValue);
|
||||
console.groupEnd();
|
||||
}
|
||||
return primitiveReturnValue;
|
||||
};
|
||||
|
||||
module.exports = execute;
|
||||
|
|
|
@ -6,8 +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')
|
||||
'scratch3_operators': require('../blocks/scratch3_operators')
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
var Timer = require('../util/timer');
|
||||
var Thread = require('./thread');
|
||||
var YieldTimers = require('../util/yieldtimers.js');
|
||||
var execute = require('./execute.js');
|
||||
|
||||
function Sequencer (runtime) {
|
||||
|
@ -53,16 +52,8 @@ Sequencer.prototype.stepThreads = function (threads) {
|
|||
// Normal-mode thread: step.
|
||||
this.startThread(activeThread);
|
||||
} else if (activeThread.status === Thread.STATUS_YIELD) {
|
||||
// Yield-mode thread: check if the time has passed.
|
||||
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
|
||||
// Yielding thread: do nothing for this step.
|
||||
continue;
|
||||
}
|
||||
if (activeThread.stack.length === 0 &&
|
||||
activeThread.status === Thread.STATUS_DONE) {
|
||||
|
@ -101,7 +92,7 @@ Sequencer.prototype.startThread = function (thread) {
|
|||
// move to done.
|
||||
if (thread.status === Thread.STATUS_RUNNING &&
|
||||
thread.peekStack() === currentBlockId) {
|
||||
this.proceedThread(thread, currentBlockId);
|
||||
this.proceedThread(thread);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -128,6 +119,27 @@ Sequencer.prototype.stepToSubstack = function (thread, substackNum) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Step a thread into an input reporter, and manage its status appropriately.
|
||||
* @param {!Thread} thread Thread object to step to reporter.
|
||||
* @param {!string} blockId ID of reporter block.
|
||||
* @param {!string} inputName Name of input on parent block.
|
||||
* @return {boolean} True if yielded, false if it finished immediately.
|
||||
*/
|
||||
Sequencer.prototype.stepToReporter = function (thread, blockId, inputName) {
|
||||
var currentStackFrame = thread.peekStackFrame();
|
||||
// Push to the stack to evaluate the reporter block.
|
||||
thread.pushStack(blockId);
|
||||
// Save name of input for `Thread.pushReportedValue`.
|
||||
currentStackFrame.waitingReporter = inputName;
|
||||
// Actually execute the block.
|
||||
this.startThread(thread);
|
||||
// If a reporter yielded, caller must wait for it to unyield.
|
||||
// The value will be populated once the reporter unyields,
|
||||
// and passed up to the currentStackFrame on next execution.
|
||||
return thread.status === Thread.STATUS_YIELD;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish stepping a thread and proceed it to the next block.
|
||||
* @param {!Thread} thread Thread object to proceed.
|
||||
|
@ -136,7 +148,8 @@ 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;
|
||||
// If the block was yielding, move back to running state.
|
||||
thread.status = Thread.STATUS_RUNNING;
|
||||
// Pop from the stack - finished this level of execution.
|
||||
thread.popStack();
|
||||
// Push next connected block, if there is one.
|
||||
|
@ -148,6 +161,10 @@ Sequencer.prototype.proceedThread = function (thread) {
|
|||
while (thread.peekStack() === null && thread.stack.length > 0) {
|
||||
thread.popStack();
|
||||
}
|
||||
// If we still can't find a next block to run, mark the thread as done.
|
||||
if (thread.peekStack() === null) {
|
||||
thread.status = Thread.STATUS_DONE;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Sequencer;
|
||||
|
|
|
@ -28,32 +28,27 @@ function Thread (firstBlock) {
|
|||
* @type {number}
|
||||
*/
|
||||
this.status = 0; /* Thread.STATUS_RUNNING */
|
||||
|
||||
/**
|
||||
* Yield timer ID (for checking when the thread should unyield).
|
||||
* @type {number}
|
||||
*/
|
||||
this.yieldTimerId = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread status for initialized or running thread.
|
||||
* Threads are in this state when the primitive is called for the first time.
|
||||
* This is the default state for a thread - execution should run normally,
|
||||
* stepping from block to block.
|
||||
* @const
|
||||
*/
|
||||
Thread.STATUS_RUNNING = 0;
|
||||
|
||||
/**
|
||||
* Thread status for a yielded thread.
|
||||
* Threads are in this state when a primitive has yielded.
|
||||
* Threads are in this state when a primitive has yielded; execution is paused
|
||||
* until the relevant primitive unyields.
|
||||
* @const
|
||||
*/
|
||||
Thread.STATUS_YIELD = 1;
|
||||
|
||||
/**
|
||||
* Thread status for a finished/done thread.
|
||||
* Thread is moved to this state when the interpreter
|
||||
* can proceed with execution.
|
||||
* Thread is in this state when there are no more blocks to execute.
|
||||
* @const
|
||||
*/
|
||||
Thread.STATUS_DONE = 2;
|
||||
|
@ -67,7 +62,11 @@ Thread.prototype.pushStack = function (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({});
|
||||
this.stackFrames.push({
|
||||
reported: {}, // Collects reported input values.
|
||||
waitingReporter: null, // Name of waiting reporter.
|
||||
executionContext: {} // A context passed to block implementations.
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -97,6 +96,27 @@ Thread.prototype.peekStackFrame = function () {
|
|||
return this.stackFrames[this.stackFrames.length - 1];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get stack frame above the current top.
|
||||
* @return {?Object} Second to last stack frame stored on this thread.
|
||||
*/
|
||||
Thread.prototype.peekParentStackFrame = function () {
|
||||
return this.stackFrames[this.stackFrames.length - 2];
|
||||
};
|
||||
|
||||
/**
|
||||
* Push a reported value to the parent of the current stack frame.
|
||||
* @param {!Any} value Reported value to push.
|
||||
*/
|
||||
Thread.prototype.pushReportedValue = function (value) {
|
||||
var parentStackFrame = this.peekParentStackFrame();
|
||||
if (parentStackFrame) {
|
||||
var waitingReporter = parentStackFrame.waitingReporter;
|
||||
parentStackFrame.reported[waitingReporter] = value;
|
||||
parentStackFrame.waitingReporter = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Yields the thread.
|
||||
*/
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/**
|
||||
* @fileoverview Timers that are synchronized with the Scratch sequencer.
|
||||
*/
|
||||
var Timer = require('./timer');
|
||||
|
||||
function YieldTimers () {}
|
||||
|
||||
/**
|
||||
* Shared collection of timers.
|
||||
* Each timer is a [Function, number] with the callback
|
||||
* and absolute time for it to run.
|
||||
* @type {Object.<number,Array>}
|
||||
*/
|
||||
YieldTimers.timers = {};
|
||||
|
||||
/**
|
||||
* Monotonically increasing timer ID.
|
||||
* @type {number}
|
||||
*/
|
||||
YieldTimers.timerId = 0;
|
||||
|
||||
/**
|
||||
* Utility for measuring time.
|
||||
* @type {!Timer}
|
||||
*/
|
||||
YieldTimers.globalTimer = new Timer();
|
||||
|
||||
/**
|
||||
* The timeout function is passed to primitives and is intended
|
||||
* as a convenient replacement for window.setTimeout.
|
||||
* The sequencer will attempt to resolve the timer every time
|
||||
* the yielded thread would have been stepped.
|
||||
* @param {!Function} callback To be called when the timer is done.
|
||||
* @param {number} timeDelta Time to wait, in ms.
|
||||
* @return {number} Timer ID to be used with other methods.
|
||||
*/
|
||||
YieldTimers.timeout = function (callback, timeDelta) {
|
||||
var id = ++YieldTimers.timerId;
|
||||
YieldTimers.timers[id] = [
|
||||
callback,
|
||||
YieldTimers.globalTimer.time() + timeDelta
|
||||
];
|
||||
return id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempt to resolve a timeout.
|
||||
* If the time has passed, call the callback.
|
||||
* Otherwise, do nothing.
|
||||
* @param {number} id Timer ID to resolve.
|
||||
* @return {boolean} True if the timer has resolved.
|
||||
*/
|
||||
YieldTimers.resolve = function (id) {
|
||||
var timer = YieldTimers.timers[id];
|
||||
if (!timer) {
|
||||
// No such timer.
|
||||
return false;
|
||||
}
|
||||
var callback = timer[0];
|
||||
var time = timer[1];
|
||||
if (YieldTimers.globalTimer.time() < time) {
|
||||
// Not done yet.
|
||||
return false;
|
||||
}
|
||||
// Execute the callback and remove the timer.
|
||||
callback();
|
||||
delete YieldTimers.timers[id];
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reject a timer so the callback never executes.
|
||||
* @param {number} id Timer ID to reject.
|
||||
*/
|
||||
YieldTimers.reject = function (id) {
|
||||
if (YieldTimers.timers[id]) {
|
||||
delete YieldTimers.timers[id];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reject all timers currently stored.
|
||||
* Especially useful for a Scratch "stop."
|
||||
*/
|
||||
YieldTimers.rejectAll = function () {
|
||||
YieldTimers.timers = {};
|
||||
YieldTimers.timerId = 0;
|
||||
};
|
||||
|
||||
module.exports = YieldTimers;
|
Loading…
Add table
Add a link
Reference in a new issue