mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-07-26 14:12:32 -04:00
Merge pull request #1335 from mzgoddard/fix-execute
fix asynchronous block execution
This commit is contained in:
commit
b33d9ba4e0
2 changed files with 63 additions and 17 deletions
src/engine
|
@ -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…
Add table
Add a link
Reference in a new issue