From bd405ecc4aabce1b157331bdc00d2b3525988d9b Mon Sep 17 00:00:00 2001 From: griffpatch Date: Sat, 28 Jan 2017 16:43:37 +0000 Subject: [PATCH 01/12] Optimisation - Only check browser compatability once This saves doing the checks everytime the time functions are referenced --- src/util/timer.js | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/util/timer.js b/src/util/timer.js index 17c66a567..a0b620112 100644 --- a/src/util/timer.js +++ b/src/util/timer.js @@ -27,13 +27,12 @@ Timer.prototype.startTime = 0; * Return the currently known absolute time, in ms precision. * @returns {number} ms elapsed since 1 January 1970 00:00:00 UTC. */ -Timer.prototype.time = function () { - if (Date.now) { +Timer.prototype.time = Date.now ? + function () { return Date.now(); - } else { + } : function () { return new Date().getTime(); - } -}; + }; /** * Returns a time accurate relative to other times produced by this function. @@ -42,14 +41,13 @@ Timer.prototype.time = function () { * Not guaranteed to produce the same absolute values per-system. * @returns {number} ms-scale accurate time relative to other relative times. */ -Timer.prototype.relativeTime = function () { - if (typeof self !== 'undefined' && - self.performance && 'now' in self.performance) { - return self.performance.now(); - } else { - return this.time(); - } -}; +Timer.prototype.relativeTime = + (typeof self !== 'undefined' && self.performance && 'now' in self.performance) ? + function () { + return self.performance.now(); + } : function () { + return this.time(); + }; /** * Start a timer for measuring elapsed time, From 30cdef57822b148b16f7ac522728c34271524baf Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Fri, 3 Feb 2017 15:13:39 +0000 Subject: [PATCH 02/12] chore(package): update webpack-dev-server to version 2.3.0 https://greenkeeper.io/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 17baf3138..0b7410140 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,6 @@ "tap": "10.0.2", "travis-after-all": "1.4.4", "webpack": "2.2.1", - "webpack-dev-server": "1.16.3" + "webpack-dev-server": "2.3.0" } } From 7d69ecc00595c936efee2561ea73bd1dfcf07395 Mon Sep 17 00:00:00 2001 From: griffpatch Date: Wed, 8 Feb 2017 08:11:10 +0000 Subject: [PATCH 03/12] Optimisation - Only check browser compatability once Disable use of more accurate timer temporarilly due to performance. However, with the rearrange and introduction of the 'nowObj' it turns out most of the delay is in resolving 'self.performance'... so keeping this cached in nowObj actually mitigates most of the delay. --- src/util/timer.js | 50 +++++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/src/util/timer.js b/src/util/timer.js index a0b620112..9a60e5688 100644 --- a/src/util/timer.js +++ b/src/util/timer.js @@ -23,16 +23,36 @@ var Timer = function () {}; */ Timer.prototype.startTime = 0; +/** + * Disable use of self.performance for now as it results in lower performance + * However, instancing it like below (caching the self.performance to a local variable) negates most of the issues. + * @type {boolean} + */ +var USE_PERFORMANCE = false; + +/** + * Legacy object to allow for us to call now to get the old style date time (for backwards compatibility) + * @deprecated This is only called via the nowObj.now() if no other means is possible... + */ +var legacyDateCode = { + now: function () { + return new Date().getTime(); + } +}; + +/** + * Use this object to route all time functions through single access points. + */ +var nowObj = (USE_PERFORMANCE && typeof self !== 'undefined' && self.performance && 'now' in self.performance) ? + self.performance : Date.now ? Date : legacyDateCode; + /** * Return the currently known absolute time, in ms precision. * @returns {number} ms elapsed since 1 January 1970 00:00:00 UTC. */ -Timer.prototype.time = Date.now ? - function () { - return Date.now(); - } : function () { - return new Date().getTime(); - }; +Timer.prototype.time = function () { + return nowObj.now(); +}; /** * Returns a time accurate relative to other times produced by this function. @@ -41,28 +61,20 @@ Timer.prototype.time = Date.now ? * Not guaranteed to produce the same absolute values per-system. * @returns {number} ms-scale accurate time relative to other relative times. */ -Timer.prototype.relativeTime = - (typeof self !== 'undefined' && self.performance && 'now' in self.performance) ? - function () { - return self.performance.now(); - } : function () { - return this.time(); - }; +Timer.prototype.relativeTime = function () { + return nowObj.now(); +}; /** * Start a timer for measuring elapsed time, * at the most accurate precision possible. */ Timer.prototype.start = function () { - this.startTime = this.relativeTime(); + this.startTime = nowObj.now(); }; -/** - * Check time elapsed since `timer.start` was called. - * @returns {number} Time elapsed, in ms (possibly sub-ms precision). - */ Timer.prototype.timeElapsed = function () { - return this.relativeTime() - this.startTime; + return nowObj.now() - this.startTime; }; module.exports = Timer; From cf215bf0bcac1906393029b4e6f861ad012b4e2e Mon Sep 17 00:00:00 2001 From: griffpatch Date: Wed, 8 Feb 2017 08:38:50 +0000 Subject: [PATCH 04/12] Fix for the "Empty or white space strings equal 0" bug See: Empty or white space strings equal 0 #435 --- src/util/cast.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/util/cast.js b/src/util/cast.js index a8a1535b8..c2e1cfe0f 100644 --- a/src/util/cast.js +++ b/src/util/cast.js @@ -100,6 +100,12 @@ Cast.toRgbColorObject = function (value) { Cast.compare = function (v1, v2) { var n1 = Number(v1); var n2 = Number(v2); + if (n1 === 0 && (v1 == null || typeof v1 === 'string' && v1.trim().length === 0)) { + n1 = NaN; + } + if (n2 === 0 && (v2 == null || typeof v2 === 'string' && v2.trim().length === 0)) { + n2 = NaN; + } if (isNaN(n1) || isNaN(n2)) { // At least one argument can't be converted to a number. // Scratch compares strings as case insensitive. From 9cb595312e4a03502bdd3dbb191727a286070c33 Mon Sep 17 00:00:00 2001 From: griffpatch Date: Wed, 8 Feb 2017 09:44:10 +0000 Subject: [PATCH 05/12] Implement "Stop this script" function Existing implementation incorrectly terminates the entire thread. See: http://llk.github.io/scratch-vm/#144142535 --- src/blocks/scratch3_control.js | 2 +- src/engine/execute.js | 4 ++-- src/engine/thread.js | 21 +++++++++++++++++++++ test/unit/blocks_control.js | 8 ++++---- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js index 28737b361..285b241ab 100644 --- a/src/blocks/scratch3_control.js +++ b/src/blocks/scratch3_control.js @@ -110,7 +110,7 @@ Scratch3ControlBlocks.prototype.stop = function (args, util) { option === 'other scripts in stage') { util.stopOtherTargetThreads(); } else if (option === 'this script') { - util.stopThread(); + util.stopThisScript(); } }; diff --git a/src/engine/execute.js b/src/engine/execute.js index 44951aba3..226a866a7 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -183,8 +183,8 @@ var execute = function (sequencer, thread) { stopOtherTargetThreads: function () { runtime.stopForTarget(target, thread); }, - stopThread: function () { - sequencer.retireThread(thread); + stopThisScript: function () { + thread.stopThisScript(); }, startProcedure: function (procedureCode) { sequencer.stepToProcedure(thread, procedureCode); diff --git a/src/engine/thread.js b/src/engine/thread.js index 9693b26f1..f093f886e 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -140,6 +140,27 @@ Thread.prototype.popStack = function () { return this.stack.pop(); }; +/** + * Pop back down the stack frame until we hit a procedure call or the stack frame is emptied + */ +Thread.prototype.stopThisScript = function () { + var blockID = this.peekStack(); + while (blockID !== null) { + var block = this.target.blocks.getBlock(blockID); + if (typeof block !== 'undefined' && block.opcode === 'procedures_callnoreturn') { + break; + } + this.popStack(); + blockID = this.peekStack(); + } + + if (this.stack.length === 0) { + // Clean up! + this.requestScriptGlowInFrame = false; + this.status = Thread.STATUS_DONE; + } +}; + /** * Get top stack item. * @return {?string} Block ID on top of stack. diff --git a/test/unit/blocks_control.js b/test/unit/blocks_control.js index 2dc6049df..7867524ce 100644 --- a/test/unit/blocks_control.js +++ b/test/unit/blocks_control.js @@ -104,7 +104,7 @@ test('stop', function (t) { var state = { stopAll: 0, stopOtherTargetThreads: 0, - stopThread: 0 + stopThisScript: 0 }; var util = { stopAll: function () { @@ -113,8 +113,8 @@ test('stop', function (t) { stopOtherTargetThreads: function () { state.stopOtherTargetThreads++; }, - stopThread: function () { - state.stopThread++; + stopThisScript: function () { + state.stopThisScript++; } }; @@ -125,6 +125,6 @@ test('stop', function (t) { c.stop({STOP_OPTION: 'this script'}, util); t.strictEqual(state.stopAll, 1); t.strictEqual(state.stopOtherTargetThreads, 2); - t.strictEqual(state.stopThread, 1); + t.strictEqual(state.stopThisScript, 1); t.end(); }); From 2ba177aa0f7e69e236c877ae6006ff97d61bdc7a Mon Sep 17 00:00:00 2001 From: griffpatch Date: Thu, 9 Feb 2017 08:50:37 +0000 Subject: [PATCH 06/12] Bug - Return pen opacity to opaque When setting the pen color using pen blocks, the opacity should be reset to fully opaque if no alpha is supplied. --- src/blocks/scratch3_pen.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/blocks/scratch3_pen.js b/src/blocks/scratch3_pen.js index c8f98341e..9b9f64919 100644 --- a/src/blocks/scratch3_pen.js +++ b/src/blocks/scratch3_pen.js @@ -147,6 +147,7 @@ Scratch3PenBlocks.prototype._updatePenColor = function (penState) { penState.penAttributes.color4f[0] = rgb.r / 255.0; penState.penAttributes.color4f[1] = rgb.g / 255.0; penState.penAttributes.color4f[2] = rgb.b / 255.0; + penState.penAttributes.color4f[3] = 1; }; /** @@ -260,6 +261,8 @@ Scratch3PenBlocks.prototype.setPenColorToColor = function (args, util) { penState.penAttributes.color4f[2] = rgb.b / 255.0; if (rgb.hasOwnProperty('a')) { // Will there always be an 'a'? penState.penAttributes.color4f[3] = rgb.a / 255.0; + } else { + penState.penAttributes.color4f[3] = 1; } }; From 8354774fcf3f4b6d61b545aebe09f00ea63724a0 Mon Sep 17 00:00:00 2001 From: SillyInventor Date: Thu, 9 Feb 2017 14:09:01 -0500 Subject: [PATCH 07/12] Switch from return null to return empty string in keyCodeToScratchKey --- src/io/keyboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/keyboard.js b/src/io/keyboard.js index da0e41625..c7a210803 100644 --- a/src/io/keyboard.js +++ b/src/io/keyboard.js @@ -56,7 +56,7 @@ Keyboard.prototype._keyCodeToScratchKey = function (keyCode) { case 39: return 'right arrow'; case 40: return 'down arrow'; } - return null; + return ''; }; /** From cd750b5dda4ee7a845fd90349cd6075e3d0f0bd0 Mon Sep 17 00:00:00 2001 From: Eric Rosenbaum Date: Thu, 9 Feb 2017 14:52:15 -0500 Subject: [PATCH 08/12] pass volume to audioengine playnote --- src/blocks/scratch3_sound.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/blocks/scratch3_sound.js b/src/blocks/scratch3_sound.js index 20d137580..e9f9df4d7 100644 --- a/src/blocks/scratch3_sound.js +++ b/src/blocks/scratch3_sound.js @@ -135,8 +135,9 @@ Scratch3SoundBlocks.prototype.playNoteForBeats = function (args, util) { var beats = Cast.toNumber(args.BEATS); var soundState = this._getSoundState(util.target); var inst = soundState.currentInstrument; + var vol = soundState.volume; if (typeof this.runtime.audioEngine === 'undefined') return; - return this.runtime.audioEngine.playNoteForBeatsWithInst(note, beats, inst); + return this.runtime.audioEngine.playNoteForBeatsWithInstAndVol(note, beats, inst, vol); }; Scratch3SoundBlocks.prototype.playDrumForBeats = function (args, util) { From 3324f51193079329b2d7a469d2e0cc850031d46f Mon Sep 17 00:00:00 2001 From: SillyInventor Date: Thu, 9 Feb 2017 18:18:53 -0500 Subject: [PATCH 09/12] Added some unit tests for some thread functions --- test/unit/engine_thread.js | 107 ++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 1 deletion(-) diff --git a/test/unit/engine_thread.js b/test/unit/engine_thread.js index 3bda4fc69..a06a316a7 100644 --- a/test/unit/engine_thread.js +++ b/test/unit/engine_thread.js @@ -3,6 +3,111 @@ var Thread = require('../../src/engine/thread'); test('spec', function (t) { t.type(Thread, 'function'); - // @todo + + var th = new Thread('arbitraryString'); + t.type(th, 'object'); + t.ok(th instanceof Thread); + t.type(th.pushStack, 'function'); + t.type(th.reuseStackForNextBlock, 'function'); + t.type(th.popStack, 'function'); + t.type(th.stopThisScript, 'function'); + t.type(th.peekStack, 'function'); + t.type(th.peekStackFrame, 'function'); + t.type(th.peekParentStackFrame, 'function'); + t.type(th.pushReportedValue, 'function'); + t.type(th.pushParam, 'function'); + t.type(th.peekStack, 'function'); + t.type(th.getParam, 'function'); + t.type(th.atStackTop, 'function'); + t.type(th.goToNextBlock, 'function'); + t.type(th.isRecursiveCall, 'function'); + + t.end(); +}); + +test('pushStack', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + + t.end(); +}); + +test('popStack', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + t.strictEquals(th.popStack(), 'arbitraryString'); + t.strictEquals(th.popStack(), undefined); + + t.end(); +}); + +test('atStackTop', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + th.pushStack('secondString'); + t.strictEquals(th.atStackTop(), false); + th.popStack(); + t.strictEquals(th.atStackTop(), true); + + t.end(); +}); + +test('reuseStackForNextBlock', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + th.reuseStackForNextBlock('secondString'); + t.strictEquals(th.popStack(), 'secondString'); + + t.end(); +}); + +test('peekStackFrame', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + t.strictEquals(th.peekStackFrame().warpMode, false); + th.popStack(); + t.strictEquals(th.peekStackFrame(), null); + + t.end(); +}); + +test('peekParentStackFrame', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + th.peekStackFrame().warpMode = true; + t.strictEquals(th.peekParentStackFrame(), null); + th.pushStack('secondString'); + t.strictEquals(th.peekParentStackFrame().warpMode, true); + + t.end(); +}); + +test('pushReportedValue', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + th.pushStack('secondString'); + th.pushReportedValue('value'); + t.strictEquals(th.peekParentStackFrame().reported.null, 'value'); + + t.end(); +}); + +test('peekStack', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + t.strictEquals(th.peekStack(), 'arbitraryString'); + th.popStack(); + t.strictEquals(th.peekStack(), null); + + t.end(); +}); + +test('PushGetParam', function (t) { + var th = new Thread('arbitraryString'); + th.pushStack('arbitraryString'); + th.pushParam('testParam', 'testValue'); + t.strictEquals(th.peekStackFrame().params.testParam, 'testValue'); + t.strictEquals(th.getParam('testParam'), 'testValue'); + t.end(); }); From ad5bc1afbda3e9e5fa0618a0d9de84a1a76d8b21 Mon Sep 17 00:00:00 2001 From: griffpatch Date: Fri, 10 Feb 2017 08:02:11 +0000 Subject: [PATCH 10/12] Pull white space checks into function Clean up code by pulling white space checks into a seperate helper function --- src/util/cast.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/util/cast.js b/src/util/cast.js index c2e1cfe0f..79e76b134 100644 --- a/src/util/cast.js +++ b/src/util/cast.js @@ -90,6 +90,15 @@ Cast.toRgbColorObject = function (value) { return color; }; +/** + * Determine if a Scratch argument is a white space string (or null / empty). + * @param {*} val value to check. + * @return {boolean} True if the argument is all white spaces or null / empty. + */ +Cast.isWhiteSpace = function (val) { + return val === null || typeof val === 'string' && val.trim().length === 0; +}; + /** * Compare two values, using Scratch cast, case-insensitive string compare, etc. * In Scratch 2.0, this is captured by `interp.compare.` @@ -100,10 +109,9 @@ Cast.toRgbColorObject = function (value) { Cast.compare = function (v1, v2) { var n1 = Number(v1); var n2 = Number(v2); - if (n1 === 0 && (v1 == null || typeof v1 === 'string' && v1.trim().length === 0)) { + if (n1 === 0 && Cast.isWhiteSpace(v1)) { n1 = NaN; - } - if (n2 === 0 && (v2 == null || typeof v2 === 'string' && v2.trim().length === 0)) { + } else if (n2 === 0 && Cast.isWhiteSpace(v2)) { n2 = NaN; } if (isNaN(n1) || isNaN(n2)) { From e8949dcdf5b8843f7bd1f2a4c189d7e8eb38f479 Mon Sep 17 00:00:00 2001 From: SillyInventor Date: Fri, 10 Feb 2017 13:06:39 -0500 Subject: [PATCH 11/12] Added remaining tests --- test/unit/engine_thread.js | 164 +++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/test/unit/engine_thread.js b/test/unit/engine_thread.js index a06a316a7..815de5ca3 100644 --- a/test/unit/engine_thread.js +++ b/test/unit/engine_thread.js @@ -1,5 +1,7 @@ var test = require('tap').test; 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(Thread, 'function'); @@ -111,3 +113,165 @@ test('PushGetParam', function (t) { t.end(); }); + +test('goToNextBlock', function (t) { + var th = new Thread('arbitraryString'); + var s = new Sprite(); + var rt = new RenderedTarget(s, null); + var block1 = {fields: Object, + id: 'arbitraryString', + inputs: Object, + STEPS: Object, + block: 'fakeBlock', + name: 'STEPS', + next: 'secondString', + opcode: 'motion_movesteps', + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + var block2 = {fields: Object, + id: 'secondString', + inputs: Object, + STEPS: Object, + block: 'fakeBlock', + name: 'STEPS', + next: null, + opcode: 'procedures_callnoreturn', + mutation: {proccode: 'fakeCode'}, + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + + rt.blocks.createBlock(block1); + rt.blocks.createBlock(block2); + rt.blocks.createBlock(block2); + th.target = rt; + + t.strictEquals(th.peekStack(), null); + th.pushStack('secondString'); + t.strictEquals(th.peekStack(), 'secondString'); + th.goToNextBlock(); + t.strictEquals(th.peekStack(), null); + th.pushStack('secondString'); + th.pushStack('arbitraryString'); + t.strictEquals(th.peekStack(), 'arbitraryString'); + th.goToNextBlock(); + t.strictEquals(th.peekStack(), 'secondString'); + th.goToNextBlock(); + t.strictEquals(th.peekStack(), null); + + t.end(); +}); + +test('stopThisScript', function (t) { + var th = new Thread('arbitraryString'); + var s = new Sprite(); + var rt = new RenderedTarget(s, null); + var block1 = {fields: Object, + id: 'arbitraryString', + inputs: Object, + STEPS: Object, + block: 'fakeBlock', + name: 'STEPS', + next: null, + opcode: 'motion_movesteps', + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + var block2 = {fields: Object, + id: 'secondString', + inputs: Object, + STEPS: Object, + block: 'fakeBlock', + name: 'STEPS', + next: null, + opcode: 'procedures_callnoreturn', + mutation: {proccode: 'fakeCode'}, + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + + rt.blocks.createBlock(block1); + rt.blocks.createBlock(block2); + th.target = rt; + + th.stopThisScript(); + t.strictEquals(th.peekStack(), null); + th.pushStack('arbitraryString'); + t.strictEquals(th.peekStack(), 'arbitraryString'); + th.stopThisScript(); + t.strictEquals(th.peekStack(), null); + th.pushStack('arbitraryString'); + th.pushStack('secondString'); + th.stopThisScript(); + t.strictEquals(th.peekStack(), 'secondString'); + + t.end(); +}); + +test('isRecursiveCall', function (t) { + var th = new Thread('arbitraryString'); + var s = new Sprite(); + var rt = new RenderedTarget(s, null); + var block1 = {fields: Object, + id: 'arbitraryString', + inputs: Object, + STEPS: Object, + block: 'fakeBlock', + name: 'STEPS', + next: null, + opcode: 'motion_movesteps', + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + var block2 = {fields: Object, + id: 'secondString', + inputs: Object, + STEPS: Object, + block: 'fakeBlock', + name: 'STEPS', + next: null, + opcode: 'procedures_callnoreturn', + mutation: {proccode: 'fakeCode'}, + parent: null, + shadow: false, + topLevel: true, + x: 0, + y: 0 + }; + + rt.blocks.createBlock(block1); + rt.blocks.createBlock(block2); + th.target = rt; + + t.strictEquals(th.isRecursiveCall('fakeCode'), false); + th.pushStack('secondString'); + t.strictEquals(th.isRecursiveCall('fakeCode'), false); + th.pushStack('arbitraryString'); + t.strictEquals(th.isRecursiveCall('fakeCode'), true); + th.pushStack('arbitraryString'); + t.strictEquals(th.isRecursiveCall('fakeCode'), true); + th.popStack(); + t.strictEquals(th.isRecursiveCall('fakeCode'), true); + th.popStack(); + t.strictEquals(th.isRecursiveCall('fakeCode'), false); + th.popStack(); + t.strictEquals(th.isRecursiveCall('fakeCode'), false); + + t.end(); +}); From 243d68f88bef66d085870177b179341cad63d8ec Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Fri, 10 Feb 2017 14:21:37 -0800 Subject: [PATCH 12/12] Use Webpack to generate the whole playground The previous configuration mixed Webpack output with static content in order to create the playground. This change moves that static content from `/playground/` into `/src/playground/` and adds a Webpack rule to copy it into the playground as part of the build. On the surface this might seem unnecessary, but it provides at least two benefits: - It's no longer possible to accidentally load stale build output through `webpack-dev-server` in the event of a misconfiguration. This was very easy in the previous configuration, and might in fact be the only way that `webpack-dev-server` ever worked for this repository. - It's simpler to ensure that various rules apply to the hand-authored content and not build outputs. This includes lint rules, `.gitignore`, IDE symbol search paths, etc. --- .gitignore | 8 +--- {playground => src/playground}/index.html | 0 {playground => src/playground}/playground.css | 0 {playground => src/playground}/playground.js | 41 +++++++++---------- webpack.config.js | 4 +- 5 files changed, 24 insertions(+), 29 deletions(-) rename {playground => src/playground}/index.html (100%) rename {playground => src/playground}/playground.css (100%) rename {playground => src/playground}/playground.js (92%) diff --git a/.gitignore b/.gitignore index fdf767042..2f3825877 100644 --- a/.gitignore +++ b/.gitignore @@ -14,10 +14,4 @@ npm-* # Build /dist -/playground/assets -/playground/media -/playground/scratch-vm.js -/playground/scratch-vm.js.map -/playground/vendor.js -/playground/vendor.js.map -/playground/zenburn.css +/playground diff --git a/playground/index.html b/src/playground/index.html similarity index 100% rename from playground/index.html rename to src/playground/index.html diff --git a/playground/playground.css b/src/playground/playground.css similarity index 100% rename from playground/playground.css rename to src/playground/playground.css diff --git a/playground/playground.js b/src/playground/playground.js similarity index 92% rename from playground/playground.js rename to src/playground/playground.js index cb5a81a3e..9ee924122 100644 --- a/playground/playground.js +++ b/src/playground/playground.js @@ -1,4 +1,3 @@ - var loadProject = function () { var id = location.hash.substring(1); if (id.length < 1 || !isFinite(id)) { @@ -7,7 +6,7 @@ var loadProject = function () { var url = 'https://projects.scratch.mit.edu/internalapi/project/' + id + '/get/'; var r = new XMLHttpRequest(); - r.onreadystatechange = function() { + r.onreadystatechange = function () { if (this.readyState === 4) { if (r.status === 200) { window.vm.loadProject(this.responseText); @@ -18,7 +17,7 @@ var loadProject = function () { r.send(); }; -window.onload = function() { +window.onload = function () { // Lots of global variables to make debugging easier // Instantiate the VM. var vm = new window.VirtualMachine(); @@ -74,7 +73,7 @@ window.onload = function() { // Playground data tabs. // Block representation tab. var blockexplorer = document.getElementById('blockexplorer'); - var updateBlockExplorer = function(blocks) { + var updateBlockExplorer = function (blocks) { blockexplorer.innerHTML = JSON.stringify(blocks, null, 2); window.hljs.highlightBlock(blockexplorer); }; @@ -83,7 +82,7 @@ window.onload = function() { var threadexplorer = document.getElementById('threadexplorer'); var cachedThreadJSON = ''; var updateThreadExplorer = function (newJSON) { - if (newJSON != cachedThreadJSON) { + if (newJSON !== cachedThreadJSON) { cachedThreadJSON = newJSON; threadexplorer.innerHTML = cachedThreadJSON; window.hljs.highlightBlock(threadexplorer); @@ -101,7 +100,7 @@ window.onload = function() { // VM handlers. // Receipt of new playground data (thread, block representations). - vm.on('playgroundData', function(data) { + vm.on('playgroundData', function (data) { updateThreadExplorer(data.threads); updateBlockExplorer(data.blocks); }); @@ -125,7 +124,7 @@ window.onload = function() { var targetOption = document.createElement('option'); targetOption.setAttribute('value', data.targetList[i].id); // If target id matches editingTarget id, select it. - if (data.targetList[i].id == data.editingTarget) { + if (data.targetList[i].id === data.editingTarget) { targetOption.setAttribute('selected', 'selected'); } targetOption.appendChild( @@ -139,23 +138,23 @@ window.onload = function() { }; // Feedback for stacks and blocks running. - vm.on('SCRIPT_GLOW_ON', function(data) { + vm.on('SCRIPT_GLOW_ON', function (data) { workspace.glowStack(data.id, true); }); - vm.on('SCRIPT_GLOW_OFF', function(data) { + vm.on('SCRIPT_GLOW_OFF', function (data) { workspace.glowStack(data.id, false); }); - vm.on('BLOCK_GLOW_ON', function(data) { + vm.on('BLOCK_GLOW_ON', function (data) { workspace.glowBlock(data.id, true); }); - vm.on('BLOCK_GLOW_OFF', function(data) { + vm.on('BLOCK_GLOW_OFF', function (data) { workspace.glowBlock(data.id, false); }); - vm.on('VISUAL_REPORT', function(data) { + vm.on('VISUAL_REPORT', function (data) { workspace.reportValue(data.id, data.value); }); - vm.on('SPRITE_INFO_REPORT', function(data) { + vm.on('SPRITE_INFO_REPORT', function (data) { if (data.id !== selectedTarget.value) return; // Not the editingTarget document.getElementById('sinfo-x').value = data.x; document.getElementById('sinfo-y').value = data.y; @@ -213,7 +212,7 @@ window.onload = function() { // Feed keyboard events as VM I/O events. document.addEventListener('keydown', function (e) { // Don't capture keys intended for Blockly inputs. - if (e.target != document && e.target != document.body) { + if (e.target !== document && e.target !== document.body) { return; } window.vm.postIOData('keyboard', { @@ -222,7 +221,7 @@ window.onload = function() { }); e.preventDefault(); }); - document.addEventListener('keyup', function(e) { + document.addEventListener('keyup', function (e) { // Always capture up events, // even those that have switched to other targets. window.vm.postIOData('keyboard', { @@ -230,7 +229,7 @@ window.onload = function() { isDown: false }); // E.g., prevent scroll. - if (e.target != document && e.target != document.body) { + if (e.target !== document && e.target !== document.body) { e.preventDefault(); } }); @@ -239,25 +238,25 @@ window.onload = function() { vm.start(); // Inform VM of animation frames. - var animate = function() { + var animate = function () { stats.update(); requestAnimationFrame(animate); }; requestAnimationFrame(animate); // Handlers for green flag and stop all. - document.getElementById('greenflag').addEventListener('click', function() { + document.getElementById('greenflag').addEventListener('click', function () { vm.greenFlag(); }); - document.getElementById('stopall').addEventListener('click', function() { + document.getElementById('stopall').addEventListener('click', function () { vm.stopAll(); }); - document.getElementById('turbomode').addEventListener('change', function() { + document.getElementById('turbomode').addEventListener('change', function () { var turboOn = document.getElementById('turbomode').checked; vm.setTurboMode(turboOn); }); document.getElementById('compatmode').addEventListener('change', - function() { + function () { var compatibilityMode = document.getElementById('compatmode').checked; vm.setCompatibilityMode(compatibilityMode); }); diff --git a/webpack.config.js b/webpack.config.js index f58b7a1bf..29edbfcd1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,7 +5,7 @@ var webpack = require('webpack'); var base = { devServer: { - contentBase: path.resolve(__dirname, 'playground'), + contentBase: false, host: '0.0.0.0', port: process.env.PORT || 8073 }, @@ -108,6 +108,8 @@ module.exports = [ to: 'media' }, { from: 'node_modules/highlightjs/styles/zenburn.css' + }, { + from: 'src/playground' }]) ]) })