From bd76395676c3af1ad276c306b81fabe470088d19 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Mon, 13 Feb 2017 14:44:51 -0800 Subject: [PATCH 1/6] Minor cleanup around cloning methods --- src/sprites/rendered-target.js | 6 +++--- src/sprites/sprite.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) 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); From 18107cde7b26ffe954e5a98ffc86e9ec655ec6c6 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Mon, 13 Feb 2017 14:35:50 -0800 Subject: [PATCH 2/6] Reverse target iteration in `allScriptsDo` The `allStacksAndOwnersDo` function in Scratch 2.0 runtime iterates targets in reverse and projects sometimes rely on that for correct initialization. If, for example, each sprite runs a "go back 999 layers" or "go to front" block as its first action, the order of execution will determine the ordering of the layers. This change makes Scratch 3.0 match the Scratch 2.0 execution order. --- src/engine/runtime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/runtime.js b/src/engine/runtime.js index fc3b17a06..d9e59d36a 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++) { From 3b3daf8aca77530823036a37fb45ca94755cf38d Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 14 Feb 2017 16:33:42 -0800 Subject: [PATCH 3/6] Add hat execution order test --- test/fixtures/hat-execution-order.sb2 | Bin 0 -> 4547 bytes test/integration/hat-execution-order.js | 39 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 test/fixtures/hat-execution-order.sb2 create mode 100644 test/integration/hat-execution-order.js diff --git a/test/fixtures/hat-execution-order.sb2 b/test/fixtures/hat-execution-order.sb2 new file mode 100644 index 0000000000000000000000000000000000000000..f9a7efdcc1583a0df68400c77cb46467fccf8841 GIT binary patch literal 4547 zcmeI0X;>528pnr(#1IIg$Rdkj-y2aj(NI9yLIlFDUJwFdUlJgK@W@((f(QjE)wW1M zx$FocDQXH_30{`UzAFkC4MqtjA*j{rorw2hxz6iCH{$C0C#ynM-bO?M>6r=0GrlBHPW zjD@Tgn;!`=#+|7=Z$?UND{3?_y{bcqy?3oA6>X`JIwqMFWxZ#|x2jfHI@96>PwA^N z7dxBF=tln>=9ucFvcWfbGDo70LK&m#Ra+&Dt^M)UTupP7|&9XW50N1H}!; z$|*28LThNqm$gKHOEj%4;$lLjLK3FcD(93dyOL_fN$ekWtXgIwWmc-=3Qf<^o3E3x z3C|W^JkF}e*Zbiazp;?AOfRD7_wc80u1U+~PU`MFaJjWL`ssDgf@zO>_Nl6Pqg}aV zn^C#wx#{Z=UvUGzl|4hENGCtm65o|k>Dm*@W+kSFZF^;qrxnqLrlkeB&wE6Xa!~=| z_(dwlMQ~}Epvv4%l~Da*#%MUcPLm_GRG1!)oc6`oJ`_y5g>oUg2nr)B;?H^anhK5- z!5z|nGtfD>Vt5I6tAc?~?!#LCXe7lb{%vu1K)CpYar2aCukmBtC7#69Dd_E6kdb}eDjC?gqmVsA#my2$>8#+{ z$Cn+l`|aMWaNN!wg5KjRbsrrs{^^ibI?;`%=k4=$PR+IZie;$1yRG3e!#wXQSwA8kL1kdFvz}z%*W$JZ(xGjW0gWX2EX?R}qWPWb?-=HX zD9}MCB=zTu<{X6^HLOlE4p(o|(MMDQK1KQjX@DFeA1If;{{M z_YIbRH5<8Fu}Zt&RlgN3c0u+V&ojJ8;+5k}w^IwAu++CwhkaPxKHFrUPFzpAqGS}4 zN_|XbmA@M?k0_R6oo}Bn%_R+Io*p=3)&NLCQ0Z_QGttB>dZtbOf`n_z9+}eWJxF*)-zFBgbz#jzh@#H@w{v(!6nEM5z<&y-zo`9@joE}`{@hpTbJQLH_;9h(xVh=v z{{s@sxThNpK_m-f&BrDXQV3Vg5#cy7}N05vZV-+&ZAB2tN> z;63926#C1SSk$tNyE0b&2aN@lI@vh5C)aPrpK-P&tfr{?QL}xw^*kT4q%~~sNK7WH zQ;@^~8@93@e`{}e)$fIxXF2E1TNh^+Bue+&_*3Fr6DvZeY(wN6nUSqYMfcMqvSP~e zdsN>n7$*BZNMxp(#nd>_lxkQmZFwcooUlTdM!~d?1b`*@EtBQKyqu95F}$PQA(v?9 zPF{FubyZM^eODnNsx*HmFt8TO?4mGV-?n<)kaqV-BuuMj?UX4zq zx1(vE?e*hyfu4MZk-$3B%3!yIv^TG8yroOfLGAzXVG<-sCC4<&a%A6rjV^c*OK~8p z1JNln==vlAk)o-xA7S0PuSDG)zI)MYQ12G|*0Et28@HucZPH{2`^>~q{g{|NM{z2l zAJrc`L1s5Yigp_5oiwb~$11NNNc@iZw#n6r`Ug_WcSLlhebCkF4(K}RD^NE+73vudO7O#(8 z!peLyxBTkm7z82iC{?s=TGC8xCnWK8>1xT!tYGpL`dz;0cL+ITF2Ylk_?CbdfgpiD z|4{)hpO21@e_O0qetKKAUX}+sgZG=PjU^j>$9l;{#0M9$v1FqUSTC^_```*TmTc6? ddWnamaA2Tn2zZ!qp9jha2Eaj|P$iTN@Gm;r?+O3_ literal 0 HcmV?d00001 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); +}); From af770981611c2cba9a72b8353430721b6c17cd54 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Wed, 15 Feb 2017 15:31:20 -0800 Subject: [PATCH 4/6] Update playground paths in README.md A recent change moved the location of the playground source files and the URL used to access the playground when running the dev server. This change updates README.md to reflect the new locations. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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(); From 2fc2ee2c4bfc781bc4be3d98e925093f869e5abd Mon Sep 17 00:00:00 2001 From: SillyInventor Date: Thu, 16 Feb 2017 14:30:00 -0500 Subject: [PATCH 5/6] Adding sequencer unit tests for each function --- test/unit/engine_sequencer.js | 172 +++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) 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(); }); From 9f364b93eb850da9fa9713c5191b85d713c9806d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 18 Feb 2017 02:13:29 +0000 Subject: [PATCH 6/6] chore(package): update tap to version 10.1.2 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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"