scratch-vm/src/index.js

323 lines
10 KiB
JavaScript
Raw Normal View History

2016-04-18 17:20:30 -04:00
var EventEmitter = require('events');
var util = require('util');
var Runtime = require('./engine/runtime');
2016-08-31 13:56:05 -04:00
var sb2import = require('./import/sb2import');
var Sprite = require('./sprites/sprite');
var Blocks = require('./engine/blocks');
2016-04-18 17:20:30 -04:00
/**
* Whether the environment is a WebWorker.
* @const{boolean}
*/
var ENV_WORKER = typeof importScripts === 'function';
2016-04-18 17:20:30 -04:00
/**
* Handles connections between blocks, stage, and extensions.
*
* @author Andrew Sliwinski <ascii@media.mit.edu>
*/
function VirtualMachine () {
var instance = this;
// Bind event emitter and runtime to VM instance
EventEmitter.call(instance);
/**
* VM runtime, to store blocks, I/O devices, sprites/targets, etc.
* @type {!Runtime}
2016-04-18 17:20:30 -04:00
*/
instance.runtime = new Runtime();
/**
* The "currently editing"/selected target ID for the VM.
* Block events from any Blockly workspace are routed to this target.
* @type {!string}
*/
instance.editingTarget = null;
2016-06-21 15:30:27 -04:00
// Runtime emits are passed along as VM emits.
instance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (id) {
instance.emit(Runtime.SCRIPT_GLOW_ON, {id: id});
2016-06-21 15:30:27 -04:00
});
instance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (id) {
instance.emit(Runtime.SCRIPT_GLOW_OFF, {id: id});
2016-06-21 15:30:27 -04:00
});
instance.runtime.on(Runtime.BLOCK_GLOW_ON, function (id) {
instance.emit(Runtime.BLOCK_GLOW_ON, {id: id});
});
instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (id) {
instance.emit(Runtime.BLOCK_GLOW_OFF, {id: id});
});
instance.runtime.on(Runtime.VISUAL_REPORT, function (id, value) {
instance.emit(Runtime.VISUAL_REPORT, {id: id, value: value});
});
2016-04-18 17:20:30 -04:00
}
2016-06-21 15:30:27 -04:00
/**
* Inherit from EventEmitter
*/
util.inherits(VirtualMachine, EventEmitter);
/**
* Start running the VM - do this before anything else.
*/
VirtualMachine.prototype.start = function () {
this.runtime.start();
};
/**
* "Green flag" handler - start all threads starting with a green flag.
*/
VirtualMachine.prototype.greenFlag = function () {
this.runtime.greenFlag();
};
/**
* Stop all threads and running activities.
*/
VirtualMachine.prototype.stopAll = function () {
this.runtime.stopAll();
};
/**
* Get data for playground. Data comes back in an emitted event.
*/
VirtualMachine.prototype.getPlaygroundData = function () {
this.emit('playgroundData', {
blocks: this.editingTarget.blocks,
2016-06-21 15:30:27 -04:00
threads: this.runtime.threads
});
};
/**
* Handle an animation frame.
*/
VirtualMachine.prototype.animationFrame = function () {
this.runtime.animationFrame();
};
/**
* Post I/O data to the virtual devices.
* @param {?string} device Name of virtual I/O device.
* @param {Object} data Any data object to post to the I/O device.
*/
VirtualMachine.prototype.postIOData = function (device, data) {
if (this.runtime.ioDevices[device]) {
this.runtime.ioDevices[device].postData(data);
}
};
/**
* Load a project from a Scratch 2.0 JSON representation.
* @param {?string} json JSON string representing the project.
*/
VirtualMachine.prototype.loadProject = function (json) {
2016-08-31 13:56:05 -04:00
// @todo: Handle other formats, e.g., Scratch 1.4, Scratch 3.0.
sb2import(json, this.runtime);
// Select the first target for editing, e.g., the stage.
this.editingTarget = this.runtime.targets[0];
// Update the VM user's knowledge of targets and blocks on the workspace.
this.emitTargetsUpdate();
this.emitWorkspaceUpdate();
this.runtime.setEditingTarget(this.editingTarget);
};
/**
* Temporary way to make an empty project, in case the desired project
* cannot be loaded from the online server.
*/
VirtualMachine.prototype.createEmptyProject = function () {
// Stage.
var blocks2 = new Blocks();
var stage = new Sprite(blocks2);
stage.name = 'Stage';
stage.costumes.push({
skin: '/assets/stage.png',
name: 'backdrop1',
bitmapResolution: 1,
rotationCenterX: 240,
rotationCenterY: 180
});
var target2 = stage.createClone();
this.runtime.targets.push(target2);
target2.x = 0;
target2.y = 0;
target2.direction = 90;
target2.size = 200;
target2.visible = true;
target2.isStage = true;
// Sprite1 (cat).
var blocks1 = new Blocks();
var sprite = new Sprite(blocks1);
sprite.name = 'Sprite1';
sprite.costumes.push({
skin: '/assets/scratch_cat.svg',
name: 'costume1',
bitmapResolution: 1,
rotationCenterX: 47,
rotationCenterY: 55
});
var target1 = sprite.createClone();
this.runtime.targets.push(target1);
target1.x = 0;
target1.y = 0;
target1.direction = 90;
target1.size = 100;
target1.visible = true;
this.editingTarget = this.runtime.targets[0];
this.emitTargetsUpdate();
this.emitWorkspaceUpdate();
};
/**
* Set an editing target. An editor UI can use this function to switch
* between editing different targets, sprites, etc.
* After switching the editing target, the VM may emit updates
* to the list of targets and any attached workspace blocks
* (see `emitTargetsUpdate` and `emitWorkspaceUpdate`).
* @param {string} targetId Id of target to set as editing.
*/
VirtualMachine.prototype.setEditingTarget = function (targetId) {
2016-09-02 09:47:27 -04:00
// Has the target id changed? If not, exit.
if (targetId == this.editingTarget.id) {
return;
}
var target = this.runtime.getTargetById(targetId);
if (target) {
this.editingTarget = target;
// Emit appropriate UI updates.
this.emitTargetsUpdate();
this.emitWorkspaceUpdate();
this.runtime.setEditingTarget(target);
}
};
/**
* Emit metadata about available targets.
* An editor UI could use this to display a list of targets and show
* the currently editing one.
*/
VirtualMachine.prototype.emitTargetsUpdate = function () {
this.emit('targetsUpdate', {
// [[target id, human readable target name], ...].
targetList: this.runtime.targets.map(function(target) {
return [target.id, target.getName()];
}),
// Currently editing target id.
editingTarget: this.editingTarget.id
});
};
/**
* Emit an Blockly/scratch-blocks compatible XML representation
* of the current editing target's blocks.
*/
VirtualMachine.prototype.emitWorkspaceUpdate = function () {
this.emit('workspaceUpdate', {
'xml': this.editingTarget.blocks.toXML()
});
};
/*
2016-06-21 15:30:27 -04:00
* Worker handlers: for all public methods available above,
* we must also provide a message handler in case the VM is run
* from a worker environment.
*/
if (ENV_WORKER) {
self.importScripts(
'./node_modules/scratch-render/render-worker.js'
);
self.renderer = new self.RenderWebGLWorker();
self.vmInstance = new VirtualMachine();
self.onmessage = function (e) {
var messageData = e.data;
switch (messageData.method) {
case 'start':
self.vmInstance.runtime.start();
break;
case 'greenFlag':
self.vmInstance.runtime.greenFlag();
break;
case 'stopAll':
self.vmInstance.runtime.stopAll();
break;
case 'blockListener':
if (self.vmInstance.editingTarget) {
self.vmInstance.editingTarget.blocks.blocklyListen(
messageData.args,
false,
self.vmInstance.runtime
);
}
break;
case 'flyoutBlockListener':
if (self.vmInstance.editingTarget) {
self.vmInstance.editingTarget.blocks.blocklyListen(
messageData.args,
true,
self.vmInstance.runtime
);
}
break;
case 'getPlaygroundData':
self.postMessage({
method: 'playgroundData',
blocks: self.vmInstance.editingTarget.blocks,
threads: self.vmInstance.runtime.threads
});
break;
case 'animationFrame':
self.vmInstance.animationFrame();
break;
case 'postIOData':
self.vmInstance.postIOData(messageData.device, messageData.data);
break;
case 'setEditingTarget':
self.vmInstance.setEditingTarget(messageData.targetId);
break;
case 'createEmptyProject':
self.vmInstance.createEmptyProject();
break;
case 'loadProject':
self.vmInstance.loadProject(messageData.json);
break;
default:
if (e.data.id == 'RendererConnected') {
//initRenderWorker();
}
self.renderer.onmessage(e);
break;
}
};
// Bind runtime's emitted events to postmessages.
self.vmInstance.runtime.on(Runtime.SCRIPT_GLOW_ON, function (id) {
self.postMessage({method: Runtime.SCRIPT_GLOW_ON, id: id});
});
self.vmInstance.runtime.on(Runtime.SCRIPT_GLOW_OFF, function (id) {
self.postMessage({method: Runtime.SCRIPT_GLOW_OFF, id: id});
});
self.vmInstance.runtime.on(Runtime.BLOCK_GLOW_ON, function (id) {
self.postMessage({method: Runtime.BLOCK_GLOW_ON, id: id});
});
self.vmInstance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (id) {
self.postMessage({method: Runtime.BLOCK_GLOW_OFF, id: id});
});
self.vmInstance.runtime.on(Runtime.VISUAL_REPORT, function (id, value) {
self.postMessage({method: Runtime.VISUAL_REPORT, id: id, value: value});
});
self.vmInstance.on('workspaceUpdate', function(data) {
self.postMessage({method: 'workspaceUpdate',
xml: data.xml
});
});
self.vmInstance.on('targetsUpdate', function(data) {
self.postMessage({method: 'targetsUpdate',
targetList: data.targetList,
editingTarget: data.editingTarget
});
});
}
2016-04-18 17:20:30 -04:00
/**
* Export and bind to `window`
*/
module.exports = VirtualMachine;
if (typeof window !== 'undefined') window.VirtualMachine = module.exports;