diff --git a/src/engine/execute.js b/src/engine/execute.js index 6c58cd156..add9c9909 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -1,10 +1,19 @@ +var Thread = require('./thread'); + /** * If set, block calls, args, and return values will be logged to the console. * @const {boolean} */ var DEBUG_BLOCK_CALLS = true; -var execute = function (sequencer, thread) { +/** + * Execute a block. + * @param {!Sequencer} sequencer Which sequencer is executing. + * @param {!Thread} thread Thread which to read and execute. + * @param {string=} opt_waitingInputName If evaluating an input, its name. + * @return {?Any} Reported value, if available immediately. + */ +var execute = function (sequencer, thread, opt_waitingInputName) { var runtime = sequencer.runtime; // Current block to execute is the one on the top of the stack. @@ -27,11 +36,29 @@ 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 a value for this input waiting in the stack frame? + if (currentStackFrame.reported && + currentStackFrame.reported[inputName]) { + // Use that value. + argValues[inputName] = currentStackFrame.reported[inputName]; + } else { + // Otherwise, we need to evaluate the block. + // Push to the stack to evaluate this input. + thread.pushStack(inputBlockId); + if (DEBUG_BLOCK_CALLS) { + console.time('Yielding reporter evaluation'); + } + var result = execute(sequencer, thread, inputName); + // Did the reporter yield? + if (thread.status === Thread.STATUS_YIELD) { + // Reporter yielded; don't pop stack and 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.popStack(); + argValues[inputName] = result; + } } if (!opcode) { @@ -51,21 +78,29 @@ var execute = function (sequencer, thread) { console.log('and stack frame: ', currentStackFrame); } var primitiveReturnValue = null; - // @todo deal with the return value primitiveReturnValue = blockFunction(argValues, { yield: thread.yield.bind(thread), done: function() { sequencer.proceedThread(thread); }, + report: function(reportedValue) { + thread.pushReportedValue(opt_waitingInputName, reportedValue); + if (DEBUG_BLOCK_CALLS) { + console.log('Reported: ', reportedValue, + ' for ', opt_waitingInputName); + console.timeEnd('Yielding reporter evaluation'); + } + sequencer.proceedThread(thread); + }, timeout: thread.addTimeout.bind(thread), - stackFrame: currentStackFrame, + stackFrame: currentStackFrame.executionContext, startSubstack: function (substackNum) { sequencer.stepToSubstack(thread, substackNum); } }); if (DEBUG_BLOCK_CALLS) { console.log('ending stack frame: ', currentStackFrame); - console.log('returned: ', primitiveReturnValue); + console.log('returned immediately: ', primitiveReturnValue); console.groupEnd(); } return primitiveReturnValue; diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 1541d1c6f..e82d4054c 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -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) { @@ -101,7 +100,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); } }; diff --git a/src/engine/thread.js b/src/engine/thread.js index bd991db27..65b251648 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -69,7 +69,10 @@ 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. + executionContext: {} // A context passed to block implementations. + }); } }; @@ -99,6 +102,18 @@ Thread.prototype.peekStackFrame = function () { return this.stackFrames[this.stackFrames.length - 1]; }; +/** + * Push a reported value to the parent of the current stack frame. + * @param {!string} inputName Name of input reported. + * @param {!Any} value Reported value to push. + */ +Thread.prototype.pushReportedValue = function (inputName, value) { + var parentStackFrame = this.stackFrames[this.stackFrames.length - 2]; + if (parentStackFrame) { + parentStackFrame.reported[inputName] = value; + } +}; + /** * Yields the thread. */