mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
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:
parent
78847de660
commit
bbe7981703
2 changed files with 168 additions and 47 deletions
153
src/engine/block-utility.js
Normal file
153
src/engine/block-utility.js
Normal 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;
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue