mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-01-10 15:02:06 -05:00
WIP
This commit is contained in:
parent
655556273a
commit
f9f47ed103
20 changed files with 2113 additions and 13 deletions
17
.eslintrc
Normal file
17
.eslintrc
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"curly": [2, "multi-line"],
|
||||||
|
"eol-last": [2],
|
||||||
|
"indent": [2, 4],
|
||||||
|
"quotes": [2, "single"],
|
||||||
|
"linebreak-style": [2, "unix"],
|
||||||
|
"max-len": [2, 80, 4],
|
||||||
|
"semi": [2, "always"],
|
||||||
|
"strict": [2, "never"]
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"node": true,
|
||||||
|
"browser": true
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended"
|
||||||
|
}
|
5
.npmrc
5
.npmrc
|
@ -1,5 +0,0 @@
|
||||||
engine-strict=true
|
|
||||||
save-exact=true
|
|
||||||
save-prefix=~
|
|
||||||
init-license=BSD-3-Clause
|
|
||||||
init-author-name=Massachusetts Institute of Technology
|
|
15
Makefile
15
Makefile
|
@ -1,12 +1,21 @@
|
||||||
ESLINT=./node_modules/.bin/eslint
|
ESLINT=./node_modules/.bin/eslint
|
||||||
NODE=node
|
NODE=node
|
||||||
TAP=./node_modules/.bin/tap
|
TAP=./node_modules/.bin/tap
|
||||||
|
WEBPACK=./node_modules/.bin/webpack --progress --colors
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
build:
|
||||||
|
$(WEBPACK)
|
||||||
|
|
||||||
|
watch:
|
||||||
|
$(WEBPACK) --watch
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
$(ESLINT) ./*.js
|
$(ESLINT) ./src/*.js
|
||||||
$(ESLINT) ./lib/*.js
|
$(ESLINT) ./src/**/*.js
|
||||||
$(ESLINT) ./test/**/*.js
|
$(ESLINT) ./test/**/*.js
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
@ -21,4 +30,4 @@ benchmark:
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
.PHONY: lint test coverage benchmark
|
.PHONY: build lint test coverage benchmark
|
||||||
|
|
22
README.md
22
README.md
|
@ -5,9 +5,29 @@
|
||||||
npm install scratch-vm
|
npm install scratch-vm
|
||||||
```
|
```
|
||||||
|
|
||||||
## Integration
|
## Setup
|
||||||
```js
|
```js
|
||||||
|
var VirtualMachine = require('scratch-vm');
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
|
||||||
|
// Block events
|
||||||
|
|
||||||
|
// UI events
|
||||||
|
|
||||||
|
// Listen for events
|
||||||
|
```
|
||||||
|
|
||||||
|
## Standalone Build
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="/path/to/vm.js"></script>
|
||||||
|
<script>
|
||||||
|
var vm = new window.VirtualMachine();
|
||||||
|
// do things
|
||||||
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
0
index.js
0
index.js
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "scratch-vm",
|
"name": "scratch-vm",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "Virtual Machine for Scratch 3.0",
|
||||||
"author": "Massachusetts Institute of Technology",
|
"author": "Massachusetts Institute of Technology",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"homepage": "https://github.com/LLK/scratch-vm#readme",
|
"homepage": "https://github.com/LLK/scratch-vm#readme",
|
||||||
|
@ -9,13 +9,15 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+ssh://git@github.com/LLK/scratch-vm.git"
|
"url": "git+ssh://git@github.com/LLK/scratch-vm.git"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "./src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "make test"
|
"test": "make test"
|
||||||
},
|
},
|
||||||
|
"dependencies": {},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"benchmark": "2.1.0",
|
"benchmark": "2.1.0",
|
||||||
"eslint": "2.7.0",
|
"eslint": "2.7.0",
|
||||||
"tap": "5.7.1"
|
"tap": "5.7.1",
|
||||||
|
"webpack": "1.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
41
src/engine/primatives.js
Normal file
41
src/engine/primatives.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
function Primitives () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Primitives.prototype.event_whenflagclicked = function (thread, runtime) {
|
||||||
|
// No-op: flags are started by the interpreter but don't do any action
|
||||||
|
// Take 1/3 second to show running state
|
||||||
|
if (Date.now() - thread.blockFirstTime < 300) {
|
||||||
|
thread.yield = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Primitives.prototype.control_repeat = function (thread, runtime) {
|
||||||
|
// Take 1/3 second to show running state
|
||||||
|
if (Date.now() - thread.blockFirstTime < 300) {
|
||||||
|
thread.yield = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (thread.repeatCounter == -1) {
|
||||||
|
thread.repeatCounter = 10; // @todo from the arg
|
||||||
|
}
|
||||||
|
if (thread.repeatCounter > 0) {
|
||||||
|
thread.repeatCounter -= 1;
|
||||||
|
runtime.interpreter.startSubstack(thread);
|
||||||
|
} else {
|
||||||
|
thread.repeatCounter = -1;
|
||||||
|
thread.nextBlock = runtime.getNextBlock(thread.blockPointer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Primitives.prototype.control_forever = function (thread, runtime) {
|
||||||
|
// Take 1/3 second to show running state
|
||||||
|
if (Date.now() - thread.blockFirstTime < 300) {
|
||||||
|
thread.yield = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
runtime.interpreter.startSubstack(thread);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Primitives;
|
119
src/engine/runtime.js
Normal file
119
src/engine/runtime.js
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
var Primitives = require('./primatives');
|
||||||
|
var Sequencer = require('./sequencer');
|
||||||
|
var Thread = require('./thread');
|
||||||
|
|
||||||
|
var STEP_THREADS_INTERVAL = 1000 / 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple runtime for blocks.
|
||||||
|
*/
|
||||||
|
function Runtime () {
|
||||||
|
this.sequencer = new Sequencer(this);
|
||||||
|
this.primitives = new Primitives();
|
||||||
|
|
||||||
|
// State
|
||||||
|
this.blocks = {};
|
||||||
|
this.stacks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Runtime.prototype.createBlock = function (e) {
|
||||||
|
// Create new block
|
||||||
|
this.blocks[e.id] = {
|
||||||
|
id: e.id,
|
||||||
|
opcode: e.opcode,
|
||||||
|
next: null,
|
||||||
|
inputs: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push block id to stacks array. New blocks are always a stack even if only
|
||||||
|
// momentary. If the new block is added to an existing stack this stack will
|
||||||
|
// be removed by the `moveBlock` method below.
|
||||||
|
this.stacks.push(e.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype.moveBlock = function (e) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
// Block has a new parent
|
||||||
|
if (e.oldParent === undefined && e.newParent !== undefined) {
|
||||||
|
// Remove stack
|
||||||
|
_this._deleteStack(e.id);
|
||||||
|
|
||||||
|
// Update new parent
|
||||||
|
if (e.newInput === undefined) {
|
||||||
|
_this.blocks[e.newParent].next = e.id;
|
||||||
|
} else {
|
||||||
|
_this.blocks[e.newParent].inputs[e.newInput] = e.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block was removed from parent
|
||||||
|
if (e.newParentId === undefined && e.oldParent !== undefined) {
|
||||||
|
// Add stack
|
||||||
|
_this.stacks.push(e.id);
|
||||||
|
|
||||||
|
// Update old parent
|
||||||
|
if (e.oldInput === undefined) {
|
||||||
|
_this.blocks[e.oldParent].next = null;
|
||||||
|
} else {
|
||||||
|
delete _this.blocks[e.oldParent].inputs[e.oldInput];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype.changeBlock = function (e) {
|
||||||
|
// @todo
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype.deleteBlock = function (e) {
|
||||||
|
// @todo Stop threads running on this stack
|
||||||
|
|
||||||
|
// Delete children
|
||||||
|
var block = this.blocks[e.id];
|
||||||
|
if (block.next !== null) {
|
||||||
|
this.deleteBlock({id: block.next});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete inputs
|
||||||
|
for (var i in block.inputs) {
|
||||||
|
this.deleteBlock({id: block.inputs[i]});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete stack
|
||||||
|
this._deleteStack(e.id);
|
||||||
|
|
||||||
|
// Delete block
|
||||||
|
delete this.blocks[e.id];
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype.runAllStacks = function () {
|
||||||
|
// @todo
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype.runStack = function () {
|
||||||
|
// @todo
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype.stopAllStacks = function () {
|
||||||
|
// @todo
|
||||||
|
};
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Runtime.prototype._deleteStack = function (id) {
|
||||||
|
var i = this.stacks.indexOf(id);
|
||||||
|
if (i > -1) this.stacks.splice(i, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype._getNextBlock = function (id) {
|
||||||
|
if (typeof this.blocks[id] === 'undefined') return null;
|
||||||
|
return this.blocks[id].next;
|
||||||
|
};
|
||||||
|
|
||||||
|
Runtime.prototype._getSubstack = function (id) {
|
||||||
|
if (typeof this.blocks[id] === 'undefined') return null;
|
||||||
|
return this.blocks[id].inputs['SUBSTACK'];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Runtime;
|
29
src/engine/sequencer.js
Normal file
29
src/engine/sequencer.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
var Timer = require('../util/timer');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
function Sequencer (runtime) {
|
||||||
|
// Bi-directional binding for runtime
|
||||||
|
this.runtime = runtime;
|
||||||
|
|
||||||
|
// State
|
||||||
|
this.runningThreads = [];
|
||||||
|
this.workTime = 30;
|
||||||
|
this.timer = new Timer();
|
||||||
|
this.currentTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sequencer.prototype.stepAllThreads = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Sequencer.prototype.stepThread = function (thread) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Sequencer.prototype.startSubstack = function (thread) {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Sequencer;
|
19
src/engine/thread.js
Normal file
19
src/engine/thread.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* Thread is an internal data structure used by the interpreter. It holds the
|
||||||
|
* state of a thread so it can continue from where it left off, and it has
|
||||||
|
* a stack to support nested control structures and procedure calls.
|
||||||
|
*
|
||||||
|
* @param {String} Unique block identifier
|
||||||
|
*/
|
||||||
|
function Thread (id) {
|
||||||
|
this.topBlockId = id;
|
||||||
|
this.blockPointer = id;
|
||||||
|
this.blockFirstTime = -1;
|
||||||
|
this.nextBlock = null;
|
||||||
|
this.waiting = null;
|
||||||
|
this.runningDeviceBlock = false;
|
||||||
|
this.stack = [];
|
||||||
|
this.repeatCounter = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Thread;
|
148
src/index.js
Normal file
148
src/index.js
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
var EventEmitter = require('events');
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
var Runtime = require('./engine/runtime');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
instance.runtime = new Runtime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event listener for blockly. Handles validation and serves as a generic
|
||||||
|
* adapter between the blocks and the runtime interface.
|
||||||
|
*
|
||||||
|
* @param {Object} Blockly "block" event
|
||||||
|
*/
|
||||||
|
instance.blockListener = function (e) {
|
||||||
|
// Validate event
|
||||||
|
if (typeof e !== 'object') return;
|
||||||
|
if (typeof e.blockId !== 'string') return;
|
||||||
|
|
||||||
|
// Blocks
|
||||||
|
switch (e.type) {
|
||||||
|
case 'create':
|
||||||
|
instance.runtime.createBlock({
|
||||||
|
id: e.blockId,
|
||||||
|
opcode: e.xml.attributes.type.value
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'move':
|
||||||
|
instance.runtime.moveBlock({
|
||||||
|
id: e.blockId,
|
||||||
|
oldParent: e.oldParentId,
|
||||||
|
oldInput: e.oldInputName,
|
||||||
|
newParent: e.newParentId,
|
||||||
|
newInput: e.newInputName
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'change':
|
||||||
|
instance.runtime.changeBlock({
|
||||||
|
id: e.blockId
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
instance.runtime.deleteBlock({
|
||||||
|
id: e.blockId
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// @todo UI listener
|
||||||
|
|
||||||
|
// @todo Forward runtime events
|
||||||
|
|
||||||
|
// Event dispatcher
|
||||||
|
// this.types = keymirror({
|
||||||
|
// // Messages to runtime
|
||||||
|
// CREATE_BLOCK: null,
|
||||||
|
// MOVE_BLOCK: null,
|
||||||
|
// CHANGE_BLOCK: null,
|
||||||
|
// DELETE_BLOCK: null,
|
||||||
|
//
|
||||||
|
// ADD_DEVICE: null,
|
||||||
|
// REMOVE_DEVICE: null,
|
||||||
|
//
|
||||||
|
// RUN_STRIP: null,
|
||||||
|
// RUN_ALL_STRIPS: null,
|
||||||
|
// STOP_ALL_STRIPS: null,
|
||||||
|
// RUN_PALETTE_BLOCK: null,
|
||||||
|
//
|
||||||
|
// // Messages from runtime - subscribe to these
|
||||||
|
// FEEDBACK_EXECUTING_BLOCK: null,
|
||||||
|
// FEEDBACK_STOPPED_EXECUTING_BLOCK: null,
|
||||||
|
// DEVICE_RUN_OP: null,
|
||||||
|
// DEVICE_STOP_OP: null,
|
||||||
|
//
|
||||||
|
// // Tell back the interpreter device has finished an op
|
||||||
|
// DEVICE_FINISHED_OP: null
|
||||||
|
// });
|
||||||
|
|
||||||
|
// Bind block event stream
|
||||||
|
// setTimeout(function () {
|
||||||
|
// _this.emit('foo', 'bar');
|
||||||
|
// }, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherit from EventEmitter
|
||||||
|
*/
|
||||||
|
util.inherits(VirtualMachine, EventEmitter);
|
||||||
|
|
||||||
|
// VirtualMachine.prototype.changeListener = function (e) {
|
||||||
|
// var _this = this;
|
||||||
|
// console.dir(this);
|
||||||
|
//
|
||||||
|
// switch (e.type) {
|
||||||
|
// case 'create':
|
||||||
|
// console.dir(e);
|
||||||
|
// _this.runtime.createBlock(
|
||||||
|
// e.blockId,
|
||||||
|
// event.xml.attributes.type.value
|
||||||
|
// );
|
||||||
|
// break;
|
||||||
|
// case 'change':
|
||||||
|
// // @todo
|
||||||
|
// break;
|
||||||
|
// case 'move':
|
||||||
|
// // @todo
|
||||||
|
// break;
|
||||||
|
// case 'delete':
|
||||||
|
// // @todo
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// VirtualMachine.prototype.tapListener = function (e) {
|
||||||
|
// // @todo
|
||||||
|
// };
|
||||||
|
|
||||||
|
VirtualMachine.prototype.start = function () {
|
||||||
|
this.runtime.runAllGreenFlags();
|
||||||
|
};
|
||||||
|
|
||||||
|
VirtualMachine.prototype.stop = function () {
|
||||||
|
this.runtime.stop();
|
||||||
|
};
|
||||||
|
|
||||||
|
VirtualMachine.prototype.save = function () {
|
||||||
|
// @todo Serialize runtime state
|
||||||
|
};
|
||||||
|
|
||||||
|
VirtualMachine.prototype.load = function () {
|
||||||
|
// @todo Deserialize and apply runtime state
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export and bind to `window`
|
||||||
|
*/
|
||||||
|
module.exports = VirtualMachine;
|
||||||
|
if (typeof window !== 'undefined') window.VirtualMachine = module.exports;
|
21
src/util/timer.js
Normal file
21
src/util/timer.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @todo Swap out Date.now() with microtime module that works in node & browsers
|
||||||
|
*/
|
||||||
|
function Timer () {
|
||||||
|
this.startTime = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer.prototype.time = function () {
|
||||||
|
return Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
Timer.prototype.start = function () {
|
||||||
|
this.startTime = this.time();
|
||||||
|
};
|
||||||
|
|
||||||
|
Timer.prototype.stop = function () {
|
||||||
|
return this.startTime - this.time();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Timer;
|
26
test/fixtures/blocks.js
vendored
Normal file
26
test/fixtures/blocks.js
vendored
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
var events = require('events');
|
||||||
|
var util = require('util');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates event emitter / listener patterns from Scratch Blocks.
|
||||||
|
*
|
||||||
|
* @author Andrew Sliwinski <ascii@media.mit.edu>
|
||||||
|
*/
|
||||||
|
function Blocks () {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherit from EventEmitter to enable messaging.
|
||||||
|
*/
|
||||||
|
util.inherits(VirtualMachine, events.EventEmitter);
|
||||||
|
|
||||||
|
Blocks.prototype.spaghetti = function () {
|
||||||
|
this.emit('');
|
||||||
|
};
|
||||||
|
|
||||||
|
Blocks.prototype.spam = function () {
|
||||||
|
this.emit('');
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Blocks;
|
81
test/unit/runtime.js
Normal file
81
test/unit/runtime.js
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
var test = require('tap').test;
|
||||||
|
var Runtime = require('../../src/engine/runtime');
|
||||||
|
|
||||||
|
test('spec', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
|
||||||
|
t.type(Runtime, 'function');
|
||||||
|
t.type(r, 'object');
|
||||||
|
t.ok(r instanceof Runtime);
|
||||||
|
|
||||||
|
t.type(r.blocks, 'object');
|
||||||
|
t.type(r.stacks, 'object');
|
||||||
|
t.ok(Array.isArray(r.stacks));
|
||||||
|
|
||||||
|
t.type(r.createBlock, 'function');
|
||||||
|
t.type(r.moveBlock, 'function');
|
||||||
|
t.type(r.changeBlock, 'function');
|
||||||
|
t.type(r.deleteBlock, 'function');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('create', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
r.createBlock({
|
||||||
|
id: 'foo',
|
||||||
|
opcode: 'TEST_BLOCK'
|
||||||
|
});
|
||||||
|
|
||||||
|
t.type(r.blocks['foo'], 'object');
|
||||||
|
t.equal(r.blocks['foo'].opcode, 'TEST_BLOCK');
|
||||||
|
t.notEqual(r.stacks.indexOf('foo'), -1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('move', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
r.createBlock({
|
||||||
|
id: 'foo',
|
||||||
|
opcode: 'TEST_BLOCK'
|
||||||
|
});
|
||||||
|
r.createBlock({
|
||||||
|
id: 'bar',
|
||||||
|
opcode: 'TEST_BLOCK'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Attach 'bar' to the end of 'foo'
|
||||||
|
r.moveBlock({
|
||||||
|
id: 'bar',
|
||||||
|
newParent: 'foo'
|
||||||
|
});
|
||||||
|
t.equal(r.stacks.length, 1);
|
||||||
|
t.equal(Object.keys(r.blocks).length, 2);
|
||||||
|
t.equal(r.blocks['foo'].next, 'bar');
|
||||||
|
|
||||||
|
// Detach 'bar' from 'foo'
|
||||||
|
r.moveBlock({
|
||||||
|
id: 'bar',
|
||||||
|
oldParent: 'foo'
|
||||||
|
});
|
||||||
|
t.equal(r.stacks.length, 2);
|
||||||
|
t.equal(Object.keys(r.blocks).length, 2);
|
||||||
|
t.equal(r.blocks['foo'].next, null);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delete', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
r.createBlock({
|
||||||
|
id: 'foo',
|
||||||
|
opcode: 'TEST_BLOCK'
|
||||||
|
});
|
||||||
|
r.deleteBlock({
|
||||||
|
id: 'foo'
|
||||||
|
});
|
||||||
|
|
||||||
|
t.type(r.blocks['foo'], 'undefined');
|
||||||
|
t.equal(r.stacks.indexOf('foo'), -1);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -1,6 +1,18 @@
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
var vm = require('../../index');
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
test('spec', function (t) {
|
test('spec', function (t) {
|
||||||
|
var vm = new VirtualMachine('foo');
|
||||||
|
|
||||||
|
t.type(VirtualMachine, 'function');
|
||||||
|
t.type(vm, 'object');
|
||||||
|
|
||||||
|
t.type(vm.blockListener, 'function');
|
||||||
|
// t.type(vm.uiListener, 'function');
|
||||||
|
// t.type(vm.start, 'function');
|
||||||
|
// t.type(vm.stop, 'function');
|
||||||
|
// t.type(vm.save, 'function');
|
||||||
|
// t.type(vm.load, 'function');
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
10
test/unit/thread.js
Normal file
10
test/unit/thread.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
var test = require('tap').test;
|
||||||
|
var Thread = require('../../src/engine/thread');
|
||||||
|
|
||||||
|
test('spec', function (t) {
|
||||||
|
var thread = new Thread('foo');
|
||||||
|
|
||||||
|
t.type(Thread, 'function');
|
||||||
|
t.type(thread, 'object');
|
||||||
|
t.end();
|
||||||
|
});
|
41
test/unit/timer.js
Normal file
41
test/unit/timer.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
var test = require('tap').test;
|
||||||
|
var Timer = require('../../src/util/timer');
|
||||||
|
|
||||||
|
test('spec', function (t) {
|
||||||
|
var timer = new Timer();
|
||||||
|
|
||||||
|
t.type(Timer, 'function');
|
||||||
|
t.type(timer, 'object');
|
||||||
|
|
||||||
|
t.type(timer.startTime, 'number');
|
||||||
|
t.type(timer.time, 'function');
|
||||||
|
t.type(timer.start, 'function');
|
||||||
|
t.type(timer.stop, 'function');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('time', function (t) {
|
||||||
|
var timer = new Timer();
|
||||||
|
var time = timer.time();
|
||||||
|
|
||||||
|
t.ok(Date.now() >= time);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('start / stop', function (t) {
|
||||||
|
var timer = new Timer();
|
||||||
|
var start = timer.time();
|
||||||
|
var delay = 100;
|
||||||
|
var threshold = 1000 / 60; // 60 hz
|
||||||
|
|
||||||
|
// Start timer
|
||||||
|
timer.start();
|
||||||
|
|
||||||
|
// Wait and stop timer
|
||||||
|
setTimeout(function () {
|
||||||
|
var stop = timer.stop();
|
||||||
|
t.ok(stop >= -(delay + threshold) && stop <= -(delay - threshold));
|
||||||
|
t.end();
|
||||||
|
}, delay);
|
||||||
|
});
|
1
vm.min.js
vendored
Normal file
1
vm.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
18
webpack.config.js
Normal file
18
webpack.config.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
var webpack = require('webpack');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: {
|
||||||
|
'vm': './src/index.js',
|
||||||
|
'vm.min': './src/index.js'
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: __dirname,
|
||||||
|
filename: '[name].js'
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
|
include: /\.min\.js$/,
|
||||||
|
minimize: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
};
|
Loading…
Reference in a new issue