From d2c637a6211c23caa5b2c6bee3450735492fb041 Mon Sep 17 00:00:00 2001 From: "Michael \"Z\" Goddard" Date: Thu, 12 Jul 2018 18:19:05 -0400 Subject: [PATCH] fix asynchronous block execution Freeze promise waiting blocks into an array of reported values. Thaw those reported values into parent values of matching blocks by id. If the reporting block was waiting on a promise and reported a value assign it to its parent value at that future time. --- src/engine/execute.js | 74 +++++++++++++++++++++++++++++++++---------- src/engine/thread.js | 6 ++++ 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/engine/execute.js b/src/engine/execute.js index 9c53d66c3..be35e31b5 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -102,7 +102,7 @@ const handleReport = function (resolvedValue, sequencer, thread, blockCached) { } }; -const handlePromise = (primitiveReportedValue, sequencer, thread, blockCached) => { +const handlePromise = (primitiveReportedValue, sequencer, thread, blockCached, lastOperation) => { if (thread.status === Thread.STATUS_RUNNING) { // Primitive returned a promise; automatically yield thread. thread.status = Thread.STATUS_PROMISE_WAIT; @@ -110,7 +110,8 @@ const handlePromise = (primitiveReportedValue, sequencer, thread, blockCached) = // Promise handlers primitiveReportedValue.then(resolvedValue => { handleReport(resolvedValue, sequencer, thread, blockCached); - if (typeof resolvedValue === 'undefined') { + // If its a command block. + if (lastOperation && typeof resolvedValue === 'undefined') { let stackFrame; let nextBlockId; do { @@ -131,8 +132,6 @@ const handlePromise = (primitiveReportedValue, sequencer, thread, blockCached) = } while (stackFrame !== null && !stackFrame.isLoop); thread.pushStack(nextBlockId); - } else { - thread.popStack(); } }, rejectionReason => { // Promise rejected: the primitive had some error. @@ -407,7 +406,44 @@ const execute = function (sequencer, thread) { const reported = currentStackFrame.reported; // Reinstate all the previous values. for (; i < reported.length; i++) { - const {opCached, inputValue} = reported[i]; + const {opCached: oldOpCached, inputValue} = reported[i]; + + const opCached = ops.find(op => op.id === oldOpCached); + + if (opCached) { + const inputName = opCached._parentKey; + const argValues = opCached._parentValues; + + if (inputName === 'BROADCAST_INPUT') { + // Something is plugged into the broadcast input. + // Cast it to a string. We don't need an id here. + argValues.BROADCAST_OPTION.id = null; + argValues.BROADCAST_OPTION.name = cast.toString(inputValue); + } else { + argValues[inputName] = inputValue; + } + } + } + + // Find the last reported block that is still in the set of operations. + // This way if the last operation was removed, we'll find the next + // candidate. If an earlier block that was performed was removed then + // we'll find the index where the last operation is now. + if (reported.length > 0) { + const lastExisting = reported.reverse().find(report => ops.find(op => op.id === report.opCached)); + if (lastExisting) { + i = ops.findIndex(opCached => opCached.id === lastExisting.opCached) + 1; + } else { + i = 0; + } + } + + // The reporting block must exist and must be the next one in the sequence of operations. + if (thread.justReported !== null && ops[i] && ops[i].id === currentStackFrame.reporting) { + const opCached = ops[i]; + const inputValue = thread.justReported; + + thread.justReported = null; const inputName = opCached._parentKey; const argValues = opCached._parentValues; @@ -420,17 +456,11 @@ const execute = function (sequencer, thread) { } else { argValues[inputName] = inputValue; } + + i += 1; } - // Find the last reported block that is still in the set of operations. - // This way if the last operation was removed, we'll find the next - // candidate. If an earlier block that was performed was removed then - // we'll find the index where the last operation is now. - if (reported.length > 0) { - const lastExisting = reported.reverse().find(report => ops.find(op => op.id === report[0].id)); - i = ops.findIndex(opCached => opCached.id === lastExisting.id) + 1; - } - + currentStackFrame.reporting = null; currentStackFrame.reported = null; } @@ -471,23 +501,33 @@ const execute = function (sequencer, thread) { // If it's a promise, wait until promise resolves. if (isPromise(primitiveReportedValue)) { - handlePromise(primitiveReportedValue, sequencer, thread, opCached); + handlePromise(primitiveReportedValue, sequencer, thread, opCached, last); + // Store the already reported values. They will be thawed into the + // future versions of the same operations by block id. The reporting + // operation if it is promise waiting will set its parent value at + // that time. + thread.justReported = null; + currentStackFrame.reporting = ops[i].id; currentStackFrame.reported = ops.slice(0, i).map(reportedCached => { const inputName = reportedCached._parentKey; const reportedValues = reportedCached._parentValues; if (inputName === 'BROADCAST_INPUT') { return { - opCached: reportedCached, + opCached: reportedCached.id, inputValue: reportedValues[inputName].BROADCAST_OPTION.name }; } return { - opCached: reportedCached, + opCached: reportedCached.id, inputValue: reportedValues[inputName] }; }); + + // We are waiting for a promise. Stop running this set of operations + // and continue them later after thawing the reported values. + break; } else if (thread.status === Thread.STATUS_RUNNING) { if (last) { if (typeof primitiveReportedValue === 'undefined') { diff --git a/src/engine/thread.js b/src/engine/thread.js index 9c07f6637..316642222 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -32,6 +32,12 @@ class _StackFrame { */ this.justReported = null; + /** + * The active block that is waiting on a promise. + * @type {string} + */ + this.reporting = ''; + /** * Persists reported inputs during async block. * @type {Object}