2016-04-18 18:03:18 -04:00
|
|
|
var EventEmitter = require('events');
|
2016-04-26 14:20:44 -04:00
|
|
|
var Sequencer = require('./sequencer');
|
2016-10-13 17:15:49 -04:00
|
|
|
var Blocks = require('./blocks');
|
2016-04-26 16:50:49 -04:00
|
|
|
var Thread = require('./thread');
|
2016-04-18 18:03:18 -04:00
|
|
|
var util = require('util');
|
|
|
|
|
2016-08-15 21:37:36 -04:00
|
|
|
// Virtual I/O devices.
|
|
|
|
var Clock = require('../io/clock');
|
2016-09-02 11:23:09 -04:00
|
|
|
var Keyboard = require('../io/keyboard');
|
2016-08-15 21:37:36 -04:00
|
|
|
var Mouse = require('../io/mouse');
|
|
|
|
|
2016-05-02 12:20:27 -04:00
|
|
|
var defaultBlockPackages = {
|
2016-06-09 11:45:58 -04:00
|
|
|
'scratch3_control': require('../blocks/scratch3_control'),
|
|
|
|
'scratch3_event': require('../blocks/scratch3_event'),
|
2016-06-30 00:11:47 -04:00
|
|
|
'scratch3_looks': require('../blocks/scratch3_looks'),
|
2016-06-29 20:56:55 -04:00
|
|
|
'scratch3_motion': require('../blocks/scratch3_motion'),
|
2016-08-15 21:37:36 -04:00
|
|
|
'scratch3_operators': require('../blocks/scratch3_operators'),
|
2016-09-21 16:38:33 -04:00
|
|
|
'scratch3_sensing': require('../blocks/scratch3_sensing'),
|
2016-10-03 17:43:24 -04:00
|
|
|
'scratch3_data': require('../blocks/scratch3_data'),
|
|
|
|
'scratch3_procedures': require('../blocks/scratch3_procedures')
|
2016-05-02 12:20:27 -04:00
|
|
|
};
|
|
|
|
|
2016-04-18 17:20:30 -04:00
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* Manages targets, scripts, and the sequencer.
|
2016-04-18 17:20:30 -04:00
|
|
|
*/
|
2016-09-20 15:07:05 -04:00
|
|
|
function Runtime () {
|
2016-04-18 18:03:18 -04:00
|
|
|
// Bind event emitter
|
2016-04-26 09:49:52 -04:00
|
|
|
EventEmitter.call(this);
|
2016-04-18 17:20:30 -04:00
|
|
|
|
2016-04-26 14:20:44 -04:00
|
|
|
// State for the runtime
|
2016-04-26 15:51:14 -04:00
|
|
|
|
|
|
|
/**
|
2016-06-29 13:48:30 -04:00
|
|
|
* Target management and storage.
|
2016-08-31 12:03:41 -04:00
|
|
|
* @type {Array.<!Target>}
|
2016-04-26 15:51:14 -04:00
|
|
|
*/
|
2016-08-31 12:03:41 -04:00
|
|
|
this.targets = [];
|
2016-04-26 14:20:44 -04:00
|
|
|
|
2016-04-26 15:51:14 -04:00
|
|
|
/**
|
|
|
|
* A list of threads that are currently running in the VM.
|
|
|
|
* Threads are added when execution starts and pruned when execution ends.
|
|
|
|
* @type {Array.<Thread>}
|
|
|
|
*/
|
|
|
|
this.threads = [];
|
|
|
|
|
2016-04-26 14:20:44 -04:00
|
|
|
/** @type {!Sequencer} */
|
2016-04-26 16:50:49 -04:00
|
|
|
this.sequencer = new Sequencer(this);
|
2016-05-02 12:20:27 -04:00
|
|
|
|
2016-10-13 17:15:49 -04:00
|
|
|
this.flyoutBlocks = new Blocks();
|
|
|
|
|
2016-05-02 12:20:27 -04:00
|
|
|
/**
|
|
|
|
* Map to look up a block primitive's implementation function by its opcode.
|
|
|
|
* This is a two-step lookup: package name first, then primitive name.
|
|
|
|
* @type {Object.<string, Function>}
|
|
|
|
*/
|
|
|
|
this._primitives = {};
|
2016-08-23 18:12:32 -04:00
|
|
|
this._hats = {};
|
2016-08-29 10:26:26 -04:00
|
|
|
this._edgeActivatedHatValues = {};
|
2016-05-02 12:20:27 -04:00
|
|
|
this._registerBlockPackages();
|
2016-08-15 21:37:36 -04:00
|
|
|
|
|
|
|
this.ioDevices = {
|
|
|
|
'clock': new Clock(),
|
2016-09-03 16:33:45 -04:00
|
|
|
'keyboard': new Keyboard(this),
|
2016-09-12 17:16:10 -04:00
|
|
|
'mouse': new Mouse(this)
|
2016-08-15 21:37:36 -04:00
|
|
|
};
|
2016-09-08 09:40:53 -04:00
|
|
|
|
|
|
|
this._scriptGlowsPreviousFrame = [];
|
|
|
|
this._editingTarget = null;
|
2016-09-15 19:37:12 -04:00
|
|
|
/**
|
|
|
|
* Currently known number of clones.
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
this._cloneCounter = 0;
|
2016-04-18 17:20:30 -04:00
|
|
|
}
|
|
|
|
|
2016-10-04 18:19:52 -04:00
|
|
|
/**
|
|
|
|
* Width of the stage, in pixels.
|
|
|
|
* @const {number}
|
|
|
|
*/
|
|
|
|
Runtime.STAGE_WIDTH = 480;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Height of the stage, in pixels.
|
|
|
|
* @const {number}
|
|
|
|
*/
|
|
|
|
Runtime.STAGE_HEIGHT = 360;
|
|
|
|
|
2016-05-02 13:09:38 -04:00
|
|
|
/**
|
2016-09-08 09:40:53 -04:00
|
|
|
* Event name for glowing a script.
|
2016-05-02 13:09:38 -04:00
|
|
|
* @const {string}
|
|
|
|
*/
|
2016-09-08 09:40:53 -04:00
|
|
|
Runtime.SCRIPT_GLOW_ON = 'STACK_GLOW_ON';
|
2016-05-02 13:09:38 -04:00
|
|
|
|
|
|
|
/**
|
2016-09-08 09:40:53 -04:00
|
|
|
* Event name for unglowing a script.
|
2016-05-02 13:09:38 -04:00
|
|
|
* @const {string}
|
|
|
|
*/
|
2016-09-08 09:40:53 -04:00
|
|
|
Runtime.SCRIPT_GLOW_OFF = 'STACK_GLOW_OFF';
|
2016-05-02 13:09:38 -04:00
|
|
|
|
|
|
|
/**
|
2016-09-08 09:40:53 -04:00
|
|
|
* Event name for glowing a block.
|
2016-05-02 13:09:38 -04:00
|
|
|
* @const {string}
|
|
|
|
*/
|
|
|
|
Runtime.BLOCK_GLOW_ON = 'BLOCK_GLOW_ON';
|
|
|
|
|
|
|
|
/**
|
2016-09-08 09:40:53 -04:00
|
|
|
* Event name for unglowing a block.
|
2016-05-02 13:09:38 -04:00
|
|
|
* @const {string}
|
|
|
|
*/
|
|
|
|
Runtime.BLOCK_GLOW_OFF = 'BLOCK_GLOW_OFF';
|
|
|
|
|
2016-07-07 19:42:38 -04:00
|
|
|
/**
|
|
|
|
* Event name for visual value report.
|
|
|
|
* @const {string}
|
|
|
|
*/
|
|
|
|
Runtime.VISUAL_REPORT = 'VISUAL_REPORT';
|
|
|
|
|
2016-04-18 18:03:18 -04:00
|
|
|
/**
|
|
|
|
* Inherit from EventEmitter
|
|
|
|
*/
|
|
|
|
util.inherits(Runtime, EventEmitter);
|
|
|
|
|
2016-04-26 16:50:49 -04:00
|
|
|
/**
|
|
|
|
* How rapidly we try to step threads, in ms.
|
|
|
|
*/
|
2016-07-01 16:30:33 -04:00
|
|
|
Runtime.THREAD_STEP_INTERVAL = 1000 / 60;
|
2016-04-26 16:50:49 -04:00
|
|
|
|
2016-09-15 19:37:12 -04:00
|
|
|
/**
|
|
|
|
* How many clones can be created at a time.
|
|
|
|
* @const {number}
|
|
|
|
*/
|
|
|
|
Runtime.MAX_CLONES = 300;
|
2016-04-18 17:20:30 -04:00
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
2016-04-26 16:50:49 -04:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
2016-05-02 12:20:27 -04:00
|
|
|
/**
|
|
|
|
* Register default block packages with this runtime.
|
|
|
|
* @todo Prefix opcodes with package name.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
Runtime.prototype._registerBlockPackages = function () {
|
|
|
|
for (var packageName in defaultBlockPackages) {
|
|
|
|
if (defaultBlockPackages.hasOwnProperty(packageName)) {
|
2016-05-02 14:54:32 -04:00
|
|
|
// @todo pass a different runtime depending on package privilege?
|
2016-05-02 14:07:40 -04:00
|
|
|
var packageObject = new (defaultBlockPackages[packageName])(this);
|
2016-08-23 18:12:32 -04:00
|
|
|
// Collect primitives from package.
|
|
|
|
if (packageObject.getPrimitives) {
|
|
|
|
var packagePrimitives = packageObject.getPrimitives();
|
|
|
|
for (var op in packagePrimitives) {
|
|
|
|
if (packagePrimitives.hasOwnProperty(op)) {
|
|
|
|
this._primitives[op] =
|
|
|
|
packagePrimitives[op].bind(packageObject);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Collect hat metadata from package.
|
|
|
|
if (packageObject.getHats) {
|
|
|
|
var packageHats = packageObject.getHats();
|
|
|
|
for (var hatName in packageHats) {
|
|
|
|
if (packageHats.hasOwnProperty(hatName)) {
|
|
|
|
this._hats[hatName] = packageHats[hatName];
|
|
|
|
}
|
2016-05-02 12:20:27 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the function associated with the given opcode.
|
|
|
|
* @param {!string} opcode The opcode to look up.
|
|
|
|
* @return {Function} The function which implements the opcode.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.getOpcodeFunction = function (opcode) {
|
|
|
|
return this._primitives[opcode];
|
|
|
|
};
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
2016-08-23 18:12:32 -04:00
|
|
|
/**
|
|
|
|
* Return whether an opcode represents a hat block.
|
|
|
|
* @param {!string} opcode The opcode to look up.
|
|
|
|
* @return {Boolean} True if the op is known to be a hat.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.getIsHat = function (opcode) {
|
2016-08-29 10:03:21 -04:00
|
|
|
return this._hats.hasOwnProperty(opcode);
|
2016-08-23 18:12:32 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-08-29 10:26:26 -04:00
|
|
|
* Return whether an opcode represents an edge-activated hat block.
|
2016-08-23 18:12:32 -04:00
|
|
|
* @param {!string} opcode The opcode to look up.
|
2016-08-29 10:26:26 -04:00
|
|
|
* @return {Boolean} True if the op is known to be a edge-activated hat.
|
2016-08-23 18:12:32 -04:00
|
|
|
*/
|
2016-08-29 10:26:26 -04:00
|
|
|
Runtime.prototype.getIsEdgeActivatedHat = function (opcode) {
|
2016-08-29 10:03:21 -04:00
|
|
|
return this._hats.hasOwnProperty(opcode) &&
|
2016-08-29 10:26:26 -04:00
|
|
|
this._hats[opcode].edgeActivated;
|
2016-08-23 18:12:32 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-08-29 10:26:26 -04:00
|
|
|
* Update an edge-activated hat block value.
|
2016-08-23 18:12:32 -04:00
|
|
|
* @param {!string} blockId ID of hat to store value for.
|
2016-08-29 10:26:26 -04:00
|
|
|
* @param {*} newValue Value to store for edge-activated hat.
|
|
|
|
* @return {*} The old value for the edge-activated hat.
|
2016-08-23 18:12:32 -04:00
|
|
|
*/
|
2016-08-29 10:26:26 -04:00
|
|
|
Runtime.prototype.updateEdgeActivatedValue = function (blockId, newValue) {
|
|
|
|
var oldValue = this._edgeActivatedHatValues[blockId];
|
|
|
|
this._edgeActivatedHatValues[blockId] = newValue;
|
2016-08-23 18:12:32 -04:00
|
|
|
return oldValue;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
2016-08-29 10:26:26 -04:00
|
|
|
* Clear all edge-activaed hat values.
|
2016-08-23 18:12:32 -04:00
|
|
|
*/
|
2016-08-29 10:26:26 -04:00
|
|
|
Runtime.prototype.clearEdgeActivatedValues = function () {
|
|
|
|
this._edgeActivatedHatValues = {};
|
2016-08-23 18:12:32 -04:00
|
|
|
};
|
|
|
|
|
2016-09-20 15:07:05 -04:00
|
|
|
/**
|
|
|
|
* Attach the renderer
|
|
|
|
* @param {!RenderWebGL} renderer The renderer to attach
|
|
|
|
*/
|
|
|
|
Runtime.prototype.attachRenderer = function (renderer) {
|
|
|
|
this.renderer = renderer;
|
|
|
|
};
|
|
|
|
|
2016-08-23 18:12:32 -04:00
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
|
2016-04-26 16:50:49 -04:00
|
|
|
/**
|
|
|
|
* Create a thread and push it to the list of threads.
|
2016-09-15 19:37:12 -04:00
|
|
|
* @param {!string} id ID of block that starts the stack.
|
|
|
|
* @param {!Target} target Target to run thread on.
|
2016-08-23 15:47:21 -04:00
|
|
|
* @return {!Thread} The newly created thread.
|
2016-04-26 16:50:49 -04:00
|
|
|
*/
|
2016-09-15 19:37:12 -04:00
|
|
|
Runtime.prototype._pushThread = function (id, target) {
|
2016-04-26 16:50:49 -04:00
|
|
|
var thread = new Thread(id);
|
2016-09-15 19:37:12 -04:00
|
|
|
thread.setTarget(target);
|
2016-06-09 17:08:30 -04:00
|
|
|
thread.pushStack(id);
|
2016-04-26 16:50:49 -04:00
|
|
|
this.threads.push(thread);
|
2016-08-23 15:47:21 -04:00
|
|
|
return thread;
|
2016-04-26 16:50:49 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Remove a thread from the list of threads.
|
2016-04-29 17:36:09 -04:00
|
|
|
* @param {?Thread} thread Thread object to remove from actives
|
2016-04-26 16:50:49 -04:00
|
|
|
*/
|
2016-04-29 17:36:09 -04:00
|
|
|
Runtime.prototype._removeThread = function (thread) {
|
2016-10-13 23:00:46 -04:00
|
|
|
// Inform sequencer to stop executing that thread.
|
|
|
|
this.sequencer.retireThread(thread);
|
|
|
|
// Remove from the list.
|
2016-04-29 17:36:09 -04:00
|
|
|
var i = this.threads.indexOf(thread);
|
2016-05-02 13:09:38 -04:00
|
|
|
if (i > -1) {
|
|
|
|
this.threads.splice(i, 1);
|
|
|
|
}
|
2016-04-26 16:50:49 -04:00
|
|
|
};
|
|
|
|
|
2016-08-29 10:01:31 -04:00
|
|
|
/**
|
|
|
|
* Return whether a thread is currently active/running.
|
|
|
|
* @param {?Thread} thread Thread object to check.
|
|
|
|
* @return {Boolean} True if the thread is active/running.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.isActiveThread = function (thread) {
|
|
|
|
return this.threads.indexOf(thread) > -1;
|
|
|
|
};
|
|
|
|
|
2016-04-29 17:31:04 -04:00
|
|
|
/**
|
2016-08-11 11:11:27 -04:00
|
|
|
* Toggle a script.
|
|
|
|
* @param {!string} topBlockId ID of block that starts the script.
|
2016-04-29 17:31:04 -04:00
|
|
|
*/
|
2016-08-11 11:11:27 -04:00
|
|
|
Runtime.prototype.toggleScript = function (topBlockId) {
|
|
|
|
// Remove any existing thread.
|
2016-04-29 17:31:04 -04:00
|
|
|
for (var i = 0; i < this.threads.length; i++) {
|
2016-08-11 11:11:27 -04:00
|
|
|
if (this.threads[i].topBlock == topBlockId) {
|
2016-04-29 17:31:04 -04:00
|
|
|
this._removeThread(this.threads[i]);
|
2016-04-29 17:39:18 -04:00
|
|
|
return;
|
2016-04-29 17:31:04 -04:00
|
|
|
}
|
|
|
|
}
|
2016-08-11 11:11:27 -04:00
|
|
|
// Otherwise add it.
|
2016-09-15 19:37:12 -04:00
|
|
|
this._pushThread(topBlockId, this._editingTarget);
|
2016-04-29 17:31:04 -04:00
|
|
|
};
|
|
|
|
|
2016-04-29 17:58:31 -04:00
|
|
|
/**
|
2016-08-23 18:12:32 -04:00
|
|
|
* Run a function `f` for all scripts in a workspace.
|
|
|
|
* `f` will be called with two parameters:
|
2016-08-29 10:18:49 -04:00
|
|
|
* - the top block ID of the script.
|
|
|
|
* - the target that owns the script.
|
2016-08-23 18:12:32 -04:00
|
|
|
* @param {!Function} f Function to call for each script.
|
|
|
|
* @param {Target=} opt_target Optionally, a target to restrict to.
|
2016-04-29 17:58:31 -04:00
|
|
|
*/
|
2016-08-23 18:12:32 -04:00
|
|
|
Runtime.prototype.allScriptsDo = function (f, opt_target) {
|
|
|
|
var targets = this.targets;
|
|
|
|
if (opt_target) {
|
|
|
|
targets = [opt_target];
|
2016-04-29 17:58:31 -04:00
|
|
|
}
|
2016-08-23 18:12:32 -04:00
|
|
|
for (var t = 0; t < targets.length; t++) {
|
|
|
|
var target = targets[t];
|
2016-08-11 11:11:27 -04:00
|
|
|
var scripts = target.blocks.getScripts();
|
|
|
|
for (var j = 0; j < scripts.length; j++) {
|
2016-08-29 10:18:49 -04:00
|
|
|
var topBlockId = scripts[j];
|
|
|
|
f(topBlockId, target);
|
2016-05-03 17:39:02 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-23 18:12:32 -04:00
|
|
|
/**
|
2016-08-29 10:26:26 -04:00
|
|
|
* Start all relevant hats.
|
|
|
|
* @param {!string} requestedHatOpcode Opcode of hats to start.
|
2016-08-23 18:12:32 -04:00
|
|
|
* @param {Object=} opt_matchFields Optionally, fields to match on the hat.
|
|
|
|
* @param {Target=} opt_target Optionally, a target to restrict to.
|
2016-08-29 10:26:26 -04:00
|
|
|
* @return {Array.<Thread>} List of threads started by this function.
|
2016-08-23 18:12:32 -04:00
|
|
|
*/
|
2016-08-29 10:26:26 -04:00
|
|
|
Runtime.prototype.startHats = function (requestedHatOpcode,
|
2016-08-23 18:12:32 -04:00
|
|
|
opt_matchFields, opt_target) {
|
2016-08-29 10:18:49 -04:00
|
|
|
if (!this._hats.hasOwnProperty(requestedHatOpcode)) {
|
|
|
|
// No known hat with this opcode.
|
|
|
|
return;
|
|
|
|
}
|
2016-08-23 18:12:32 -04:00
|
|
|
var instance = this;
|
|
|
|
var newThreads = [];
|
2016-08-29 10:18:49 -04:00
|
|
|
// Consider all scripts, looking for hats with opcode `requestedHatOpcode`.
|
|
|
|
this.allScriptsDo(function(topBlockId, target) {
|
|
|
|
var potentialHatOpcode = target.blocks.getBlock(topBlockId).opcode;
|
|
|
|
if (potentialHatOpcode !== requestedHatOpcode) {
|
2016-08-23 18:12:32 -04:00
|
|
|
// Not the right hat.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Match any requested fields.
|
|
|
|
// For example: ensures that broadcasts match.
|
|
|
|
// This needs to happen before the block is evaluated
|
|
|
|
// (i.e., before the predicate can be run) because "broadcast and wait"
|
2016-08-29 10:26:26 -04:00
|
|
|
// needs to have a precise collection of started threads.
|
2016-08-29 10:18:49 -04:00
|
|
|
var hatFields = target.blocks.getFields(topBlockId);
|
2016-08-23 18:12:32 -04:00
|
|
|
if (opt_matchFields) {
|
|
|
|
for (var matchField in opt_matchFields) {
|
2016-08-29 10:18:49 -04:00
|
|
|
if (hatFields[matchField].value !==
|
2016-08-23 18:12:32 -04:00
|
|
|
opt_matchFields[matchField]) {
|
|
|
|
// Field mismatch.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-29 10:18:49 -04:00
|
|
|
// Look up metadata for the relevant hat.
|
|
|
|
var hatMeta = instance._hats[requestedHatOpcode];
|
|
|
|
if (hatMeta.restartExistingThreads) {
|
2016-08-29 10:26:26 -04:00
|
|
|
// If `restartExistingThreads` is true, we should stop
|
|
|
|
// any existing threads starting with the top block.
|
2016-08-29 10:18:49 -04:00
|
|
|
for (var i = 0; i < instance.threads.length; i++) {
|
2016-09-15 19:37:12 -04:00
|
|
|
if (instance.threads[i].topBlock === topBlockId &&
|
2016-10-13 22:14:19 -04:00
|
|
|
instance.threads[i].target == target) {
|
2016-08-29 10:18:49 -04:00
|
|
|
instance._removeThread(instance.threads[i]);
|
2016-08-23 18:12:32 -04:00
|
|
|
}
|
2016-08-29 10:18:49 -04:00
|
|
|
}
|
|
|
|
} else {
|
2016-08-29 10:26:26 -04:00
|
|
|
// If `restartExistingThreads` is false, we should
|
|
|
|
// give up if any threads with the top block are running.
|
2016-08-29 10:18:49 -04:00
|
|
|
for (var j = 0; j < instance.threads.length; j++) {
|
2016-09-15 19:37:12 -04:00
|
|
|
if (instance.threads[j].topBlock === topBlockId &&
|
2016-10-13 22:14:19 -04:00
|
|
|
instance.threads[j].target == target) {
|
2016-08-29 10:18:49 -04:00
|
|
|
// Some thread is already running.
|
|
|
|
return;
|
2016-08-23 18:12:32 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-08-29 10:18:49 -04:00
|
|
|
// Start the thread with this top block.
|
2016-09-15 19:37:12 -04:00
|
|
|
newThreads.push(instance._pushThread(topBlockId, target));
|
2016-08-23 18:12:32 -04:00
|
|
|
}, opt_target);
|
|
|
|
return newThreads;
|
|
|
|
};
|
|
|
|
|
2016-10-17 13:43:38 -04:00
|
|
|
/**
|
|
|
|
* Dispose all targets. Return to clean state.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.dispose = function () {
|
|
|
|
this.stopAll();
|
|
|
|
this.targets.map(this.disposeTarget, this);
|
|
|
|
};
|
|
|
|
|
2016-09-15 19:37:12 -04:00
|
|
|
/**
|
|
|
|
* Dispose of a target.
|
|
|
|
* @param {!Target} target Target to dispose of.
|
|
|
|
*/
|
2016-10-17 13:43:38 -04:00
|
|
|
Runtime.prototype.disposeTarget = function (disposingTarget) {
|
|
|
|
this.targets = this.targets.filter(function (target) {
|
|
|
|
if (disposingTarget !== target) return true;
|
|
|
|
// Allow target to do dispose actions.
|
|
|
|
target.dispose();
|
|
|
|
// Remove from list of targets.
|
|
|
|
return false;
|
|
|
|
});
|
2016-09-15 19:37:12 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Stop any threads acting on the target.
|
|
|
|
* @param {!Target} target Target to stop threads for.
|
2016-10-13 23:00:46 -04:00
|
|
|
* @param {Thread=} opt_threadException Optional thread to skip.
|
2016-09-15 19:37:12 -04:00
|
|
|
*/
|
2016-10-13 23:00:46 -04:00
|
|
|
Runtime.prototype.stopForTarget = function (target, opt_threadException) {
|
2016-09-15 19:37:12 -04:00
|
|
|
// Stop any threads on the target.
|
|
|
|
for (var i = 0; i < this.threads.length; i++) {
|
2016-10-13 23:00:46 -04:00
|
|
|
if (this.threads[i] === opt_threadException) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-09-15 19:37:12 -04:00
|
|
|
if (this.threads[i].target == target) {
|
|
|
|
this._removeThread(this.threads[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-23 18:12:32 -04:00
|
|
|
/**
|
|
|
|
* Start all threads that start with the green flag.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.greenFlag = function () {
|
2016-09-15 19:37:12 -04:00
|
|
|
this.stopAll();
|
2016-08-23 18:12:32 -04:00
|
|
|
this.ioDevices.clock.resetProjectTimer();
|
2016-08-29 10:26:26 -04:00
|
|
|
this.clearEdgeActivatedValues();
|
2016-09-21 16:31:07 -04:00
|
|
|
// Inform all targets of the green flag.
|
|
|
|
for (var i = 0; i < this.targets.length; i++) {
|
|
|
|
this.targets[i].onGreenFlag();
|
|
|
|
}
|
2016-08-29 10:26:26 -04:00
|
|
|
this.startHats('event_whenflagclicked');
|
2016-08-23 18:12:32 -04:00
|
|
|
};
|
|
|
|
|
2016-04-29 17:58:31 -04:00
|
|
|
/**
|
2016-09-15 19:37:12 -04:00
|
|
|
* Stop "everything."
|
2016-04-29 17:58:31 -04:00
|
|
|
*/
|
|
|
|
Runtime.prototype.stopAll = function () {
|
2016-09-15 19:37:12 -04:00
|
|
|
// Dispose all clones.
|
|
|
|
var newTargets = [];
|
|
|
|
for (var i = 0; i < this.targets.length; i++) {
|
|
|
|
if (this.targets[i].hasOwnProperty('isOriginal') &&
|
|
|
|
!this.targets[i].isOriginal) {
|
|
|
|
this.targets[i].dispose();
|
|
|
|
} else {
|
|
|
|
newTargets.push(this.targets[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.targets = newTargets;
|
|
|
|
// Dispose all threads.
|
2016-05-03 14:11:37 -04:00
|
|
|
var threadsCopy = this.threads.slice();
|
|
|
|
while (threadsCopy.length > 0) {
|
2016-06-07 20:59:34 -04:00
|
|
|
var poppedThread = threadsCopy.pop();
|
|
|
|
this._removeThread(poppedThread);
|
2016-04-29 17:58:31 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-04-26 16:50:49 -04:00
|
|
|
/**
|
|
|
|
* Repeatedly run `sequencer.stepThreads` and filter out
|
|
|
|
* inactive threads after each iteration.
|
|
|
|
*/
|
|
|
|
Runtime.prototype._step = function () {
|
2016-08-29 10:26:26 -04:00
|
|
|
// Find all edge-activated hats, and add them to threads to be evaluated.
|
2016-08-23 18:12:32 -04:00
|
|
|
for (var hatType in this._hats) {
|
|
|
|
var hat = this._hats[hatType];
|
2016-08-29 10:26:26 -04:00
|
|
|
if (hat.edgeActivated) {
|
|
|
|
this.startHats(hatType);
|
2016-08-23 18:12:32 -04:00
|
|
|
}
|
|
|
|
}
|
2016-04-26 16:50:49 -04:00
|
|
|
var inactiveThreads = this.sequencer.stepThreads(this.threads);
|
2016-09-08 09:40:53 -04:00
|
|
|
this._updateScriptGlows();
|
2016-08-29 09:52:34 -04:00
|
|
|
for (var i = 0; i < inactiveThreads.length; i++) {
|
|
|
|
this._removeThread(inactiveThreads[i]);
|
2016-04-26 16:50:49 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-09-08 09:40:53 -04:00
|
|
|
Runtime.prototype.setEditingTarget = function (editingTarget) {
|
|
|
|
this._scriptGlowsPreviousFrame = [];
|
|
|
|
this._editingTarget = editingTarget;
|
|
|
|
this._updateScriptGlows();
|
|
|
|
};
|
|
|
|
|
|
|
|
Runtime.prototype._updateScriptGlows = function () {
|
|
|
|
// Set of scripts that request a glow this frame.
|
|
|
|
var requestedGlowsThisFrame = [];
|
|
|
|
// Final set of scripts glowing during this frame.
|
|
|
|
var finalScriptGlows = [];
|
|
|
|
// Find all scripts that should be glowing.
|
|
|
|
for (var i = 0; i < this.threads.length; i++) {
|
|
|
|
var thread = this.threads[i];
|
2016-09-15 19:37:12 -04:00
|
|
|
var target = thread.target;
|
2016-09-08 09:40:53 -04:00
|
|
|
if (thread.requestScriptGlowInFrame && target == this._editingTarget) {
|
|
|
|
var blockForThread = thread.peekStack() || thread.topBlock;
|
|
|
|
var script = target.blocks.getTopLevelScript(blockForThread);
|
2016-10-13 17:15:49 -04:00
|
|
|
if (!script) {
|
|
|
|
// Attempt to find in flyout blocks.
|
|
|
|
script = this.flyoutBlocks.getTopLevelScript(blockForThread);
|
|
|
|
}
|
2016-09-15 13:51:40 -04:00
|
|
|
if (script) {
|
|
|
|
requestedGlowsThisFrame.push(script);
|
|
|
|
}
|
2016-09-08 09:40:53 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Compare to previous frame.
|
|
|
|
for (var j = 0; j < this._scriptGlowsPreviousFrame.length; j++) {
|
|
|
|
var previousFrameGlow = this._scriptGlowsPreviousFrame[j];
|
|
|
|
if (requestedGlowsThisFrame.indexOf(previousFrameGlow) < 0) {
|
|
|
|
// Glow turned off.
|
|
|
|
this.glowScript(previousFrameGlow, false);
|
|
|
|
} else {
|
|
|
|
// Still glowing.
|
|
|
|
finalScriptGlows.push(previousFrameGlow);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (var k = 0; k < requestedGlowsThisFrame.length; k++) {
|
|
|
|
var currentFrameGlow = requestedGlowsThisFrame[k];
|
|
|
|
if (this._scriptGlowsPreviousFrame.indexOf(currentFrameGlow) < 0) {
|
|
|
|
// Glow turned on.
|
|
|
|
this.glowScript(currentFrameGlow, true);
|
|
|
|
finalScriptGlows.push(currentFrameGlow);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this._scriptGlowsPreviousFrame = finalScriptGlows;
|
|
|
|
};
|
|
|
|
|
2016-09-15 13:51:40 -04:00
|
|
|
/**
|
|
|
|
* "Quiet" a script's glow: stop the VM from generating glow/unglow events
|
|
|
|
* about that script. Use when a script has just been deleted, but we may
|
|
|
|
* still be tracking glow data about it.
|
|
|
|
* @param {!string} scriptBlockId Id of top-level block in script to quiet.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.quietGlow = function (scriptBlockId) {
|
|
|
|
var index = this._scriptGlowsPreviousFrame.indexOf(scriptBlockId);
|
|
|
|
if (index > -1) {
|
|
|
|
this._scriptGlowsPreviousFrame.splice(index, 1);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-05-02 18:09:02 -04:00
|
|
|
/**
|
|
|
|
* Emit feedback for block glowing (used in the sequencer).
|
|
|
|
* @param {?string} blockId ID for the block to update glow
|
|
|
|
* @param {boolean} isGlowing True to turn on glow; false to turn off.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.glowBlock = function (blockId, isGlowing) {
|
|
|
|
if (isGlowing) {
|
|
|
|
this.emit(Runtime.BLOCK_GLOW_ON, blockId);
|
|
|
|
} else {
|
|
|
|
this.emit(Runtime.BLOCK_GLOW_OFF, blockId);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-23 15:47:21 -04:00
|
|
|
/**
|
|
|
|
* Emit feedback for script glowing.
|
|
|
|
* @param {?string} topBlockId ID for the top block to update glow
|
|
|
|
* @param {boolean} isGlowing True to turn on glow; false to turn off.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.glowScript = function (topBlockId, isGlowing) {
|
|
|
|
if (isGlowing) {
|
2016-09-08 09:40:53 -04:00
|
|
|
this.emit(Runtime.SCRIPT_GLOW_ON, topBlockId);
|
2016-08-23 15:47:21 -04:00
|
|
|
} else {
|
2016-09-08 09:40:53 -04:00
|
|
|
this.emit(Runtime.SCRIPT_GLOW_OFF, topBlockId);
|
2016-08-23 15:47:21 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-07-07 19:42:38 -04:00
|
|
|
/**
|
|
|
|
* Emit value for reporter to show in the blocks.
|
|
|
|
* @param {string} blockId ID for the block.
|
|
|
|
* @param {string} value Value to show associated with the block.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.visualReport = function (blockId, value) {
|
|
|
|
this.emit(Runtime.VISUAL_REPORT, blockId, String(value));
|
|
|
|
};
|
|
|
|
|
2016-06-29 13:48:30 -04:00
|
|
|
/**
|
2016-09-15 19:37:12 -04:00
|
|
|
* Get a target by its id.
|
|
|
|
* @param {string} targetId Id of target to find.
|
|
|
|
* @return {?Target} The target, if found.
|
2016-06-29 13:48:30 -04:00
|
|
|
*/
|
2016-09-15 19:37:12 -04:00
|
|
|
Runtime.prototype.getTargetById = function (targetId) {
|
|
|
|
for (var i = 0; i < this.targets.length; i++) {
|
|
|
|
var target = this.targets[i];
|
|
|
|
if (target.id == targetId) {
|
2016-06-29 13:48:30 -04:00
|
|
|
return target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-08-31 12:18:24 -04:00
|
|
|
/**
|
2016-09-15 19:37:12 -04:00
|
|
|
* Get the first original (non-clone-block-created) sprite given a name.
|
|
|
|
* @param {string} spriteName Name of sprite to look for.
|
|
|
|
* @return {?Target} Target representing a sprite of the given name.
|
2016-08-31 12:18:24 -04:00
|
|
|
*/
|
2016-09-15 19:37:12 -04:00
|
|
|
Runtime.prototype.getSpriteTargetByName = function (spriteName) {
|
2016-08-31 12:18:24 -04:00
|
|
|
for (var i = 0; i < this.targets.length; i++) {
|
|
|
|
var target = this.targets[i];
|
2016-09-15 19:37:12 -04:00
|
|
|
if (target.sprite && target.sprite.name == spriteName) {
|
2016-08-31 12:18:24 -04:00
|
|
|
return target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-09-15 19:37:12 -04:00
|
|
|
/**
|
|
|
|
* Update the clone counter to track how many clones are created.
|
|
|
|
* @param {number} changeAmount How many clones have been created/destroyed.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.changeCloneCounter = function (changeAmount) {
|
|
|
|
this._cloneCounter += changeAmount;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return whether there are clones available.
|
|
|
|
* @return {boolean} True until the number of clones hits Runtime.MAX_CLONES.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.clonesAvailable = function () {
|
|
|
|
return this._cloneCounter < Runtime.MAX_CLONES;
|
|
|
|
};
|
|
|
|
|
2016-09-08 09:40:27 -04:00
|
|
|
/**
|
|
|
|
* Get a target representing the Scratch stage, if one exists.
|
|
|
|
* @return {?Target} The target, if found.
|
|
|
|
*/
|
|
|
|
Runtime.prototype.getTargetForStage = function () {
|
|
|
|
for (var i = 0; i < this.targets.length; i++) {
|
|
|
|
var target = this.targets[i];
|
|
|
|
if (target.isStage) {
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-06-21 14:29:40 -04:00
|
|
|
/**
|
2016-07-01 11:52:43 -04:00
|
|
|
* Handle an animation frame from the main thread.
|
2016-06-21 14:29:40 -04:00
|
|
|
*/
|
2016-07-01 11:52:43 -04:00
|
|
|
Runtime.prototype.animationFrame = function () {
|
2016-09-20 02:42:05 -04:00
|
|
|
if (this.renderer) {
|
|
|
|
this.renderer.draw();
|
2016-06-21 14:29:40 -04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-04-26 16:50:49 -04:00
|
|
|
/**
|
|
|
|
* Set up timers to repeatedly step in a browser
|
|
|
|
*/
|
|
|
|
Runtime.prototype.start = function () {
|
2016-07-01 11:52:43 -04:00
|
|
|
self.setInterval(function() {
|
2016-04-26 16:50:49 -04:00
|
|
|
this._step();
|
|
|
|
}.bind(this), Runtime.THREAD_STEP_INTERVAL);
|
|
|
|
};
|
|
|
|
|
2016-04-18 17:20:30 -04:00
|
|
|
module.exports = Runtime;
|