mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-03-13 17:04:39 -04:00
Merge remote-tracking branch 'LLK/develop' into develop
# Conflicts: # src/engine/runtime.js
This commit is contained in:
commit
d121a4c6fa
22 changed files with 3155 additions and 3044 deletions
11
README.md
11
README.md
|
@ -11,6 +11,7 @@ npm install https://github.com/LLK/scratch-vm.git
|
|||
```
|
||||
|
||||
## Setup
|
||||
For an extended setup example, check out the /playground directory, which includes a fully running VM instance.
|
||||
```js
|
||||
var VirtualMachine = require('scratch-vm');
|
||||
var vm = new VirtualMachine();
|
||||
|
@ -23,6 +24,16 @@ flyoutWorkspace.addChangeListener(vm.flyoutBlockListener);
|
|||
// Run threads
|
||||
vm.runtime.start();
|
||||
```
|
||||
## Development Server and Playground
|
||||
For convenience, we've included a development server with the VM. This is useful because the VM can take advantage of executing in a WebWorker, which is not permitted in a local file. To start the server, run:
|
||||
|
||||
```bash
|
||||
make serve
|
||||
```
|
||||
and go to [http://localhost:8080/](http://localhost:8080/) - you will be redirected to the playground, which demonstrates various tools and internal state.
|
||||
|
||||

|
||||
|
||||
|
||||
## Standalone Build
|
||||
```bash
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"json-loader": "0.5.4",
|
||||
"scratch-blocks": "git+https://git@github.com/LLK/scratch-blocks.git",
|
||||
"scratch-render": "git+https://git@github.com/LLK/scratch-render.git",
|
||||
"stats.js": "0.16.0",
|
||||
"tap": "5.7.1",
|
||||
"webpack": "1.13.0",
|
||||
"webpack-dev-server": "1.14.1"
|
||||
|
|
|
@ -796,7 +796,8 @@
|
|||
</category>
|
||||
<category name="More Blocks" colour="#FF6680"></category>
|
||||
</xml>
|
||||
|
||||
<!-- FPS counter -->
|
||||
<script src="../node_modules/stats.js/build/stats.min.js"></script>
|
||||
<!-- Syntax highlighter -->
|
||||
<script src="../node_modules/highlightjs/highlight.pack.min.js"></script>
|
||||
<!-- Scratch Blocks -->
|
||||
|
|
|
@ -29,6 +29,12 @@ window.onload = function() {
|
|||
});
|
||||
window.workspace = workspace;
|
||||
|
||||
// FPS counter.
|
||||
var stats = new window.Stats();
|
||||
document.getElementById('tab-renderexplorer').appendChild(stats.dom);
|
||||
stats.dom.style.position = 'relative';
|
||||
stats.begin();
|
||||
|
||||
// Block events.
|
||||
// @todo: Re-enable flyout listening after fixing GH-69.
|
||||
workspace.addChangeListener(vm.blockListener);
|
||||
|
@ -82,11 +88,31 @@ window.onload = function() {
|
|||
workspace.reportValue(data.id, data.value);
|
||||
});
|
||||
|
||||
// Feed mouse events as VM I/O events.
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
var rect = canvas.getBoundingClientRect();
|
||||
var coordinates = {
|
||||
x: e.clientX - rect.left - rect.width / 2,
|
||||
y: e.clientY - rect.top - rect.height / 2
|
||||
};
|
||||
window.vm.postIOData('mouse', coordinates);
|
||||
});
|
||||
canvas.addEventListener('mousedown', function (e) {
|
||||
window.vm.postIOData('mouse', {isDown: true});
|
||||
e.preventDefault();
|
||||
});
|
||||
canvas.addEventListener('mouseup', function (e) {
|
||||
window.vm.postIOData('mouse', {isDown: false});
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
// Run threads
|
||||
vm.start();
|
||||
|
||||
// Inform VM of animation frames.
|
||||
var animate = function() {
|
||||
stats.end();
|
||||
stats.begin();
|
||||
window.vm.animationFrame();
|
||||
requestAnimationFrame(animate);
|
||||
};
|
||||
|
|
|
@ -30,15 +30,15 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) {
|
|||
util.stackFrame.loopCounter = parseInt(args.TIMES);
|
||||
}
|
||||
// Only execute once per frame.
|
||||
// When the substack finishes, `repeat` will be executed again and
|
||||
// When the branch finishes, `repeat` will be executed again and
|
||||
// the second branch will be taken, yielding for the rest of the frame.
|
||||
if (!util.stackFrame.executedInFrame) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
// Decrease counter
|
||||
util.stackFrame.loopCounter--;
|
||||
// If we still have some left, start the substack
|
||||
// If we still have some left, start the branch.
|
||||
if (util.stackFrame.loopCounter >= 0) {
|
||||
util.startSubstack();
|
||||
util.startBranch();
|
||||
}
|
||||
} else {
|
||||
util.stackFrame.executedInFrame = false;
|
||||
|
@ -48,13 +48,13 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) {
|
|||
|
||||
Scratch3ControlBlocks.prototype.repeatUntil = function(args, util) {
|
||||
// Only execute once per frame.
|
||||
// When the substack finishes, `repeat` will be executed again and
|
||||
// When the branch finishes, `repeat` will be executed again and
|
||||
// the second branch will be taken, yielding for the rest of the frame.
|
||||
if (!util.stackFrame.executedInFrame) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
// If the condition is true, start the substack.
|
||||
// If the condition is true, start the branch.
|
||||
if (!args.CONDITION) {
|
||||
util.startSubstack();
|
||||
util.startBranch();
|
||||
}
|
||||
} else {
|
||||
util.stackFrame.executedInFrame = false;
|
||||
|
@ -64,11 +64,11 @@ Scratch3ControlBlocks.prototype.repeatUntil = function(args, util) {
|
|||
|
||||
Scratch3ControlBlocks.prototype.forever = function(args, util) {
|
||||
// Only execute once per frame.
|
||||
// When the substack finishes, `forever` will be executed again and
|
||||
// When the branch finishes, `forever` will be executed again and
|
||||
// the second branch will be taken, yielding for the rest of the frame.
|
||||
if (!util.stackFrame.executedInFrame) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
util.startSubstack();
|
||||
util.startBranch();
|
||||
} else {
|
||||
util.stackFrame.executedInFrame = false;
|
||||
util.yieldFrame();
|
||||
|
@ -85,24 +85,24 @@ Scratch3ControlBlocks.prototype.wait = function(args) {
|
|||
|
||||
Scratch3ControlBlocks.prototype.if = function(args, util) {
|
||||
// Only execute one time. `if` will be returned to
|
||||
// when the substack finishes, but it shouldn't execute again.
|
||||
// when the branch finishes, but it shouldn't execute again.
|
||||
if (util.stackFrame.executedInFrame === undefined) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
if (args.CONDITION) {
|
||||
util.startSubstack();
|
||||
util.startBranch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Scratch3ControlBlocks.prototype.ifElse = function(args, util) {
|
||||
// Only execute one time. `ifElse` will be returned to
|
||||
// when the substack finishes, but it shouldn't execute again.
|
||||
// when the branch finishes, but it shouldn't execute again.
|
||||
if (util.stackFrame.executedInFrame === undefined) {
|
||||
util.stackFrame.executedInFrame = true;
|
||||
if (args.CONDITION) {
|
||||
util.startSubstack(1);
|
||||
util.startBranch(1);
|
||||
} else {
|
||||
util.startSubstack(2);
|
||||
util.startBranch(2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
var Cast = require('../util/cast.js');
|
||||
|
||||
function Scratch3OperatorsBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
|
@ -27,61 +29,63 @@ Scratch3OperatorsBlocks.prototype.getPrimitives = function() {
|
|||
'operator_and': this.and,
|
||||
'operator_or': this.or,
|
||||
'operator_not': this.not,
|
||||
'operator_random': this.random
|
||||
'operator_random': this.random,
|
||||
'operator_join': this.join,
|
||||
'operator_letter_of': this.letterOf,
|
||||
'operator_length': this.length,
|
||||
'operator_mod': this.mod,
|
||||
'operator_round': this.round,
|
||||
'operator_mathop_menu': this.mathopMenu,
|
||||
'operator_mathop': this.mathop
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.number = function (args) {
|
||||
var num = Number(args.NUM);
|
||||
if (num !== num) {
|
||||
// NaN
|
||||
return 0;
|
||||
}
|
||||
return num;
|
||||
return Cast.toNumber(args.NUM);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.text = function (args) {
|
||||
return String(args.TEXT);
|
||||
return Cast.toString(args.TEXT);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.add = function (args) {
|
||||
return args.NUM1 + args.NUM2;
|
||||
return Cast.toNumber(args.NUM1) + Cast.toNumber(args.NUM2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.subtract = function (args) {
|
||||
return args.NUM1 - args.NUM2;
|
||||
return Cast.toNumber(args.NUM1) - Cast.toNumber(args.NUM2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.multiply = function (args) {
|
||||
return args.NUM1 * args.NUM2;
|
||||
return Cast.toNumber(args.NUM1) * Cast.toNumber(args.NUM2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.divide = function (args) {
|
||||
return args.NUM1 / args.NUM2;
|
||||
return Cast.toNumber(args.NUM1) / Cast.toNumber(args.NUM2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.lt = function (args) {
|
||||
return Boolean(args.OPERAND1 < args.OPERAND2);
|
||||
return Cast.compare(args.OPERAND1, args.OPERAND2) < 0;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.equals = function (args) {
|
||||
return Boolean(args.OPERAND1 == args.OPERAND2);
|
||||
return Cast.compare(args.OPERAND1, args.OPERAND2) == 0;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.gt = function (args) {
|
||||
return Boolean(args.OPERAND1 > args.OPERAND2);
|
||||
return Cast.compare(args.OPERAND1, args.OPERAND2) > 0;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.and = function (args) {
|
||||
return Boolean(args.OPERAND1 && args.OPERAND2);
|
||||
return Cast.toBoolean(args.OPERAND1 && args.OPERAND2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.or = function (args) {
|
||||
return Boolean(args.OPERAND1 || args.OPERAND2);
|
||||
return Cast.toBoolean(args.OPERAND1 || args.OPERAND2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.not = function (args) {
|
||||
return Boolean(!args.OPERAND);
|
||||
return Cast.toBoolean(!args.OPERAND);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.random = function (args) {
|
||||
|
@ -97,4 +101,61 @@ Scratch3OperatorsBlocks.prototype.random = function (args) {
|
|||
return (Math.random() * (high - low)) + low;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.join = function (args) {
|
||||
return Cast.toString(args.STRING1) + Cast.toString(args.STRING2);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.letterOf = function (args) {
|
||||
var index = Cast.toNumber(args.LETTER) - 1;
|
||||
var str = Cast.toString(args.STRING);
|
||||
// Out of bounds?
|
||||
if (index < 0 || index >= str.length) {
|
||||
return '';
|
||||
}
|
||||
return str.charAt(index);
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.length = function (args) {
|
||||
return Cast.toString(args.STRING).length;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.mod = function (args) {
|
||||
var n = Cast.toNumber(args.NUM1);
|
||||
var modulus = Cast.toNumber(args.NUM2);
|
||||
var result = n % modulus;
|
||||
// Scratch mod is kept positive.
|
||||
if (result / modulus < 0) result += modulus;
|
||||
return result;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.round = function (args) {
|
||||
return Math.round(Cast.toNumber(args.NUM));
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.mathopMenu = function (args) {
|
||||
return args.OPERATOR;
|
||||
};
|
||||
|
||||
Scratch3OperatorsBlocks.prototype.mathop = function (args) {
|
||||
var operator = Cast.toString(args.OPERATOR).toLowerCase();
|
||||
var n = Cast.toNumber(args.NUM);
|
||||
switch (operator) {
|
||||
case 'abs': return Math.abs(n);
|
||||
case 'floor': return Math.floor(n);
|
||||
case 'ceiling': return Math.ceil(n);
|
||||
case 'sqrt': return Math.sqrt(n);
|
||||
case 'sin': return Math.sin((Math.PI * n) / 180);
|
||||
case 'cos': return Math.cos((Math.PI * n) / 180);
|
||||
case 'tan': return Math.tan((Math.PI * n) / 180);
|
||||
case 'asin': return (Math.asin(n) * 180) / Math.PI;
|
||||
case 'acos': return (Math.acos(n) * 180) / Math.PI;
|
||||
case 'atan': return (Math.atan(n) * 180) / Math.PI;
|
||||
case 'ln': return Math.log(n);
|
||||
case 'log': return Math.log(n) / Math.LN10;
|
||||
case 'e ^': return Math.exp(n);
|
||||
case '10 ^': return Math.pow(10, n);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
module.exports = Scratch3OperatorsBlocks;
|
||||
|
|
43
src/blocks/scratch3_sensing.js
Normal file
43
src/blocks/scratch3_sensing.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
function Scratch3SensingBlocks(runtime) {
|
||||
/**
|
||||
* The runtime instantiating this block package.
|
||||
* @type {Runtime}
|
||||
*/
|
||||
this.runtime = runtime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the block primitives implemented by this package.
|
||||
* @return {Object.<string, Function>} Mapping of opcode to Function.
|
||||
*/
|
||||
Scratch3SensingBlocks.prototype.getPrimitives = function() {
|
||||
return {
|
||||
'sensing_timer': this.getTimer,
|
||||
'sensing_resettimer': this.resetTimer,
|
||||
'sensing_mousex': this.getMouseX,
|
||||
'sensing_mousey': this.getMouseY,
|
||||
'sensing_mousedown': this.getMouseDown
|
||||
};
|
||||
};
|
||||
|
||||
Scratch3SensingBlocks.prototype.getTimer = function (args, util) {
|
||||
return util.ioQuery('clock', 'projectTimer');
|
||||
};
|
||||
|
||||
Scratch3SensingBlocks.prototype.resetTimer = function (args, util) {
|
||||
util.ioQuery('clock', 'resetProjectTimer');
|
||||
};
|
||||
|
||||
Scratch3SensingBlocks.prototype.getMouseX = function (args, util) {
|
||||
return util.ioQuery('mouse', 'getX');
|
||||
};
|
||||
|
||||
Scratch3SensingBlocks.prototype.getMouseY = function (args, util) {
|
||||
return util.ioQuery('mouse', 'getY');
|
||||
};
|
||||
|
||||
Scratch3SensingBlocks.prototype.getMouseDown = function (args, util) {
|
||||
return util.ioQuery('mouse', 'getIsDown');
|
||||
};
|
||||
|
||||
module.exports = Scratch3SensingBlocks;
|
|
@ -15,19 +15,19 @@ function Blocks () {
|
|||
this._blocks = {};
|
||||
|
||||
/**
|
||||
* All stacks in the workspace.
|
||||
* A list of block IDs that represent stacks (first block in stack).
|
||||
* All top-level scripts in the workspace.
|
||||
* A list of block IDs that represent scripts (i.e., first block in script).
|
||||
* @type {Array.<String>}
|
||||
*/
|
||||
this._stacks = [];
|
||||
this._scripts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Blockly inputs that represent statements/substacks
|
||||
* Blockly inputs that represent statements/branch.
|
||||
* are prefixed with this string.
|
||||
* @const{string}
|
||||
*/
|
||||
Blocks.SUBSTACK_INPUT_PREFIX = 'SUBSTACK';
|
||||
Blocks.BRANCH_INPUT_PREFIX = 'SUBSTACK';
|
||||
|
||||
/**
|
||||
* Provide an object with metadata for the requested block ID.
|
||||
|
@ -39,11 +39,11 @@ Blocks.prototype.getBlock = function (blockId) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Get all known top-level blocks that start stacks.
|
||||
* Get all known top-level blocks that start scripts.
|
||||
* @return {Array.<string>} List of block IDs.
|
||||
*/
|
||||
Blocks.prototype.getStacks = function () {
|
||||
return this._stacks;
|
||||
Blocks.prototype.getScripts = function () {
|
||||
return this._scripts;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -57,19 +57,19 @@ Blocks.prototype.getNextBlock = function (id) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Get the substack for a particular C-shaped block
|
||||
* @param {?string} id ID for block to get the substack for
|
||||
* @param {?number} substackNum Which substack to select (e.g. for if-else)
|
||||
* @return {?string} ID of block in the substack
|
||||
* Get the branch for a particular C-shaped block.
|
||||
* @param {?string} id ID for block to get the branch for.
|
||||
* @param {?number} branchNum Which branch to select (e.g. for if-else).
|
||||
* @return {?string} ID of block in the branch.
|
||||
*/
|
||||
Blocks.prototype.getSubstack = function (id, substackNum) {
|
||||
Blocks.prototype.getBranch = function (id, branchNum) {
|
||||
var block = this._blocks[id];
|
||||
if (typeof block === 'undefined') return null;
|
||||
if (!substackNum) substackNum = 1;
|
||||
if (!branchNum) branchNum = 1;
|
||||
|
||||
var inputName = Blocks.SUBSTACK_INPUT_PREFIX;
|
||||
if (substackNum > 1) {
|
||||
inputName += substackNum;
|
||||
var inputName = Blocks.BRANCH_INPUT_PREFIX;
|
||||
if (branchNum > 1) {
|
||||
inputName += branchNum;
|
||||
}
|
||||
|
||||
// Empty C-block?
|
||||
|
@ -98,17 +98,17 @@ Blocks.prototype.getFields = function (id) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Get all non-substack inputs for a block.
|
||||
* Get all non-branch inputs for a block.
|
||||
* @param {?string} id ID of block to query.
|
||||
* @return {!Object} All non-substack inputs and their associated blocks.
|
||||
* @return {!Object} All non-branch inputs and their associated blocks.
|
||||
*/
|
||||
Blocks.prototype.getInputs = function (id) {
|
||||
if (typeof this._blocks[id] === 'undefined') return null;
|
||||
var inputs = {};
|
||||
for (var input in this._blocks[id].inputs) {
|
||||
// Ignore blocks prefixed with substack prefix.
|
||||
if (input.substring(0, Blocks.SUBSTACK_INPUT_PREFIX.length)
|
||||
!= Blocks.SUBSTACK_INPUT_PREFIX) {
|
||||
// Ignore blocks prefixed with branch prefix.
|
||||
if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length)
|
||||
!= Blocks.BRANCH_INPUT_PREFIX) {
|
||||
inputs[input] = this._blocks[id].inputs[input];
|
||||
}
|
||||
}
|
||||
|
@ -136,10 +136,10 @@ Blocks.prototype.generateBlockListener = function (isFlyout, opt_runtime) {
|
|||
if (typeof e !== 'object') return;
|
||||
if (typeof e.blockId !== 'string') return;
|
||||
|
||||
// UI event: clicked stacks toggle in the runtime.
|
||||
// UI event: clicked scripts toggle in the runtime.
|
||||
if (e.element === 'stackclick') {
|
||||
if (opt_runtime) {
|
||||
opt_runtime.toggleStack(e.blockId);
|
||||
opt_runtime.toggleScript(e.blockId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ Blocks.prototype.generateBlockListener = function (isFlyout, opt_runtime) {
|
|||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Block management: create blocks and stacks from a `create` event
|
||||
* Block management: create blocks and scripts from a `create` event
|
||||
* @param {!Object} block Blockly create event to be processed
|
||||
* @param {boolean} opt_isFlyoutBlock Whether the block is in the flyout.
|
||||
*/
|
||||
|
@ -190,12 +190,12 @@ Blocks.prototype.createBlock = function (block, opt_isFlyoutBlock) {
|
|||
// Create new block
|
||||
this._blocks[block.id] = block;
|
||||
|
||||
// Push block id to stacks array.
|
||||
// Push block id to scripts array.
|
||||
// Blocks are added as a top-level stack if they are marked as a top-block
|
||||
// (if they were top-level XML in the event) and if they are not
|
||||
// flyout blocks.
|
||||
if (!opt_isFlyoutBlock && block.topLevel) {
|
||||
this._addStack(block.id);
|
||||
this._addScript(block.id);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -233,10 +233,10 @@ Blocks.prototype.moveBlock = function (e) {
|
|||
|
||||
// Has the block become a top-level block?
|
||||
if (e.newParent === undefined) {
|
||||
this._addStack(e.id);
|
||||
this._addScript(e.id);
|
||||
} else {
|
||||
// Remove stack, if one exists.
|
||||
this._deleteStack(e.id);
|
||||
// Remove script, if one exists.
|
||||
this._deleteScript(e.id);
|
||||
// Otherwise, try to connect it in its new place.
|
||||
if (e.newInput !== undefined) {
|
||||
// Moved to the new parent's input.
|
||||
|
@ -252,11 +252,11 @@ Blocks.prototype.moveBlock = function (e) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Block management: delete blocks and their associated stacks
|
||||
* @param {!Object} e Blockly delete event to be processed
|
||||
* Block management: delete blocks and their associated scripts.
|
||||
* @param {!Object} e Blockly delete event to be processed.
|
||||
*/
|
||||
Blocks.prototype.deleteBlock = function (e) {
|
||||
// @todo In runtime, stop threads running on this stack
|
||||
// @todo In runtime, stop threads running on this script.
|
||||
|
||||
// Get block
|
||||
var block = this._blocks[e.id];
|
||||
|
@ -266,7 +266,7 @@ Blocks.prototype.deleteBlock = function (e) {
|
|||
this.deleteBlock({id: block.next});
|
||||
}
|
||||
|
||||
// Delete inputs (including substacks)
|
||||
// Delete inputs (including branches)
|
||||
for (var input in block.inputs) {
|
||||
// If it's null, the block in this input moved away.
|
||||
if (block.inputs[input].block !== null) {
|
||||
|
@ -274,36 +274,36 @@ Blocks.prototype.deleteBlock = function (e) {
|
|||
}
|
||||
}
|
||||
|
||||
// Delete stack
|
||||
this._deleteStack(e.id);
|
||||
// Delete any script starting with this block.
|
||||
this._deleteScript(e.id);
|
||||
|
||||
// Delete block
|
||||
// Delete block itself.
|
||||
delete this._blocks[e.id];
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Helper to add a stack to `this._stacks`
|
||||
* @param {?string} id ID of block that starts the stack
|
||||
* Helper to add a stack to `this._scripts`.
|
||||
* @param {?string} topBlockId ID of block that starts the script.
|
||||
*/
|
||||
Blocks.prototype._addStack = function (id) {
|
||||
var i = this._stacks.indexOf(id);
|
||||
if (i > -1) return; // Already in stacks.
|
||||
this._stacks.push(id);
|
||||
Blocks.prototype._addScript = function (topBlockId) {
|
||||
var i = this._scripts.indexOf(topBlockId);
|
||||
if (i > -1) return; // Already in scripts.
|
||||
this._scripts.push(topBlockId);
|
||||
// Update `topLevel` property on the top block.
|
||||
this._blocks[id].topLevel = true;
|
||||
this._blocks[topBlockId].topLevel = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper to remove a stack from `this._stacks`
|
||||
* @param {?string} id ID of block that starts the stack
|
||||
* Helper to remove a script from `this._scripts`.
|
||||
* @param {?string} topBlockId ID of block that starts the script.
|
||||
*/
|
||||
Blocks.prototype._deleteStack = function (id) {
|
||||
var i = this._stacks.indexOf(id);
|
||||
if (i > -1) this._stacks.splice(i, 1);
|
||||
Blocks.prototype._deleteScript = function (topBlockId) {
|
||||
var i = this._scripts.indexOf(topBlockId);
|
||||
if (i > -1) this._scripts.splice(i, 1);
|
||||
// Update `topLevel` property on the top block.
|
||||
if (this._blocks[id]) this._blocks[id].topLevel = false;
|
||||
if (this._blocks[topBlockId]) this._blocks[topBlockId].topLevel = false;
|
||||
};
|
||||
|
||||
module.exports = Blocks;
|
||||
|
|
|
@ -58,7 +58,7 @@ var execute = function (sequencer, thread) {
|
|||
// If we've gotten this far, all of the input blocks are evaluated,
|
||||
// and `argValues` is fully populated. So, execute the block primitive.
|
||||
// First, clear `currentStackFrame.reported`, so any subsequent execution
|
||||
// (e.g., on return from a substack) gets fresh inputs.
|
||||
// (e.g., on return from a branch) gets fresh inputs.
|
||||
currentStackFrame.reported = {};
|
||||
|
||||
var primitiveReportedValue = null;
|
||||
|
@ -74,10 +74,17 @@ var execute = function (sequencer, thread) {
|
|||
sequencer.proceedThread(thread);
|
||||
},
|
||||
stackFrame: currentStackFrame.executionContext,
|
||||
startSubstack: function (substackNum) {
|
||||
sequencer.stepToSubstack(thread, substackNum);
|
||||
startBranch: function (branchNum) {
|
||||
sequencer.stepToBranch(thread, branchNum);
|
||||
},
|
||||
target: target
|
||||
target: target,
|
||||
ioQuery: function (device, func, args) {
|
||||
// Find the I/O device and execute the query/function call.
|
||||
if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
|
||||
var devObject = runtime.ioDevices[device];
|
||||
return devObject[func].call(devObject, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Deal with any reported value.
|
||||
|
|
|
@ -3,17 +3,22 @@ var Sequencer = require('./sequencer');
|
|||
var Thread = require('./thread');
|
||||
var util = require('util');
|
||||
|
||||
// Virtual I/O devices.
|
||||
var Clock = require('../io/clock');
|
||||
var Mouse = require('../io/mouse');
|
||||
|
||||
var defaultBlockPackages = {
|
||||
'scratch3_control': require('../blocks/scratch3_control'),
|
||||
'scratch3_event': require('../blocks/scratch3_event'),
|
||||
'scratch3_looks': require('../blocks/scratch3_looks'),
|
||||
'scratch3_motion': require('../blocks/scratch3_motion'),
|
||||
'scratch3_operators': require('../blocks/scratch3_operators'),
|
||||
'scratch3_sound': require('../blocks/scratch3_sound')
|
||||
'scratch3_sound': require('../blocks/scratch3_sound'),
|
||||
'scratch3_sensing': require('../blocks/scratch3_sensing')
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages targets, stacks, and the sequencer.
|
||||
* Manages targets, scripts, and the sequencer.
|
||||
* @param {!Array.<Target>} targets List of targets for this runtime.
|
||||
*/
|
||||
function Runtime (targets) {
|
||||
|
@ -44,6 +49,11 @@ function Runtime (targets) {
|
|||
*/
|
||||
this._primitives = {};
|
||||
this._registerBlockPackages();
|
||||
|
||||
this.ioDevices = {
|
||||
'clock': new Clock(),
|
||||
'mouse': new Mouse()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -147,39 +157,39 @@ Runtime.prototype._removeThread = function (thread) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Toggle a stack
|
||||
* @param {!string} stackId ID of block that starts the stack
|
||||
* Toggle a script.
|
||||
* @param {!string} topBlockId ID of block that starts the script.
|
||||
*/
|
||||
Runtime.prototype.toggleStack = function (stackId) {
|
||||
// Remove any existing thread
|
||||
Runtime.prototype.toggleScript = function (topBlockId) {
|
||||
// Remove any existing thread.
|
||||
for (var i = 0; i < this.threads.length; i++) {
|
||||
if (this.threads[i].topBlock == stackId) {
|
||||
if (this.threads[i].topBlock == topBlockId) {
|
||||
this._removeThread(this.threads[i]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Otherwise add it
|
||||
this._pushThread(stackId);
|
||||
// Otherwise add it.
|
||||
this._pushThread(topBlockId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Green flag, which stops currently running threads
|
||||
* and adds all top-level stacks that start with the green flag
|
||||
* and adds all top-level scripts that start with the green flag
|
||||
*/
|
||||
Runtime.prototype.greenFlag = function () {
|
||||
// Remove all existing threads
|
||||
for (var i = 0; i < this.threads.length; i++) {
|
||||
this._removeThread(this.threads[i]);
|
||||
}
|
||||
// Add all top stacks with green flag
|
||||
// Add all top scripts with green flag
|
||||
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];
|
||||
var scripts = target.blocks.getScripts();
|
||||
for (var j = 0; j < scripts.length; j++) {
|
||||
var topBlock = scripts[j];
|
||||
if (target.blocks.getBlock(topBlock).opcode ===
|
||||
'event_whenflagclicked') {
|
||||
this._pushThread(stacks[j]);
|
||||
this._pushThread(scripts[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ Sequencer.prototype.stepThreads = function (threads) {
|
|||
Sequencer.prototype.startThread = function (thread) {
|
||||
var currentBlockId = thread.peekStack();
|
||||
if (!currentBlockId) {
|
||||
// A "null block" - empty substack.
|
||||
// A "null block" - empty branch.
|
||||
// Yield for the frame.
|
||||
thread.popStack();
|
||||
thread.setStatus(Thread.STATUS_YIELD_FRAME);
|
||||
|
@ -102,22 +102,22 @@ Sequencer.prototype.startThread = function (thread) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Step a thread into a block's substack.
|
||||
* @param {!Thread} thread Thread object to step to substack.
|
||||
* @param {Number} substackNum Which substack to step to (i.e., 1, 2).
|
||||
* Step a thread into a block's branch.
|
||||
* @param {!Thread} thread Thread object to step to branch.
|
||||
* @param {Number} branchNum Which branch to step to (i.e., 1, 2).
|
||||
*/
|
||||
Sequencer.prototype.stepToSubstack = function (thread, substackNum) {
|
||||
if (!substackNum) {
|
||||
substackNum = 1;
|
||||
Sequencer.prototype.stepToBranch = function (thread, branchNum) {
|
||||
if (!branchNum) {
|
||||
branchNum = 1;
|
||||
}
|
||||
var currentBlockId = thread.peekStack();
|
||||
var substackId = this.runtime.targetForThread(thread).blocks.getSubstack(
|
||||
var branchId = this.runtime.targetForThread(thread).blocks.getBranch(
|
||||
currentBlockId,
|
||||
substackNum
|
||||
branchNum
|
||||
);
|
||||
if (substackId) {
|
||||
// Push substack ID to the thread's stack.
|
||||
thread.pushStack(substackId);
|
||||
if (branchId) {
|
||||
// Push branch ID to the thread's stack.
|
||||
thread.pushStack(branchId);
|
||||
} else {
|
||||
// Push null, so we come back to the current block.
|
||||
thread.pushStack(null);
|
||||
|
|
14
src/index.js
14
src/index.js
|
@ -101,6 +101,17 @@ 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);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Worker handlers: for all public methods available above,
|
||||
* we must also provide a message handler in case the VM is run
|
||||
|
@ -140,6 +151,9 @@ if (ENV_WORKER) {
|
|||
case 'animationFrame':
|
||||
self.vmInstance.animationFrame();
|
||||
break;
|
||||
case 'postIOData':
|
||||
self.vmInstance.postIOData(messageData.device, messageData.data);
|
||||
break;
|
||||
default:
|
||||
if (e.data.id == 'RendererConnected') {
|
||||
//initRenderWorker();
|
||||
|
|
16
src/io/clock.js
Normal file
16
src/io/clock.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
var Timer = require('../util/timer');
|
||||
|
||||
function Clock () {
|
||||
this._projectTimer = new Timer();
|
||||
this._projectTimer.start();
|
||||
}
|
||||
|
||||
Clock.prototype.projectTimer = function () {
|
||||
return this._projectTimer.timeElapsed() / 1000;
|
||||
};
|
||||
|
||||
Clock.prototype.resetProjectTimer = function () {
|
||||
this._projectTimer.start();
|
||||
};
|
||||
|
||||
module.exports = Clock;
|
33
src/io/mouse.js
Normal file
33
src/io/mouse.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
var MathUtil = require('../util/math-util');
|
||||
|
||||
function Mouse () {
|
||||
this._x = 0;
|
||||
this._y = 0;
|
||||
this._isDown = false;
|
||||
}
|
||||
|
||||
Mouse.prototype.postData = function(data) {
|
||||
if (data.x) {
|
||||
this._x = data.x;
|
||||
}
|
||||
if (data.y) {
|
||||
this._y = data.y;
|
||||
}
|
||||
if (typeof data.isDown !== 'undefined') {
|
||||
this._isDown = data.isDown;
|
||||
}
|
||||
};
|
||||
|
||||
Mouse.prototype.getX = function () {
|
||||
return MathUtil.clamp(this._x, -240, 240);
|
||||
};
|
||||
|
||||
Mouse.prototype.getY = function () {
|
||||
return MathUtil.clamp(-this._y, -180, 180);
|
||||
};
|
||||
|
||||
Mouse.prototype.getIsDown = function () {
|
||||
return this._isDown;
|
||||
};
|
||||
|
||||
module.exports = Mouse;
|
88
src/util/cast.js
Normal file
88
src/util/cast.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
function Cast () {}
|
||||
|
||||
/**
|
||||
* @fileoverview
|
||||
* Utilities for casting and comparing Scratch data-types.
|
||||
* Scratch behaves slightly differently from JavaScript in many respects,
|
||||
* and these differences should be encapsulated below.
|
||||
* For example, in Scratch, add(1, join("hello", world")) -> 1.
|
||||
* This is because "hello world" is cast to 0.
|
||||
* In JavaScript, 1 + Number("hello" + "world") would give you NaN.
|
||||
* Use when coercing a value before computation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Scratch cast to number.
|
||||
* Treats NaN as 0.
|
||||
* In Scratch 2.0, this is captured by `interp.numArg.`
|
||||
* @param {*} value Value to cast to number.
|
||||
* @return {number} The Scratch-casted number value.
|
||||
*/
|
||||
Cast.toNumber = function (value) {
|
||||
var n = Number(value);
|
||||
if (isNaN(n)) {
|
||||
// Scratch treats NaN as 0, when needed as a number.
|
||||
// E.g., 0 + NaN -> 0.
|
||||
return 0;
|
||||
}
|
||||
return n;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scratch cast to boolean.
|
||||
* In Scratch 2.0, this is captured by `interp.boolArg.`
|
||||
* Treats some string values differently from JavaScript.
|
||||
* @param {*} value Value to cast to boolean.
|
||||
* @return {boolean} The Scratch-casted boolean value.
|
||||
*/
|
||||
Cast.toBoolean = function (value) {
|
||||
// Already a boolean?
|
||||
if (typeof value === 'boolean') {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
// These specific strings are treated as false in Scratch.
|
||||
if ((value == '') ||
|
||||
(value == '0') ||
|
||||
(value.toLowerCase() == 'false')) {
|
||||
return false;
|
||||
}
|
||||
// All other strings treated as true.
|
||||
return true;
|
||||
}
|
||||
// Coerce other values and numbers.
|
||||
return Boolean(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Scratch cast to string.
|
||||
* @param {*} value Value to cast to string.
|
||||
* @return {string} The Scratch-casted string value.
|
||||
*/
|
||||
Cast.toString = function (value) {
|
||||
return String(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare two values, using Scratch cast, case-insensitive string compare, etc.
|
||||
* In Scratch 2.0, this is captured by `interp.compare.`
|
||||
* @param {*} v1 First value to compare.
|
||||
* @param {*} v2 Second value to compare.
|
||||
* @returns {Number} Negative number if v1 < v2; 0 if equal; positive otherwise.
|
||||
*/
|
||||
Cast.compare = function (v1, v2) {
|
||||
var n1 = Number(v1);
|
||||
var n2 = Number(v2);
|
||||
if (isNaN(n1) || isNaN(n2)) {
|
||||
// At least one argument can't be converted to a number.
|
||||
// Scratch compares strings as case insensitive.
|
||||
var s1 = String(v1).toLowerCase();
|
||||
var s2 = String(v2).toLowerCase();
|
||||
return s1.localeCompare(s2);
|
||||
} else {
|
||||
// Compare as numbers.
|
||||
return n1 - n2;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Cast;
|
|
@ -231,6 +231,14 @@ VirtualMachine.prototype.getPlaygroundData = function () {
|
|||
this.vmWorker.postMessage({method: 'getPlaygroundData'});
|
||||
};
|
||||
|
||||
VirtualMachine.prototype.postIOData = function (device, data) {
|
||||
this.vmWorker.postMessage({
|
||||
method: 'postIOData',
|
||||
device: device,
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
VirtualMachine.prototype.start = function () {
|
||||
this.vmWorker.postMessage({method: 'start'});
|
||||
};
|
||||
|
|
4
test/fixtures/events.json
vendored
4
test/fixtures/events.json
vendored
|
@ -12,13 +12,13 @@
|
|||
"!6Ahqg4f}Ljl}X5Hws?Z"
|
||||
]
|
||||
},
|
||||
"createsubstack": {
|
||||
"createbranch": {
|
||||
"name": "block",
|
||||
"xml": {
|
||||
"outerHTML": "<block type=\"control_forever\" id=\"r9`RpL74T6*SXPKv7}Dq\" x=\"61\" y=\"90\"><statement name=\"SUBSTACK\"><block type=\"control_wait\" id=\"{Rwt[LFtD1-JPAi-qf:.\"><value name=\"DURATION\"><shadow type=\"math_number\" id=\"VMDxt_9SYe5{*eNRe5dZ\"><field name=\"NUM\">1</field></shadow></value></block></statement></block>"
|
||||
}
|
||||
},
|
||||
"createtwosubstacks": {
|
||||
"createtwobranches": {
|
||||
"name": "block",
|
||||
"xml": {
|
||||
"outerHTML": "<block type=\"control_if_else\" id=\"8W?lmIY!Tgnh)~0!G#9-\" x=\"87\" y=\"159\"><statement name=\"SUBSTACK\"><block type=\"event_broadcast\" id=\"lgU2GGtwlREuasCB02Vr\"></block></statement><statement name=\"SUBSTACK2\"><block type=\"event_broadcast\" id=\"Gb]N,2P;|J%F?pxSwz(2\"></block></statement></block>"
|
||||
|
|
|
@ -43,8 +43,8 @@ test('create event', function (t) {
|
|||
t.end();
|
||||
});
|
||||
|
||||
test('create with substack', function (t) {
|
||||
var result = adapter(events.createsubstack);
|
||||
test('create with branch', function (t) {
|
||||
var result = adapter(events.createbranch);
|
||||
// Outer block
|
||||
t.type(result[0].id, 'string');
|
||||
t.type(result[0].opcode, 'string');
|
||||
|
@ -53,22 +53,22 @@ test('create with substack', function (t) {
|
|||
t.type(result[0].inputs['SUBSTACK'], 'object');
|
||||
t.type(result[0].topLevel, 'boolean');
|
||||
t.equal(result[0].topLevel, true);
|
||||
// In substack
|
||||
var substackBlockId = result[0].inputs['SUBSTACK']['block'];
|
||||
t.type(substackBlockId, 'string');
|
||||
// Find actual substack block
|
||||
var substackBlock = null;
|
||||
// In branch
|
||||
var branchBlockId = result[0].inputs['SUBSTACK']['block'];
|
||||
t.type(branchBlockId, 'string');
|
||||
// Find actual branch block
|
||||
var branchBlock = null;
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
if (result[i].id == substackBlockId) {
|
||||
substackBlock = result[i];
|
||||
if (result[i].id == branchBlockId) {
|
||||
branchBlock = result[i];
|
||||
}
|
||||
}
|
||||
t.type(substackBlock, 'object');
|
||||
t.type(branchBlock, 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('create with two substacks', function (t) {
|
||||
var result = adapter(events.createtwosubstacks);
|
||||
test('create with two branches', function (t) {
|
||||
var result = adapter(events.createtwobranches);
|
||||
// Outer block
|
||||
t.type(result[0].id, 'string');
|
||||
t.type(result[0].opcode, 'string');
|
||||
|
@ -78,24 +78,24 @@ test('create with two substacks', function (t) {
|
|||
t.type(result[0].inputs['SUBSTACK2'], 'object');
|
||||
t.type(result[0].topLevel, 'boolean');
|
||||
t.equal(result[0].topLevel, true);
|
||||
// In substacks
|
||||
var firstSubstackBlockId = result[0].inputs['SUBSTACK']['block'];
|
||||
var secondSubstackBlockId = result[0].inputs['SUBSTACK2']['block'];
|
||||
t.type(firstSubstackBlockId, 'string');
|
||||
t.type(secondSubstackBlockId, 'string');
|
||||
// Find actual substack blocks
|
||||
var firstSubstackBlock = null;
|
||||
var secondSubstackBlock = null;
|
||||
// In branchs
|
||||
var firstBranchBlockId = result[0].inputs['SUBSTACK']['block'];
|
||||
var secondBranchBlockId = result[0].inputs['SUBSTACK2']['block'];
|
||||
t.type(firstBranchBlockId, 'string');
|
||||
t.type(secondBranchBlockId, 'string');
|
||||
// Find actual branch blocks
|
||||
var firstBranchBlock = null;
|
||||
var secondBranchBlock = null;
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
if (result[i].id == firstSubstackBlockId) {
|
||||
firstSubstackBlock = result[i];
|
||||
if (result[i].id == firstBranchBlockId) {
|
||||
firstBranchBlock = result[i];
|
||||
}
|
||||
if (result[i].id == secondSubstackBlockId) {
|
||||
secondSubstackBlock = result[i];
|
||||
if (result[i].id == secondBranchBlockId) {
|
||||
secondBranchBlock = result[i];
|
||||
}
|
||||
}
|
||||
t.type(firstSubstackBlock, 'object');
|
||||
t.type(secondSubstackBlock, 'object');
|
||||
t.type(firstBranchBlock, 'object');
|
||||
t.type(secondBranchBlock, 'object');
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
|
|
@ -9,17 +9,17 @@ test('spec', function (t) {
|
|||
t.ok(b instanceof Blocks);
|
||||
|
||||
t.type(b._blocks, 'object');
|
||||
t.type(b._stacks, 'object');
|
||||
t.ok(Array.isArray(b._stacks));
|
||||
t.type(b._scripts, 'object');
|
||||
t.ok(Array.isArray(b._scripts));
|
||||
|
||||
t.type(b.createBlock, 'function');
|
||||
t.type(b.moveBlock, 'function');
|
||||
t.type(b.changeBlock, 'function');
|
||||
t.type(b.deleteBlock, 'function');
|
||||
t.type(b.getBlock, 'function');
|
||||
t.type(b.getStacks, 'function');
|
||||
t.type(b.getScripts, 'function');
|
||||
t.type(b.getNextBlock, 'function');
|
||||
t.type(b.getSubstack, 'function');
|
||||
t.type(b.getBranch, 'function');
|
||||
t.type(b.getOpcode, 'function');
|
||||
|
||||
|
||||
|
@ -44,11 +44,11 @@ test('getBlock', function (t) {
|
|||
t.end();
|
||||
});
|
||||
|
||||
test('getStacks', function (t) {
|
||||
test('getScripts', function (t) {
|
||||
var b = new Blocks();
|
||||
var stacks = b.getStacks();
|
||||
t.type(stacks, 'object');
|
||||
t.equals(stacks.length, 0);
|
||||
var scripts = b.getScripts();
|
||||
t.type(scripts, 'object');
|
||||
t.equals(scripts.length, 0);
|
||||
// Create two top-level blocks and one not.
|
||||
b.createBlock({
|
||||
id: 'foo',
|
||||
|
@ -75,12 +75,12 @@ test('getStacks', function (t) {
|
|||
topLevel: false
|
||||
});
|
||||
|
||||
stacks = b.getStacks();
|
||||
t.type(stacks, 'object');
|
||||
t.equals(stacks.length, 2);
|
||||
t.ok(stacks.indexOf('foo') > -1);
|
||||
t.ok(stacks.indexOf('foo2') > -1);
|
||||
t.equals(stacks.indexOf('foo3'), -1);
|
||||
scripts = b.getScripts();
|
||||
t.type(scripts, 'object');
|
||||
t.equals(scripts.length, 2);
|
||||
t.ok(scripts.indexOf('foo') > -1);
|
||||
t.ok(scripts.indexOf('foo2') > -1);
|
||||
t.equals(scripts.indexOf('foo3'), -1);
|
||||
t.end();
|
||||
|
||||
});
|
||||
|
@ -119,9 +119,9 @@ test('getNextBlock', function (t) {
|
|||
t.end();
|
||||
});
|
||||
|
||||
test('getSubstack', function (t) {
|
||||
test('getBranch', function (t) {
|
||||
var b = new Blocks();
|
||||
// Single substack
|
||||
// Single branch
|
||||
b.createBlock({
|
||||
id: 'foo',
|
||||
opcode: 'TEST_BLOCK',
|
||||
|
@ -144,18 +144,18 @@ test('getSubstack', function (t) {
|
|||
topLevel: false
|
||||
});
|
||||
|
||||
var substack = b.getSubstack('foo');
|
||||
t.equals(substack, 'foo2');
|
||||
var branch = b.getBranch('foo');
|
||||
t.equals(branch, 'foo2');
|
||||
|
||||
var notSubstack = b.getSubstack('?');
|
||||
t.equals(notSubstack, null);
|
||||
var notBranch = b.getBranch('?');
|
||||
t.equals(notBranch, null);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('getSubstack2', function (t) {
|
||||
test('getBranch2', function (t) {
|
||||
var b = new Blocks();
|
||||
// Second substack
|
||||
// Second branch
|
||||
b.createBlock({
|
||||
id: 'foo',
|
||||
opcode: 'TEST_BLOCK',
|
||||
|
@ -190,15 +190,15 @@ test('getSubstack2', function (t) {
|
|||
topLevel: false
|
||||
});
|
||||
|
||||
var substack1 = b.getSubstack('foo', 1);
|
||||
var substack2 = b.getSubstack('foo', 2);
|
||||
t.equals(substack1, 'foo2');
|
||||
t.equals(substack2, 'foo3');
|
||||
var branch1 = b.getBranch('foo', 1);
|
||||
var branch2 = b.getBranch('foo', 2);
|
||||
t.equals(branch1, 'foo2');
|
||||
t.equals(branch2, 'foo3');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('getSubstack with none', function (t) {
|
||||
test('getBranch with none', function (t) {
|
||||
var b = new Blocks();
|
||||
b.createBlock({
|
||||
id: 'foo',
|
||||
|
@ -208,8 +208,8 @@ test('getSubstack with none', function (t) {
|
|||
inputs: {},
|
||||
topLevel: true
|
||||
});
|
||||
var noSubstack = b.getSubstack('foo');
|
||||
t.equals(noSubstack, null);
|
||||
var noBranch = b.getBranch('foo');
|
||||
t.equals(noBranch, null);
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -244,7 +244,7 @@ test('create', function (t) {
|
|||
|
||||
t.type(b._blocks['foo'], 'object');
|
||||
t.equal(b._blocks['foo'].opcode, 'TEST_BLOCK');
|
||||
t.notEqual(b._stacks.indexOf('foo'), -1);
|
||||
t.notEqual(b._scripts.indexOf('foo'), -1);
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -272,7 +272,7 @@ test('move', function (t) {
|
|||
id: 'bar',
|
||||
newParent: 'foo'
|
||||
});
|
||||
t.equal(b._stacks.length, 1);
|
||||
t.equal(b._scripts.length, 1);
|
||||
t.equal(Object.keys(b._blocks).length, 2);
|
||||
t.equal(b._blocks['foo'].next, 'bar');
|
||||
|
||||
|
@ -281,7 +281,7 @@ test('move', function (t) {
|
|||
id: 'bar',
|
||||
oldParent: 'foo'
|
||||
});
|
||||
t.equal(b._stacks.length, 2);
|
||||
t.equal(b._scripts.length, 2);
|
||||
t.equal(Object.keys(b._blocks).length, 2);
|
||||
t.equal(b._blocks['foo'].next, null);
|
||||
|
||||
|
@ -360,7 +360,7 @@ test('delete', function (t) {
|
|||
});
|
||||
|
||||
t.type(b._blocks['foo'], 'undefined');
|
||||
t.equal(b._stacks.indexOf('foo'), -1);
|
||||
t.equal(b._scripts.indexOf('foo'), -1);
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -398,9 +398,9 @@ test('delete chain', function (t) {
|
|||
t.type(b._blocks['foo'], 'undefined');
|
||||
t.type(b._blocks['foo2'], 'undefined');
|
||||
t.type(b._blocks['foo3'], 'undefined');
|
||||
t.equal(b._stacks.indexOf('foo'), -1);
|
||||
t.equal(b._scripts.indexOf('foo'), -1);
|
||||
t.equal(Object.keys(b._blocks).length, 0);
|
||||
t.equal(b._stacks.length, 0);
|
||||
t.equal(b._scripts.length, 0);
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -461,8 +461,8 @@ test('delete inputs', function (t) {
|
|||
t.type(b._blocks['foo2'], 'undefined');
|
||||
t.type(b._blocks['foo3'], 'undefined');
|
||||
t.type(b._blocks['foo4'], 'undefined');
|
||||
t.equal(b._stacks.indexOf('foo'), -1);
|
||||
t.equal(b._scripts.indexOf('foo'), -1);
|
||||
t.equal(Object.keys(b._blocks).length, 0);
|
||||
t.equal(b._stacks.length, 0);
|
||||
t.equal(b._scripts.length, 0);
|
||||
t.end();
|
||||
});
|
||||
|
|
12
vm.min.js
vendored
12
vm.min.js
vendored
File diff suppressed because one or more lines are too long
15
vm.worker.js
15
vm.worker.js
|
@ -97,6 +97,14 @@
|
|||
this.vmWorker.postMessage({method: 'getPlaygroundData'});
|
||||
};
|
||||
|
||||
VirtualMachine.prototype.postIOData = function (device, data) {
|
||||
this.vmWorker.postMessage({
|
||||
method: 'postIOData',
|
||||
device: device,
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
VirtualMachine.prototype.start = function () {
|
||||
this.vmWorker.postMessage({method: 'start'});
|
||||
};
|
||||
|
@ -109,6 +117,10 @@
|
|||
this.vmWorker.postMessage({method: 'stopAll'});
|
||||
};
|
||||
|
||||
VirtualMachine.prototype.animationFrame = function () {
|
||||
this.vmWorker.postMessage({method: 'animationFrame'});
|
||||
};
|
||||
|
||||
/**
|
||||
* Export and bind to `window`
|
||||
*/
|
||||
|
@ -1026,6 +1038,9 @@
|
|||
var queueIndex = -1;
|
||||
|
||||
function cleanUpNextTick() {
|
||||
if (!draining || !currentQueue) {
|
||||
return;
|
||||
}
|
||||
draining = false;
|
||||
if (currentQueue.length) {
|
||||
queue = currentQueue.concat(queue);
|
||||
|
|
Loading…
Reference in a new issue