Skip to content

Commit

Permalink
Revert "Revert "Push reported""
Browse files Browse the repository at this point in the history
This reverts commit 107adad.
  • Loading branch information
mzgoddard committed Apr 16, 2018
1 parent 5b9dc4d commit d04d6b2
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 65 deletions.
19 changes: 19 additions & 0 deletions src/engine/blocks-execute-cache.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @fileoverview
* Access point for private method shared between blocks.js and execute.js for
* caching execute information.
*/

/**
* A private method shared with execute to build an object containing the block
* information execute needs and that is reset when other cached Blocks info is
* reset.
* @param {Blocks} blocks Blocks containing the expected blockId
* @param {string} blockId blockId for the desired execute cache
*/
exports.getCached = function () {
throw new Error('blocks.js has not initialized BlocksExecuteCache');
};

// Call after the default throwing getCached is assigned for Blocks to replace.
require('./blocks');
40 changes: 38 additions & 2 deletions src/engine/blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const xmlEscape = require('../util/xml-escape');
const MonitorRecord = require('./monitor-record');
const Clone = require('../util/clone');
const {Map} = require('immutable');
const BlocksExecuteCache = require('./blocks-execute-cache');

/**
* @fileoverview
Expand Down Expand Up @@ -47,7 +48,14 @@ class Blocks {
* Cache procedure definitions by block id
* @type {object.<string, ?string>}
*/
procedureDefinitions: {}
procedureDefinitions: {},

/**
* A cache for execute to use and store on. Only available to
* execute.
* @type {object.<string, object>}
*/
_executeCached: {}
};

}
Expand Down Expand Up @@ -359,6 +367,7 @@ class Blocks {
this._cache.inputs = {};
this._cache.procedureParamNames = {};
this._cache.procedureDefinitions = {};
this._cache._executeCached = {};
}

/**
Expand Down Expand Up @@ -435,7 +444,7 @@ class Blocks {
const isSpriteSpecific = optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) &&
optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific;
block.targetId = isSpriteSpecific ? optRuntime.getEditingTarget().id : null;

if (wasMonitored && !block.isMonitored) {
optRuntime.requestRemoveMonitor(block.id);
} else if (!wasMonitored && block.isMonitored) {
Expand Down Expand Up @@ -859,4 +868,31 @@ class Blocks {
}
}

/**
* A private method shared with execute to build an object containing the block
* information execute needs and that is reset when other cached Blocks info is
* reset.
* @param {Blocks} blocks Blocks containing the expected blockId
* @param {string} blockId blockId for the desired execute cache
* @return {object} execute cache object
*/
BlocksExecuteCache.getCached = function (blocks, blockId) {
const block = blocks.getBlock(blockId);
if (typeof block === 'undefined') return null;
let cached = blocks._cache._executeCached[blockId];
if (typeof cached !== 'undefined') {
return cached;
}

cached = {
_initialized: false,
opcode: blocks.getOpcode(block),
fields: blocks.getFields(block),
inputs: blocks.getInputs(block),
mutation: blocks.getMutation(block)
};
blocks._cache._executeCached[blockId] = cached;
return cached;
};

module.exports = Blocks;
173 changes: 118 additions & 55 deletions src/engine/execute.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const BlockUtility = require('./block-utility');
const BlocksExecuteCache = require('./blocks-execute-cache');
const log = require('../util/log');
const Thread = require('./thread');
const {Map} = require('immutable');
Expand Down Expand Up @@ -98,32 +99,27 @@ const handleReport = function (
}
};

/**
* A convenienve constant to hide that the recursiveCall argument to execute is
* a boolean trap.
* @const {boolean}
*/
const RECURSIVE = true;

/**
* Execute a block.
* @param {!Sequencer} sequencer Which sequencer is executing.
* @param {!Thread} thread Thread which to read and execute.
* @param {boolean} recursiveCall is execute called from another execute call?
*/
const execute = function (sequencer, thread) {
const execute = function (sequencer, thread, recursiveCall) {
const runtime = sequencer.runtime;
const target = thread.target;

// Stop if block or target no longer exists.
if (target === null) {
// No block found: stop the thread; script no longer exists.
sequencer.retireThread(thread);
return;
}

// Current block to execute is the one on the top of the stack.
const currentBlockId = thread.peekStack();
const currentStackFrame = thread.peekStackFrame();

let blockContainer;
if (thread.updateMonitor) {
blockContainer = runtime.monitorBlocks;
} else {
blockContainer = target.blocks;
}
let blockContainer = thread.blockContainer;
let block = blockContainer.getBlock(currentBlockId);
if (typeof block === 'undefined') {
blockContainer = runtime.flyoutBlocks;
Expand All @@ -136,33 +132,83 @@ const execute = function (sequencer, thread) {
}
}

const opcode = blockContainer.getOpcode(block);
const fields = blockContainer.getFields(block);
const inputs = blockContainer.getInputs(block);
const blockFunction = runtime.getOpcodeFunction(opcode);
const isHat = runtime.getIsHat(opcode);
const blockCached = BlocksExecuteCache.getCached(blockContainer, currentBlockId);
if (blockCached._initialized !== true) {
const {opcode, fields, inputs} = blockCached;

// 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';

if (!opcode) {
log.warn(`Could not get opcode for block: ${currentBlockId}`);
return;
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;
blockCached._shadowValue = fieldKeys.length === 1 && fields[fieldKeys[0]].value;

// 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);
blockCached._isFieldVariable = fieldKeys.length === 1 && fieldKeys.includes('VARIABLE');
blockCached._fieldVariable = blockCached._isFieldVariable ?
{
id: fields.VARIABLE.id,
name: fields.VARIABLE.value
} :
null;
blockCached._isFieldList = fieldKeys.length === 1 && fieldKeys.includes('LIST');
blockCached._fieldList = blockCached._isFieldList ?
{
id: fields.LIST.id,
name: fields.LIST.value
} :
null;
blockCached._isFieldBroadcastOption = fieldKeys.length === 1 && fieldKeys.includes('BROADCAST_OPTION');
blockCached._fieldBroadcastOption = blockCached._isFieldBroadcastOption ?
{
id: fields.BROADCAST_OPTION.id,
name: fields.BROADCAST_OPTION.value
} :
null;
blockCached._isFieldKnown = blockCached._isFieldVariable ||
blockCached._isFieldList || blockCached._isFieldBroadcastOption;

// 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;
}

const opcode = blockCached.opcode;
const fields = blockCached._fields;
const inputs = blockCached._inputs;
const mutation = blockCached.mutation;
const blockFunction = blockCached._blockFunction;
const isHat = blockCached._isHat;

// 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.
if (typeof blockFunction === 'undefined') {
if (isHat) {
// Skip through the block (hat with no predicate).
if (!blockCached._definedBlockFunction) {
if (!opcode) {
log.warn(`Could not get opcode for block: ${currentBlockId}`);
return;
}
const keys = Object.keys(fields);
if (keys.length === 1 && Object.keys(inputs).length === 0) {

if (recursiveCall === RECURSIVE && blockCached._isShadowBlock) {
// One field and no inputs - treat as arg.
handleReport(fields[keys[0]].value, sequencer, thread, currentBlockId, opcode, isHat);
thread.pushReportedValue(blockCached._shadowValue);
thread.status = Thread.STATUS_RUNNING;
} else if (isHat) {
// Skip through the block (hat with no predicate).
return;
} else {
log.warn(`Could not get implementation for opcode: ${opcode}`);
}
Expand All @@ -174,24 +220,22 @@ const execute = function (sequencer, thread) {
const argValues = {};

// Add all fields on this block to the argValues.
for (const fieldName in fields) {
if (!fields.hasOwnProperty(fieldName)) continue;
if (fieldName === 'VARIABLE' || fieldName === 'LIST' ||
fieldName === 'BROADCAST_OPTION') {
argValues[fieldName] = {
id: fields[fieldName].id,
name: fields[fieldName].value
};
} else {
if (blockCached._isFieldKnown) {
if (blockCached._isFieldVariable) {
argValues.VARIABLE = blockCached._fieldVariable;
} else if (blockCached._isFieldList) {
argValues.LIST = blockCached._fieldList;
} else if (blockCached._isFieldBroadcastOption) {
argValues.BROADCAST_OPTION = blockCached._fieldBroadcastOption;
}
} else {
for (const fieldName in fields) {
argValues[fieldName] = fields[fieldName].value;
}
}

// Recursively evaluate input blocks.
for (const inputName in inputs) {
if (!inputs.hasOwnProperty(inputName)) continue;
// Do not evaluate the internal custom command block within definition
if (inputName === 'custom_block') continue;
const input = inputs[inputName];
const inputBlockId = input.block;
// Is there no value for this input waiting in the stack frame?
Expand All @@ -202,8 +246,16 @@ const execute = function (sequencer, thread) {
// Save name of input for `Thread.pushReportedValue`.
currentStackFrame.waitingReporter = inputName;
// Actually execute the block.
execute(sequencer, thread);
execute(sequencer, thread, RECURSIVE);
if (thread.status === Thread.STATUS_PROMISE_WAIT) {
for (const _inputName in inputs) {
if (_inputName === inputName) break;
if (_inputName === 'BROADCAST_INPUT') {
currentStackFrame.reported[_inputName] = argValues[_inputName].name;
} else {
currentStackFrame.reported[_inputName] = argValues[_inputName];
}
}
return;
}

Expand All @@ -212,7 +264,23 @@ const execute = function (sequencer, thread) {
currentStackFrame.waitingReporter = null;
thread.popStack();
}
const inputValue = currentStackFrame.reported[inputName];
let inputValue;
if (
currentStackFrame.waitingReporter === null
) {
inputValue = currentStackFrame.justReported;
} else if (currentStackFrame.waitingReporter === inputName) {
inputValue = currentStackFrame.justReported;
currentStackFrame.waitingReporter = null;
// 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];
}
if (inputName === 'BROADCAST_INPUT') {
const broadcastInput = inputs[inputName];
// Check if something is plugged into the broadcast block, or
Expand All @@ -239,16 +307,7 @@ const execute = function (sequencer, thread) {
}

// Add any mutation to args (e.g., for procedures).
const mutation = blockContainer.getMutation(block);
if (mutation !== null) {
argValues.mutation = mutation;
}

// 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 = {};
argValues.mutation = mutation;

let primitiveReportedValue = null;
blockUtility.sequencer = sequencer;
Expand All @@ -271,7 +330,7 @@ const execute = function (sequencer, thread) {
runtime.profiler.records.push(runtime.profiler.STOP, performance.now());
}

if (typeof primitiveReportedValue === 'undefined') {
if (recursiveCall !== RECURSIVE && typeof primitiveReportedValue === 'undefined') {
// No value reported - potentially a command block.
// Edge-activated hats don't request a glow; all commands do.
thread.requestScriptGlowInFrame = true;
Expand Down Expand Up @@ -318,7 +377,11 @@ const execute = function (sequencer, thread) {
thread.popStack();
});
} else if (thread.status === Thread.STATUS_RUNNING) {
handleReport(primitiveReportedValue, sequencer, thread, currentBlockId, opcode, isHat);
if (recursiveCall === RECURSIVE) {
thread.pushReportedValue(primitiveReportedValue);
} else {
handleReport(primitiveReportedValue, sequencer, thread, currentBlockId, opcode, isHat);
}
}
};

Expand Down
4 changes: 4 additions & 0 deletions src/engine/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,9 @@ class Runtime extends EventEmitter {
thread.target = target;
thread.stackClick = opts.stackClick;
thread.updateMonitor = opts.updateMonitor;
thread.blockContainer = opts.updateMonitor ?
this.monitorBlocks :
target.blocks;

thread.pushStack(id);
this.threads.push(thread);
Expand Down Expand Up @@ -976,6 +979,7 @@ class Runtime extends EventEmitter {
newThread.target = thread.target;
newThread.stackClick = thread.stackClick;
newThread.updateMonitor = thread.updateMonitor;
newThread.blockContainer = thread.blockContainer;
newThread.pushStack(thread.topBlock);
const i = this.threads.indexOf(thread);
if (i > -1) {
Expand Down
6 changes: 5 additions & 1 deletion src/engine/sequencer.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,11 @@ class Sequencer {
this.runtime.profiler.records.push(
this.runtime.profiler.START, executeProfilerId, null, performance.now());
}
execute(this, thread);
if (thread.target === null) {
this.retireThread(thread);
} else {
execute(this, thread);
}
if (this.runtime.profiler !== null) {
// this.runtime.profiler.stop();
this.runtime.profiler.records.push(this.runtime.profiler.STOP, performance.now());
Expand Down
Loading

0 comments on commit d04d6b2

Please sign in to comment.