mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 23:12:24 -05:00
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:
parent
ea1bb602f9
commit
d2c637a621
2 changed files with 63 additions and 17 deletions
|
@ -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,7 +406,44 @@ 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 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 inputName = opCached._parentKey;
|
||||||
const argValues = opCached._parentValues;
|
const argValues = opCached._parentValues;
|
||||||
|
@ -420,17 +456,11 @@ const execute = function (sequencer, thread) {
|
||||||
} else {
|
} else {
|
||||||
argValues[inputName] = inputValue;
|
argValues[inputName] = inputValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the last reported block that is still in the set of operations.
|
currentStackFrame.reporting = null;
|
||||||
// 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.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') {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in a new issue