diff --git a/README.md b/README.md index 2888fc6c0..512bc1645 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ npm start ``` ## Playground -To run the Playground, make sure the dev server's running and go to [http://localhost:8073/](http://localhost:8073/) - you will be directed to the playground, which demonstrates various tools and internal state. +To run the Playground, make sure the dev server's running and go to [http://localhost:8073/playground/](http://localhost:8073/playground/) - you will be directed to the playground, which demonstrates various tools and internal state. ![VM Playground Screenshot](https://i.imgur.com/nOCNqEc.gif) @@ -50,7 +50,7 @@ npm run build ``` ## How to include in a Node.js App -For an extended setup example, check out the /playground directory, which includes a fully running VM instance. +For an extended setup example, check out the /src/playground directory, which includes a fully running VM instance. ```js var VirtualMachine = require('scratch-vm'); var vm = new VirtualMachine(); diff --git a/package.json b/package.json index 49d4c8043..459a971a0 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "scratch-render": "latest", "script-loader": "0.7.0", "stats.js": "0.17.0", - "tap": "10.1.1", + "tap": "10.1.2", "travis-after-all": "1.4.4", "webpack": "2.2.1", "webpack-dev-server": "2.3.0" diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 02ad90d3c..1632aa28d 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -414,7 +414,7 @@ Runtime.prototype.allScriptsDo = function (f, optTarget) { if (optTarget) { targets = [optTarget]; } - for (var t = 0; t < targets.length; t++) { + for (var t = targets.length - 1; t >= 0; t--) { var target = targets[t]; var scripts = target.blocks.getScripts(); for (var j = 0; j < scripts.length; j++) { diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index 9bbc20b23..0a0a115f9 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -582,7 +582,7 @@ RenderedTarget.prototype.goBackLayers = function (nLayers) { /** * Move behind some other rendered target. - * @param {!Clone} other Other rendered target to move behind. + * @param {!RenderedTarget} other Other rendered target to move behind. */ RenderedTarget.prototype.goBehindOther = function (other) { if (this.renderer) { @@ -637,11 +637,11 @@ RenderedTarget.prototype.keepInFence = function (newX, newY, optFence) { /** * Make a clone, copying any run-time properties. * If we've hit the global clone limit, returns null. - * @return {!RenderedTarget} New clone. + * @return {RenderedTarget} New clone. */ RenderedTarget.prototype.makeClone = function () { if (!this.runtime.clonesAvailable() || this.isStage) { - return; // Hit max clone limit, or this is the stage. + return null; // Hit max clone limit, or this is the stage. } this.runtime.changeCloneCounter(1); var newClone = this.sprite.createClone(); diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js index bccc5d909..81626f3a7 100644 --- a/src/sprites/sprite.js +++ b/src/sprites/sprite.js @@ -46,7 +46,7 @@ var Sprite = function (blocks, runtime) { /** * Create a clone of this sprite. - * @returns {!Clone} Newly created clone. + * @returns {!RenderedTarget} Newly created clone. */ Sprite.prototype.createClone = function () { var newClone = new RenderedTarget(this, this.runtime); diff --git a/test/fixtures/hat-execution-order.sb2 b/test/fixtures/hat-execution-order.sb2 new file mode 100644 index 000000000..f9a7efdcc Binary files /dev/null and b/test/fixtures/hat-execution-order.sb2 differ diff --git a/test/integration/hat-execution-order.js b/test/integration/hat-execution-order.js new file mode 100644 index 000000000..0a302624b --- /dev/null +++ b/test/integration/hat-execution-order.js @@ -0,0 +1,39 @@ +var path = require('path'); +var test = require('tap').test; +var extract = require('../fixtures/extract'); +var VirtualMachine = require('../../src/index'); + +var projectUri = path.resolve(__dirname, '../fixtures/hat-execution-order.sb2'); +var project = extract(projectUri); + +test('complex', function (t) { + var vm = new VirtualMachine(); + + // Evaluate playground data and exit + vm.on('playgroundData', function (e) { + var threads = JSON.parse(e.threads); + t.ok(threads.length === 0); + + var results = vm.runtime.targets[0].lists.results.contents; + t.deepEqual(results, ['3', '2', '1', 'stage']); + + t.end(); + process.nextTick(process.exit); + }); + + // Start VM, load project, and run + t.doesNotThrow(function () { + vm.start(); + vm.clear(); + vm.setCompatibilityMode(false); + vm.setTurboMode(false); + vm.loadProject(project); + vm.greenFlag(); + }); + + // After two seconds, get playground data and stop + setTimeout(function () { + vm.getPlaygroundData(); + vm.stopAll(); + }, 2000); +}); diff --git a/test/unit/engine_sequencer.js b/test/unit/engine_sequencer.js index 23ec4c13d..1e000ad36 100644 --- a/test/unit/engine_sequencer.js +++ b/test/unit/engine_sequencer.js @@ -1,8 +1,178 @@ var test = require('tap').test; var Sequencer = require('../../src/engine/sequencer'); +var Runtime = require('../../src/engine/runtime'); +var Thread = require('../../src/engine/thread'); +var RenderedTarget = require('../../src/sprites/rendered-target'); +var Sprite = require('../../src/sprites/sprite'); test('spec', function (t) { t.type(Sequencer, 'function'); - // @todo + + var r = new Runtime(); + var s = new Sequencer(r); + + t.type(s, 'object'); + t.ok(s instanceof Sequencer); + + t.type(s.stepThreads, 'function'); + t.type(s.stepThread, 'function'); + t.type(s.stepToBranch, 'function'); + t.type(s.stepToProcedure, 'function'); + t.type(s.retireThread, 'function'); + + t.end(); +}); + +var randomString = function () { + var top = Math.random().toString(36); + return top.substring(7); +}; + +var generateBlock = function (id) { + var block = {fields: Object, + id: id, + inputs: {}, + STEPS: Object, + block: 'fakeBlock', + name: 'fakeName', + next: null, + opcode: 'procedures_defnoreturn', + mutation: {proccode: 'fakeCode'}, + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + return block; +}; + +var generateBlockInput = function (id, next, inp) { + var block = {fields: Object, + id: id, + inputs: {SUBSTACK: {block: inp, name: 'SUBSTACK'}}, + STEPS: Object, + block: 'fakeBlock', + name: 'fakeName', + next: next, + opcode: 'procedures_defnoreturn', + mutation: {proccode: 'fakeCode'}, + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + return block; +}; + +var generateThread = function (runtime) { + var s = new Sprite(); + var rt = new RenderedTarget(s, runtime); + var th = new Thread(randomString()); + + var next = randomString(); + var inp = randomString(); + var name = th.topBlock; + + rt.blocks.createBlock(generateBlockInput(name, next, inp)); + th.pushStack(name); + rt.blocks.createBlock(generateBlock(inp)); + + for (var i = 0; i < 10; i++) { + name = next; + next = randomString(); + inp = randomString(); + + rt.blocks.createBlock(generateBlockInput(name, next, inp)); + th.pushStack(name); + rt.blocks.createBlock(generateBlock(inp)); + } + rt.blocks.createBlock(generateBlock(next)); + th.pushStack(next); + th.target = rt; + + runtime.threads.push(th); + + return th; +}; + +test('stepThread', function (t) { + var r = new Runtime(); + var s = new Sequencer(r); + var th = generateThread(r); + t.notEquals(th.status, Thread.STATUS_DONE); + s.stepThread(th); + t.strictEquals(th.status, Thread.STATUS_DONE); + th = generateThread(r); + th.status = Thread.STATUS_YIELD; + s.stepThread(th); + t.notEquals(th.status, Thread.STATUS_DONE); + th.status = Thread.STATUS_PROMISE_WAIT; + s.stepThread(th); + t.notEquals(th.status, Thread.STATUS_DONE); + + t.end(); +}); + +test('stepToBranch', function (t) { + var r = new Runtime(); + var s = new Sequencer(r); + var th = generateThread(r); + s.stepToBranch(th, 2, false); + t.strictEquals(th.peekStack(), null); + th.popStack(); + s.stepToBranch(th, 1, false); + t.strictEquals(th.peekStack(), null); + th.popStack(); + th.popStack(); + s.stepToBranch(th, 1, false); + t.notEquals(th.peekStack(), null); + + t.end(); +}); + +test('retireThread', function (t) { + var r = new Runtime(); + var s = new Sequencer(r); + var th = generateThread(r); + t.strictEquals(th.stack.length, 12); + s.retireThread(th); + t.strictEquals(th.stack.length, 0); + t.strictEquals(th.status, Thread.STATUS_DONE); + + t.end(); +}); + +test('stepToProcedure', function (t) { + var r = new Runtime(); + var s = new Sequencer(r); + var th = generateThread(r); + var expectedBlock = th.peekStack(); + s.stepToProcedure(th, ''); + t.strictEquals(th.peekStack(), expectedBlock); + s.stepToProcedure(th, 'faceCode'); + t.strictEquals(th.peekStack(), expectedBlock); + s.stepToProcedure(th, 'faceCode'); + th.target.blocks.getBlock(th.stack[th.stack.length - 4]).mutation.proccode = 'othercode'; + expectedBlock = th.stack[th.stack.length - 4]; + s.stepToProcedure(th, 'othercode'); + t.strictEquals(th.peekStack(), expectedBlock); + + + t.end(); +}); + +test('stepThreads', function (t) { + var r = new Runtime(); + r.currentStepTime = Infinity; + var s = new Sequencer(r); + t.strictEquals(s.stepThreads().length, 0); + generateThread(r); + t.strictEquals(r.threads.length, 1); + t.strictEquals(s.stepThreads().length, 0); + r.threads[0].status = Thread.STATUS_RUNNING; + t.strictEquals(s.stepThreads().length, 1); + t.end(); });