diff --git a/src/engine/block-utility.js b/src/engine/block-utility.js new file mode 100644 index 000000000..9fee1f7b6 --- /dev/null +++ b/src/engine/block-utility.js @@ -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.} 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.} 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; diff --git a/src/engine/execute.js b/src/engine/execute.js index e0da40cc2..fda8054ad 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -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); } };