Skip to content

Commit

Permalink
Merge pull request #1118 from mzgoddard/recycle-stack-frame
Browse files Browse the repository at this point in the history
Add Thread.StackFrame class
  • Loading branch information
rschamp committed May 8, 2018
2 parents 784705d + 0634e96 commit b657d6a
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 28 deletions.
6 changes: 5 additions & 1 deletion src/engine/block-utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ class BlockUtility {
* @type {object}
*/
get stackFrame () {
return this.thread.peekStackFrame().executionContext;
const frame = this.thread.peekStackFrame();
if (frame.executionContext === null) {
frame.executionContext = {};
}
return frame.executionContext;
}

/**
Expand Down
11 changes: 7 additions & 4 deletions src/engine/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,9 @@ const execute = function (sequencer, thread, recursiveCall) {
// Actually execute the block.
execute(sequencer, thread, RECURSIVE);
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
// onto a member of the currentStackFrame that can be used once
// the nested block resolves to rebuild argValues up to this
Expand Down Expand Up @@ -310,10 +313,10 @@ const execute = function (sequencer, thread, recursiveCall) {
currentStackFrame.justReported = null;
// We have rebuilt argValues with all the stored values in the
// currentStackFrame from the nested block's promise resolving.
// Using the reported value from the block we waited on, reset the
// storage member of currentStackFrame so the next execute call at
// this level can use it in a clean state.
currentStackFrame.reported = {};
// Using the reported value from the block we waited on, unset the
// value. The next execute needing to store reported values will
// creates its own temporary storage.
currentStackFrame.reported = null;
} else if (typeof currentStackFrame.reported[inputName] !== 'undefined') {
inputValue = currentStackFrame.reported[inputName];
}
Expand Down
147 changes: 124 additions & 23 deletions src/engine/thread.js
Original file line number Diff line number Diff line change
@@ -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.
* @param {?string} firstBlock First block to execute in the thread.
Expand All @@ -20,7 +133,7 @@ class Thread {

/**
* Stack frames for the thread. Store metadata for the executing blocks.
* @type {Array.<Object>}
* @type {Array.<_StackFrame>}
*/
this.stackFrames = [];

Expand Down Expand Up @@ -122,20 +235,8 @@ class Thread {
// Push an empty stack frame, if we need one.
// Might not, if we just popped the stack.
if (this.stack.length > this.stackFrames.length) {
// Copy warp mode from any higher level.
let warpMode = false;
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.
});
const parent = this.stackFrames[this.stackFrames.length - 1];
this.stackFrames.push(_StackFrame.create(typeof parent !== 'undefined' && parent.warpMode));
}
}

Expand All @@ -146,21 +247,15 @@ class Thread {
*/
reuseStackForNextBlock (blockId) {
this.stack[this.stack.length - 1] = blockId;
const frame = this.stackFrames[this.stackFrames.length - 1];
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 = {};
this.stackFrames[this.stackFrames.length - 1].reuse();
}

/**
* Pop last block on the stack and its stack frame.
* @return {string} Block ID popped from the stack.
*/
popStack () {
this.stackFrames.pop();
_StackFrame.release(this.stackFrames.pop());
return this.stack.pop();
}

Expand Down Expand Up @@ -229,6 +324,9 @@ class Thread {
*/
pushParam (paramName, value) {
const stackFrame = this.peekStackFrame();
if (stackFrame.params === null) {
stackFrame.params = {};
}
stackFrame.params[paramName] = value;
}

Expand All @@ -240,6 +338,9 @@ class Thread {
getParam (paramName) {
for (let i = this.stackFrames.length - 1; i >= 0; i--) {
const frame = this.stackFrames[i];
if (frame.params === null) {
continue;
}
if (frame.params.hasOwnProperty(paramName)) {
return frame.params[paramName];
}
Expand Down

0 comments on commit b657d6a

Please sign in to comment.