From 526a26010149dc2be3ec2365f8d12a2dfee28c3c Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 9 Jun 2016 17:08:30 -0400 Subject: [PATCH] Simplify execution by removing nextBlock Everything is managed by the stack, including what the execute() function does. --- src/engine/execute.js | 29 ++++++++--------- src/engine/runtime.js | 1 + src/engine/sequencer.js | 70 ++++++++++++++++++++++++----------------- src/engine/thread.js | 27 ++++++++-------- 4 files changed, 70 insertions(+), 57 deletions(-) diff --git a/src/engine/execute.js b/src/engine/execute.js index 1393b4354..e8ca1417e 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -6,40 +6,43 @@ var YieldTimers = require('../util/yieldtimers.js'); */ var DEBUG_BLOCK_CALLS = true; -var execute = function (sequencer, thread, blockId) { +var execute = function (sequencer, thread) { var runtime = sequencer.runtime; + // Current block to execute is the one on the top of the stack. + var currentBlockId = thread.peekStack(); + var currentStackFrame = thread.peekStackFrame(); + // Save the yield timer ID, in case a primitive makes a new one // @todo hack - perhaps patch this to allow more than one timer per // primitive, for example... var oldYieldTimerId = YieldTimers.timerId; - var opcode = runtime.blocks.getOpcode(blockId); - - // Push the current block to the stack - thread.pushStack(blockId); - var currentStackFrame = thread.getLastStackFrame(); + var opcode = runtime.blocks.getOpcode(currentBlockId); // Generate values for arguments (inputs). var argValues = {}; // Add all fields on this block to the argValues. - var fields = runtime.blocks.getFields(blockId); + var fields = runtime.blocks.getFields(currentBlockId); for (var fieldName in fields) { argValues[fieldName] = fields[fieldName].value; } // Recursively evaluate input blocks. - var inputs = runtime.blocks.getInputs(blockId); + var inputs = runtime.blocks.getInputs(currentBlockId); for (var inputName in inputs) { var input = inputs[inputName]; var inputBlockId = input.block; - var result = execute(sequencer, thread, inputBlockId); + // Push to the stack to evaluate this input. + thread.pushStack(inputBlockId); + var result = execute(sequencer, thread); + thread.popStack(); argValues[input.name] = result; } if (!opcode) { - console.warn('Could not get opcode for block: ' + blockId); + console.warn('Could not get opcode for block: ' + currentBlockId); console.groupEnd(); return; } @@ -62,12 +65,12 @@ var execute = function (sequencer, thread, blockId) { primitiveReturnValue = blockFunction(argValues, { yield: thread.yield.bind(thread), done: function() { - sequencer.proceedThread(thread, blockId); + sequencer.proceedThread(thread); }, timeout: YieldTimers.timeout, stackFrame: currentStackFrame, startSubstack: function () { - sequencer.stepToSubstack(thread, blockId); + sequencer.stepToSubstack(thread); } }); } @@ -86,8 +89,6 @@ var execute = function (sequencer, thread, blockId) { console.log('returned: ', primitiveReturnValue); console.groupEnd(); } - // Pop the stack and stack frame - thread.popStack(); return primitiveReturnValue; } }; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 07debdd64..a56c063cb 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -122,6 +122,7 @@ Runtime.prototype.getOpcodeFunction = function (opcode) { Runtime.prototype._pushThread = function (id) { this.emit(Runtime.STACK_GLOW_ON, id); var thread = new Thread(id); + thread.pushStack(id); this.threads.push(thread); }; diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 168fccf6a..fa4b6e661 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -49,17 +49,18 @@ Sequencer.prototype.stepThreads = function (threads) { var activeThread = threads[i]; if (activeThread.status === Thread.STATUS_RUNNING) { // Normal-mode thread: step. - this.stepThread(activeThread); + this.startThread(activeThread); } else if (activeThread.status === Thread.STATUS_YIELD) { // Yield-mode thread: check if the time has passed. - YieldTimers.resolve(activeThread.yieldTimerId); - numYieldingThreads++; + if (!YieldTimers.resolve(activeThread.yieldTimerId)) { + numYieldingThreads++; + } } else if (activeThread.status === Thread.STATUS_DONE) { // Moved to a done state - finish up activeThread.status = Thread.STATUS_RUNNING; // @todo Deal with the return value } - if (activeThread.nextBlock === null && + if (activeThread.stack.length === 0 && activeThread.status === Thread.STATUS_DONE) { // Finished with this thread - tell runtime to clean it up. inactiveThreads.push(activeThread); @@ -78,21 +79,27 @@ Sequencer.prototype.stepThreads = function (threads) { * Step the requested thread * @param {!Thread} thread Thread object to step */ -Sequencer.prototype.stepThread = function (thread) { - // Save the current block and set the nextBlock. - // If the primitive would like to do control flow, - // it can overwrite nextBlock. - var currentBlockId = thread.nextBlock; +Sequencer.prototype.startThread = function (thread) { + var currentBlockId = thread.peekStack(); if (!currentBlockId || !this.runtime.blocks.getBlock(currentBlockId)) { thread.status = Thread.STATUS_DONE; return; } // Start showing run feedback in the editor. this.runtime.glowBlock(currentBlockId, true); - // Execute the block - execute(this, thread, currentBlockId, false); - // If the block executed without yielding, move to done. - if (thread.status === Thread.STATUS_RUNNING && !thread.switchedStack) { + + // Push the current block to the stack, if executing for the first time. + if (thread.peekStack() != currentBlockId) { + thread.pushStack(currentBlockId); + } + + // Execute the current block + execute(this, thread); + + // If the block executed without yielding and without doing control flow, + // move to done. + if (thread.status === Thread.STATUS_RUNNING && + thread.peekStack() === currentBlockId) { this.proceedThread(thread, currentBlockId); } }; @@ -102,15 +109,16 @@ Sequencer.prototype.stepThread = function (thread) { * @param {!Thread} thread Thread object to step to substack. * @param {string} currentBlockId Block which owns a substack to step to. */ -Sequencer.prototype.stepToSubstack = function (thread, currentBlockId) { - // Set nextBlock to the start of the substack - var substack = this.runtime.blocks.getSubstack(currentBlockId); - if (substack && substack.value) { - thread.nextBlock = substack.value; +Sequencer.prototype.stepToSubstack = function (thread) { + var currentBlockId = thread.peekStack(); + var substackId = this.runtime.blocks.getSubstack(currentBlockId); + if (substackId) { + // Push substack ID to the thread's stack. + thread.pushStack(substackId); } else { - thread.nextBlock = null; + // Push null, so we come back to the current block. + thread.pushStack(null); } - thread.switchedStack = true; }; /** @@ -118,17 +126,21 @@ Sequencer.prototype.stepToSubstack = function (thread, currentBlockId) { * @param {!Thread} thread Thread object to proceed. * @param {string} currentBlockId Block we are finished with. */ -Sequencer.prototype.proceedThread = function (thread, currentBlockId) { - // Stop showing run feedback in the editor. +Sequencer.prototype.proceedThread = function (thread) { + var currentBlockId = thread.peekStack(); + // Mark the status as done and proceed to the next block. this.runtime.glowBlock(currentBlockId, false); - // Mark the thread as done and proceed to the next block. thread.status = Thread.STATUS_DONE; - // Refresh nextBlock in case it has changed during a yield. - thread.nextBlock = this.runtime.blocks.getNextBlock(currentBlockId); - // If none is available, attempt to pop from the stack. - // First attempt to pop from the stack - if (!thread.nextBlock && thread.stack.length > 0) { - thread.nextBlock = thread.popStack(); + // Pop from the stack - finished this level of execution. + thread.popStack(); + // Push next connected block, if there is one. + var nextBlockId = this.runtime.blocks.getNextBlock(currentBlockId); + if (nextBlockId) { + thread.pushStack(nextBlockId); + } + // Pop from the stack until we have a next block. + while (thread.peekStack() === null && thread.stack.length > 0) { + thread.popStack(); } }; diff --git a/src/engine/thread.js b/src/engine/thread.js index 4142ba88b..c98efab48 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -9,11 +9,7 @@ function Thread (firstBlock) { * @type {!string} */ this.topBlock = firstBlock; - /** - * ID of next block that the thread will execute, or null if none. - * @type {?string} - */ - this.nextBlock = firstBlock; + /** * Stack for the thread. When the sequencer enters a control structure, * the block is pushed onto the stack so we know where to exit. @@ -38,12 +34,6 @@ function Thread (firstBlock) { * @type {number} */ this.yieldTimerId = -1; - - /** - * Whether the thread has switched stack in the course of execution. - * @type {boolean} - */ - this.switchedStack = false; } /** @@ -83,7 +73,7 @@ Thread.prototype.pushStack = function (blockId) { /** * Pop last block on the stack and its stack frame. - * @returns {string} Block ID popped from the stack. + * @return {string} Block ID popped from the stack. */ Thread.prototype.popStack = function () { this.stackFrames.pop(); @@ -91,10 +81,19 @@ Thread.prototype.popStack = function () { }; /** - * Get last stack frame. + * Get top stack item. + * @return {?string} Block ID on top of stack. + */ +Thread.prototype.peekStack = function () { + return this.stack[this.stack.length - 1]; +}; + + +/** + * Get top stack frame. * @return {?Object} Last stack frame stored on this thread. */ -Thread.prototype.getLastStackFrame = function () { +Thread.prototype.peekStackFrame = function () { return this.stackFrames[this.stackFrames.length - 1]; };