WIP for demo

This commit is contained in:
Christopher Willis-Ford 2017-08-29 11:43:09 -07:00
parent aaa7784d39
commit d43749d675
6 changed files with 78 additions and 13 deletions

View file

@ -115,6 +115,12 @@ class SharedDispatch {
_remoteTransferCall (provider, service, method, transfer, ...args) { _remoteTransferCall (provider, service, method, transfer, ...args) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const responseId = this._storeCallbacks(resolve, reject); const responseId = this._storeCallbacks(resolve, reject);
/** @TODO: remove this hack! this is just here so we don't try to send `util` to a worker */
if ((args.length > 0) && (typeof args[args.length - 1].yield === 'function')) {
args.pop();
}
if (transfer) { if (transfer) {
provider.postMessage({service, method, responseId, args}, transfer); provider.postMessage({service, method, responseId, args}, transfer);
} else { } else {

View file

@ -294,6 +294,14 @@ class Runtime extends EventEmitter {
return 'MONITORS_UPDATE'; return 'MONITORS_UPDATE';
} }
/**
* Event name for reporting that an extension was added.
* @const {string}
*/
static get EXTENSION_WAS_ADDED () {
return 'EXTENSION_WAS_ADDED';
}
/** /**
* How rapidly we try to step threads by default, in ms. * How rapidly we try to step threads by default, in ms.
*/ */
@ -371,10 +379,12 @@ class Runtime extends EventEmitter {
for (const blockInfo of extensionInfo.blocks) { for (const blockInfo of extensionInfo.blocks) {
const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo); const convertedBlock = this._convertForScratchBlocks(blockInfo, categoryInfo);
const opcode = convertedBlock.json.id; const opcode = convertedBlock.json.type;
categoryInfo.blocks.push(convertedBlock); categoryInfo.blocks.push(convertedBlock);
this._primitives[opcode] = convertedBlock.info.func; this._primitives[opcode] = convertedBlock.info.func;
} }
this.emit(Runtime.EXTENSION_WAS_ADDED, categoryInfo.blocks);
} }
/** /**
@ -387,10 +397,8 @@ class Runtime extends EventEmitter {
_convertForScratchBlocks (blockInfo, categoryInfo) { _convertForScratchBlocks (blockInfo, categoryInfo) {
const extendedOpcode = `${categoryInfo.id}.${blockInfo.opcode}`; const extendedOpcode = `${categoryInfo.id}.${blockInfo.opcode}`;
const blockJSON = { const blockJSON = {
id: extendedOpcode, type: extendedOpcode,
inputsInline: true, inputsInline: true,
previousStatement: null, // null = available connection; undefined = hat block
nextStatement: null, // null = available connection; undefined = terminal
category: categoryInfo.name, category: categoryInfo.name,
colour: categoryInfo.color1, colour: categoryInfo.color1,
colourSecondary: categoryInfo.color2, colourSecondary: categoryInfo.color2,
@ -436,6 +444,10 @@ class Runtime extends EventEmitter {
switch (blockInfo.blockType) { switch (blockInfo.blockType) {
case BlockType.COMMAND: case BlockType.COMMAND:
blockJSON.outputShape = ScratchBlocks.OUTPUT_SHAPE_SQUARE; blockJSON.outputShape = ScratchBlocks.OUTPUT_SHAPE_SQUARE;
blockJSON.previousStatement = null; // null = available connection; undefined = hat
if (!blockInfo.isTerminal) {
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
}
break; break;
case BlockType.REPORTER: case BlockType.REPORTER:
blockJSON.output = 'String'; // TODO: distinguish number & string here? blockJSON.output = 'String'; // TODO: distinguish number & string here?
@ -447,7 +459,7 @@ class Runtime extends EventEmitter {
break; break;
case BlockType.HAT: case BlockType.HAT:
blockJSON.outputShape = ScratchBlocks.OUTPUT_SHAPE_SQUARE; blockJSON.outputShape = ScratchBlocks.OUTPUT_SHAPE_SQUARE;
delete blockJSON.previousStatement; blockJSON.nextStatement = null; // null = available connection; undefined = terminal
break; break;
case BlockType.CONDITIONAL: case BlockType.CONDITIONAL:
// Statement inputs get names like 'SUBSTACK', 'SUBSTACK2', 'SUBSTACK3', ... // Statement inputs get names like 'SUBSTACK', 'SUBSTACK2', 'SUBSTACK3', ...
@ -458,6 +470,8 @@ class Runtime extends EventEmitter {
}; };
} }
blockJSON.outputShape = ScratchBlocks.OUTPUT_SHAPE_SQUARE; blockJSON.outputShape = ScratchBlocks.OUTPUT_SHAPE_SQUARE;
blockJSON.previousStatement = null; // null = available connection; undefined = hat
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
break; break;
} }

View file

@ -65,10 +65,6 @@ class ExtensionManager {
}); });
} }
foo () {
return this.loadExtensionURL('extensions/example-extension.js');
}
/** /**
* Load an extension by URL * Load an extension by URL
* @param {string} extensionURL - the URL for the extension to load * @param {string} extensionURL - the URL for the extension to load
@ -77,7 +73,7 @@ class ExtensionManager {
loadExtensionURL (extensionURL) { loadExtensionURL (extensionURL) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// If we `require` this at the global level it breaks non-webpack targets, including tests // If we `require` this at the global level it breaks non-webpack targets, including tests
const ExtensionWorker = require('worker-loader!./extension-worker'); const ExtensionWorker = require('worker-loader?name=extension-worker.js!./extension-worker');
this.pendingExtensions.push({extensionURL, resolve, reject}); this.pendingExtensions.push({extensionURL, resolve, reject});
dispatch.addWorker(new ExtensionWorker()); dispatch.addWorker(new ExtensionWorker());

View file

@ -33,6 +33,28 @@ class ExampleExtension {
// Required: the list of blocks implemented by this extension, // Required: the list of blocks implemented by this extension,
// in the order intended for display. // in the order intended for display.
blocks: [ blocks: [
{
opcode: 'example-noop',
blockType: Scratch.BlockType.COMMAND,
blockAllThreads: false,
text: 'do nothing',
func: 'noop'
},
{
opcode: 'example-conditional',
blockType: Scratch.BlockType.CONDITIONAL,
branchCount: 4,
isTerminal: true,
blockAllThreads: false,
text: 'choose [BRANCH]',
arguments: {
BRANCH: {
type: Scratch.ArgumentType.NUMBER,
defaultValue: 1
}
},
func: 'noop'
},
{ {
// Required: the machine-readable name of this operation. // Required: the machine-readable name of this operation.
// This will appear in project JSON. Must not contain a '.' character. // This will appear in project JSON. Must not contain a '.' character.
@ -110,6 +132,18 @@ class ExampleExtension {
// ['sprite', 'stage'] // ['sprite', 'stage']
filter: ['someBlocks.wedo2', 'sprite', 'stage'] filter: ['someBlocks.wedo2', 'sprite', 'stage']
}, },
{
opcode: 'example-Boolean',
blockType: Scratch.BlockType.BOOLEAN,
text: 'return true',
func: 'returnTrue'
},
{
opcode: 'example-hat',
blockType: Scratch.BlockType.HAT,
text: 'after forever',
func: 'returnFalse'
},
{ {
// Another block... // Another block...
} }
@ -179,6 +213,17 @@ class ExampleExtension {
return ['Letter ', args.LETTER_NUM, ' of ', args.TEXT, ' is ', result, '.'].join(''); return ['Letter ', args.LETTER_NUM, ' of ', args.TEXT, ' is ', result, '.'].join('');
} }
noop () {
}
returnTrue () {
return true;
}
returnFalse () {
return false;
}
} }
Scratch.extensions.register(new ExampleExtension()); Scratch.extensions.register(new ExampleExtension());

View file

@ -1,10 +1,8 @@
const VirtualMachine = require('./virtual-machine'); const VirtualMachine = require('./virtual-machine');
const CentralDispatch = require('./dispatch/central-dispatch'); const CentralDispatch = require('./dispatch/central-dispatch');
const ExtensionManager = require('./extension-support/extension-manager');
global.Scratch = global.Scratch || {}; global.Scratch = global.Scratch || {};
global.Scratch.dispatch = CentralDispatch; global.Scratch.dispatch = CentralDispatch;
global.Scratch.extensions = new ExtensionManager();
module.exports = VirtualMachine; module.exports = VirtualMachine;

View file

@ -1,6 +1,7 @@
const EventEmitter = require('events'); const EventEmitter = require('events');
const centralDispatch = require('./dispatch/central-dispatch'); const centralDispatch = require('./dispatch/central-dispatch');
const ExtensionManager = require('./extension-support/extension-manager');
const log = require('./util/log'); const log = require('./util/log');
const Runtime = require('./engine/runtime'); const Runtime = require('./engine/runtime');
const sb2 = require('./serialization/sb2'); const sb2 = require('./serialization/sb2');
@ -63,6 +64,11 @@ class VirtualMachine extends EventEmitter {
this.runtime.on(Runtime.MONITORS_UPDATE, monitorList => { this.runtime.on(Runtime.MONITORS_UPDATE, monitorList => {
this.emit(Runtime.MONITORS_UPDATE, monitorList); this.emit(Runtime.MONITORS_UPDATE, monitorList);
}); });
this.runtime.on(Runtime.EXTENSION_WAS_ADDED, blocksInfo => {
this.emit(Runtime.EXTENSION_WAS_ADDED, blocksInfo);
});
this.extensionManager = new ExtensionManager();
this.blockListener = this.blockListener.bind(this); this.blockListener = this.blockListener.bind(this);
this.flyoutBlockListener = this.flyoutBlockListener.bind(this); this.flyoutBlockListener = this.flyoutBlockListener.bind(this);