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.
This commit is contained in:
Michael "Z" Goddard 2018-07-12 18:19:05 -04:00
parent ea1bb602f9
commit d2c637a621
No known key found for this signature in database
GPG key ID: 762CD40DD5349872
2 changed files with 63 additions and 17 deletions

View file

@ -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) { if (thread.status === Thread.STATUS_RUNNING) {
// Primitive returned a promise; automatically yield thread. // Primitive returned a promise; automatically yield thread.
thread.status = Thread.STATUS_PROMISE_WAIT; thread.status = Thread.STATUS_PROMISE_WAIT;
@ -110,7 +110,8 @@ const handlePromise = (primitiveReportedValue, sequencer, thread, blockCached) =
// Promise handlers // Promise handlers
primitiveReportedValue.then(resolvedValue => { primitiveReportedValue.then(resolvedValue => {
handleReport(resolvedValue, sequencer, thread, blockCached); handleReport(resolvedValue, sequencer, thread, blockCached);
if (typeof resolvedValue === 'undefined') { // If its a command block.
if (lastOperation && typeof resolvedValue === 'undefined') {
let stackFrame; let stackFrame;
let nextBlockId; let nextBlockId;
do { do {
@ -131,8 +132,6 @@ const handlePromise = (primitiveReportedValue, sequencer, thread, blockCached) =
} while (stackFrame !== null && !stackFrame.isLoop); } while (stackFrame !== null && !stackFrame.isLoop);
thread.pushStack(nextBlockId); thread.pushStack(nextBlockId);
} else {
thread.popStack();
} }
}, rejectionReason => { }, rejectionReason => {
// Promise rejected: the primitive had some error. // Promise rejected: the primitive had some error.
@ -407,8 +406,11 @@ const execute = function (sequencer, thread) {
const reported = currentStackFrame.reported; const reported = currentStackFrame.reported;
// Reinstate all the previous values. // Reinstate all the previous values.
for (; i < reported.length; i++) { 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 inputName = opCached._parentKey;
const argValues = opCached._parentValues; const argValues = opCached._parentValues;
@ -421,16 +423,44 @@ const execute = function (sequencer, thread) {
argValues[inputName] = inputValue; argValues[inputName] = inputValue;
} }
} }
}
// Find the last reported block that is still in the set of operations. // 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 // This way if the last operation was removed, we'll find the next
// candidate. If an earlier block that was performed was removed then // candidate. If an earlier block that was performed was removed then
// we'll find the index where the last operation is now. // we'll find the index where the last operation is now.
if (reported.length > 0) { if (reported.length > 0) {
const lastExisting = reported.reverse().find(report => ops.find(op => op.id === report[0].id)); const lastExisting = reported.reverse().find(report => ops.find(op => op.id === report.opCached));
i = ops.findIndex(opCached => opCached.id === lastExisting.id) + 1; 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;
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;
}
i += 1;
}
currentStackFrame.reporting = null;
currentStackFrame.reported = null; currentStackFrame.reported = null;
} }
@ -471,23 +501,33 @@ const execute = function (sequencer, thread) {
// If it's a promise, wait until promise resolves. // If it's a promise, wait until promise resolves.
if (isPromise(primitiveReportedValue)) { 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 => { currentStackFrame.reported = ops.slice(0, i).map(reportedCached => {
const inputName = reportedCached._parentKey; const inputName = reportedCached._parentKey;
const reportedValues = reportedCached._parentValues; const reportedValues = reportedCached._parentValues;
if (inputName === 'BROADCAST_INPUT') { if (inputName === 'BROADCAST_INPUT') {
return { return {
opCached: reportedCached, opCached: reportedCached.id,
inputValue: reportedValues[inputName].BROADCAST_OPTION.name inputValue: reportedValues[inputName].BROADCAST_OPTION.name
}; };
} }
return { return {
opCached: reportedCached, opCached: reportedCached.id,
inputValue: reportedValues[inputName] 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) { } else if (thread.status === Thread.STATUS_RUNNING) {
if (last) { if (last) {
if (typeof primitiveReportedValue === 'undefined') { if (typeof primitiveReportedValue === 'undefined') {

View file

@ -32,6 +32,12 @@ class _StackFrame {
*/ */
this.justReported = null; this.justReported = null;
/**
* The active block that is waiting on a promise.
* @type {string}
*/
this.reporting = '';
/** /**
* Persists reported inputs during async block. * Persists reported inputs during async block.
* @type {Object} * @type {Object}