Straw-man implementation of targets/sprites/clones

This commit is contained in:
Tim Mickel 2016-06-29 13:48:30 -04:00
parent 1a48e75341
commit 809528abdc
7 changed files with 99 additions and 45 deletions

View file

@ -7,6 +7,7 @@ var Thread = require('./thread');
*/ */
var execute = function (sequencer, thread) { var execute = function (sequencer, thread) {
var runtime = sequencer.runtime; var runtime = sequencer.runtime;
var target = runtime.targetForThread(thread);
// Current block to execute is the one on the top of the stack. // Current block to execute is the one on the top of the stack.
var currentBlockId = thread.peekStack(); var currentBlockId = thread.peekStack();
@ -29,13 +30,13 @@ var execute = function (sequencer, thread) {
var argValues = {}; var argValues = {};
// Add all fields on this block to the 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) { for (var fieldName in fields) {
argValues[fieldName] = fields[fieldName].value; argValues[fieldName] = fields[fieldName].value;
} }
// Recursively evaluate input blocks. // Recursively evaluate input blocks.
var inputs = runtime.blocks.getInputs(currentBlockId); var inputs = target.blocks.getInputs(currentBlockId);
for (var inputName in inputs) { for (var inputName in inputs) {
var input = inputs[inputName]; var input = inputs[inputName];
var inputBlockId = input.block; var inputBlockId = input.block;

View file

@ -10,19 +10,19 @@ var defaultBlockPackages = {
}; };
/** /**
* Manages blocks, stacks, and the sequencer. * Manages targets, stacks, and the sequencer.
* @param {!Blocks} blocks Blocks instance for this runtime. * @param {!Array.<Target>} targets List of targets for this runtime.
*/ */
function Runtime (blocks) { function Runtime (targets) {
// Bind event emitter // Bind event emitter
EventEmitter.call(this); EventEmitter.call(this);
// State for the runtime // 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. * A list of threads that are currently running in the VM.
@ -163,32 +163,13 @@ Runtime.prototype.greenFlag = function () {
this._removeThread(this.threads[i]); this._removeThread(this.threads[i]);
} }
// Add all top stacks with green flag // Add all top stacks with green flag
var stacks = this.blocks.getStacks(); for (var t = 0; t < this.targets.length; t++) {
for (var j = 0; j < stacks.length; j++) { var target = this.targets[t];
var topBlock = stacks[j]; var stacks = target.blocks.getStacks();
if (this.blocks.getBlock(topBlock).opcode === 'event_whenflagclicked') { for (var j = 0; j < stacks.length; j++) {
this._pushThread(stacks[j]); var topBlock = stacks[j];
} if (target.blocks.getBlock(topBlock).opcode ===
} 'event_whenflagclicked') {
};
/**
* 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) {
this._pushThread(stacks[j]); this._pushThread(stacks[j]);
} }
} }
@ -228,9 +209,6 @@ Runtime.prototype._step = function () {
* @param {boolean} isGlowing True to turn on glow; false to turn off. * @param {boolean} isGlowing True to turn on glow; false to turn off.
*/ */
Runtime.prototype.glowBlock = function (blockId, isGlowing) { Runtime.prototype.glowBlock = function (blockId, isGlowing) {
if (!this.blocks.getBlock(blockId)) {
return;
}
if (isGlowing) { if (isGlowing) {
this.emit(Runtime.BLOCK_GLOW_ON, blockId); this.emit(Runtime.BLOCK_GLOW_ON, blockId);
} else { } 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. * setInterval implementation that works in a WebWorker or not.
* @param {?Function} fcn Function to call. * @param {?Function} fcn Function to call.

View file

@ -106,7 +106,7 @@ Sequencer.prototype.stepToSubstack = function (thread, substackNum) {
substackNum = 1; substackNum = 1;
} }
var currentBlockId = thread.peekStack(); var currentBlockId = thread.peekStack();
var substackId = this.runtime.blocks.getSubstack( var substackId = this.runtime.targetForThread(thread).blocks.getSubstack(
currentBlockId, currentBlockId,
substackNum substackNum
); );
@ -153,7 +153,8 @@ Sequencer.prototype.proceedThread = function (thread) {
// Pop from the stack - finished this level of execution. // Pop from the stack - finished this level of execution.
thread.popStack(); thread.popStack();
// Push next connected block, if there is one. // 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) { if (nextBlockId) {
thread.pushStack(nextBlockId); thread.pushStack(nextBlockId);
} }

20
src/engine/target.js Normal file
View file

@ -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;

View file

@ -1,7 +1,7 @@
var EventEmitter = require('events'); var EventEmitter = require('events');
var util = require('util'); var util = require('util');
var Blocks = require('./engine/blocks'); var Sprite = require('./sprites/sprite');
var Runtime = require('./engine/runtime'); var Runtime = require('./engine/runtime');
/** /**
@ -21,18 +21,22 @@ function VirtualMachine () {
// Bind event emitter and runtime to VM instance // Bind event emitter and runtime to VM instance
// @todo Post message (Web Worker) polyfill // @todo Post message (Web Worker) polyfill
EventEmitter.call(instance); EventEmitter.call(instance);
instance.blocks = new Blocks(); // @todo support multiple targets/sprites.
instance.runtime = new Runtime(instance.blocks); // 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. * Event listeners for scratch-blocks.
*/ */
instance.blockListener = ( instance.blockListener = (
instance.blocks.generateBlockListener(false, instance.runtime) exampleSprite.blocks.generateBlockListener(false, instance.runtime)
); );
instance.flyoutBlockListener = ( instance.flyoutBlockListener = (
instance.blocks.generateBlockListener(true, instance.runtime) exampleSprite.blocks.generateBlockListener(true, instance.runtime)
); );
// Runtime emits are passed along as VM emits. // Runtime emits are passed along as VM emits.
@ -81,7 +85,7 @@ VirtualMachine.prototype.stopAll = function () {
*/ */
VirtualMachine.prototype.getPlaygroundData = function () { VirtualMachine.prototype.getPlaygroundData = function () {
this.emit('playgroundData', { this.emit('playgroundData', {
blocks: this.blocks, blocks: this.exampleSprite.blocks,
threads: this.runtime.threads threads: this.runtime.threads
}); });
}; };
@ -114,7 +118,7 @@ if (ENV_WORKER) {
case 'getPlaygroundData': case 'getPlaygroundData':
self.postMessage({ self.postMessage({
method: 'playgroundData', method: 'playgroundData',
blocks: self.vmInstance.blocks, blocks: self.vmInstance.exampleSprite.blocks,
threads: self.vmInstance.runtime.threads threads: self.vmInstance.runtime.threads
}); });
break; break;

16
src/sprites/clone.js Normal file
View file

@ -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;

17
src/sprites/sprite.js Normal file
View file

@ -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;