Spaces:
Running
Running
const Thread = require('./thread'); | |
const Timer = require('../util/timer'); | |
/** | |
* @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; | |
this._nowObj = { | |
now: () => this.sequencer.runtime.currentMSecs | |
}; | |
} | |
/** | |
* 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; | |
} | |
/** | |
* Use the runtime's currentMSecs value as a timestamp value for now | |
* This is useful in some cases where we need compatibility with Scratch 2 | |
* @type {function} | |
*/ | |
get nowObj () { | |
if (this.runtime) { | |
return this._nowObj; | |
} | |
return null; | |
} | |
/** | |
* The stack frame used by loop and other blocks to track internal state. | |
* @type {object} | |
*/ | |
get stackFrame () { | |
const frame = this.thread.peekStackFrame(); | |
if (frame.executionContext === null) { | |
frame.executionContext = {}; | |
} | |
return frame.executionContext; | |
} | |
/** | |
* Check the stack timer and return a boolean based on whether it has finished or not. | |
* @return {boolean} - true if the stack timer has finished. | |
*/ | |
stackTimerFinished () { | |
const timeElapsed = this.stackFrame.timer.timeElapsed(); | |
if (timeElapsed < this.stackFrame.duration) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Check if the stack timer needs initialization. | |
* @return {boolean} - true if the stack timer needs to be initialized. | |
*/ | |
stackTimerNeedsInit () { | |
return !this.stackFrame.timer; | |
} | |
/** | |
* Create and start a stack timer | |
* @param {number} duration - a duration in milliseconds to set the timer for. | |
*/ | |
startStackTimer (duration) { | |
if (this.nowObj) { | |
this.stackFrame.timer = new Timer(this.nowObj); | |
} else { | |
this.stackFrame.timer = new Timer(); | |
} | |
this.stackFrame.timer.start(); | |
this.stackFrame.duration = duration; | |
} | |
/** | |
* Set the thread to yield. | |
*/ | |
yield () { | |
this.thread.status = Thread.STATUS_YIELD; | |
} | |
/** | |
* pm: Set the thread to the running state. | |
*/ | |
defaultStatus () { | |
this.thread.status = Thread.STATUS_RUNNING; | |
} | |
/** | |
* Set the thread to yield until the next tick of the runtime. | |
*/ | |
yieldTick () { | |
this.thread.status = Thread.STATUS_YIELD_TICK; | |
} | |
/** | |
* 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); | |
} | |
/** | |
* Get the branch for a particular C-shaped block, and it's target. | |
* @param {string} id ID for block to get the branch for. | |
* @param {string} branchId Which branch to select (e.g. for if-else). | |
* @return {string} ID of block in the branch. | |
*/ | |
getBranchAndTarget (id, branchId) { | |
const result = this.thread.blockContainer.getBranch(id, branchId); | |
if (result) { | |
return [result, this.thread.target]; | |
} | |
return this.sequencer.runtime.getBranchAndTarget(id, branchId); | |
} | |
/** | |
* 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 and ids of parameters for the given procedure. | |
* @param {string} procedureCode Procedure code for procedure to query. | |
* @return {Array.<string>} List of param names for a procedure. | |
*/ | |
getProcedureParamNamesAndIds (procedureCode) { | |
return this.thread.target.blocks.getProcedureParamNamesAndIds(procedureCode); | |
} | |
/** | |
* Get names, ids, and defaults of parameters for the given procedure. | |
* @param {string} procedureCode Procedure code for procedure to query. | |
* @return {Array.<string>} List of param names for a procedure. | |
*/ | |
getProcedureParamNamesIdsAndDefaults (procedureCode) { | |
return this.thread.target.blocks.getProcedureParamNamesIdsAndDefaults(procedureCode); | |
} | |
/** | |
* Initialize procedure parameters in the thread before pushing parameters. | |
*/ | |
initParams () { | |
this.thread.initParams(); | |
} | |
/** | |
* 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) { | |
// Store thread and sequencer to ensure we can return to the calling block's context. | |
// startHats may execute further blocks and dirty the BlockUtility's execution context | |
// and confuse the calling block when we return to it. | |
const callerThread = this.thread; | |
const callerSequencer = this.sequencer; | |
const result = this.sequencer.runtime.startHats(requestedHat, optMatchFields, optTarget); | |
// Restore thread and sequencer to prior values before we return to the calling block. | |
this.thread = callerThread; | |
this.sequencer = callerSequencer; | |
return result; | |
} | |
/** | |
* 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; | |