Extract blockUtility from execute to reduce allocations

blockUtility as an object literal inside execute creates 11 objects,
one for the object, and 10 for the function closures. As a separate
object and allocated once, setting its sequencer and thread members,
block functions can share the same util object. Extract blockUtility to
cut down on allocations.
This commit is contained in:
Michael "Z" Goddard 2017-11-03 11:46:57 -04:00
parent 78847de660
commit bbe7981703
No known key found for this signature in database
GPG key ID: 762CD40DD5349872
2 changed files with 168 additions and 47 deletions

153
src/engine/block-utility.js Normal file
View file

@ -0,0 +1,153 @@
const Thread = require('./thread');
/**
* @fileoverview
* Interface provided to block primitive functions for interacting with the
* runtime, thread, target, and convenient methods.
*/
class BlockUtility {
constructor (sequencer = null, thread = null) {
/**
* A sequencer block primitives use to branch or start procedures with
* @type {?Sequencer}
*/
this.sequencer = sequencer;
/**
* The block primitives thread with the block's target, stackFrame and
* modifiable status.
* @type {?Thread}
*/
this.thread = thread;
}
/**
* The target the primitive is working on.
* @type {Target}
*/
get target () {
return this.thread.target;
}
/**
* The runtime the block primitive is running in.
* @type {Runtime}
*/
get runtime () {
return this.sequencer.runtime;
}
/**
* The stack frame used by loop and other blocks to track internal state.
* @type {object}
*/
get stackFrame () {
return this.thread.peekStackFrame().executionContext;
}
/**
* Set the thread to yield.
*/
yield () {
this.thread.status = Thread.STATUS_YIELD;
}
/**
* Start a branch in the current block.
* @param {number} branchNum Which branch to step to (i.e., 1, 2).
* @param {boolean} isLoop Whether this block is a loop.
*/
startBranch (branchNum, isLoop) {
this.sequencer.stepToBranch(this.thread, branchNum, isLoop);
}
/**
* Stop all threads.
*/
stopAll () {
this.sequencer.runtime.stopAll();
}
/**
* Stop threads other on this target other than the thread holding the
* executed block.
*/
stopOtherTargetThreads () {
this.sequencer.runtime.stopForTarget(this.thread.target, this.thread);
}
/**
* Stop this thread.
*/
stopThisScript () {
this.thread.stopThisScript();
}
/**
* Start a specified procedure on this thread.
* @param {string} procedureCode Procedure code for procedure to start.
*/
startProcedure (procedureCode) {
this.sequencer.stepToProcedure(this.thread, procedureCode);
}
/**
* Get names for parameters for the given procedure.
* @param {string} procedureCode Procedure code for procedure to query.
* @return {Array.<string>} List of param names for a procedure.
*/
getProcedureParamNames (procedureCode) {
return this.thread.target.blocks.getProcedureParamNames(procedureCode);
}
/**
* Store a procedure parameter value by its name.
* @param {string} paramName The procedure's parameter name.
* @param {*} paramValue The procedure's parameter value.
*/
pushParam (paramName, paramValue) {
this.thread.pushParam(paramName, paramValue);
}
/**
* Retrieve the stored parameter value for a given parameter name.
* @param {string} paramName The procedure's parameter name.
* @return {*} The parameter's current stored value.
*/
getParam (paramName) {
return this.thread.getParam(paramName);
}
/**
* Start all relevant hats.
* @param {!string} requestedHat Opcode of hats to start.
* @param {object=} optMatchFields Optionally, fields to match on the hat.
* @param {Target=} optTarget Optionally, a target to restrict to.
* @return {Array.<Thread>} List of threads started by this function.
*/
startHats (requestedHat, optMatchFields, optTarget) {
return (
this.sequencer.runtime.startHats(requestedHat, optMatchFields, optTarget)
);
}
/**
* Query a named IO device.
* @param {string} device The name of like the device, like keyboard.
* @param {string} func The name of the device's function to query.
* @param {Array.<*>} args Arguments to pass to the device's function.
* @return {*} The expected output for the device's function.
*/
ioQuery (device, func, args) {
// Find the I/O device and execute the query/function call.
if (
this.sequencer.runtime.ioDevices[device] &&
this.sequencer.runtime.ioDevices[device][func]) {
const devObject = this.sequencer.runtime.ioDevices[device];
return devObject[func].apply(devObject, args);
}
}
}
module.exports = BlockUtility;

View file

@ -1,7 +1,14 @@
const BlockUtility = require('./block-utility');
const log = require('../util/log');
const Thread = require('./thread');
const {Map} = require('immutable');
/**
* Single BlockUtility instance reused by execute for every pritimive ran.
* @const
*/
const blockUtility = new BlockUtility();
/**
* Utility function to determine if a value is a Promise.
* @param {*} value Value to check for a Promise.
@ -20,12 +27,13 @@ const isPromise = function (value) {
* @param {!Thread} thread Thread containing the primitive.
* @param {!string} currentBlockId Id of the block in its thread for value from
* the primitive.
* @param {!string} opcode opcode used to identify a block function primitive.
* @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 (
resolvedValue, sequencer, thread, currentBlockId, isHat) {
resolvedValue, sequencer, thread, currentBlockId, opcode, isHat) {
thread.pushReportedValue(resolvedValue);
if (isHat) {
// Hat predicate was evaluated.
@ -131,7 +139,7 @@ const execute = function (sequencer, thread) {
const keys = Object.keys(fields);
if (keys.length === 1 && Object.keys(inputs).length === 0) {
// One field and no inputs - treat as arg.
handleReport(fields[keys[0]].value, sequencer, thread, currentBlockId, isHat);
handleReport(fields[keys[0]].value, sequencer, thread, currentBlockId, opcode, isHat);
} else {
log.warn(`Could not get implementation for opcode: ${opcode}`);
}
@ -193,49 +201,9 @@ const execute = function (sequencer, thread) {
currentStackFrame.reported = {};
let primitiveReportedValue = null;
primitiveReportedValue = blockFunction(argValues, {
stackFrame: currentStackFrame.executionContext,
target: target,
yield: function () {
thread.status = Thread.STATUS_YIELD;
},
startBranch: function (branchNum, isLoop) {
sequencer.stepToBranch(thread, branchNum, isLoop);
},
stopAll: function () {
runtime.stopAll();
},
stopOtherTargetThreads: function () {
runtime.stopForTarget(target, thread);
},
stopThisScript: function () {
thread.stopThisScript();
},
startProcedure: function (procedureCode) {
sequencer.stepToProcedure(thread, procedureCode);
},
getProcedureParamNames: function (procedureCode) {
return blockContainer.getProcedureParamNames(procedureCode);
},
pushParam: function (paramName, paramValue) {
thread.pushParam(paramName, paramValue);
},
getParam: function (paramName) {
return thread.getParam(paramName);
},
startHats: function (requestedHat, optMatchFields, optTarget) {
return (
runtime.startHats(requestedHat, optMatchFields, optTarget)
);
},
ioQuery: function (device, func, args) {
// Find the I/O device and execute the query/function call.
if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
const devObject = runtime.ioDevices[device];
return devObject[func].apply(devObject, args);
}
}
});
blockUtility.sequencer = sequencer;
blockUtility.thread = thread;
primitiveReportedValue = blockFunction(argValues, blockUtility);
if (typeof primitiveReportedValue === 'undefined') {
// No value reported - potentially a command block.
@ -251,7 +219,7 @@ const execute = function (sequencer, thread) {
}
// Promise handlers
primitiveReportedValue.then(resolvedValue => {
handleReport(resolvedValue, sequencer, thread, currentBlockId, isHat);
handleReport(resolvedValue, sequencer, thread, currentBlockId, opcode, isHat);
if (typeof resolvedValue === 'undefined') {
let stackFrame;
let nextBlockId;
@ -284,7 +252,7 @@ const execute = function (sequencer, thread) {
thread.popStack();
});
} else if (thread.status === Thread.STATUS_RUNNING) {
handleReport(primitiveReportedValue, sequencer, thread, currentBlockId, isHat);
handleReport(primitiveReportedValue, sequencer, thread, currentBlockId, opcode, isHat);
}
};