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) {
// 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') {

View file

@ -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}