mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge pull request #2240 from LLK/revert-2145-raise-params
Revert "Raise params to the next frame when pushing"
This commit is contained in:
commit
f6f97ef3a7
9 changed files with 163 additions and 268 deletions
|
@ -13,43 +13,46 @@ class Scratch3ProcedureBlocks {
|
||||||
*/
|
*/
|
||||||
getPrimitives () {
|
getPrimitives () {
|
||||||
return {
|
return {
|
||||||
// procedures_definition is the top block of a procedure but has no
|
procedures_definition: this.definition,
|
||||||
// effect of its own.
|
|
||||||
procedures_definition: null,
|
|
||||||
|
|
||||||
procedures_call: this.call,
|
procedures_call: this.call,
|
||||||
argument_reporter_string_number: this.argumentReporterStringNumber,
|
argument_reporter_string_number: this.argumentReporterStringNumber,
|
||||||
argument_reporter_boolean: this.argumentReporterBoolean
|
argument_reporter_boolean: this.argumentReporterBoolean
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
definition () {
|
||||||
|
// No-op: execute the blocks.
|
||||||
|
}
|
||||||
|
|
||||||
call (args, util) {
|
call (args, util) {
|
||||||
const procedureCode = args.mutation.proccode;
|
if (!util.stackFrame.executed) {
|
||||||
const paramNamesIdsAndDefaults = util.getProcedureParamNamesIdsAndDefaults(procedureCode);
|
const procedureCode = args.mutation.proccode;
|
||||||
|
const paramNamesIdsAndDefaults = util.getProcedureParamNamesIdsAndDefaults(procedureCode);
|
||||||
|
|
||||||
// If null, procedure could not be found, which can happen if custom
|
// If null, procedure could not be found, which can happen if custom
|
||||||
// block is dragged between sprites without the definition.
|
// block is dragged between sprites without the definition.
|
||||||
// Match Scratch 2.0 behavior and noop.
|
// Match Scratch 2.0 behavior and noop.
|
||||||
if (paramNamesIdsAndDefaults === null) {
|
if (paramNamesIdsAndDefaults === null) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults;
|
|
||||||
|
|
||||||
util.startProcedure(procedureCode);
|
|
||||||
|
|
||||||
// Initialize params for the current stackFrame to {}, even if the procedure does
|
|
||||||
// not take any arguments. This is so that `getParam` down the line does not look
|
|
||||||
// at earlier stack frames for the values of a given parameter (#1729)
|
|
||||||
util.initParams();
|
|
||||||
for (let i = 0; i < paramIds.length; i++) {
|
|
||||||
if (args.hasOwnProperty(paramIds[i])) {
|
|
||||||
util.pushParam(paramNames[i], args[paramIds[i]]);
|
|
||||||
} else {
|
|
||||||
util.pushParam(paramNames[i], paramDefaults[i]);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
const [paramNames, paramIds, paramDefaults] = paramNamesIdsAndDefaults;
|
||||||
|
|
||||||
|
// Initialize params for the current stackFrame to {}, even if the procedure does
|
||||||
|
// not take any arguments. This is so that `getParam` down the line does not look
|
||||||
|
// at earlier stack frames for the values of a given parameter (#1729)
|
||||||
|
util.initParams();
|
||||||
|
for (let i = 0; i < paramIds.length; i++) {
|
||||||
|
if (args.hasOwnProperty(paramIds[i])) {
|
||||||
|
util.pushParam(paramNames[i], args[paramIds[i]]);
|
||||||
|
} else {
|
||||||
|
util.pushParam(paramNames[i], paramDefaults[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
util.stackFrame.executed = true;
|
||||||
|
util.startProcedure(procedureCode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
argumentReporterStringNumber (args, util) {
|
argumentReporterStringNumber (args, util) {
|
||||||
|
|
|
@ -60,7 +60,11 @@ class BlockUtility {
|
||||||
* @type {object}
|
* @type {object}
|
||||||
*/
|
*/
|
||||||
get stackFrame () {
|
get stackFrame () {
|
||||||
return this.thread.getExecutionContext();
|
const frame = this.thread.peekStackFrame();
|
||||||
|
if (frame.executionContext === null) {
|
||||||
|
frame.executionContext = {};
|
||||||
|
}
|
||||||
|
return frame.executionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -279,7 +279,7 @@ class BlockCached {
|
||||||
// Assign opcode isHat and blockFunction data to avoid dynamic lookups.
|
// Assign opcode isHat and blockFunction data to avoid dynamic lookups.
|
||||||
this._isHat = runtime.getIsHat(opcode);
|
this._isHat = runtime.getIsHat(opcode);
|
||||||
this._blockFunction = runtime.getOpcodeFunction(opcode);
|
this._blockFunction = runtime.getOpcodeFunction(opcode);
|
||||||
this._definedBlockFunction = typeof this._blockFunction === 'function';
|
this._definedBlockFunction = typeof this._blockFunction !== 'undefined';
|
||||||
|
|
||||||
// Store the current shadow value if there is a shadow value.
|
// Store the current shadow value if there is a shadow value.
|
||||||
const fieldKeys = Object.keys(fields);
|
const fieldKeys = Object.keys(fields);
|
||||||
|
@ -385,6 +385,7 @@ const execute = function (sequencer, thread) {
|
||||||
|
|
||||||
// Current block to execute is the one on the top of the stack.
|
// Current block to execute is the one on the top of the stack.
|
||||||
const currentBlockId = thread.peekStack();
|
const currentBlockId = thread.peekStack();
|
||||||
|
const currentStackFrame = thread.peekStackFrame();
|
||||||
|
|
||||||
let blockContainer = thread.blockContainer;
|
let blockContainer = thread.blockContainer;
|
||||||
let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);
|
let blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId, BlockCached);
|
||||||
|
@ -403,8 +404,8 @@ const execute = function (sequencer, thread) {
|
||||||
const length = ops.length;
|
const length = ops.length;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
if (thread.reported !== null) {
|
if (currentStackFrame.reported !== null) {
|
||||||
const reported = thread.reported;
|
const reported = currentStackFrame.reported;
|
||||||
// Reinstate all the previous values.
|
// Reinstate all the previous values.
|
||||||
for (; i < reported.length; i++) {
|
for (; i < reported.length; i++) {
|
||||||
const {opCached: oldOpCached, inputValue} = reported[i];
|
const {opCached: oldOpCached, inputValue} = reported[i];
|
||||||
|
@ -440,7 +441,7 @@ const execute = function (sequencer, thread) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The reporting block must exist and must be the next one in the sequence of operations.
|
// The reporting block must exist and must be the next one in the sequence of operations.
|
||||||
if (thread.justReported !== null && ops[i] && ops[i].id === thread.reportingBlockId) {
|
if (thread.justReported !== null && ops[i] && ops[i].id === currentStackFrame.reporting) {
|
||||||
const opCached = ops[i];
|
const opCached = ops[i];
|
||||||
const inputValue = thread.justReported;
|
const inputValue = thread.justReported;
|
||||||
|
|
||||||
|
@ -461,8 +462,8 @@ const execute = function (sequencer, thread) {
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
thread.reportingBlockId = null;
|
currentStackFrame.reporting = null;
|
||||||
thread.reported = null;
|
currentStackFrame.reported = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; i < length; i++) {
|
for (; i < length; i++) {
|
||||||
|
@ -517,8 +518,8 @@ const execute = function (sequencer, thread) {
|
||||||
// operation if it is promise waiting will set its parent value at
|
// operation if it is promise waiting will set its parent value at
|
||||||
// that time.
|
// that time.
|
||||||
thread.justReported = null;
|
thread.justReported = null;
|
||||||
thread.reportingBlockId = ops[i].id;
|
currentStackFrame.reporting = ops[i].id;
|
||||||
thread.reported = ops.slice(0, i).map(reportedCached => {
|
currentStackFrame.reported = ops.slice(0, i).map(reportedCached => {
|
||||||
const inputName = reportedCached._parentKey;
|
const inputName = reportedCached._parentKey;
|
||||||
const reportedValues = reportedCached._parentValues;
|
const reportedValues = reportedCached._parentValues;
|
||||||
|
|
||||||
|
|
|
@ -724,7 +724,7 @@ class Runtime extends EventEmitter {
|
||||||
if (packageObject.getPrimitives) {
|
if (packageObject.getPrimitives) {
|
||||||
const packagePrimitives = packageObject.getPrimitives();
|
const packagePrimitives = packageObject.getPrimitives();
|
||||||
for (const op in packagePrimitives) {
|
for (const op in packagePrimitives) {
|
||||||
if (typeof packagePrimitives[op] === 'function') {
|
if (packagePrimitives.hasOwnProperty(op)) {
|
||||||
this._primitives[op] =
|
this._primitives[op] =
|
||||||
packagePrimitives[op].bind(packageObject);
|
packagePrimitives[op].bind(packageObject);
|
||||||
}
|
}
|
||||||
|
@ -1567,7 +1567,7 @@ class Runtime extends EventEmitter {
|
||||||
isActiveThread (thread) {
|
isActiveThread (thread) {
|
||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
thread.stackFrame !== null &&
|
thread.stack.length > 0 &&
|
||||||
thread.status !== Thread.STATUS_DONE) &&
|
thread.status !== Thread.STATUS_DONE) &&
|
||||||
this.threads.indexOf(thread) > -1);
|
this.threads.indexOf(thread) > -1);
|
||||||
}
|
}
|
||||||
|
@ -1744,20 +1744,11 @@ class Runtime extends EventEmitter {
|
||||||
// Start the thread with this top block.
|
// Start the thread with this top block.
|
||||||
newThreads.push(this._pushThread(topBlockId, target));
|
newThreads.push(this._pushThread(topBlockId, target));
|
||||||
}, optTarget);
|
}, optTarget);
|
||||||
// For compatibility with Scratch 2, edge triggered hats need to be
|
// For compatibility with Scratch 2, edge triggered hats need to be processed before
|
||||||
// processed before threads are stepped. See ScratchRuntime.as for
|
// threads are stepped. See ScratchRuntime.as for original implementation
|
||||||
// original implementation.
|
|
||||||
//
|
|
||||||
// TODO: Move the execute call to sequencer. Maybe in a method call
|
|
||||||
// stepHat or stepOne.
|
|
||||||
newThreads.forEach(thread => {
|
newThreads.forEach(thread => {
|
||||||
execute(this.sequencer, thread);
|
execute(this.sequencer, thread);
|
||||||
if (thread.status !== Thread.STATUS_DONE) {
|
thread.goToNextBlock();
|
||||||
thread.goToNextBlock();
|
|
||||||
if (thread.stackFrame === null) {
|
|
||||||
this.sequencer.retireThread(thread);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return newThreads;
|
return newThreads;
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,7 @@ class Sequencer {
|
||||||
for (let i = 0; i < threads.length; i++) {
|
for (let i = 0; i < threads.length; i++) {
|
||||||
const activeThread = this.activeThread = threads[i];
|
const activeThread = this.activeThread = threads[i];
|
||||||
// Check if the thread is done so it is not executed.
|
// Check if the thread is done so it is not executed.
|
||||||
if (activeThread.stackFrame === null ||
|
if (activeThread.stack.length === 0 ||
|
||||||
activeThread.status === Thread.STATUS_DONE) {
|
activeThread.status === Thread.STATUS_DONE) {
|
||||||
// Finished with this thread.
|
// Finished with this thread.
|
||||||
stoppedThread = true;
|
stoppedThread = true;
|
||||||
|
@ -137,7 +137,7 @@ class Sequencer {
|
||||||
}
|
}
|
||||||
// Check if the thread completed while it just stepped to make
|
// Check if the thread completed while it just stepped to make
|
||||||
// sure we remove it before the next iteration of all threads.
|
// sure we remove it before the next iteration of all threads.
|
||||||
if (activeThread.stackFrame === null ||
|
if (activeThread.stack.length === 0 ||
|
||||||
activeThread.status === Thread.STATUS_DONE) {
|
activeThread.status === Thread.STATUS_DONE) {
|
||||||
// Finished with this thread.
|
// Finished with this thread.
|
||||||
stoppedThread = true;
|
stoppedThread = true;
|
||||||
|
@ -156,7 +156,7 @@ class Sequencer {
|
||||||
let nextActiveThread = 0;
|
let nextActiveThread = 0;
|
||||||
for (let i = 0; i < this.runtime.threads.length; i++) {
|
for (let i = 0; i < this.runtime.threads.length; i++) {
|
||||||
const thread = this.runtime.threads[i];
|
const thread = this.runtime.threads[i];
|
||||||
if (thread.stackFrame !== null &&
|
if (thread.stack.length !== 0 &&
|
||||||
thread.status !== Thread.STATUS_DONE) {
|
thread.status !== Thread.STATUS_DONE) {
|
||||||
this.runtime.threads[nextActiveThread] = thread;
|
this.runtime.threads[nextActiveThread] = thread;
|
||||||
nextActiveThread++;
|
nextActiveThread++;
|
||||||
|
@ -184,7 +184,7 @@ class Sequencer {
|
||||||
thread.popStack();
|
thread.popStack();
|
||||||
|
|
||||||
// Did the null follow a hat block?
|
// Did the null follow a hat block?
|
||||||
if (thread.peekStackFrame() === null) {
|
if (thread.stack.length === 0) {
|
||||||
thread.status = Thread.STATUS_DONE;
|
thread.status = Thread.STATUS_DONE;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ class Sequencer {
|
||||||
while (!thread.peekStack()) {
|
while (!thread.peekStack()) {
|
||||||
thread.popStack();
|
thread.popStack();
|
||||||
|
|
||||||
if (thread.stackFrame === null) {
|
if (thread.stack.length === 0) {
|
||||||
// No more stack to run!
|
// No more stack to run!
|
||||||
thread.status = Thread.STATUS_DONE;
|
thread.status = Thread.STATUS_DONE;
|
||||||
return;
|
return;
|
||||||
|
@ -299,11 +299,7 @@ class Sequencer {
|
||||||
currentBlockId,
|
currentBlockId,
|
||||||
branchNum
|
branchNum
|
||||||
);
|
);
|
||||||
if (isLoop) {
|
thread.peekStackFrame().isLoop = isLoop;
|
||||||
const stackFrame = thread.peekStackFrame();
|
|
||||||
stackFrame.needsReset = true;
|
|
||||||
stackFrame.isLoop = true;
|
|
||||||
}
|
|
||||||
if (branchId) {
|
if (branchId) {
|
||||||
// Push branch ID to the thread's stack.
|
// Push branch ID to the thread's stack.
|
||||||
thread.pushStack(branchId);
|
thread.pushStack(branchId);
|
||||||
|
@ -365,9 +361,7 @@ class Sequencer {
|
||||||
*/
|
*/
|
||||||
retireThread (thread) {
|
retireThread (thread) {
|
||||||
thread.stack = [];
|
thread.stack = [];
|
||||||
thread.pointer = null;
|
thread.stackFrame = [];
|
||||||
thread.stackFrames = [];
|
|
||||||
thread.stackFrame = null;
|
|
||||||
thread.requestScriptGlowInFrame = false;
|
thread.requestScriptGlowInFrame = false;
|
||||||
thread.status = Thread.STATUS_DONE;
|
thread.status = Thread.STATUS_DONE;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,23 +5,14 @@
|
||||||
const _stackFrameFreeList = [];
|
const _stackFrameFreeList = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default params object for stack frames outside of a procedure.
|
* A frame used for each level of the stack. A general purpose
|
||||||
*
|
* place to store a bunch of execution context and parameters
|
||||||
* StackFrame.params uses a null prototype object. It does not have Object
|
* @param {boolean} warpMode Whether this level of the stack is warping
|
||||||
* methods like hasOwnProperty. With a null prototype
|
|
||||||
* `typeof params[key] !== 'undefined'` has similar behaviour to hasOwnProperty.
|
|
||||||
* @type {object}
|
|
||||||
*/
|
|
||||||
const defaultParams = Object.create(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A frame used for each level of the stack. A general purpose place to store a
|
|
||||||
* bunch of execution contexts and parameters.
|
|
||||||
* @constructor
|
* @constructor
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
class _StackFrame {
|
class _StackFrame {
|
||||||
constructor () {
|
constructor (warpMode) {
|
||||||
/**
|
/**
|
||||||
* Whether this level of the stack is a loop.
|
* Whether this level of the stack is a loop.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
@ -29,62 +20,90 @@ class _StackFrame {
|
||||||
this.isLoop = false;
|
this.isLoop = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this level is in warp mode. Set to true by the sequencer for
|
* Whether this level is in warp mode. Is set by some legacy blocks and
|
||||||
* some procedures.
|
* "turbo mode"
|
||||||
*
|
|
||||||
* After being set to true at the beginning of a procedure a thread
|
|
||||||
* will be in warpMode until it pops a stack frame to reveal one that
|
|
||||||
* is not in warpMode. Either this value is always false for a stack
|
|
||||||
* frame or always true after a procedure sets it.
|
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
this.warpMode = false;
|
this.warpMode = warpMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reported value from just executed block.
|
||||||
|
* @type {Any}
|
||||||
|
*/
|
||||||
|
this.justReported = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active block that is waiting on a promise.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.reporting = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists reported inputs during async block.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.reported = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of waiting reporter.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.waitingReporter = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Procedure parameters.
|
* Procedure parameters.
|
||||||
*
|
|
||||||
* After being set by a procedure these values do not change and they
|
|
||||||
* will be copied to deeper stack frames.
|
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
this.params = defaultParams;
|
this.params = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A context passed to block implementations.
|
* A context passed to block implementations.
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
this.executionContext = {};
|
this.executionContext = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* Has this frame changed and need a reset?
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
this.needsReset = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset some properties of the frame to default values. Used to recycle.
|
* Reset all properties of the frame to pristine null and false states.
|
||||||
|
* Used to recycle.
|
||||||
* @return {_StackFrame} this
|
* @return {_StackFrame} this
|
||||||
*/
|
*/
|
||||||
reset () {
|
reset () {
|
||||||
|
|
||||||
this.isLoop = false;
|
this.isLoop = false;
|
||||||
this.executionContext = {};
|
this.warpMode = false;
|
||||||
this.needsReset = false;
|
this.justReported = null;
|
||||||
|
this.reported = null;
|
||||||
|
this.waitingReporter = null;
|
||||||
|
this.params = null;
|
||||||
|
this.executionContext = null;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create or recycle a stack frame object.
|
* Reuse an active stack frame in the stack.
|
||||||
* @param {_StackFrame} parent Parent frame to copy "immutable" values.
|
* @param {?boolean} warpMode defaults to current warpMode
|
||||||
* @returns {_StackFrame} The clean stack frame with correct warpMode
|
* @returns {_StackFrame} this
|
||||||
* setting.
|
|
||||||
*/
|
*/
|
||||||
static create (parent) {
|
reuse (warpMode = this.warpMode) {
|
||||||
const stackFrame = _stackFrameFreeList.pop() || new _StackFrame();
|
this.reset();
|
||||||
stackFrame.warpMode = parent.warpMode;
|
this.warpMode = Boolean(warpMode);
|
||||||
stackFrame.params = parent.params;
|
return this;
|
||||||
return stackFrame;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create or recycle a stack frame object.
|
||||||
|
* @param {boolean} warpMode Enable warpMode on this frame.
|
||||||
|
* @returns {_StackFrame} The clean stack frame with correct warpMode setting.
|
||||||
|
*/
|
||||||
|
static create (warpMode) {
|
||||||
|
const stackFrame = _stackFrameFreeList.pop();
|
||||||
|
if (typeof stackFrame !== 'undefined') {
|
||||||
|
stackFrame.warpMode = Boolean(warpMode);
|
||||||
|
return stackFrame;
|
||||||
|
}
|
||||||
|
return new _StackFrame(warpMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,22 +111,12 @@ class _StackFrame {
|
||||||
* @param {_StackFrame} stackFrame The frame to reset and recycle.
|
* @param {_StackFrame} stackFrame The frame to reset and recycle.
|
||||||
*/
|
*/
|
||||||
static release (stackFrame) {
|
static release (stackFrame) {
|
||||||
if (stackFrame !== null) {
|
if (typeof stackFrame !== 'undefined') {
|
||||||
_stackFrameFreeList.push(
|
_stackFrameFreeList.push(stackFrame.reset());
|
||||||
stackFrame.needsReset ? stackFrame.reset() : stackFrame
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The initial stack frame for all threads. A call to pushStack will create the
|
|
||||||
* first to be used frame for a thread. That first frame will use the initial
|
|
||||||
* values from initialStackFrame.
|
|
||||||
* @type {_StackFrame}
|
|
||||||
*/
|
|
||||||
const initialStackFrame = new _StackFrame();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A thread is a running stack context and all the metadata needed.
|
* A thread is a running stack context and all the metadata needed.
|
||||||
* @param {?string} firstBlock First block to execute in the thread.
|
* @param {?string} firstBlock First block to execute in the thread.
|
||||||
|
@ -128,25 +137,12 @@ class Thread {
|
||||||
*/
|
*/
|
||||||
this.stack = [];
|
this.stack = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* The "instruction" pointer the thread is currently at. This
|
|
||||||
* determines what block is executed.
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.pointer = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stack frames for the thread. Store metadata for the executing blocks.
|
* Stack frames for the thread. Store metadata for the executing blocks.
|
||||||
* @type {Array.<_StackFrame>}
|
* @type {Array.<_StackFrame>}
|
||||||
*/
|
*/
|
||||||
this.stackFrames = [];
|
this.stackFrames = [];
|
||||||
|
|
||||||
/**
|
|
||||||
* The current stack frame that goes along with the pointer.
|
|
||||||
* @type {_StackFrame}
|
|
||||||
*/
|
|
||||||
this.stackFrame = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of the thread, one of three states (below)
|
* Status of the thread, one of three states (below)
|
||||||
* @type {number}
|
* @type {number}
|
||||||
|
@ -190,25 +186,7 @@ class Thread {
|
||||||
*/
|
*/
|
||||||
this.warpTimer = null;
|
this.warpTimer = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The value just reported by a promise.
|
|
||||||
* @type {*}
|
|
||||||
*/
|
|
||||||
this.justReported = null;
|
this.justReported = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* The id of the block that we will report the promise resolved value
|
|
||||||
* for.
|
|
||||||
* @type {string}
|
|
||||||
*/
|
|
||||||
this.reportingBlockId = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The already reported values in a sequence of blocks to restore when
|
|
||||||
* the awaited promise resolves.
|
|
||||||
* @type {Array.<*>}
|
|
||||||
*/
|
|
||||||
this.reported = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -261,16 +239,12 @@ class Thread {
|
||||||
* @param {string} blockId Block ID to push to stack.
|
* @param {string} blockId Block ID to push to stack.
|
||||||
*/
|
*/
|
||||||
pushStack (blockId) {
|
pushStack (blockId) {
|
||||||
if (this.stackFrame === null) {
|
this.stack.push(blockId);
|
||||||
this.pointer = blockId;
|
// Push an empty stack frame, if we need one.
|
||||||
this.stackFrame = _StackFrame.create(initialStackFrame);
|
// Might not, if we just popped the stack.
|
||||||
} else {
|
if (this.stack.length > this.stackFrames.length) {
|
||||||
this.stack.push(this.pointer);
|
const parent = this.stackFrames[this.stackFrames.length - 1];
|
||||||
this.pointer = blockId;
|
this.stackFrames.push(_StackFrame.create(typeof parent !== 'undefined' && parent.warpMode));
|
||||||
|
|
||||||
const parent = this.stackFrame;
|
|
||||||
this.stackFrames.push(parent);
|
|
||||||
this.stackFrame = _StackFrame.create(parent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,47 +254,34 @@ class Thread {
|
||||||
* @param {string} blockId Block ID to push to stack.
|
* @param {string} blockId Block ID to push to stack.
|
||||||
*/
|
*/
|
||||||
reuseStackForNextBlock (blockId) {
|
reuseStackForNextBlock (blockId) {
|
||||||
this.pointer = blockId;
|
this.stack[this.stack.length - 1] = blockId;
|
||||||
if (this.stackFrame.needsReset) this.stackFrame.reset();
|
this.stackFrames[this.stackFrames.length - 1].reuse();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the instruction pointer to the last value before this stack of
|
* Pop last block on the stack and its stack frame.
|
||||||
* blocks was pushed and executed.
|
* @return {string} Block ID popped from the stack.
|
||||||
* @return {?string} Block ID popped from the stack.
|
|
||||||
*/
|
*/
|
||||||
popStack () {
|
popStack () {
|
||||||
const lastPointer = this.pointer;
|
_StackFrame.release(this.stackFrames.pop());
|
||||||
this.pointer = this.stack.pop() || null;
|
return this.stack.pop();
|
||||||
_StackFrame.release(this.stackFrame);
|
|
||||||
this.stackFrame = this.stackFrames.pop() || null;
|
|
||||||
return lastPointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the instruction pointer to the last procedure call block and resume
|
* Pop back down the stack frame until we hit a procedure call or the stack frame is emptied
|
||||||
* execution there or to the end of this thread and stop executing this
|
|
||||||
* thread.
|
|
||||||
*/
|
*/
|
||||||
stopThisScript () {
|
stopThisScript () {
|
||||||
let blockID = this.peekStack();
|
let blockID = this.peekStack();
|
||||||
while (blockID !== null) {
|
while (blockID !== null) {
|
||||||
const block = this.target.blocks.getBlock(blockID);
|
const block = this.target.blocks.getBlock(blockID);
|
||||||
if (typeof block !== 'undefined' && block.opcode === 'procedures_call') {
|
if (typeof block !== 'undefined' && block.opcode === 'procedures_call') {
|
||||||
// If we do not push this null, Sequencer will not step to the
|
|
||||||
// next block and will erroneously call the procedure a second
|
|
||||||
// time.
|
|
||||||
//
|
|
||||||
// By pushing null, Sequencer will treat it as the end of a
|
|
||||||
// substack, pop the stack and step to the next block.
|
|
||||||
this.pushStack(null);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.popStack();
|
this.popStack();
|
||||||
blockID = this.peekStack();
|
blockID = this.peekStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stackFrame === null) {
|
if (this.stack.length === 0) {
|
||||||
// Clean up!
|
// Clean up!
|
||||||
this.requestScriptGlowInFrame = false;
|
this.requestScriptGlowInFrame = false;
|
||||||
this.status = Thread.STATUS_DONE;
|
this.status = Thread.STATUS_DONE;
|
||||||
|
@ -332,7 +293,7 @@ class Thread {
|
||||||
* @return {?string} Block ID on top of stack.
|
* @return {?string} Block ID on top of stack.
|
||||||
*/
|
*/
|
||||||
peekStack () {
|
peekStack () {
|
||||||
return this.pointer;
|
return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -341,7 +302,7 @@ class Thread {
|
||||||
* @return {?object} Last stack frame stored on this thread.
|
* @return {?object} Last stack frame stored on this thread.
|
||||||
*/
|
*/
|
||||||
peekStackFrame () {
|
peekStackFrame () {
|
||||||
return this.stackFrame;
|
return this.stackFrames.length > 0 ? this.stackFrames[this.stackFrames.length - 1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -349,7 +310,7 @@ class Thread {
|
||||||
* @return {?object} Second to last stack frame stored on this thread.
|
* @return {?object} Second to last stack frame stored on this thread.
|
||||||
*/
|
*/
|
||||||
peekParentStackFrame () {
|
peekParentStackFrame () {
|
||||||
return this.stackFrames.length > 0 ? this.stackFrames[this.stackFrames.length - 1] : null;
|
return this.stackFrames.length > 1 ? this.stackFrames[this.stackFrames.length - 2] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -360,22 +321,14 @@ class Thread {
|
||||||
this.justReported = typeof value === 'undefined' ? null : value;
|
this.justReported = typeof value === 'undefined' ? null : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an execution context for a block to use.
|
|
||||||
* @returns {object} the execution context
|
|
||||||
*/
|
|
||||||
getExecutionContext () {
|
|
||||||
const frame = this.stackFrame;
|
|
||||||
frame.needsReset = true;
|
|
||||||
return frame.executionContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize procedure parameters on this stack frame.
|
* Initialize procedure parameters on this stack frame.
|
||||||
*/
|
*/
|
||||||
initParams () {
|
initParams () {
|
||||||
const stackFrame = this.stackFrame;
|
const stackFrame = this.peekStackFrame();
|
||||||
stackFrame.params = Object.create(null);
|
if (stackFrame.params === null) {
|
||||||
|
stackFrame.params = {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -385,7 +338,7 @@ class Thread {
|
||||||
* @param {*} value Value to set for parameter.
|
* @param {*} value Value to set for parameter.
|
||||||
*/
|
*/
|
||||||
pushParam (paramName, value) {
|
pushParam (paramName, value) {
|
||||||
const stackFrame = this.stackFrame;
|
const stackFrame = this.peekStackFrame();
|
||||||
stackFrame.params[paramName] = value;
|
stackFrame.params[paramName] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -395,9 +348,15 @@ class Thread {
|
||||||
* @return {*} value Value for parameter.
|
* @return {*} value Value for parameter.
|
||||||
*/
|
*/
|
||||||
getParam (paramName) {
|
getParam (paramName) {
|
||||||
const stackFrame = this.stackFrame;
|
for (let i = this.stackFrames.length - 1; i >= 0; i--) {
|
||||||
if (typeof stackFrame.params[paramName] !== 'undefined') {
|
const frame = this.stackFrames[i];
|
||||||
return stackFrame.params[paramName];
|
if (frame.params === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (frame.params.hasOwnProperty(paramName)) {
|
||||||
|
return frame.params[paramName];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -417,26 +376,26 @@ class Thread {
|
||||||
* where execution proceeds from one block to the next.
|
* where execution proceeds from one block to the next.
|
||||||
*/
|
*/
|
||||||
goToNextBlock () {
|
goToNextBlock () {
|
||||||
const nextBlockId = this.target.blocks.getNextBlock(this.pointer);
|
const nextBlockId = this.target.blocks.getNextBlock(this.peekStack());
|
||||||
this.reuseStackForNextBlock(nextBlockId);
|
this.reuseStackForNextBlock(nextBlockId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to determine whether a procedure call is recursive, by examining
|
* Attempt to determine whether a procedure call is recursive,
|
||||||
* the stack.
|
* by examining the stack.
|
||||||
* @param {!string} procedureCode Procedure code of procedure being called.
|
* @param {!string} procedureCode Procedure code of procedure being called.
|
||||||
* @return {boolean} True if the call appears recursive.
|
* @return {boolean} True if the call appears recursive.
|
||||||
*/
|
*/
|
||||||
isRecursiveCall (procedureCode) {
|
isRecursiveCall (procedureCode) {
|
||||||
const stackHeight = this.stack.length;
|
let callCount = 5; // Max number of enclosing procedure calls to examine.
|
||||||
// Limit the number of stack levels that are examined for procedures.
|
const sp = this.stack.length - 1;
|
||||||
const stackBottom = Math.max(stackHeight - 5, 0);
|
for (let i = sp - 1; i >= 0; i--) {
|
||||||
for (let i = stackHeight - 1; i >= stackBottom; i--) {
|
|
||||||
const block = this.target.blocks.getBlock(this.stack[i]);
|
const block = this.target.blocks.getBlock(this.stack[i]);
|
||||||
if (block.opcode === 'procedures_call' &&
|
if (block.opcode === 'procedures_call' &&
|
||||||
block.mutation.proccode === procedureCode) {
|
block.mutation.proccode === procedureCode) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (--callCount < 0) return false;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -120,40 +120,16 @@ test('stepToBranch', t => {
|
||||||
const r = new Runtime();
|
const r = new Runtime();
|
||||||
const s = new Sequencer(r);
|
const s = new Sequencer(r);
|
||||||
const th = generateThread(r);
|
const th = generateThread(r);
|
||||||
|
|
||||||
// Push substack 2 (null).
|
|
||||||
s.stepToBranch(th, 2, false);
|
s.stepToBranch(th, 2, false);
|
||||||
t.strictEquals(th.peekStack(), null);
|
t.strictEquals(th.peekStack(), null);
|
||||||
th.popStack();
|
th.popStack();
|
||||||
t.strictEquals(th.peekStackFrame().isLoop, false);
|
|
||||||
// Push substack 1 (null).
|
|
||||||
s.stepToBranch(th, 1, false);
|
s.stepToBranch(th, 1, false);
|
||||||
t.strictEquals(th.peekStack(), null);
|
t.strictEquals(th.peekStack(), null);
|
||||||
th.popStack();
|
th.popStack();
|
||||||
t.strictEquals(th.peekStackFrame().isLoop, false);
|
|
||||||
// Push loop substack (null).
|
|
||||||
s.stepToBranch(th, 1, true);
|
|
||||||
t.strictEquals(th.peekStack(), null);
|
|
||||||
th.popStack();
|
th.popStack();
|
||||||
t.strictEquals(th.peekStackFrame().isLoop, true);
|
|
||||||
// isLoop resets when thread goes to next block.
|
|
||||||
th.goToNextBlock();
|
|
||||||
t.strictEquals(th.peekStackFrame().isLoop, false);
|
|
||||||
th.popStack();
|
|
||||||
// Push substack 1 (not null).
|
|
||||||
s.stepToBranch(th, 1, false);
|
s.stepToBranch(th, 1, false);
|
||||||
t.notEquals(th.peekStack(), null);
|
t.notEquals(th.peekStack(), null);
|
||||||
th.popStack();
|
|
||||||
t.strictEquals(th.peekStackFrame().isLoop, false);
|
|
||||||
// Push loop substack (not null).
|
|
||||||
s.stepToBranch(th, 1, true);
|
|
||||||
t.notEquals(th.peekStack(), null);
|
|
||||||
th.popStack();
|
|
||||||
t.strictEquals(th.peekStackFrame().isLoop, true);
|
|
||||||
// isLoop resets when thread goes to next block.
|
|
||||||
th.goToNextBlock();
|
|
||||||
t.strictEquals(th.peekStackFrame().isLoop, false);
|
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -161,7 +137,7 @@ test('retireThread', t => {
|
||||||
const r = new Runtime();
|
const r = new Runtime();
|
||||||
const s = new Sequencer(r);
|
const s = new Sequencer(r);
|
||||||
const th = generateThread(r);
|
const th = generateThread(r);
|
||||||
t.strictEquals(th.stack.length, 11);
|
t.strictEquals(th.stack.length, 12);
|
||||||
s.retireThread(th);
|
s.retireThread(th);
|
||||||
t.strictEquals(th.stack.length, 0);
|
t.strictEquals(th.stack.length, 0);
|
||||||
t.strictEquals(th.status, Thread.STATUS_DONE);
|
t.strictEquals(th.status, Thread.STATUS_DONE);
|
||||||
|
|
|
@ -40,7 +40,7 @@ test('popStack', t => {
|
||||||
const th = new Thread('arbitraryString');
|
const th = new Thread('arbitraryString');
|
||||||
th.pushStack('arbitraryString');
|
th.pushStack('arbitraryString');
|
||||||
t.strictEquals(th.popStack(), 'arbitraryString');
|
t.strictEquals(th.popStack(), 'arbitraryString');
|
||||||
t.strictEquals(th.popStack(), null);
|
t.strictEquals(th.popStack(), undefined);
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
@ -209,54 +209,21 @@ test('stopThisScript', t => {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0
|
y: 0
|
||||||
};
|
};
|
||||||
const block3 = {fields: Object,
|
|
||||||
id: 'thirdString',
|
|
||||||
inputs: Object,
|
|
||||||
STEPS: Object,
|
|
||||||
block: 'fakeBlock',
|
|
||||||
name: 'STEPS',
|
|
||||||
next: null,
|
|
||||||
opcode: 'procedures_definition',
|
|
||||||
mutation: {proccode: 'fakeCode'},
|
|
||||||
parent: null,
|
|
||||||
shadow: false,
|
|
||||||
topLevel: true,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
rt.blocks.createBlock(block1);
|
rt.blocks.createBlock(block1);
|
||||||
rt.blocks.createBlock(block2);
|
rt.blocks.createBlock(block2);
|
||||||
rt.blocks.createBlock(block3);
|
|
||||||
th.target = rt;
|
th.target = rt;
|
||||||
|
|
||||||
th.stopThisScript();
|
th.stopThisScript();
|
||||||
t.strictEquals(th.peekStack(), null);
|
t.strictEquals(th.peekStack(), null);
|
||||||
t.strictEquals(th.peekStackFrame(), null);
|
|
||||||
|
|
||||||
th.pushStack('arbitraryString');
|
th.pushStack('arbitraryString');
|
||||||
t.strictEquals(th.peekStack(), 'arbitraryString');
|
t.strictEquals(th.peekStack(), 'arbitraryString');
|
||||||
t.notEqual(th.peekStackFrame(), null);
|
|
||||||
th.stopThisScript();
|
th.stopThisScript();
|
||||||
t.strictEquals(th.peekStack(), null);
|
t.strictEquals(th.peekStack(), null);
|
||||||
t.strictEquals(th.peekStackFrame(), null);
|
|
||||||
|
|
||||||
th.pushStack('arbitraryString');
|
th.pushStack('arbitraryString');
|
||||||
th.pushStack('secondString');
|
th.pushStack('secondString');
|
||||||
th.stopThisScript();
|
th.stopThisScript();
|
||||||
t.strictEquals(th.peekStack(), null);
|
t.strictEquals(th.peekStack(), 'secondString');
|
||||||
t.same(th.stack, ['arbitraryString', 'secondString']);
|
|
||||||
t.notEqual(th.peekStackFrame(), null);
|
|
||||||
|
|
||||||
while (th.peekStackFrame()) th.popStack();
|
|
||||||
|
|
||||||
th.pushStack('arbitraryString');
|
|
||||||
th.pushStack('secondString');
|
|
||||||
th.pushStack('thirdString');
|
|
||||||
th.stopThisScript();
|
|
||||||
t.strictEquals(th.peekStack(), null);
|
|
||||||
t.same(th.stack, ['arbitraryString', 'secondString']);
|
|
||||||
t.notEqual(th.peekStackFrame(), null);
|
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue