From 809528abdc4c60bb941df03daa06798ddd767941 Mon Sep 17 00:00:00 2001 From: Tim Mickel Date: Wed, 29 Jun 2016 13:48:30 -0400 Subject: [PATCH] Straw-man implementation of targets/sprites/clones --- src/engine/execute.js | 5 ++-- src/engine/runtime.js | 63 +++++++++++++++++++---------------------- src/engine/sequencer.js | 5 ++-- src/engine/target.js | 20 +++++++++++++ src/index.js | 18 +++++++----- src/sprites/clone.js | 16 +++++++++++ src/sprites/sprite.js | 17 +++++++++++ 7 files changed, 99 insertions(+), 45 deletions(-) create mode 100644 src/engine/target.js create mode 100644 src/sprites/clone.js create mode 100644 src/sprites/sprite.js diff --git a/src/engine/execute.js b/src/engine/execute.js index 188f0bf52..001178d56 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -7,6 +7,7 @@ var Thread = require('./thread'); */ var execute = function (sequencer, thread) { var runtime = sequencer.runtime; + var target = runtime.targetForThread(thread); // Current block to execute is the one on the top of the stack. var currentBlockId = thread.peekStack(); @@ -29,13 +30,13 @@ var execute = function (sequencer, thread) { var argValues = {}; // Add all fields on this block to the argValues. - var fields = runtime.blocks.getFields(currentBlockId); + var fields = target.blocks.getFields(currentBlockId); for (var fieldName in fields) { argValues[fieldName] = fields[fieldName].value; } // Recursively evaluate input blocks. - var inputs = runtime.blocks.getInputs(currentBlockId); + var inputs = target.blocks.getInputs(currentBlockId); for (var inputName in inputs) { var input = inputs[inputName]; var inputBlockId = input.block; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 8c4c862e1..a0db919ae 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -10,19 +10,19 @@ var defaultBlockPackages = { }; /** - * Manages blocks, stacks, and the sequencer. - * @param {!Blocks} blocks Blocks instance for this runtime. + * Manages targets, stacks, and the sequencer. + * @param {!Array.} targets List of targets for this runtime. */ -function Runtime (blocks) { +function Runtime (targets) { // Bind event emitter EventEmitter.call(this); // State for the runtime /** - * Block management and storage + * Target management and storage. */ - this.blocks = blocks; + this.targets = targets; /** * A list of threads that are currently running in the VM. @@ -163,32 +163,13 @@ Runtime.prototype.greenFlag = function () { this._removeThread(this.threads[i]); } // Add all top stacks with green flag - var stacks = this.blocks.getStacks(); - for (var j = 0; j < stacks.length; j++) { - var topBlock = stacks[j]; - if (this.blocks.getBlock(topBlock).opcode === 'event_whenflagclicked') { - this._pushThread(stacks[j]); - } - } -}; - -/** - * Distance sensor hack - */ -Runtime.prototype.startDistanceSensors = function () { - // Add all top stacks with distance sensor - var stacks = this.blocks.getStacks(); - for (var j = 0; j < stacks.length; j++) { - var topBlock = stacks[j]; - if (this.blocks.getBlock(topBlock).opcode === - 'wedo_whendistanceclose') { - var alreadyRunning = false; - for (var k = 0; k < this.threads.length; k++) { - if (this.threads[k].topBlock === topBlock) { - alreadyRunning = true; - } - } - if (!alreadyRunning) { + for (var t = 0; t < this.targets.length; t++) { + var target = this.targets[t]; + var stacks = target.blocks.getStacks(); + for (var j = 0; j < stacks.length; j++) { + var topBlock = stacks[j]; + if (target.blocks.getBlock(topBlock).opcode === + 'event_whenflagclicked') { this._pushThread(stacks[j]); } } @@ -228,9 +209,6 @@ Runtime.prototype._step = function () { * @param {boolean} isGlowing True to turn on glow; false to turn off. */ Runtime.prototype.glowBlock = function (blockId, isGlowing) { - if (!this.blocks.getBlock(blockId)) { - return; - } if (isGlowing) { this.emit(Runtime.BLOCK_GLOW_ON, blockId); } else { @@ -238,6 +216,23 @@ Runtime.prototype.glowBlock = function (blockId, isGlowing) { } }; +/** + * Return the Target for a particular thread. + * @param {!Thread} thread Thread to determine target for. + * @return {?Target} Target object, if one exists. + */ +Runtime.prototype.targetForThread = function (thread) { + // @todo This is a messy solution, + // but prevents having circular data references. + // Have a map or some other way to associate target with threads. + for (var t = 0; t < this.targets.length; t++) { + var target = this.targets[t]; + if (target.blocks.getBlock(thread.topBlock)) { + return target; + } + } +}; + /** * setInterval implementation that works in a WebWorker or not. * @param {?Function} fcn Function to call. diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 1498d6320..e70955e69 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -106,7 +106,7 @@ Sequencer.prototype.stepToSubstack = function (thread, substackNum) { substackNum = 1; } var currentBlockId = thread.peekStack(); - var substackId = this.runtime.blocks.getSubstack( + var substackId = this.runtime.targetForThread(thread).blocks.getSubstack( currentBlockId, substackNum ); @@ -153,7 +153,8 @@ Sequencer.prototype.proceedThread = function (thread) { // Pop from the stack - finished this level of execution. thread.popStack(); // Push next connected block, if there is one. - var nextBlockId = this.runtime.blocks.getNextBlock(currentBlockId); + var nextBlockId = (this.runtime.targetForThread(thread). + blocks.getNextBlock(currentBlockId)); if (nextBlockId) { thread.pushStack(nextBlockId); } diff --git a/src/engine/target.js b/src/engine/target.js new file mode 100644 index 000000000..ad7d192c6 --- /dev/null +++ b/src/engine/target.js @@ -0,0 +1,20 @@ +var Blocks = require('./blocks'); + +/** + * @fileoverview + * A Target is an abstract "code-running" object for the Scratch VM. + * Examples include sprites/clones or potentially physical-world devices. + */ + +/** + * @param {?Blocks} blocks Blocks instance for the blocks owned by this target. + * @constructor + */ +function Target (blocks) { + if (!blocks) { + blocks = new Blocks(this); + } + this.blocks = blocks; +} + +module.exports = Target; diff --git a/src/index.js b/src/index.js index 174f05b1c..08109c0a9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ var EventEmitter = require('events'); var util = require('util'); -var Blocks = require('./engine/blocks'); +var Sprite = require('./sprites/sprite'); var Runtime = require('./engine/runtime'); /** @@ -21,18 +21,22 @@ function VirtualMachine () { // Bind event emitter and runtime to VM instance // @todo Post message (Web Worker) polyfill EventEmitter.call(instance); - instance.blocks = new Blocks(); - instance.runtime = new Runtime(instance.blocks); + // @todo support multiple targets/sprites. + // This is just a demo/example. + var exampleSprite = new Sprite(); + var exampleTargets = [exampleSprite.clones[0]]; + instance.exampleSprite = exampleSprite; + instance.runtime = new Runtime(exampleTargets); /** * Event listeners for scratch-blocks. */ instance.blockListener = ( - instance.blocks.generateBlockListener(false, instance.runtime) + exampleSprite.blocks.generateBlockListener(false, instance.runtime) ); instance.flyoutBlockListener = ( - instance.blocks.generateBlockListener(true, instance.runtime) + exampleSprite.blocks.generateBlockListener(true, instance.runtime) ); // Runtime emits are passed along as VM emits. @@ -81,7 +85,7 @@ VirtualMachine.prototype.stopAll = function () { */ VirtualMachine.prototype.getPlaygroundData = function () { this.emit('playgroundData', { - blocks: this.blocks, + blocks: this.exampleSprite.blocks, threads: this.runtime.threads }); }; @@ -114,7 +118,7 @@ if (ENV_WORKER) { case 'getPlaygroundData': self.postMessage({ method: 'playgroundData', - blocks: self.vmInstance.blocks, + blocks: self.vmInstance.exampleSprite.blocks, threads: self.vmInstance.runtime.threads }); break; diff --git a/src/sprites/clone.js b/src/sprites/clone.js new file mode 100644 index 000000000..afbd56f1c --- /dev/null +++ b/src/sprites/clone.js @@ -0,0 +1,16 @@ +var util = require('util'); +var Target = require('../engine/target'); + +function Clone(spriteBlocks) { + Target.call(this, spriteBlocks); +} +util.inherits(Clone, Target); + +// Clone-level properties +Clone.prototype.x = 0; + +Clone.prototype.y = 0; + +Clone.prototype.direction = 90; + +module.exports = Clone; diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js new file mode 100644 index 000000000..1608b0571 --- /dev/null +++ b/src/sprites/sprite.js @@ -0,0 +1,17 @@ +var Clone = require('./clone'); +var Blocks = require('../engine/blocks'); + +function Sprite (blocks) { + // Sprites have: shared blocks, shared costumes, shared variables, etc. + if (!blocks) { + // Shared set of blocks for all clones. + blocks = new Blocks(); + } + this.blocks = blocks; + this.clones = []; + + // Initial single clone with the shared blocks. + this.clones.push(new Clone(this.blocks)); +} + +module.exports = Sprite;