Merge pull request from mzgoddard/execute-handle-report-block-utility

Extract handleReport and BlockUtility from inside the execute function
This commit is contained in:
Ray Schamp 2017-11-07 12:19:23 -05:00 committed by GitHub
commit 180e545b4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 227 additions and 97 deletions

View file

@ -245,11 +245,12 @@ class Scratch3LooksBlocks {
sayforsecs (args, util) {
this.say(args, util);
const _target = util.target;
return new Promise(resolve => {
this._bubbleTimeout = setTimeout(() => {
this._bubbleTimeout = null;
// Clear say bubble and proceed.
this._updateBubble(util.target, 'say', '');
this._updateBubble(_target, 'say', '');
resolve();
}, 1000 * args.SECS);
});
@ -261,11 +262,12 @@ class Scratch3LooksBlocks {
thinkforsecs (args, util) {
this.think(args, util);
const _target = util.target;
return new Promise(resolve => {
this._bubbleTimeout = setTimeout(() => {
this._bubbleTimeout = null;
// Clear say bubble and proceed.
this._updateBubble(util.target, 'think', '');
this._updateBubble(_target, 'think', '');
resolve();
}, 1000 * args.SECS);
});

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.
@ -11,6 +18,63 @@ const isPromise = function (value) {
return value && value.then && typeof value.then === 'function';
};
/**
* Handle any reported value from the primitive, either directly returned
* or after a promise resolves.
* @param {*} resolvedValue Value eventually returned from the primitive.
* @param {!Sequencer} sequencer Sequencer stepping the thread for the ran
* primitive.
* @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, opcode, isHat) {
thread.pushReportedValue(resolvedValue);
if (isHat) {
// Hat predicate was evaluated.
if (sequencer.runtime.getIsEdgeActivatedHat(opcode)) {
// If this is an edge-activated hat, only proceed if the value is
// true and used to be false, or the stack was activated explicitly
// via stack click
if (!thread.stackClick) {
const oldEdgeValue = sequencer.runtime.updateEdgeActivatedValue(
currentBlockId,
resolvedValue
);
const edgeWasActivated = !oldEdgeValue && resolvedValue;
if (!edgeWasActivated) {
sequencer.retireThread(thread);
}
}
} else if (!resolvedValue) {
// Not an edge-activated hat: retire the thread
// if predicate was false.
sequencer.retireThread(thread);
}
} else {
// In a non-hat, report the value visually if necessary if
// at the top of the thread stack.
if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
if (thread.stackClick) {
sequencer.runtime.visualReport(currentBlockId, resolvedValue);
}
if (thread.updateMonitor) {
sequencer.runtime.requestUpdateMonitor(Map({
id: currentBlockId,
value: String(resolvedValue)
}));
}
}
// Finished any yields.
thread.status = Thread.STATUS_RUNNING;
}
};
/**
* Execute a block.
* @param {!Sequencer} sequencer Which sequencer is executing.
@ -61,55 +125,6 @@ const execute = function (sequencer, thread) {
return;
}
/**
* Handle any reported value from the primitive, either directly returned
* or after a promise resolves.
* @param {*} resolvedValue Value eventually returned from the primitive.
*/
// @todo move this to callback attached to the thread when we have performance
// metrics (dd)
const handleReport = function (resolvedValue) {
thread.pushReportedValue(resolvedValue);
if (isHat) {
// Hat predicate was evaluated.
if (runtime.getIsEdgeActivatedHat(opcode)) {
// If this is an edge-activated hat, only proceed if
// the value is true and used to be false, or the stack was activated
// explicitly via stack click
if (!thread.stackClick) {
const oldEdgeValue = runtime.updateEdgeActivatedValue(
currentBlockId,
resolvedValue
);
const edgeWasActivated = !oldEdgeValue && resolvedValue;
if (!edgeWasActivated) {
sequencer.retireThread(thread);
}
}
} else if (!resolvedValue) {
// Not an edge-activated hat: retire the thread
// if predicate was false.
sequencer.retireThread(thread);
}
} else {
// In a non-hat, report the value visually if necessary if
// at the top of the thread stack.
if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
if (thread.stackClick) {
runtime.visualReport(currentBlockId, resolvedValue);
}
if (thread.updateMonitor) {
runtime.requestUpdateMonitor(Map({
id: currentBlockId,
value: String(resolvedValue)
}));
}
}
// Finished any yields.
thread.status = Thread.STATUS_RUNNING;
}
};
// Hats and single-field shadows are implemented slightly differently
// from regular blocks.
// For hats: if they have an associated block function,
@ -124,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);
handleReport(fields[keys[0]].value, sequencer, thread, currentBlockId, opcode, isHat);
} else {
log.warn(`Could not get implementation for opcode: ${opcode}`);
}
@ -186,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.
@ -244,7 +219,7 @@ const execute = function (sequencer, thread) {
}
// Promise handlers
primitiveReportedValue.then(resolvedValue => {
handleReport(resolvedValue);
handleReport(resolvedValue, sequencer, thread, currentBlockId, opcode, isHat);
if (typeof resolvedValue === 'undefined') {
let stackFrame;
let nextBlockId;
@ -277,7 +252,7 @@ const execute = function (sequencer, thread) {
thread.popStack();
});
} else if (thread.status === Thread.STATUS_RUNNING) {
handleReport(primitiveReportedValue);
handleReport(primitiveReportedValue, sequencer, thread, currentBlockId, opcode, isHat);
}
};