Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Thread.StackFrame class #1118

Merged
merged 1 commit into from
May 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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());

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

This comment was marked as abuse.

}
}
}

/**
* 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