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}