scratch-vm/src/engine/runtime.js

185 lines
5.1 KiB
JavaScript
Raw Normal View History

2016-04-18 18:03:18 -04:00
var EventEmitter = require('events');
var Sequencer = require('./sequencer');
2016-04-18 18:03:18 -04:00
var util = require('util');
2016-04-18 17:20:30 -04:00
/**
* Manages blocks, stacks, and the sequencer.
2016-04-18 17:20:30 -04:00
*/
function Runtime () {
2016-04-18 18:03:18 -04:00
// Bind event emitter
EventEmitter.call(this);
2016-04-18 17:20:30 -04:00
// State for the runtime
2016-04-26 15:51:14 -04:00
/**
* All blocks in the workspace.
* Keys are block IDs, values are metadata about the block.
* @type {Object.<string, Object>}
*/
2016-04-18 17:20:30 -04:00
this.blocks = {};
2016-04-26 15:51:14 -04:00
/**
* All stacks in the workspace.
* A list of block IDs that represent stacks (first block in stack).
* @type {Array.<String>}
*/
2016-04-18 17:20:30 -04:00
this.stacks = [];
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 = [];
/** @type {!Sequencer} */
this.sequencer = new Sequencer();
2016-04-18 17:20:30 -04:00
}
2016-04-18 18:03:18 -04:00
/**
* Inherit from EventEmitter
*/
util.inherits(Runtime, EventEmitter);
/**
* Block management: create blocks and stacks from a `create` event
* @param {!Object} block Blockly create event to be processed
*/
Runtime.prototype.createBlock = function (block) {
2016-04-18 17:20:30 -04:00
// Create new block
this.blocks[block.id] = block;
// Walk each field and add any shadow blocks
// @todo Expand this to cover vertical / nested blocks
for (var i in block.fields) {
var shadows = block.fields[i].blocks;
for (var y in shadows) {
var shadow = shadows[y];
this.blocks[shadow.id] = shadow;
2016-04-26 09:54:14 -04:00
}
}
2016-04-18 17:20:30 -04:00
// Push block id to stacks array. New blocks are always a stack even if only
// momentary. If the new block is added to an existing stack this stack will
// be removed by the `moveBlock` method below.
this.stacks.push(block.id);
};
/**
* Block management: change block field values
* @param {!Object} args Blockly change event to be processed
*/
Runtime.prototype.changeBlock = function (args) {
// Validate
if (args.element !== 'field') return;
if (typeof this.blocks[args.id] === 'undefined') return;
if (typeof this.blocks[args.id].fields[args.name] === 'undefined') return;
// Update block value
this.blocks[args.id].fields[args.name].value = args.value;
2016-04-18 17:20:30 -04:00
};
/**
* Block management: move blocks from parent to parent
* @param {!Object} e Blockly move event to be processed
*/
2016-04-18 17:20:30 -04:00
Runtime.prototype.moveBlock = function (e) {
var _this = this;
// Block has a new parent
if (e.oldParent === undefined && e.newParent !== undefined) {
// Remove stack
_this._deleteStack(e.id);
// Update new parent
if (e.newField === undefined) {
2016-04-18 17:20:30 -04:00
_this.blocks[e.newParent].next = e.id;
} else {
_this.blocks[e.newParent].fields[e.newField] = {
name: e.newField,
value: e.id,
blocks: {}
};
2016-04-18 17:20:30 -04:00
}
}
// Block was removed from parent
if (e.newParent === undefined && e.oldParent !== undefined) {
2016-04-18 17:20:30 -04:00
// Add stack
_this.stacks.push(e.id);
// Update old parent
if (e.oldField === undefined) {
2016-04-18 17:20:30 -04:00
_this.blocks[e.oldParent].next = null;
} else {
delete _this.blocks[e.oldParent].fields[e.oldField];
2016-04-18 17:20:30 -04:00
}
}
};
/**
* Block management: delete blocks and their associated stacks
* @param {!Object} e Blockly delete event to be processed
*/
2016-04-18 17:20:30 -04:00
Runtime.prototype.deleteBlock = function (e) {
// @todo Stop threads running on this stack
// Get block
2016-04-18 17:20:30 -04:00
var block = this.blocks[e.id];
// Delete children
2016-04-18 17:20:30 -04:00
if (block.next !== null) {
this.deleteBlock({id: block.next});
}
// Delete substacks and fields
for (var field in block.fields) {
if (field === 'SUBSTACK') {
this.deleteBlock({id: block.fields[field].value});
} else {
for (var shadow in block.fields[field].blocks) {
this.deleteBlock({id: shadow});
}
}
2016-04-18 17:20:30 -04:00
}
// Delete stack
this._deleteStack(e.id);
// Delete block
delete this.blocks[e.id];
};
// -----------------------------------------------------------------------------
// -----------------------------------------------------------------------------
/**
* Helper to remove a stack from `this.stacks`
* @param {?string} id ID of block that starts the stack
*/
2016-04-18 17:20:30 -04:00
Runtime.prototype._deleteStack = function (id) {
var i = this.stacks.indexOf(id);
if (i > -1) this.stacks.splice(i, 1);
};
/**
* Helper to get the next block for a particular block
* @param {?string} id ID of block to get the next block for
* @return {?string} ID of next block in the sequence
*/
2016-04-18 17:20:30 -04:00
Runtime.prototype._getNextBlock = function (id) {
if (typeof this.blocks[id] === 'undefined') return null;
return this.blocks[id].next;
};
/**
* Helper to get the substack for a particular C-shaped block
* @param {?string} id ID for block to get the substack for
* @return {?string} ID of block in the substack
*/
2016-04-18 17:20:30 -04:00
Runtime.prototype._getSubstack = function (id) {
if (typeof this.blocks[id] === 'undefined') return null;
return this.blocks[id].fields['SUBSTACK'];
2016-04-18 17:20:30 -04:00
};
module.exports = Runtime;