2017-11-03 11:46:57 -04:00
|
|
|
const BlockUtility = require('./block-utility');
|
2018-04-10 16:29:51 -04:00
|
|
|
const BlocksExecuteCache = require('./blocks-execute-cache');
|
2017-04-17 15:10:04 -04:00
|
|
|
const log = require('../util/log');
|
|
|
|
const Thread = require('./thread');
|
2017-05-19 17:28:00 -04:00
|
|
|
const {Map} = require('immutable');
|
2017-12-08 12:19:11 -05:00
|
|
|
const cast = require('../util/cast');
|
2016-06-17 15:10:12 -04:00
|
|
|
|
2017-11-03 11:46:57 -04:00
|
|
|
/**
|
|
|
|
* Single BlockUtility instance reused by execute for every pritimive ran.
|
|
|
|
* @const
|
|
|
|
*/
|
|
|
|
const blockUtility = new BlockUtility();
|
|
|
|
|
2017-11-09 17:27:49 -05:00
|
|
|
/**
|
|
|
|
* Profiler frame name for block functions.
|
|
|
|
* @const {string}
|
|
|
|
*/
|
|
|
|
const blockFunctionProfilerFrame = 'blockFunction';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Profiler frame ID for 'blockFunction'.
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
let blockFunctionProfilerId = -1;
|
|
|
|
|
2016-08-23 18:12:32 -04:00
|
|
|
/**
|
|
|
|
* Utility function to determine if a value is a Promise.
|
|
|
|
* @param {*} value Value to check for a Promise.
|
2017-02-01 15:59:50 -05:00
|
|
|
* @return {boolean} True if the value appears to be a Promise.
|
2016-08-23 18:12:32 -04:00
|
|
|
*/
|
2017-04-17 15:10:04 -04:00
|
|
|
const isPromise = function (value) {
|
2017-11-03 11:40:20 -04:00
|
|
|
return (
|
|
|
|
value !== null &&
|
|
|
|
typeof value === 'object' &&
|
|
|
|
typeof value.then === 'function'
|
|
|
|
);
|
2016-08-23 18:12:32 -04:00
|
|
|
};
|
|
|
|
|
2017-11-03 11:43:19 -04:00
|
|
|
/**
|
|
|
|
* Handle any reported value from the primitive, either directly returned
|
|
|
|
* or after a promise resolves.
|
|
|
|
* @param {*} resolvedValue Value eventually returned from the primitive.
|
|
|
|
* @param {!Sequencer} sequencer Sequencer stepping the thread for the ran
|
|
|
|
* primitive.
|
|
|
|
* @param {!Thread} thread Thread containing the primitive.
|
|
|
|
* @param {!string} currentBlockId Id of the block in its thread for value from
|
|
|
|
* the primitive.
|
2017-11-03 11:46:57 -04:00
|
|
|
* @param {!string} opcode opcode used to identify a block function primitive.
|
2017-11-03 11:43:19 -04:00
|
|
|
* @param {!boolean} isHat Is the current block a hat?
|
|
|
|
*/
|
|
|
|
// @todo move this to callback attached to the thread when we have performance
|
|
|
|
// metrics (dd)
|
|
|
|
const handleReport = function (
|
2017-11-03 11:46:57 -04:00
|
|
|
resolvedValue, sequencer, thread, currentBlockId, opcode, isHat) {
|
2017-11-03 11:43:19 -04:00
|
|
|
thread.pushReportedValue(resolvedValue);
|
|
|
|
if (isHat) {
|
|
|
|
// Hat predicate was evaluated.
|
|
|
|
if (sequencer.runtime.getIsEdgeActivatedHat(opcode)) {
|
|
|
|
// If this is an edge-activated hat, only proceed if the value is
|
|
|
|
// true and used to be false, or the stack was activated explicitly
|
|
|
|
// via stack click
|
|
|
|
if (!thread.stackClick) {
|
|
|
|
const oldEdgeValue = sequencer.runtime.updateEdgeActivatedValue(
|
|
|
|
currentBlockId,
|
|
|
|
resolvedValue
|
|
|
|
);
|
|
|
|
const edgeWasActivated = !oldEdgeValue && resolvedValue;
|
|
|
|
if (!edgeWasActivated) {
|
|
|
|
sequencer.retireThread(thread);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (!resolvedValue) {
|
|
|
|
// Not an edge-activated hat: retire the thread
|
|
|
|
// if predicate was false.
|
|
|
|
sequencer.retireThread(thread);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// In a non-hat, report the value visually if necessary if
|
|
|
|
// at the top of the thread stack.
|
|
|
|
if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
|
|
|
|
if (thread.stackClick) {
|
|
|
|
sequencer.runtime.visualReport(currentBlockId, resolvedValue);
|
|
|
|
}
|
|
|
|
if (thread.updateMonitor) {
|
2017-11-13 16:28:50 -05:00
|
|
|
const targetId = sequencer.runtime.monitorBlocks.getBlock(currentBlockId).targetId;
|
2017-11-14 18:25:54 -05:00
|
|
|
if (targetId && !sequencer.runtime.getTargetById(targetId)) {
|
|
|
|
// Target no longer exists
|
|
|
|
return;
|
|
|
|
}
|
2017-11-03 11:43:19 -04:00
|
|
|
sequencer.runtime.requestUpdateMonitor(Map({
|
|
|
|
id: currentBlockId,
|
2017-11-13 16:28:50 -05:00
|
|
|
spriteName: targetId ? sequencer.runtime.getTargetById(targetId).getName() : null,
|
2017-11-03 11:43:19 -04:00
|
|
|
value: String(resolvedValue)
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Finished any yields.
|
|
|
|
thread.status = Thread.STATUS_RUNNING;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-04-10 16:29:51 -04:00
|
|
|
/**
|
2018-04-16 17:35:56 -04:00
|
|
|
* A convenience constant to help make use of the recursiveCall argument easier
|
|
|
|
* to read.
|
2018-04-10 16:29:51 -04:00
|
|
|
* @const {boolean}
|
|
|
|
*/
|
|
|
|
const RECURSIVE = true;
|
|
|
|
|
2018-04-16 17:35:56 -04:00
|
|
|
/**
|
|
|
|
* A simple description of the kind of information held in the fields of a block.
|
|
|
|
* @enum {string}
|
|
|
|
*/
|
|
|
|
const FieldKind = {
|
|
|
|
NONE: 'NONE',
|
|
|
|
VARIABLE: 'VARIABLE',
|
|
|
|
LIST: 'LIST',
|
|
|
|
BROADCAST_OPTION: 'BROADCAST_OPTION',
|
|
|
|
DYNAMIC: 'DYNAMIC'
|
|
|
|
};
|
|
|
|
|
2016-06-17 15:10:12 -04:00
|
|
|
/**
|
|
|
|
* Execute a block.
|
|
|
|
* @param {!Sequencer} sequencer Which sequencer is executing.
|
|
|
|
* @param {!Thread} thread Thread which to read and execute.
|
2018-04-10 16:29:51 -04:00
|
|
|
* @param {boolean} recursiveCall is execute called from another execute call?
|
2016-06-17 15:10:12 -04:00
|
|
|
*/
|
2018-04-10 16:29:51 -04:00
|
|
|
const execute = function (sequencer, thread, recursiveCall) {
|
2017-04-17 15:10:04 -04:00
|
|
|
const runtime = sequencer.runtime;
|
2016-10-13 17:15:49 -04:00
|
|
|
|
2017-02-11 09:26:23 -05:00
|
|
|
// Current block to execute is the one on the top of the stack.
|
2017-04-17 15:10:04 -04:00
|
|
|
const currentBlockId = thread.peekStack();
|
|
|
|
const currentStackFrame = thread.peekStackFrame();
|
2017-02-11 09:26:23 -05:00
|
|
|
|
2018-04-10 16:29:51 -04:00
|
|
|
let blockContainer = thread.blockContainer;
|
2017-04-17 15:10:04 -04:00
|
|
|
let block = blockContainer.getBlock(currentBlockId);
|
2017-02-16 05:54:17 -05:00
|
|
|
if (typeof block === 'undefined') {
|
2016-10-13 17:15:49 -04:00
|
|
|
blockContainer = runtime.flyoutBlocks;
|
2017-02-11 09:26:23 -05:00
|
|
|
block = blockContainer.getBlock(currentBlockId);
|
|
|
|
// Stop if block or target no longer exists.
|
2017-02-16 05:54:17 -05:00
|
|
|
if (typeof block === 'undefined') {
|
2017-02-11 09:26:23 -05:00
|
|
|
// No block found: stop the thread; script no longer exists.
|
|
|
|
sequencer.retireThread(thread);
|
|
|
|
return;
|
|
|
|
}
|
2016-10-13 17:15:49 -04:00
|
|
|
}
|
2017-02-11 09:26:23 -05:00
|
|
|
|
2018-04-10 16:29:51 -04:00
|
|
|
const blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId);
|
|
|
|
if (blockCached._initialized !== true) {
|
|
|
|
const {opcode, fields, inputs} = blockCached;
|
2016-10-13 17:15:49 -04:00
|
|
|
|
2018-04-10 16:29:51 -04:00
|
|
|
// Assign opcode isHat and blockFunction data to avoid dynamic lookups.
|
|
|
|
blockCached._isHat = runtime.getIsHat(opcode);
|
|
|
|
blockCached._blockFunction = runtime.getOpcodeFunction(opcode);
|
|
|
|
blockCached._definedBlockFunction = typeof blockCached._blockFunction !== 'undefined';
|
2016-06-09 13:27:30 -04:00
|
|
|
|
2018-04-10 16:29:51 -04:00
|
|
|
const fieldKeys = Object.keys(fields);
|
|
|
|
|
|
|
|
// Store the current shadow value if there is a shadow value.
|
|
|
|
blockCached._isShadowBlock = fieldKeys.length === 1 && Object.keys(inputs).length === 0;
|
2018-04-16 17:35:56 -04:00
|
|
|
blockCached._shadowValue = blockCached._isShadowBlock && fields[fieldKeys[0]].value;
|
2018-04-10 16:29:51 -04:00
|
|
|
|
|
|
|
// Store a fields copy. If fields is a VARIABLE, LIST, or
|
|
|
|
// BROADCAST_OPTION, store the created values so fields assignment to
|
|
|
|
// argValues does not iterate over fields.
|
|
|
|
blockCached._fields = Object.assign({}, blockCached.fields);
|
2018-04-16 17:35:56 -04:00
|
|
|
blockCached._fieldKind = fieldKeys.length > 0 ? FieldKind.DYNAMIC : FieldKind.NONE;
|
|
|
|
if (fieldKeys.length === 1 && fieldKeys.includes('VARIABLE')) {
|
|
|
|
blockCached._fieldKind = FieldKind.VARIABLE;
|
|
|
|
blockCached._fieldVariable = {
|
2018-04-10 16:29:51 -04:00
|
|
|
id: fields.VARIABLE.id,
|
|
|
|
name: fields.VARIABLE.value
|
2018-04-16 17:35:56 -04:00
|
|
|
};
|
|
|
|
} else if (fieldKeys.length === 1 && fieldKeys.includes('LIST')) {
|
|
|
|
blockCached._fieldKind = FieldKind.LIST;
|
|
|
|
blockCached._fieldList = {
|
2018-04-10 16:29:51 -04:00
|
|
|
id: fields.LIST.id,
|
|
|
|
name: fields.LIST.value
|
2018-04-16 17:35:56 -04:00
|
|
|
};
|
|
|
|
} else if (fieldKeys.length === 1 && fieldKeys.includes('BROADCAST_OPTION')) {
|
|
|
|
blockCached._fieldKind = FieldKind.BROADCAST_OPTION;
|
|
|
|
blockCached._fieldBroadcastOption = {
|
2018-04-10 16:29:51 -04:00
|
|
|
id: fields.BROADCAST_OPTION.id,
|
|
|
|
name: fields.BROADCAST_OPTION.value
|
2018-04-16 17:35:56 -04:00
|
|
|
};
|
|
|
|
}
|
2018-04-10 16:29:51 -04:00
|
|
|
|
|
|
|
// Store a modified inputs. This assures the keys are its own properties
|
|
|
|
// and that custom_block will not be evaluated.
|
|
|
|
blockCached._inputs = Object.assign({}, blockCached.inputs);
|
|
|
|
delete blockCached._inputs.custom_block;
|
|
|
|
|
|
|
|
blockCached._initialized = true;
|
2016-06-17 17:18:44 -04:00
|
|
|
}
|
|
|
|
|
2018-04-10 16:29:51 -04:00
|
|
|
const opcode = blockCached.opcode;
|
|
|
|
const fields = blockCached._fields;
|
|
|
|
const inputs = blockCached._inputs;
|
|
|
|
const mutation = blockCached.mutation;
|
|
|
|
const blockFunction = blockCached._blockFunction;
|
|
|
|
const isHat = blockCached._isHat;
|
|
|
|
|
2016-09-12 13:14:16 -04:00
|
|
|
// Hats and single-field shadows are implemented slightly differently
|
|
|
|
// from regular blocks.
|
|
|
|
// For hats: if they have an associated block function,
|
|
|
|
// it's treated as a predicate; if not, execution will proceed as a no-op.
|
|
|
|
// For single-field shadows: If the block has a single field, and no inputs,
|
|
|
|
// immediately return the value of the field.
|
2018-04-10 16:29:51 -04:00
|
|
|
if (!blockCached._definedBlockFunction) {
|
|
|
|
if (!opcode) {
|
|
|
|
log.warn(`Could not get opcode for block: ${currentBlockId}`);
|
2016-09-12 13:14:16 -04:00
|
|
|
return;
|
2017-02-11 09:26:23 -05:00
|
|
|
}
|
2018-04-10 16:29:51 -04:00
|
|
|
|
|
|
|
if (recursiveCall === RECURSIVE && blockCached._isShadowBlock) {
|
2017-02-11 09:26:23 -05:00
|
|
|
// One field and no inputs - treat as arg.
|
2018-04-10 16:29:51 -04:00
|
|
|
thread.pushReportedValue(blockCached._shadowValue);
|
|
|
|
thread.status = Thread.STATUS_RUNNING;
|
|
|
|
} else if (isHat) {
|
|
|
|
// Skip through the block (hat with no predicate).
|
|
|
|
return;
|
2016-09-12 13:14:16 -04:00
|
|
|
} else {
|
2017-04-17 15:10:04 -04:00
|
|
|
log.warn(`Could not get implementation for opcode: ${opcode}`);
|
2016-08-23 18:12:32 -04:00
|
|
|
}
|
2017-02-11 09:26:23 -05:00
|
|
|
thread.requestScriptGlowInFrame = true;
|
|
|
|
return;
|
2016-06-17 17:18:44 -04:00
|
|
|
}
|
|
|
|
|
2016-06-09 13:28:50 -04:00
|
|
|
// Generate values for arguments (inputs).
|
2017-04-17 15:10:04 -04:00
|
|
|
const argValues = {};
|
2016-06-09 13:27:30 -04:00
|
|
|
|
2016-06-09 13:28:50 -04:00
|
|
|
// Add all fields on this block to the argValues.
|
2018-04-16 17:35:56 -04:00
|
|
|
if (blockCached._fieldKind !== FieldKind.NONE) {
|
|
|
|
switch (blockCached._fieldKind) {
|
|
|
|
case FieldKind.VARIABLE:
|
2018-04-10 16:29:51 -04:00
|
|
|
argValues.VARIABLE = blockCached._fieldVariable;
|
2018-04-16 17:35:56 -04:00
|
|
|
break;
|
|
|
|
case FieldKind.LIST:
|
2018-04-10 16:29:51 -04:00
|
|
|
argValues.LIST = blockCached._fieldList;
|
2018-04-16 17:35:56 -04:00
|
|
|
break;
|
|
|
|
case FieldKind.BROADCAST_OPTION:
|
2018-04-10 16:29:51 -04:00
|
|
|
argValues.BROADCAST_OPTION = blockCached._fieldBroadcastOption;
|
2018-04-16 17:35:56 -04:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
for (const fieldName in fields) {
|
|
|
|
argValues[fieldName] = fields[fieldName].value;
|
|
|
|
}
|
2017-06-15 17:29:15 -04:00
|
|
|
}
|
2016-06-09 13:28:50 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Recursively evaluate input blocks.
|
2017-04-17 15:10:04 -04:00
|
|
|
for (const inputName in inputs) {
|
|
|
|
const input = inputs[inputName];
|
|
|
|
const inputBlockId = input.block;
|
2016-06-20 14:24:00 -04:00
|
|
|
// Is there no value for this input waiting in the stack frame?
|
2018-04-16 17:35:56 -04:00
|
|
|
if (inputBlockId !== null && currentStackFrame.waitingReporter === null) {
|
2016-06-20 14:24:00 -04:00
|
|
|
// If there's not, we need to evaluate the block.
|
2016-10-17 23:23:16 -04:00
|
|
|
// Push to the stack to evaluate the reporter block.
|
|
|
|
thread.pushStack(inputBlockId);
|
|
|
|
// Save name of input for `Thread.pushReportedValue`.
|
|
|
|
currentStackFrame.waitingReporter = inputName;
|
|
|
|
// Actually execute the block.
|
2018-04-10 16:29:51 -04:00
|
|
|
execute(sequencer, thread, RECURSIVE);
|
2016-10-17 23:23:16 -04:00
|
|
|
if (thread.status === Thread.STATUS_PROMISE_WAIT) {
|
2018-04-10 16:29:51 -04:00
|
|
|
for (const _inputName in inputs) {
|
|
|
|
if (_inputName === inputName) break;
|
|
|
|
if (_inputName === 'BROADCAST_INPUT') {
|
2018-04-16 17:35:56 -04:00
|
|
|
currentStackFrame.reported[_inputName] = argValues.BROADCAST_OPTION.name;
|
2018-04-10 16:29:51 -04:00
|
|
|
} else {
|
|
|
|
currentStackFrame.reported[_inputName] = argValues[_inputName];
|
|
|
|
}
|
|
|
|
}
|
2016-06-17 15:10:12 -04:00
|
|
|
return;
|
|
|
|
}
|
2017-02-11 09:26:23 -05:00
|
|
|
|
|
|
|
// Execution returned immediately,
|
|
|
|
// and presumably a value was reported, so pop the stack.
|
|
|
|
currentStackFrame.waitingReporter = null;
|
|
|
|
thread.popStack();
|
2016-06-17 15:10:12 -04:00
|
|
|
}
|
2018-04-10 16:29:51 -04:00
|
|
|
let inputValue;
|
2018-04-16 17:35:56 -04:00
|
|
|
if (currentStackFrame.waitingReporter === null) {
|
2018-04-10 16:29:51 -04:00
|
|
|
inputValue = currentStackFrame.justReported;
|
2018-04-16 17:35:56 -04:00
|
|
|
currentStackFrame.justReported = null;
|
2018-04-10 16:29:51 -04:00
|
|
|
} else if (currentStackFrame.waitingReporter === inputName) {
|
|
|
|
inputValue = currentStackFrame.justReported;
|
|
|
|
currentStackFrame.waitingReporter = null;
|
2018-04-16 17:35:56 -04:00
|
|
|
currentStackFrame.justReported = null;
|
2018-04-10 16:29:51 -04:00
|
|
|
// If we've gotten this far, all of the input blocks are evaluated,
|
|
|
|
// and `argValues` is fully populated. So, execute the block
|
|
|
|
// primitive. First, clear `currentStackFrame.reported`, so any
|
|
|
|
// subsequent execution (e.g., on return from a branch) gets fresh
|
|
|
|
// inputs.
|
|
|
|
currentStackFrame.reported = {};
|
|
|
|
} else if (typeof currentStackFrame.reported[inputName] !== 'undefined') {
|
|
|
|
inputValue = currentStackFrame.reported[inputName];
|
|
|
|
}
|
2017-12-08 12:19:11 -05:00
|
|
|
if (inputName === 'BROADCAST_INPUT') {
|
|
|
|
const broadcastInput = inputs[inputName];
|
|
|
|
// Check if something is plugged into the broadcast block, or
|
|
|
|
// if the shadow dropdown menu is being used.
|
|
|
|
if (broadcastInput.block === broadcastInput.shadow) {
|
|
|
|
// Shadow dropdown menu is being used.
|
|
|
|
// Get the appropriate information out of it.
|
|
|
|
const shadow = blockContainer.getBlock(broadcastInput.shadow);
|
|
|
|
const broadcastField = shadow.fields.BROADCAST_OPTION;
|
|
|
|
argValues.BROADCAST_OPTION = {
|
|
|
|
id: broadcastField.id,
|
|
|
|
name: broadcastField.value
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
// Something is plugged into the broadcast input.
|
|
|
|
// Cast it to a string. We don't need an id here.
|
2017-12-13 13:41:22 -05:00
|
|
|
argValues.BROADCAST_OPTION = {
|
2017-12-08 12:19:11 -05:00
|
|
|
name: cast.toString(inputValue)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
argValues[inputName] = inputValue;
|
|
|
|
}
|
2016-06-09 13:27:30 -04:00
|
|
|
}
|
|
|
|
|
2016-10-03 17:43:24 -04:00
|
|
|
// Add any mutation to args (e.g., for procedures).
|
2018-04-10 16:29:51 -04:00
|
|
|
argValues.mutation = mutation;
|
2016-10-03 17:43:24 -04:00
|
|
|
|
2017-04-17 15:10:04 -04:00
|
|
|
let primitiveReportedValue = null;
|
2017-11-03 11:46:57 -04:00
|
|
|
blockUtility.sequencer = sequencer;
|
|
|
|
blockUtility.thread = thread;
|
2017-11-09 17:27:49 -05:00
|
|
|
if (runtime.profiler !== null) {
|
|
|
|
if (blockFunctionProfilerId === -1) {
|
|
|
|
blockFunctionProfilerId = runtime.profiler.idByName(blockFunctionProfilerFrame);
|
|
|
|
}
|
|
|
|
// The method commented below has its code inlined underneath to reduce
|
|
|
|
// the bias recorded for the profiler's calls in this time sensitive
|
|
|
|
// execute function.
|
|
|
|
//
|
|
|
|
// runtime.profiler.start(blockFunctionProfilerId, opcode);
|
|
|
|
runtime.profiler.records.push(
|
|
|
|
runtime.profiler.START, blockFunctionProfilerId, opcode, performance.now());
|
|
|
|
}
|
2017-11-03 11:46:57 -04:00
|
|
|
primitiveReportedValue = blockFunction(argValues, blockUtility);
|
2017-11-09 17:27:49 -05:00
|
|
|
if (runtime.profiler !== null) {
|
|
|
|
// runtime.profiler.stop(blockFunctionProfilerId);
|
|
|
|
runtime.profiler.records.push(runtime.profiler.STOP, performance.now());
|
|
|
|
}
|
2016-06-24 11:14:22 -04:00
|
|
|
|
2018-04-10 16:29:51 -04:00
|
|
|
if (recursiveCall !== RECURSIVE && typeof primitiveReportedValue === 'undefined') {
|
2016-09-08 09:40:53 -04:00
|
|
|
// No value reported - potentially a command block.
|
|
|
|
// Edge-activated hats don't request a glow; all commands do.
|
|
|
|
thread.requestScriptGlowInFrame = true;
|
|
|
|
}
|
|
|
|
|
2016-06-24 11:14:22 -04:00
|
|
|
// If it's a promise, wait until promise resolves.
|
2016-08-23 18:12:32 -04:00
|
|
|
if (isPromise(primitiveReportedValue)) {
|
2016-06-28 13:39:44 -04:00
|
|
|
if (thread.status === Thread.STATUS_RUNNING) {
|
|
|
|
// Primitive returned a promise; automatically yield thread.
|
2016-10-17 23:23:16 -04:00
|
|
|
thread.status = Thread.STATUS_PROMISE_WAIT;
|
2016-06-28 13:39:44 -04:00
|
|
|
}
|
|
|
|
// Promise handlers
|
2017-04-17 15:10:04 -04:00
|
|
|
primitiveReportedValue.then(resolvedValue => {
|
2017-11-03 11:46:57 -04:00
|
|
|
handleReport(resolvedValue, sequencer, thread, currentBlockId, opcode, isHat);
|
2016-10-23 17:55:31 -04:00
|
|
|
if (typeof resolvedValue === 'undefined') {
|
2017-04-17 17:15:19 -04:00
|
|
|
let stackFrame;
|
|
|
|
let nextBlockId;
|
2017-02-20 03:20:21 -05:00
|
|
|
do {
|
|
|
|
// In the case that the promise is the last block in the current thread stack
|
|
|
|
// We need to pop out repeatedly until we find the next block.
|
2017-04-17 15:10:04 -04:00
|
|
|
const popped = thread.popStack();
|
2017-02-20 03:20:21 -05:00
|
|
|
if (popped === null) {
|
|
|
|
return;
|
|
|
|
}
|
2017-04-17 17:15:19 -04:00
|
|
|
nextBlockId = thread.target.blocks.getNextBlock(popped);
|
2017-02-20 04:40:32 -05:00
|
|
|
if (nextBlockId !== null) {
|
|
|
|
// A next block exists so break out this loop
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Investigate the next block and if not in a loop,
|
|
|
|
// then repeat and pop the next item off the stack frame
|
2017-04-17 17:15:19 -04:00
|
|
|
stackFrame = thread.peekStackFrame();
|
2017-02-20 04:40:32 -05:00
|
|
|
} while (stackFrame !== null && !stackFrame.isLoop);
|
|
|
|
|
2016-10-17 23:23:16 -04:00
|
|
|
thread.pushStack(nextBlockId);
|
2016-10-23 17:55:31 -04:00
|
|
|
} else {
|
|
|
|
thread.popStack();
|
2016-10-17 23:23:16 -04:00
|
|
|
}
|
2017-04-17 15:10:04 -04:00
|
|
|
}, rejectionReason => {
|
2016-06-24 11:19:38 -04:00
|
|
|
// Promise rejected: the primitive had some error.
|
|
|
|
// Log it and proceed.
|
2016-10-23 18:01:11 -04:00
|
|
|
log.warn('Primitive rejected promise: ', rejectionReason);
|
2016-10-17 23:23:16 -04:00
|
|
|
thread.status = Thread.STATUS_RUNNING;
|
|
|
|
thread.popStack();
|
2016-06-28 13:39:44 -04:00
|
|
|
});
|
2016-06-24 11:14:22 -04:00
|
|
|
} else if (thread.status === Thread.STATUS_RUNNING) {
|
2018-04-10 16:29:51 -04:00
|
|
|
if (recursiveCall === RECURSIVE) {
|
|
|
|
thread.pushReportedValue(primitiveReportedValue);
|
|
|
|
} else {
|
|
|
|
handleReport(primitiveReportedValue, sequencer, thread, currentBlockId, opcode, isHat);
|
|
|
|
}
|
2016-06-20 14:11:21 -04:00
|
|
|
}
|
2016-06-09 13:27:30 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = execute;
|