mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 23:12:24 -05:00
Add Thread.StackFrame class
Use a private StackFrame class to help internally manage use of Stack Frame values and memory. Create params, reported, and executionContext on demand.
This commit is contained in:
parent
16c6868779
commit
0634e962a8
3 changed files with 136 additions and 28 deletions
|
@ -43,7 +43,11 @@ class BlockUtility {
|
||||||
* @type {object}
|
* @type {object}
|
||||||
*/
|
*/
|
||||||
get stackFrame () {
|
get stackFrame () {
|
||||||
return this.thread.peekStackFrame().executionContext;
|
const frame = this.thread.peekStackFrame();
|
||||||
|
if (frame.executionContext === null) {
|
||||||
|
frame.executionContext = {};
|
||||||
|
}
|
||||||
|
return frame.executionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -277,6 +277,9 @@ const execute = function (sequencer, thread, recursiveCall) {
|
||||||
// Actually execute the block.
|
// Actually execute the block.
|
||||||
execute(sequencer, thread, RECURSIVE);
|
execute(sequencer, thread, RECURSIVE);
|
||||||
if (thread.status === Thread.STATUS_PROMISE_WAIT) {
|
if (thread.status === Thread.STATUS_PROMISE_WAIT) {
|
||||||
|
// Create a reported value on the stack frame to store the
|
||||||
|
// already built values.
|
||||||
|
currentStackFrame.reported = {};
|
||||||
// Waiting for the block to resolve, store the current argValues
|
// Waiting for the block to resolve, store the current argValues
|
||||||
// onto a member of the currentStackFrame that can be used once
|
// onto a member of the currentStackFrame that can be used once
|
||||||
// the nested block resolves to rebuild argValues up to this
|
// the nested block resolves to rebuild argValues up to this
|
||||||
|
@ -310,10 +313,10 @@ const execute = function (sequencer, thread, recursiveCall) {
|
||||||
currentStackFrame.justReported = null;
|
currentStackFrame.justReported = null;
|
||||||
// We have rebuilt argValues with all the stored values in the
|
// We have rebuilt argValues with all the stored values in the
|
||||||
// currentStackFrame from the nested block's promise resolving.
|
// currentStackFrame from the nested block's promise resolving.
|
||||||
// Using the reported value from the block we waited on, reset the
|
// Using the reported value from the block we waited on, unset the
|
||||||
// storage member of currentStackFrame so the next execute call at
|
// value. The next execute needing to store reported values will
|
||||||
// this level can use it in a clean state.
|
// creates its own temporary storage.
|
||||||
currentStackFrame.reported = {};
|
currentStackFrame.reported = null;
|
||||||
} else if (typeof currentStackFrame.reported[inputName] !== 'undefined') {
|
} else if (typeof currentStackFrame.reported[inputName] !== 'undefined') {
|
||||||
inputValue = currentStackFrame.reported[inputName];
|
inputValue = currentStackFrame.reported[inputName];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,116 @@
|
||||||
|
/**
|
||||||
|
* Recycle bin for empty stackFrame objects
|
||||||
|
* @type Array<_StackFrame>
|
||||||
|
*/
|
||||||
|
const _stackFrameFreeList = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A frame used for each level of the stack. A general purpose
|
||||||
|
* place to store a bunch of execution context and parameters
|
||||||
|
* @param {boolean} warpMode Whether this level of the stack is warping
|
||||||
|
* @constructor
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
class _StackFrame {
|
||||||
|
constructor (warpMode) {
|
||||||
|
/**
|
||||||
|
* Whether this level of the stack is a loop.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.isLoop = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this level is in warp mode. Is set by some legacy blocks and
|
||||||
|
* "turbo mode"
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.warpMode = warpMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reported value from just executed block.
|
||||||
|
* @type {Any}
|
||||||
|
*/
|
||||||
|
this.justReported = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists reported inputs during async block.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.reported = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of waiting reporter.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.waitingReporter = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procedure parameters.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.params = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A context passed to block implementations.
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
this.executionContext = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset all properties of the frame to pristine null and false states.
|
||||||
|
* Used to recycle.
|
||||||
|
* @return {_StackFrame} this
|
||||||
|
*/
|
||||||
|
reset () {
|
||||||
|
|
||||||
|
this.isLoop = false;
|
||||||
|
this.warpMode = false;
|
||||||
|
this.justReported = null;
|
||||||
|
this.reported = null;
|
||||||
|
this.waitingReporter = null;
|
||||||
|
this.params = null;
|
||||||
|
this.executionContext = null;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reuse an active stack frame in the stack.
|
||||||
|
* @param {?boolean} warpMode defaults to current warpMode
|
||||||
|
* @returns {_StackFrame} this
|
||||||
|
*/
|
||||||
|
reuse (warpMode = this.warpMode) {
|
||||||
|
this.reset();
|
||||||
|
this.warpMode = Boolean(warpMode);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a stack frame object into the recycle bin for reuse.
|
||||||
|
* @param {_StackFrame} stackFrame The frame to reset and recycle.
|
||||||
|
*/
|
||||||
|
static release (stackFrame) {
|
||||||
|
if (typeof stackFrame !== 'undefined') {
|
||||||
|
_stackFrameFreeList.push(stackFrame.reset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
@ -20,7 +133,7 @@ class Thread {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stack frames for the thread. Store metadata for the executing blocks.
|
* Stack frames for the thread. Store metadata for the executing blocks.
|
||||||
* @type {Array.<Object>}
|
* @type {Array.<_StackFrame>}
|
||||||
*/
|
*/
|
||||||
this.stackFrames = [];
|
this.stackFrames = [];
|
||||||
|
|
||||||
|
@ -122,20 +235,8 @@ class Thread {
|
||||||
// Push an empty stack frame, if we need one.
|
// Push an empty stack frame, if we need one.
|
||||||
// Might not, if we just popped the stack.
|
// Might not, if we just popped the stack.
|
||||||
if (this.stack.length > this.stackFrames.length) {
|
if (this.stack.length > this.stackFrames.length) {
|
||||||
// Copy warp mode from any higher level.
|
const parent = this.stackFrames[this.stackFrames.length - 1];
|
||||||
let warpMode = false;
|
this.stackFrames.push(_StackFrame.create(typeof parent !== 'undefined' && parent.warpMode));
|
||||||
if (this.stackFrames.length > 0 && this.stackFrames[this.stackFrames.length - 1]) {
|
|
||||||
warpMode = this.stackFrames[this.stackFrames.length - 1].warpMode;
|
|
||||||
}
|
|
||||||
this.stackFrames.push({
|
|
||||||
isLoop: false, // Whether this level of the stack is a loop.
|
|
||||||
warpMode: warpMode, // Whether this level is in warp mode.
|
|
||||||
justReported: null, // Reported value from just executed block.
|
|
||||||
reported: {}, // Persists reported inputs during async block.
|
|
||||||
waitingReporter: null, // Name of waiting reporter.
|
|
||||||
params: {}, // Procedure parameters.
|
|
||||||
executionContext: {} // A context passed to block implementations.
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -146,13 +247,7 @@ class Thread {
|
||||||
*/
|
*/
|
||||||
reuseStackForNextBlock (blockId) {
|
reuseStackForNextBlock (blockId) {
|
||||||
this.stack[this.stack.length - 1] = blockId;
|
this.stack[this.stack.length - 1] = blockId;
|
||||||
const frame = this.stackFrames[this.stackFrames.length - 1];
|
this.stackFrames[this.stackFrames.length - 1].reuse();
|
||||||
frame.isLoop = false;
|
|
||||||
// frame.warpMode = warpMode; // warp mode stays the same when reusing the stack frame.
|
|
||||||
frame.reported = {};
|
|
||||||
frame.waitingReporter = null;
|
|
||||||
frame.params = {};
|
|
||||||
frame.executionContext = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -160,7 +255,7 @@ class Thread {
|
||||||
* @return {string} Block ID popped from the stack.
|
* @return {string} Block ID popped from the stack.
|
||||||
*/
|
*/
|
||||||
popStack () {
|
popStack () {
|
||||||
this.stackFrames.pop();
|
_StackFrame.release(this.stackFrames.pop());
|
||||||
return this.stack.pop();
|
return this.stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +324,9 @@ class Thread {
|
||||||
*/
|
*/
|
||||||
pushParam (paramName, value) {
|
pushParam (paramName, value) {
|
||||||
const stackFrame = this.peekStackFrame();
|
const stackFrame = this.peekStackFrame();
|
||||||
|
if (stackFrame.params === null) {
|
||||||
|
stackFrame.params = {};
|
||||||
|
}
|
||||||
stackFrame.params[paramName] = value;
|
stackFrame.params[paramName] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +338,9 @@ class Thread {
|
||||||
getParam (paramName) {
|
getParam (paramName) {
|
||||||
for (let i = this.stackFrames.length - 1; i >= 0; i--) {
|
for (let i = this.stackFrames.length - 1; i >= 0; i--) {
|
||||||
const frame = this.stackFrames[i];
|
const frame = this.stackFrames[i];
|
||||||
|
if (frame.params === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (frame.params.hasOwnProperty(paramName)) {
|
if (frame.params.hasOwnProperty(paramName)) {
|
||||||
return frame.params[paramName];
|
return frame.params[paramName];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue