From 809528abdc4c60bb941df03daa06798ddd767941 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 29 Jun 2016 13:48:30 -0400 Subject: [PATCH 01/40] Straw-man implementation of targets/sprites/clones --- src/engine/execute.js | 5 ++-- src/engine/runtime.js | 63 +++++++++++++++++++---------------------- src/engine/sequencer.js | 5 ++-- src/engine/target.js | 20 +++++++++++++ src/index.js | 18 +++++++----- src/sprites/clone.js | 16 +++++++++++ src/sprites/sprite.js | 17 +++++++++++ 7 files changed, 99 insertions(+), 45 deletions(-) create mode 100644 src/engine/target.js create mode 100644 src/sprites/clone.js create mode 100644 src/sprites/sprite.js diff --git a/src/engine/execute.js b/src/engine/execute.js index 188f0bf52..001178d56 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -7,6 +7,7 @@ var Thread = require('./thread'); */ var execute = function (sequencer, thread) { var runtime = sequencer.runtime; + var target = runtime.targetForThread(thread); // Current block to execute is the one on the top of the stack. var currentBlockId = thread.peekStack(); @@ -29,13 +30,13 @@ var execute = function (sequencer, thread) { var argValues = {}; // Add all fields on this block to the argValues. - var fields = runtime.blocks.getFields(currentBlockId); + var fields = target.blocks.getFields(currentBlockId); for (var fieldName in fields) { argValues[fieldName] = fields[fieldName].value; } // Recursively evaluate input blocks. - var inputs = runtime.blocks.getInputs(currentBlockId); + var inputs = target.blocks.getInputs(currentBlockId); for (var inputName in inputs) { var input = inputs[inputName]; var inputBlockId = input.block; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 8c4c862e1..a0db919ae 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -10,19 +10,19 @@ var defaultBlockPackages = { }; /** - * Manages blocks, stacks, and the sequencer. - * @param {!Blocks} blocks Blocks instance for this runtime. + * Manages targets, stacks, and the sequencer. + * @param {!Array.<Target>} targets List of targets for this runtime. */ -function Runtime (blocks) { +function Runtime (targets) { // Bind event emitter EventEmitter.call(this); // State for the runtime /** - * Block management and storage + * Target management and storage. */ - this.blocks = blocks; + this.targets = targets; /** * A list of threads that are currently running in the VM. @@ -163,32 +163,13 @@ Runtime.prototype.greenFlag = function () { this._removeThread(this.threads[i]); } // Add all top stacks with green flag - var stacks = this.blocks.getStacks(); - for (var j = 0; j < stacks.length; j++) { - var topBlock = stacks[j]; - if (this.blocks.getBlock(topBlock).opcode === 'event_whenflagclicked') { - this._pushThread(stacks[j]); - } - } -}; - -/** - * Distance sensor hack - */ -Runtime.prototype.startDistanceSensors = function () { - // Add all top stacks with distance sensor - var stacks = this.blocks.getStacks(); - for (var j = 0; j < stacks.length; j++) { - var topBlock = stacks[j]; - if (this.blocks.getBlock(topBlock).opcode === - 'wedo_whendistanceclose') { - var alreadyRunning = false; - for (var k = 0; k < this.threads.length; k++) { - if (this.threads[k].topBlock === topBlock) { - alreadyRunning = true; - } - } - if (!alreadyRunning) { + 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]; + if (target.blocks.getBlock(topBlock).opcode === + 'event_whenflagclicked') { this._pushThread(stacks[j]); } } @@ -228,9 +209,6 @@ Runtime.prototype._step = function () { * @param {boolean} isGlowing True to turn on glow; false to turn off. */ Runtime.prototype.glowBlock = function (blockId, isGlowing) { - if (!this.blocks.getBlock(blockId)) { - return; - } if (isGlowing) { this.emit(Runtime.BLOCK_GLOW_ON, blockId); } else { @@ -238,6 +216,23 @@ Runtime.prototype.glowBlock = function (blockId, isGlowing) { } }; +/** + * Return the Target for a particular thread. + * @param {!Thread} thread Thread to determine target for. + * @return {?Target} Target object, if one exists. + */ +Runtime.prototype.targetForThread = function (thread) { + // @todo This is a messy solution, + // but prevents having circular data references. + // Have a map or some other way to associate target with threads. + for (var t = 0; t < this.targets.length; t++) { + var target = this.targets[t]; + if (target.blocks.getBlock(thread.topBlock)) { + return target; + } + } +}; + /** * setInterval implementation that works in a WebWorker or not. * @param {?Function} fcn Function to call. diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 1498d6320..e70955e69 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -106,7 +106,7 @@ Sequencer.prototype.stepToSubstack = function (thread, substackNum) { substackNum = 1; } var currentBlockId = thread.peekStack(); - var substackId = this.runtime.blocks.getSubstack( + var substackId = this.runtime.targetForThread(thread).blocks.getSubstack( currentBlockId, substackNum ); @@ -153,7 +153,8 @@ Sequencer.prototype.proceedThread = function (thread) { // Pop from the stack - finished this level of execution. thread.popStack(); // Push next connected block, if there is one. - var nextBlockId = this.runtime.blocks.getNextBlock(currentBlockId); + var nextBlockId = (this.runtime.targetForThread(thread). + blocks.getNextBlock(currentBlockId)); if (nextBlockId) { thread.pushStack(nextBlockId); } diff --git a/src/engine/target.js b/src/engine/target.js new file mode 100644 index 000000000..ad7d192c6 --- /dev/null +++ b/src/engine/target.js @@ -0,0 +1,20 @@ +var Blocks = require('./blocks'); + +/** + * @fileoverview + * A Target is an abstract "code-running" object for the Scratch VM. + * Examples include sprites/clones or potentially physical-world devices. + */ + +/** + * @param {?Blocks} blocks Blocks instance for the blocks owned by this target. + * @constructor + */ +function Target (blocks) { + if (!blocks) { + blocks = new Blocks(this); + } + this.blocks = blocks; +} + +module.exports = Target; diff --git a/src/index.js b/src/index.js index 174f05b1c..08109c0a9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ var EventEmitter = require('events'); var util = require('util'); -var Blocks = require('./engine/blocks'); +var Sprite = require('./sprites/sprite'); var Runtime = require('./engine/runtime'); /** @@ -21,18 +21,22 @@ function VirtualMachine () { // Bind event emitter and runtime to VM instance // @todo Post message (Web Worker) polyfill EventEmitter.call(instance); - instance.blocks = new Blocks(); - instance.runtime = new Runtime(instance.blocks); + // @todo support multiple targets/sprites. + // This is just a demo/example. + var exampleSprite = new Sprite(); + var exampleTargets = [exampleSprite.clones[0]]; + instance.exampleSprite = exampleSprite; + instance.runtime = new Runtime(exampleTargets); /** * Event listeners for scratch-blocks. */ instance.blockListener = ( - instance.blocks.generateBlockListener(false, instance.runtime) + exampleSprite.blocks.generateBlockListener(false, instance.runtime) ); instance.flyoutBlockListener = ( - instance.blocks.generateBlockListener(true, instance.runtime) + exampleSprite.blocks.generateBlockListener(true, instance.runtime) ); // Runtime emits are passed along as VM emits. @@ -81,7 +85,7 @@ VirtualMachine.prototype.stopAll = function () { */ VirtualMachine.prototype.getPlaygroundData = function () { this.emit('playgroundData', { - blocks: this.blocks, + blocks: this.exampleSprite.blocks, threads: this.runtime.threads }); }; @@ -114,7 +118,7 @@ if (ENV_WORKER) { case 'getPlaygroundData': self.postMessage({ method: 'playgroundData', - blocks: self.vmInstance.blocks, + blocks: self.vmInstance.exampleSprite.blocks, threads: self.vmInstance.runtime.threads }); break; diff --git a/src/sprites/clone.js b/src/sprites/clone.js new file mode 100644 index 000000000..afbd56f1c --- /dev/null +++ b/src/sprites/clone.js @@ -0,0 +1,16 @@ +var util = require('util'); +var Target = require('../engine/target'); + +function Clone(spriteBlocks) { + Target.call(this, spriteBlocks); +} +util.inherits(Clone, Target); + +// Clone-level properties +Clone.prototype.x = 0; + +Clone.prototype.y = 0; + +Clone.prototype.direction = 90; + +module.exports = Clone; diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js new file mode 100644 index 000000000..1608b0571 --- /dev/null +++ b/src/sprites/sprite.js @@ -0,0 +1,17 @@ +var Clone = require('./clone'); +var Blocks = require('../engine/blocks'); + +function Sprite (blocks) { + // Sprites have: shared blocks, shared costumes, shared variables, etc. + if (!blocks) { + // Shared set of blocks for all clones. + blocks = new Blocks(); + } + this.blocks = blocks; + this.clones = []; + + // Initial single clone with the shared blocks. + this.clones.push(new Clone(this.blocks)); +} + +module.exports = Sprite; From 19da0b003287d9d011b4238b3e73ff1fdcf6e64f Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 29 Jun 2016 20:56:55 -0400 Subject: [PATCH 02/40] Add renderer demo using scratch-render-webgl --- package.json | 3 +- playground/index.html | 55 ++++++++++++++++++++++++++++++++ playground/playground.css | 2 +- playground/playground.js | 13 ++++++++ src/blocks/scratch3_motion.js | 31 ++++++++++++++++++ src/blocks/scratch3_operators.js | 7 +++- src/engine/execute.js | 3 +- src/engine/runtime.js | 4 +++ src/index.js | 11 ++++++- src/sprites/clone.js | 25 +++++++++++++++ src/sprites/sprite.js | 9 ++++-- 11 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 src/blocks/scratch3_motion.js diff --git a/package.json b/package.json index 2d4d54773..2b6d7335a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,8 @@ "dependencies": { "htmlparser2": "3.9.0", "memoizee": "0.3.10", - "promise": "7.1.1" + "promise": "7.1.1", + "scratch-render-webgl": "git+https://github.com/LLK/scratch-render-webgl.git" }, "devDependencies": { "eslint": "2.7.0", diff --git a/playground/index.html b/playground/index.html index 82535807b..e11d72c82 100644 --- a/playground/index.html +++ b/playground/index.html @@ -13,9 +13,14 @@ <button id="greenflag">Green flag</button> <button id="stopall">Stop</button> <p> + <a id="renderexplorer-link" href="#">Renderer</a><br /> <a id="threadexplorer-link" href="#">VM Threads</a><br /> <a id="blockexplorer-link" href="#">VM Block Representation</a> </p> + <div id="tab-renderexplorer"> + Render<br /> + <canvas id="scratch-stage" style="width: 480px; height: 360px;"></canvas> + </div> <div id="tab-threadexplorer"> Thread explorer <pre id="threadexplorer"></pre> @@ -240,6 +245,54 @@ </value> </block> </category> + <category name="Motion"> + <block type="motion_movesteps"> + <value name="STEPS"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_turnright"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_turnleft"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_pointindirection"> + <value name="DIRECTION"> + <shadow type="math_number"> + <field name="NUM">90</field> + </shadow> + </value> + </block> + <block type="motion_pointtowards"> + <value name="TOWARDS"> + <shadow type="motion_pointtowards_menu"> + </shadow> + </value> + </block> + <block type="motion_gotoxy"> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + </category> </xml> <!-- Syntax highlighter --> @@ -249,6 +302,8 @@ <script src="../node_modules/scratch-blocks/blockly_compressed_vertical.js"></script> <script src="../node_modules/scratch-blocks/blocks_compressed.js"></script> <script src="../node_modules/scratch-blocks/blocks_compressed_vertical.js"></script> + <!-- Renderer --> + <script src="../node_modules/scratch-render-webgl/build/render-webgl.js"></script> <!-- VM Worker --> <script src="../vm.worker.js"></script> <!-- Playground --> diff --git a/playground/playground.css b/playground/playground.css index 451a7cad5..a15e1d81c 100644 --- a/playground/playground.css +++ b/playground/playground.css @@ -32,6 +32,6 @@ a { font-size: 10pt; } -#tab-blockexplorer { +#tab-blockexplorer, #tab-threadexplorer { display: none; } diff --git a/playground/playground.js b/playground/playground.js index be683ceb7..220549c9f 100644 --- a/playground/playground.js +++ b/playground/playground.js @@ -3,6 +3,10 @@ window.onload = function() { var vm = new window.VirtualMachine(); window.vm = vm; + var canvas = document.getElementById('scratch-stage'); + window.renderer = new window.RenderWebGLLocal(canvas); + window.renderer.connectWorker(window.vm.vmWorker); + var toolbox = document.getElementById('toolbox'); var workspace = window.Blockly.inject('blocks', { toolbox: toolbox, @@ -85,16 +89,25 @@ window.onload = function() { var tabBlockExplorer = document.getElementById('tab-blockexplorer'); var tabThreadExplorer = document.getElementById('tab-threadexplorer'); + var tabRenderExplorer = document.getElementById('tab-renderexplorer'); // Handlers to show different explorers. document.getElementById('threadexplorer-link').addEventListener('click', function () { tabBlockExplorer.style.display = 'none'; + tabRenderExplorer.style.display = 'none'; tabThreadExplorer.style.display = 'block'; }); document.getElementById('blockexplorer-link').addEventListener('click', function () { tabBlockExplorer.style.display = 'block'; + tabRenderExplorer.style.display = 'none'; + tabThreadExplorer.style.display = 'none'; + }); + document.getElementById('renderexplorer-link').addEventListener('click', + function () { + tabBlockExplorer.style.display = 'none'; + tabRenderExplorer.style.display = 'block'; tabThreadExplorer.style.display = 'none'; }); }; diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js new file mode 100644 index 000000000..4fcc3b056 --- /dev/null +++ b/src/blocks/scratch3_motion.js @@ -0,0 +1,31 @@ +function Scratch3MotionBlocks(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. + */ +Scratch3MotionBlocks.prototype.getPrimitives = function() { + return { + 'motion_gotoxy': this.goToXY, + 'motion_turnright': this.turnRight + }; +}; + +Scratch3MotionBlocks.prototype.goToXY = function (args, util) { + util.target.setXY(args.X, args.Y); +}; + +Scratch3MotionBlocks.prototype.turnRight = function (args, util) { + if (args.DEGREES !== args.DEGREES) { + throw "Bad degrees" + args.DEGREES; + } + util.target.setDirection(args.DEGREES + util.target.direction); +}; + +module.exports = Scratch3MotionBlocks; diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js index 75077061c..86cb50350 100644 --- a/src/blocks/scratch3_operators.js +++ b/src/blocks/scratch3_operators.js @@ -31,7 +31,12 @@ Scratch3OperatorsBlocks.prototype.getPrimitives = function() { }; Scratch3OperatorsBlocks.prototype.number = function (args) { - return Number(args.NUM); + var num = Number(args.NUM); + if (num !== num) { + // NaN + return 0; + } + return num; }; Scratch3OperatorsBlocks.prototype.text = function (args) { diff --git a/src/engine/execute.js b/src/engine/execute.js index 001178d56..aa413a76b 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -70,7 +70,8 @@ var execute = function (sequencer, thread) { stackFrame: currentStackFrame.executionContext, startSubstack: function (substackNum) { sequencer.stepToSubstack(thread, substackNum); - } + }, + target: target }); // Deal with any reported value. diff --git a/src/engine/runtime.js b/src/engine/runtime.js index a0db919ae..c51800cdb 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -6,6 +6,7 @@ var util = require('util'); var defaultBlockPackages = { 'scratch3_control': require('../blocks/scratch3_control'), 'scratch3_event': require('../blocks/scratch3_event'), + 'scratch3_motion': require('../blocks/scratch3_motion'), 'scratch3_operators': require('../blocks/scratch3_operators') }; @@ -257,6 +258,9 @@ Runtime.prototype._setInterval = function(fcn, interval) { Runtime.prototype.start = function () { this._setInterval(function() { this._step(); + if (self.renderer) { + self.renderer.draw(); + } }.bind(this), Runtime.THREAD_STEP_INTERVAL); }; diff --git a/src/index.js b/src/index.js index 08109c0a9..fff9c52db 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,7 @@ function VirtualMachine () { // @todo support multiple targets/sprites. // This is just a demo/example. var exampleSprite = new Sprite(); + exampleSprite.createClone(); var exampleTargets = [exampleSprite.clones[0]]; instance.exampleSprite = exampleSprite; instance.runtime = new Runtime(exampleTargets); @@ -96,6 +97,10 @@ VirtualMachine.prototype.getPlaygroundData = function () { * from a worker environment. */ if (ENV_WORKER) { + self.importScripts( + './node_modules/scratch-render-webgl/build/render-webgl-worker.js' + ); + self.renderer = new self.RenderWebGLWorker(); self.vmInstance = new VirtualMachine(); self.onmessage = function (e) { var messageData = e.data; @@ -123,7 +128,11 @@ if (ENV_WORKER) { }); break; default: - throw 'Unknown method' + messageData.method; + if (e.data.id == 'RendererConnected') { + //initRenderWorker(); + } + self.renderer.onmessage(e); + break; } }; // Bind runtime's emitted events to postmessages. diff --git a/src/sprites/clone.js b/src/sprites/clone.js index afbd56f1c..f4f99abbe 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -3,9 +3,19 @@ var Target = require('../engine/target'); function Clone(spriteBlocks) { Target.call(this, spriteBlocks); + this.drawableID = null; + this.initDrawable(); } util.inherits(Clone, Target); +Clone.prototype.initDrawable = function () { + var createPromise = self.renderer.createDrawable(); + var instance = this; + createPromise.then(function (id) { + instance.drawableID = id; + }); +}; + // Clone-level properties Clone.prototype.x = 0; @@ -13,4 +23,19 @@ Clone.prototype.y = 0; Clone.prototype.direction = 90; +Clone.prototype.setXY = function (x, y) { + this.x = x; + this.y = y; + self.renderer.updateDrawableProperties(this.drawableID, { + position: [this.x, this.y] + }); +}; + +Clone.prototype.setDirection = function (direction) { + this.direction = direction; + self.renderer.updateDrawableProperties(this.drawableID, { + direction: this.direction + }); +}; + module.exports = Clone; diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js index 1608b0571..1ec25b3ba 100644 --- a/src/sprites/sprite.js +++ b/src/sprites/sprite.js @@ -9,9 +9,12 @@ function Sprite (blocks) { } this.blocks = blocks; this.clones = []; - - // Initial single clone with the shared blocks. - this.clones.push(new Clone(this.blocks)); } +Sprite.prototype.createClone = function () { + var newClone = new Clone(this.blocks); + this.clones.push(newClone); + return newClone; +}; + module.exports = Sprite; From 7db38e8422b8512e80a3959e0d8243bc344c023c Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 29 Jun 2016 23:07:23 -0400 Subject: [PATCH 03/40] Implement a few math utilities --- src/util/math-util.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/util/math-util.js diff --git a/src/util/math-util.js b/src/util/math-util.js new file mode 100644 index 000000000..2dfd62a6e --- /dev/null +++ b/src/util/math-util.js @@ -0,0 +1,20 @@ +function MathUtil () {} + +MathUtil.degToRad = function (deg) { + return (Math.PI * (90 - deg)) / 180; +}; + +MathUtil.radToDeg = function (rad) { + return rad * 180 / Math.PI; +}; + +MathUtil.clamp = function (n, min, max) { + return Math.min(Math.max(n, min), max); +}; + +MathUtil.wrapClamp = function (n, min, max) { + var range = (max - min) + 1; + return n - Math.floor((n - min) / range) * range; +}; + +module.exports = MathUtil; From 727d2c0d23d00122939435f2ed16ef27ad276bd2 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 29 Jun 2016 23:07:34 -0400 Subject: [PATCH 04/40] Clamp clone direction like Scratch 2.0 --- src/sprites/clone.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sprites/clone.js b/src/sprites/clone.js index f4f99abbe..b69c68d7a 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -1,4 +1,5 @@ var util = require('util'); +var MathUtil = require('../util/math-util'); var Target = require('../engine/target'); function Clone(spriteBlocks) { @@ -32,7 +33,7 @@ Clone.prototype.setXY = function (x, y) { }; Clone.prototype.setDirection = function (direction) { - this.direction = direction; + this.direction = MathUtil.wrapClamp(direction, -179, 180); self.renderer.updateDrawableProperties(this.drawableID, { direction: this.direction }); From 42f0f66acdef27a58599d59f72c6b8ae784975ab Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 29 Jun 2016 23:08:10 -0400 Subject: [PATCH 05/40] Implement move steps, turn right, turn left, point in direction --- src/blocks/scratch3_motion.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js index 4fcc3b056..f9171d9e7 100644 --- a/src/blocks/scratch3_motion.js +++ b/src/blocks/scratch3_motion.js @@ -1,3 +1,5 @@ +var MathUtil = require('../util/math-util'); + function Scratch3MotionBlocks(runtime) { /** * The runtime instantiating this block package. @@ -12,20 +14,35 @@ function Scratch3MotionBlocks(runtime) { */ Scratch3MotionBlocks.prototype.getPrimitives = function() { return { + 'motion_movesteps': this.moveSteps, 'motion_gotoxy': this.goToXY, - 'motion_turnright': this.turnRight + 'motion_turnright': this.turnRight, + 'motion_turnleft': this.turnLeft, + 'motion_pointindirection': this.pointInDirection }; }; +Scratch3MotionBlocks.prototype.moveSteps = function (args, util) { + var radians = MathUtil.degToRad(util.target.direction); + var dx = args.STEPS * Math.cos(radians); + var dy = args.STEPS * Math.sin(radians); + util.target.setXY(util.target.x + dx, util.target.y + dy); +}; + Scratch3MotionBlocks.prototype.goToXY = function (args, util) { util.target.setXY(args.X, args.Y); }; Scratch3MotionBlocks.prototype.turnRight = function (args, util) { - if (args.DEGREES !== args.DEGREES) { - throw "Bad degrees" + args.DEGREES; - } - util.target.setDirection(args.DEGREES + util.target.direction); + util.target.setDirection(util.target.direction + args.DEGREES); +}; + +Scratch3MotionBlocks.prototype.turnLeft = function (args, util) { + util.target.setDirection(util.target.direction - args.DEGREES); +}; + +Scratch3MotionBlocks.prototype.pointInDirection = function (args, util) { + util.target.setDirection(args.DIRECTION); }; module.exports = Scratch3MotionBlocks; From 7db2981ddc66659657624be9461c2ce2c1ad74ec Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 29 Jun 2016 23:51:21 -0400 Subject: [PATCH 06/40] Add additional motion block implementations --- playground/index.html | 164 ++++++++++++++++++++++++---------- src/blocks/scratch3_motion.js | 32 ++++++- 2 files changed, 147 insertions(+), 49 deletions(-) diff --git a/playground/index.html b/playground/index.html index e11d72c82..2d256e7a2 100644 --- a/playground/index.html +++ b/playground/index.html @@ -245,54 +245,122 @@ </value> </block> </category> - <category name="Motion"> - <block type="motion_movesteps"> - <value name="STEPS"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="motion_turnright"> - <value name="DEGREES"> - <shadow type="math_number"> - <field name="NUM">15</field> - </shadow> - </value> - </block> - <block type="motion_turnleft"> - <value name="DEGREES"> - <shadow type="math_number"> - <field name="NUM">15</field> - </shadow> - </value> - </block> - <block type="motion_pointindirection"> - <value name="DIRECTION"> - <shadow type="math_number"> - <field name="NUM">90</field> - </shadow> - </value> - </block> - <block type="motion_pointtowards"> - <value name="TOWARDS"> - <shadow type="motion_pointtowards_menu"> - </shadow> - </value> - </block> - <block type="motion_gotoxy"> - <value name="X"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - <value name="Y"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - </block> - </category> + <category name="Looks"> + <block type="looks_changeeffectby"> + <value name="EFFECT"> + <shadow type="looks_effectmenu"></shadow> + </value> + <value name="CHANGE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_seteffectto"> + <value name="EFFECT"> + <shadow type="looks_effectmenu"></shadow> + </value> + <value name="VALUE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_cleargraphiceffects"></block> + <block type="looks_changesizeby"> + <value name="CHANGE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_setsizeto"> + <value name="SIZE"> + <shadow type="math_number"> + <field name="NUM">100</field> + </shadow> + </value> + </block> + <block type="looks_size"></block> + </category> + <category name="Motion"> + <block type="motion_movesteps"> + <value name="STEPS"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_turnright"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_turnleft"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_pointindirection"> + <value name="DIRECTION"> + <shadow type="math_number"> + <field name="NUM">90</field> + </shadow> + </value> + </block> + <block type="motion_pointtowards"> + <value name="TOWARDS"> + <shadow type="motion_pointtowards_menu"> + </shadow> + </value> + </block> + <block type="motion_gotoxy"> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="motion_changexby"> + <value name="DX"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_setx"> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="motion_changeyby"> + <value name="DY"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_sety"> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="motion_xposition"></block> + <block type="motion_yposition"></block> + </category> </xml> <!-- Syntax highlighter --> diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js index f9171d9e7..de9a2c480 100644 --- a/src/blocks/scratch3_motion.js +++ b/src/blocks/scratch3_motion.js @@ -18,7 +18,13 @@ Scratch3MotionBlocks.prototype.getPrimitives = function() { 'motion_gotoxy': this.goToXY, 'motion_turnright': this.turnRight, 'motion_turnleft': this.turnLeft, - 'motion_pointindirection': this.pointInDirection + 'motion_pointindirection': this.pointInDirection, + 'motion_changexby': this.changeX, + 'motion_setx': this.setX, + 'motion_changeyby': this.changeY, + 'motion_sety': this.setY, + 'motion_xposition': this.getX, + 'motion_yposition': this.getY }; }; @@ -45,4 +51,28 @@ Scratch3MotionBlocks.prototype.pointInDirection = function (args, util) { util.target.setDirection(args.DIRECTION); }; +Scratch3MotionBlocks.prototype.changeX = function (args, util) { + util.target.setXY(util.target.x + args.DX, util.target.y); +}; + +Scratch3MotionBlocks.prototype.setX = function (args, util) { + util.target.setXY(args.X, util.target.y); +}; + +Scratch3MotionBlocks.prototype.changeY = function (args, util) { + util.target.setXY(util.target.x, util.target.y + args.DY); +}; + +Scratch3MotionBlocks.prototype.setY = function (args, util) { + util.target.setXY(util.target.x, args.Y); +}; + +Scratch3MotionBlocks.prototype.getX = function (args, util) { + return util.target.x; +}; + +Scratch3MotionBlocks.prototype.getY = function (args, util) { + return util.target.y; +}; + module.exports = Scratch3MotionBlocks; From 1eaed6fff3ee68df3f65ba067b9a1c10ac2b9e59 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 30 Jun 2016 00:11:47 -0400 Subject: [PATCH 07/40] Implement graphic effects and size blocks --- playground/index.html | 4 +-- src/blocks/scratch3_looks.js | 54 ++++++++++++++++++++++++++++++++++++ src/engine/runtime.js | 1 + src/sprites/clone.js | 33 ++++++++++++++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/blocks/scratch3_looks.js diff --git a/playground/index.html b/playground/index.html index 2d256e7a2..3565073a0 100644 --- a/playground/index.html +++ b/playground/index.html @@ -252,7 +252,7 @@ </value> <value name="CHANGE"> <shadow type="math_number"> - <field name="NUM">10</field> + <field name="NUM">25</field> </shadow> </value> </block> @@ -262,7 +262,7 @@ </value> <value name="VALUE"> <shadow type="math_number"> - <field name="NUM">10</field> + <field name="NUM">0</field> </shadow> </value> </block> diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js new file mode 100644 index 000000000..af8ed9d70 --- /dev/null +++ b/src/blocks/scratch3_looks.js @@ -0,0 +1,54 @@ +function Scratch3LooksBlocks(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. + */ +Scratch3LooksBlocks.prototype.getPrimitives = function() { + return { + 'looks_effectmenu': this.effectMenu, + 'looks_changeeffectby': this.changeEffect, + 'looks_seteffectto': this.setEffect, + 'looks_cleargraphiceffects': this.clearEffects, + 'looks_changesizeby': this.changeSize, + 'looks_setsizeto': this.setSize, + 'looks_size': this.getSize + }; +}; + +Scratch3LooksBlocks.prototype.effectMenu = function (args) { + return args.EFFECT.toLowerCase(); +}; + +Scratch3LooksBlocks.prototype.changeEffect = function (args, util) { + var newValue = args.CHANGE + util.target.effects[args.EFFECT]; + util.target.setEffect(args.EFFECT, newValue); +}; + +Scratch3LooksBlocks.prototype.setEffect = function (args, util) { + util.target.setEffect(args.EFFECT, args.VALUE); +}; + +Scratch3LooksBlocks.prototype.clearEffects = function (args, util) { + util.target.clearEffects(); +}; + +Scratch3LooksBlocks.prototype.changeSize = function (args, util) { + util.target.setSize(util.target.size + args.CHANGE); +}; + +Scratch3LooksBlocks.prototype.setSize = function (args, util) { + util.target.setSize(args.SIZE); +}; + +Scratch3LooksBlocks.prototype.getSize = function (args, util) { + return util.target.size; +}; + +module.exports = Scratch3LooksBlocks; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index c51800cdb..8159729f5 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -6,6 +6,7 @@ var util = require('util'); 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') }; diff --git a/src/sprites/clone.js b/src/sprites/clone.js index b69c68d7a..cfee61556 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -24,6 +24,18 @@ Clone.prototype.y = 0; Clone.prototype.direction = 90; +Clone.prototype.size = 100; + +Clone.prototype.effects = { + 'color': 0, + 'fisheye': 0, + 'whirl': 0, + 'pixelate': 0, + 'mosaic': 0, + 'brightness': 0, + 'ghost': 0 +}; + Clone.prototype.setXY = function (x, y) { this.x = x; this.y = y; @@ -39,4 +51,25 @@ Clone.prototype.setDirection = function (direction) { }); }; +Clone.prototype.setSize = function (size) { + this.size = MathUtil.clamp(size, 5, 535); + self.renderer.updateDrawableProperties(this.drawableID, { + scale: this.size + }); +}; + +Clone.prototype.setEffect = function (effectName, value) { + this.effects[effectName] = value; + var props = {}; + props[effectName] = this.effects[effectName]; + self.renderer.updateDrawableProperties(this.drawableID, props); +}; + +Clone.prototype.clearEffects = function () { + for (var effectName in this.effects) { + this.effects[effectName] = 0; + } + self.renderer.updateDrawableProperties(this.drawableID, this.effects); +}; + module.exports = Clone; From bb5acd1ef4f4989e4c0af69ed6b8936851ecf2db Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 30 Jun 2016 19:01:19 -0400 Subject: [PATCH 08/40] Fix merge issue in execute.js --- src/engine/execute.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/execute.js b/src/engine/execute.js index aa413a76b..9317add96 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -13,7 +13,7 @@ var execute = function (sequencer, thread) { var currentBlockId = thread.peekStack(); var currentStackFrame = thread.peekStackFrame(); - var opcode = runtime.blocks.getOpcode(currentBlockId); + var opcode = target.blocks.getOpcode(currentBlockId); if (!opcode) { console.warn('Could not get opcode for block: ' + currentBlockId); From 5876681bc70b8c3456949e207f727b8feb29666d Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 30 Jun 2016 19:04:15 -0400 Subject: [PATCH 09/40] Version of random that truncates ints --- src/blocks/scratch3_operators.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js index 86cb50350..c72adf29c 100644 --- a/src/blocks/scratch3_operators.js +++ b/src/blocks/scratch3_operators.js @@ -84,16 +84,16 @@ Scratch3OperatorsBlocks.prototype.not = function (args) { }; Scratch3OperatorsBlocks.prototype.random = function (args) { - // As a demo, this implementation of random returns after 1 second of yield. - // @todo Match Scratch 2.0 implementation with int-truncation. - // See: http://bit.ly/1Qc0GzC - var examplePromise = new Promise(function(resolve) { - setTimeout(function() { - var res = (Math.random() * (args.TO - args.FROM)) + args.FROM; - resolve(res); - }, 1000); - }); - return examplePromise; + var low = args.FROM <= args.TO ? args.FROM : args.TO; + var high = args.FROM <= args.TO ? args.TO : args.FROM; + if (low == high) return low; + // If both low and high are ints, truncate the result to an int. + var lowInt = low == parseInt(low); + var highInt = high == parseInt(high); + if (lowInt && highInt) { + return low + parseInt(Math.random() * ((high + 1) - low)); + } + return (Math.random() * (high - low)) + low; }; module.exports = Scratch3OperatorsBlocks; From de6ba08866838602cbb5072e383a4c65e86cbb63 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 10:44:43 -0400 Subject: [PATCH 10/40] Add single-frame yield mode --- src/blocks/scratch3_control.js | 8 +++++++- src/engine/execute.js | 12 ++++++++++-- src/engine/sequencer.js | 15 +++++++++++---- src/engine/thread.js | 15 +++++++++++---- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js index e13edefd9..161934ea4 100644 --- a/src/blocks/scratch3_control.js +++ b/src/blocks/scratch3_control.js @@ -37,7 +37,13 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) { }; Scratch3ControlBlocks.prototype.forever = function(args, util) { - util.startSubstack(); + if (!util.stackFrame.executed) { + util.stackFrame.executed = true; + util.startSubstack(); + } else { + util.stackFrame.executed = false; + util.yieldFrame(); + } }; Scratch3ControlBlocks.prototype.wait = function(args) { diff --git a/src/engine/execute.js b/src/engine/execute.js index 9317add96..529990ca0 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -63,8 +63,14 @@ var execute = function (sequencer, thread) { var primitiveReportedValue = null; primitiveReportedValue = blockFunction(argValues, { - yield: thread.yield.bind(thread), + yield: function() { + thread.setStatus(Thread.STATUS_YIELD); + }, + yieldFrame: function() { + thread.setStatus(Thread.STATUS_YIELD_FRAME); + }, done: function() { + thread.setStatus(Thread.STATUS_RUNNING); sequencer.proceedThread(thread); }, stackFrame: currentStackFrame.executionContext, @@ -84,17 +90,19 @@ var execute = function (sequencer, thread) { if (isPromise) { if (thread.status === Thread.STATUS_RUNNING) { // Primitive returned a promise; automatically yield thread. - thread.status = Thread.STATUS_YIELD; + thread.setStatus(Thread.STATUS_YIELD); } // Promise handlers primitiveReportedValue.then(function(resolvedValue) { // Promise resolved: the primitive reported a value. thread.pushReportedValue(resolvedValue); + thread.setStatus(Thread.STATUS_RUNNING); sequencer.proceedThread(thread); }, function(rejectionReason) { // Promise rejected: the primitive had some error. // Log it and proceed. console.warn('Primitive rejected promise: ', rejectionReason); + thread.setStatus(Thread.STATUS_RUNNING); sequencer.proceedThread(thread); }); } else if (thread.status === Thread.STATUS_RUNNING) { diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index e70955e69..f780ba255 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -36,6 +36,13 @@ Sequencer.prototype.stepThreads = function (threads) { var inactiveThreads = []; // If all of the threads are yielding, we should yield. var numYieldingThreads = 0; + // Clear all yield statuses that were for the previous frame. + for (var t = 0; t < threads.length; t++) { + if (threads[t].status === Thread.STATUS_YIELD_FRAME) { + threads[t].setStatus(Thread.STATUS_RUNNING); + } + } + // While there are still threads to run and we are within WORK_TIME, // continue executing threads. while (threads.length > 0 && @@ -51,8 +58,10 @@ Sequencer.prototype.stepThreads = function (threads) { if (activeThread.status === Thread.STATUS_RUNNING) { // Normal-mode thread: step. this.startThread(activeThread); - } else if (activeThread.status === Thread.STATUS_YIELD) { + } else if (activeThread.status === Thread.STATUS_YIELD || + activeThread.status === Thread.STATUS_YIELD_FRAME) { // Yielding thread: do nothing for this step. + numYieldingThreads++; continue; } if (activeThread.stack.length === 0 && @@ -148,8 +157,6 @@ Sequencer.prototype.proceedThread = function (thread) { var currentBlockId = thread.peekStack(); // Mark the status as done and proceed to the next block. this.runtime.glowBlock(currentBlockId, false); - // If the block was yielding, move back to running state. - thread.status = Thread.STATUS_RUNNING; // Pop from the stack - finished this level of execution. thread.popStack(); // Push next connected block, if there is one. @@ -164,7 +171,7 @@ Sequencer.prototype.proceedThread = function (thread) { } // If we still can't find a next block to run, mark the thread as done. if (thread.peekStack() === null) { - thread.status = Thread.STATUS_DONE; + thread.setStatus(Thread.STATUS_DONE); } }; diff --git a/src/engine/thread.js b/src/engine/thread.js index e5c4df2e4..d1bd73fc0 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -46,12 +46,18 @@ Thread.STATUS_RUNNING = 0; */ Thread.STATUS_YIELD = 1; +/** + * Thread status for a single-frame yield. + * @const + */ +Thread.STATUS_YIELD_FRAME = 2; + /** * Thread status for a finished/done thread. * Thread is in this state when there are no more blocks to execute. * @const */ -Thread.STATUS_DONE = 2; +Thread.STATUS_DONE = 3; /** * Push stack and update stack frames appropriately. @@ -118,10 +124,11 @@ Thread.prototype.pushReportedValue = function (value) { }; /** - * Yields the thread. + * Set thread status. + * @param {number} status Enum representing thread status. */ -Thread.prototype.yield = function () { - this.status = Thread.STATUS_YIELD; +Thread.prototype.setStatus = function (status) { + this.status = status; }; module.exports = Thread; From 39c71b559d5da903159d17b8c6f670d3bd252a8d Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 10:50:31 -0400 Subject: [PATCH 11/40] Update repeat implementation to execute once per frame --- src/blocks/scratch3_control.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js index 161934ea4..d938f66ab 100644 --- a/src/blocks/scratch3_control.js +++ b/src/blocks/scratch3_control.js @@ -28,15 +28,27 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) { if (util.stackFrame.loopCounter === undefined) { util.stackFrame.loopCounter = parseInt(args.TIMES); } - // Decrease counter - util.stackFrame.loopCounter--; - // If we still have some left, start the substack - if (util.stackFrame.loopCounter >= 0) { - util.startSubstack(); + // Only execute once per frame. + // When the substack finishes, `repeat` will be executed again and + // the second branch will be taken, yielding for the rest of the frame. + if (!util.stackFrame.executed) { + util.stackFrame.executed = true; + // Decrease counter + util.stackFrame.loopCounter--; + // If we still have some left, start the substack + if (util.stackFrame.loopCounter >= 0) { + util.startSubstack(); + } + } else { + util.stackFrame.executed = false; + util.yieldFrame(); } }; Scratch3ControlBlocks.prototype.forever = function(args, util) { + // Only execute once per frame. + // When the substack finishes, `forever` will be executed again and + // the second branch will be taken, yielding for the rest of the frame. if (!util.stackFrame.executed) { util.stackFrame.executed = true; util.startSubstack(); From dda4fc8332279e6855c1ddcf3175f77bb57fb849 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 11:24:06 -0400 Subject: [PATCH 12/40] Yield frame on an empty substack --- src/engine/sequencer.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index f780ba255..838cb1cfa 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -86,9 +86,10 @@ Sequencer.prototype.stepThreads = function (threads) { Sequencer.prototype.startThread = function (thread) { var currentBlockId = thread.peekStack(); if (!currentBlockId) { - // A "null block" - empty substack. Pop the stack. + // A "null block" - empty substack. + // Yield for the frame. thread.popStack(); - thread.status = Thread.STATUS_DONE; + thread.setStatus(Thread.STATUS_YIELD_FRAME); return; } // Start showing run feedback in the editor. @@ -165,12 +166,8 @@ Sequencer.prototype.proceedThread = function (thread) { if (nextBlockId) { thread.pushStack(nextBlockId); } - // Pop from the stack until we have a next block. - while (thread.peekStack() === null && thread.stack.length > 0) { - thread.popStack(); - } - // If we still can't find a next block to run, mark the thread as done. - if (thread.peekStack() === null) { + // If we can't find a next block to run, mark the thread as done. + if (!thread.peekStack()) { thread.setStatus(Thread.STATUS_DONE); } }; From e4f6c9e90cafdb958416d9c42a6aba7be31fc113 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 11:25:26 -0400 Subject: [PATCH 13/40] "Repeat until" implementation --- src/blocks/scratch3_control.js | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js index d938f66ab..9498cc019 100644 --- a/src/blocks/scratch3_control.js +++ b/src/blocks/scratch3_control.js @@ -15,6 +15,7 @@ function Scratch3ControlBlocks(runtime) { Scratch3ControlBlocks.prototype.getPrimitives = function() { return { 'control_repeat': this.repeat, + 'control_repeat_until': this.repeatUntil, 'control_forever': this.forever, 'control_wait': this.wait, 'control_if': this.if, @@ -45,15 +46,31 @@ 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 + // 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 (!args.CONDITION) { + util.startSubstack(); + } + } else { + util.stackFrame.executedInFrame = false; + util.yieldFrame(); + } +}; + Scratch3ControlBlocks.prototype.forever = function(args, util) { // Only execute once per frame. // When the substack finishes, `forever` will be executed again and // the second branch will be taken, yielding for the rest of the frame. - if (!util.stackFrame.executed) { - util.stackFrame.executed = true; + if (!util.stackFrame.executedInFrame) { + util.stackFrame.executedInFrame = true; util.startSubstack(); } else { - util.stackFrame.executed = false; + util.stackFrame.executedInFrame = false; util.yieldFrame(); } }; @@ -69,8 +86,8 @@ 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. - if (util.stackFrame.executed === undefined) { - util.stackFrame.executed = true; + if (util.stackFrame.executedInFrame === undefined) { + util.stackFrame.executedInFrame = true; if (args.CONDITION) { util.startSubstack(); } From ad30fa80596cd3df3ffe1bb807c8e8ea446e1422 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 11:27:01 -0400 Subject: [PATCH 14/40] Temporarily remove per-block glow --- src/engine/sequencer.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 838cb1cfa..342ad8857 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -92,12 +92,8 @@ Sequencer.prototype.startThread = function (thread) { thread.setStatus(Thread.STATUS_YIELD_FRAME); return; } - // Start showing run feedback in the editor. - this.runtime.glowBlock(currentBlockId, true); - // Execute the current block execute(this, thread); - // If the block executed without yielding and without doing control flow, // move to done. if (thread.status === Thread.STATUS_RUNNING && @@ -157,7 +153,6 @@ Sequencer.prototype.stepToReporter = function (thread, blockId, inputName) { Sequencer.prototype.proceedThread = function (thread) { var currentBlockId = thread.peekStack(); // Mark the status as done and proceed to the next block. - this.runtime.glowBlock(currentBlockId, false); // Pop from the stack - finished this level of execution. thread.popStack(); // Push next connected block, if there is one. From 57217f00440153d636c3feb0b4fc2a4644676bdc Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 11:29:32 -0400 Subject: [PATCH 15/40] Rename `executed` in repeat, ifElse --- src/blocks/scratch3_control.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js index 9498cc019..d1f6a7444 100644 --- a/src/blocks/scratch3_control.js +++ b/src/blocks/scratch3_control.js @@ -32,8 +32,8 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) { // Only execute once per frame. // When the substack finishes, `repeat` will be executed again and // the second branch will be taken, yielding for the rest of the frame. - if (!util.stackFrame.executed) { - util.stackFrame.executed = true; + if (!util.stackFrame.executedInFrame) { + util.stackFrame.executedInFrame = true; // Decrease counter util.stackFrame.loopCounter--; // If we still have some left, start the substack @@ -97,8 +97,8 @@ Scratch3ControlBlocks.prototype.if = function(args, util) { 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. - if (util.stackFrame.executed === undefined) { - util.stackFrame.executed = true; + if (util.stackFrame.executedInFrame === undefined) { + util.stackFrame.executedInFrame = true; if (args.CONDITION) { util.startSubstack(1); } else { From bb68fcab25bc2ae432ca696c86e6e91f5c794720 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 11:41:52 -0400 Subject: [PATCH 16/40] Additional fix for repeat's executedInFrame --- src/blocks/scratch3_control.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js index d1f6a7444..d3cd2fdf6 100644 --- a/src/blocks/scratch3_control.js +++ b/src/blocks/scratch3_control.js @@ -41,7 +41,7 @@ Scratch3ControlBlocks.prototype.repeat = function(args, util) { util.startSubstack(); } } else { - util.stackFrame.executed = false; + util.stackFrame.executedInFrame = false; util.yieldFrame(); } }; From 660029010dbb6aed899963919c0ac7fc8b3763cc Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 11:52:43 -0400 Subject: [PATCH 17/40] Feed in requestAnimationFrame events to VM Not sure exactly how to use these yet, but it seems helpful to have them in there. --- playground/playground.js | 7 +++++++ src/engine/runtime.js | 22 +++++----------------- src/index.js | 10 ++++++++++ src/worker.js | 4 ++++ 4 files changed, 26 insertions(+), 17 deletions(-) diff --git a/playground/playground.js b/playground/playground.js index 220549c9f..a819effc7 100644 --- a/playground/playground.js +++ b/playground/playground.js @@ -79,6 +79,13 @@ window.onload = function() { // Run threads vm.start(); + // Inform VM of animation frames. + var animate = function() { + window.vm.animationFrame(); + requestAnimationFrame(animate); + }; + requestAnimationFrame(animate); + // Handlers for green flag and stop all. document.getElementById('greenflag').addEventListener('click', function() { vm.greenFlag(); diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 8159729f5..afd930246 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -236,32 +236,20 @@ Runtime.prototype.targetForThread = function (thread) { }; /** - * setInterval implementation that works in a WebWorker or not. - * @param {?Function} fcn Function to call. - * @param {number} interval Interval at which to call it. - * @return {number} Value returned by setInterval. + * Handle an animation frame from the main thread. */ -Runtime.prototype._setInterval = function(fcn, interval) { - var setInterval = null; - if (typeof window !== 'undefined' && window.setInterval) { - setInterval = window.setInterval; - } else if (typeof self !== 'undefined' && self.setInterval) { - setInterval = self.setInterval; - } else { - return; +Runtime.prototype.animationFrame = function () { + if (self.renderer) { + self.renderer.draw(); } - return setInterval(fcn, interval); }; /** * Set up timers to repeatedly step in a browser */ Runtime.prototype.start = function () { - this._setInterval(function() { + self.setInterval(function() { this._step(); - if (self.renderer) { - self.renderer.draw(); - } }.bind(this), Runtime.THREAD_STEP_INTERVAL); }; diff --git a/src/index.js b/src/index.js index fff9c52db..15b218071 100644 --- a/src/index.js +++ b/src/index.js @@ -91,6 +91,13 @@ VirtualMachine.prototype.getPlaygroundData = function () { }); }; +/** + * Handle an animation frame. + */ +VirtualMachine.prototype.animationFrame = function () { + this.runtime.animationFrame(); +}; + /* * Worker handlers: for all public methods available above, * we must also provide a message handler in case the VM is run @@ -127,6 +134,9 @@ if (ENV_WORKER) { threads: self.vmInstance.runtime.threads }); break; + case 'animationFrame': + self.vmInstance.animationFrame(); + break; default: if (e.data.id == 'RendererConnected') { //initRenderWorker(); diff --git a/src/worker.js b/src/worker.js index e96c57bf3..70e6da5cb 100644 --- a/src/worker.js +++ b/src/worker.js @@ -63,6 +63,10 @@ VirtualMachine.prototype.stopAll = function () { this.vmWorker.postMessage({method: 'stopAll'}); }; +VirtualMachine.prototype.animationFrame = function () { + this.vmWorker.postMessage({method: 'animationFrame'}); +}; + /** * Export and bind to `window` */ From 6891a3a5dd139648d945357067b909bcf8a57a32 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 12:56:45 -0400 Subject: [PATCH 18/40] Add direction reporter --- src/blocks/scratch3_motion.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js index de9a2c480..730eee03f 100644 --- a/src/blocks/scratch3_motion.js +++ b/src/blocks/scratch3_motion.js @@ -24,7 +24,8 @@ Scratch3MotionBlocks.prototype.getPrimitives = function() { 'motion_changeyby': this.changeY, 'motion_sety': this.setY, 'motion_xposition': this.getX, - 'motion_yposition': this.getY + 'motion_yposition': this.getY, + 'motion_direction': this.getDirection }; }; @@ -75,4 +76,8 @@ Scratch3MotionBlocks.prototype.getY = function (args, util) { return util.target.y; }; +Scratch3MotionBlocks.prototype.getDirection = function (args, util) { + return util.target.direction; +}; + module.exports = Scratch3MotionBlocks; From ce941c6fd81c2cdd9b4c483c4663ee03bf9593fc Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 12:56:59 -0400 Subject: [PATCH 19/40] Add show/hide blocks using ghost effect as backend --- src/blocks/scratch3_looks.js | 10 ++++++++++ src/sprites/clone.js | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js index af8ed9d70..b2bad1ef7 100644 --- a/src/blocks/scratch3_looks.js +++ b/src/blocks/scratch3_looks.js @@ -12,6 +12,8 @@ function Scratch3LooksBlocks(runtime) { */ Scratch3LooksBlocks.prototype.getPrimitives = function() { return { + 'looks_show': this.show, + 'looks_hide': this.hide, 'looks_effectmenu': this.effectMenu, 'looks_changeeffectby': this.changeEffect, 'looks_seteffectto': this.setEffect, @@ -22,6 +24,14 @@ Scratch3LooksBlocks.prototype.getPrimitives = function() { }; }; +Scratch3LooksBlocks.prototype.show = function (args, util) { + util.target.setVisible(true); +}; + +Scratch3LooksBlocks.prototype.hide = function (args, util) { + util.target.setVisible(false); +}; + Scratch3LooksBlocks.prototype.effectMenu = function (args) { return args.EFFECT.toLowerCase(); }; diff --git a/src/sprites/clone.js b/src/sprites/clone.js index cfee61556..a1c8b5c8d 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -24,6 +24,8 @@ Clone.prototype.y = 0; Clone.prototype.direction = 90; +Clone.prototype.visible = true; + Clone.prototype.size = 100; Clone.prototype.effects = { @@ -51,6 +53,16 @@ Clone.prototype.setDirection = function (direction) { }); }; +Clone.prototype.setVisible = function (visible) { + this.visible = visible; + // @todo: Until visibility is implemented in the renderer, use a ghost. + if (this.visible) { + this.setEffect('ghost', 0); + } else { + this.setEffect('ghost', 100); + } +}; + Clone.prototype.setSize = function (size) { this.size = MathUtil.clamp(size, 5, 535); self.renderer.updateDrawableProperties(this.drawableID, { From a47a9a9b7ec8ba48b5a56392d26b18589dcd2c72 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 12:57:13 -0400 Subject: [PATCH 20/40] Add show, hide, direction to palette --- playground/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/playground/index.html b/playground/index.html index 3565073a0..c08854f88 100644 --- a/playground/index.html +++ b/playground/index.html @@ -246,6 +246,8 @@ </block> </category> <category name="Looks"> + <block type="looks_show"></block> + <block type="looks_hide"></block> <block type="looks_changeeffectby"> <value name="EFFECT"> <shadow type="looks_effectmenu"></shadow> @@ -360,6 +362,7 @@ </block> <block type="motion_xposition"></block> <block type="motion_yposition"></block> + <block type="motion_direction"></block> </category> </xml> From 890be6611e7bd947108a4b8b5c0651f417ad597c Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 13:09:22 -0400 Subject: [PATCH 21/40] Only request debug data from VM thread if a debug tab is open --- playground/playground.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/playground/playground.js b/playground/playground.js index a819effc7..800894bb9 100644 --- a/playground/playground.js +++ b/playground/playground.js @@ -51,11 +51,14 @@ window.onload = function() { } }; + // Only request data from the VM thread if the appropriate tab is open. + window.exploreTabOpen = false; var getPlaygroundData = function () { vm.getPlaygroundData(); - window.requestAnimationFrame(getPlaygroundData); + if (window.exploreTabOpen) { + window.requestAnimationFrame(getPlaygroundData); + } }; - getPlaygroundData(); vm.on('playgroundData', function(data) { updateThreadExplorer(data.threads); @@ -101,18 +104,23 @@ window.onload = function() { // Handlers to show different explorers. document.getElementById('threadexplorer-link').addEventListener('click', function () { + window.exploreTabOpen = true; + getPlaygroundData(); tabBlockExplorer.style.display = 'none'; tabRenderExplorer.style.display = 'none'; tabThreadExplorer.style.display = 'block'; }); document.getElementById('blockexplorer-link').addEventListener('click', function () { + window.exploreTabOpen = true; + getPlaygroundData(); tabBlockExplorer.style.display = 'block'; tabRenderExplorer.style.display = 'none'; tabThreadExplorer.style.display = 'none'; }); document.getElementById('renderexplorer-link').addEventListener('click', function () { + window.exploreTabOpen = false; tabBlockExplorer.style.display = 'none'; tabRenderExplorer.style.display = 'block'; tabThreadExplorer.style.display = 'none'; From 0ae0ea5f22deeadb84fdeb0a746e3879625e4baf Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Fri, 1 Jul 2016 16:30:33 -0400 Subject: [PATCH 22/40] Run threads for 60fps --- 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 afd930246..c51be795d 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -77,7 +77,7 @@ util.inherits(Runtime, EventEmitter); /** * How rapidly we try to step threads, in ms. */ -Runtime.THREAD_STEP_INTERVAL = 1000 / 30; +Runtime.THREAD_STEP_INTERVAL = 1000 / 60; // ----------------------------------------------------------------------------- From 34c46adb9c6750522e8f5999fa05a8c4be8b4f75 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 6 Jul 2016 13:17:14 -0400 Subject: [PATCH 23/40] Toolbox update --- playground/index.html | 548 ++++++++++++++++++++++++------------------ 1 file changed, 310 insertions(+), 238 deletions(-) diff --git a/playground/index.html b/playground/index.html index c08854f88..8ba16c2c1 100644 --- a/playground/index.html +++ b/playground/index.html @@ -33,259 +33,302 @@ <div id="blocks"></div> <xml id="toolbox" style="display: none"> - <category name="Events"> - <block type="event_whenflagclicked"></block> - <block type="event_whenbroadcastreceived"> - <value name="BROADCAST_OPTION"> - <shadow type="event_broadcast_menu"></shadow> - </value> - </block> - <block type="event_broadcast"> - <value name="BROADCAST_OPTION"> - <shadow type="event_broadcast_menu"></shadow> - </value> - </block> - <block type="event_broadcastandwait"> - <value name="BROADCAST_OPTION"> - <shadow type="event_broadcast_menu"></shadow> - </value> - </block> - </category> - <category name="Control"> - <block type="control_wait"> - <value name="DURATION"> - <shadow type="math_number"> - <field name="NUM">1</field> - </shadow> - </value> - </block> - <block type="control_repeat"> - <value name="TIMES"> - <shadow type="math_number"> - <field name="NUM">4</field> - </shadow> - </value> - </block> - <block type="control_forever"></block> - <block type="control_if"></block> - <block type="control_if_else"></block> - <block type="control_wait_until"></block> - <block type="control_repeat_until"></block> - <block type="control_stop"> - <value name="STOP_OPTION"> - <shadow type="control_stop_menu"></shadow> - </value> - </block> - <block type="control_start_as_clone"></block> - <block type="control_create_clone_of"> - <value name="CLONE_OPTION"> - <shadow type="control_create_clone_of_menu"></shadow> - </value> - </block> - <block type="control_delete_this_clone"></block> - </category> - <category name="Operators"> - <block type="operator_add"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_subtract"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_multiply"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_divide"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_random"> - <value name="FROM"> - <shadow type="math_number"> - <field name="NUM">1</field> - </shadow> - </value> - <value name="TO"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="operator_lt"> - <value name="OPERAND1"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - <value name="OPERAND2"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - </block> - <block type="operator_equals"> - <value name="OPERAND1"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - <value name="OPERAND2"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - </block> - <block type="operator_gt"> - <value name="OPERAND1"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - <value name="OPERAND2"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - </block> - <block type="operator_and"></block> - <block type="operator_or"></block> - <block type="operator_not"></block> - <block type="operator_join"> - <value name="STRING1"> - <shadow type="text"> - <field name="TEXT">hello</field> - </shadow> - </value> - <value name="STRING2"> - <shadow type="text"> - <field name="TEXT">world</field> - </shadow> - </value> - </block> - <block type="operator_letter_of"> - <value name="LETTER"> - <shadow type="math_number"> - <field name="NUM">1</field> - </shadow> - </value> - <value name="STRING"> - <shadow type="text"> - <field name="TEXT">world</field> - </shadow> - </value> - </block> - <block type="operator_length"> - <value name="STRING"> - <shadow type="text"> - <field name="TEXT">world</field> - </shadow> - </value> - </block> - <block type="operator_mod"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_round"> - <value name="NUM"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_mathop"> - <value name="OPERATOR"> - <shadow type="operator_mathop_menu"></shadow> - </value> - <value name="NUM"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - </category> - <category name="Looks"> - <block type="looks_show"></block> - <block type="looks_hide"></block> - <block type="looks_changeeffectby"> - <value name="EFFECT"> - <shadow type="looks_effectmenu"></shadow> - </value> - <value name="CHANGE"> + <category name="Events"> + <block type="event_whenflagclicked"></block> + <block type="event_whenkeypressed"> + <value name="KEY_OPTION"> + <shadow type="event_keyoptions"></shadow> + </value> + </block> + <block type="event_whenbroadcastreceived"> + <value name="BROADCAST_OPTION"> + <shadow type="event_broadcast_menu"></shadow> + </value> + </block> + <block type="event_broadcast"> + <value name="BROADCAST_OPTION"> + <shadow type="event_broadcast_menu"></shadow> + </value> + </block> + <block type="event_broadcastandwait"> + <value name="BROADCAST_OPTION"> + <shadow type="event_broadcast_menu"></shadow> + </value> + </block> + </category> + <category name="Control"> + <block type="control_wait"> + <value name="DURATION"> + <shadow type="math_positive_number"> + <field name="NUM">1</field> + </shadow> + </value> + </block> + <block type="control_repeat"> + <value name="TIMES"> + <shadow type="math_whole_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="control_forever"></block> + <block type="control_if"></block> + <block type="control_if_else"></block> + <block type="control_wait_until"></block> + <block type="control_repeat_until"></block> + <block type="control_stop"> + <value name="STOP_OPTION"> + <shadow type="control_stop_menu"></shadow> + </value> + </block> + <block type="control_start_as_clone"></block> + <block type="control_create_clone_of"> + <value name="CLONE_OPTION"> + <shadow type="control_create_clone_of_menu"></shadow> + </value> + </block> + <block type="control_delete_this_clone"></block> + </category> + <category name="Operators"> + <block type="operator_add"> + <value name="NUM1"> <shadow type="math_number"> - <field name="NUM">25</field> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> </shadow> </value> </block> - <block type="looks_seteffectto"> - <value name="EFFECT"> - <shadow type="looks_effectmenu"></shadow> - </value> - <value name="VALUE"> + <block type="operator_subtract"> + <value name="NUM1"> <shadow type="math_number"> - <field name="NUM">0</field> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> </shadow> </value> </block> - <block type="looks_cleargraphiceffects"></block> - <block type="looks_changesizeby"> - <value name="CHANGE"> + <block type="operator_multiply"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_divide"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_random"> + <value name="FROM"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="TO"> <shadow type="math_number"> <field name="NUM">10</field> </shadow> </value> </block> - <block type="looks_setsizeto"> - <value name="SIZE"> - <shadow type="math_number"> - <field name="NUM">100</field> + <block type="operator_lt"> + <value name="OPERAND1"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + <value name="OPERAND2"> + <shadow type="text"> + <field name="TEXT"></field> </shadow> </value> </block> - <block type="looks_size"></block> - </category> - <category name="Motion"> + <block type="operator_equals"> + <value name="OPERAND1"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + <value name="OPERAND2"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + </block> + <block type="operator_gt"> + <value name="OPERAND1"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + <value name="OPERAND2"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + </block> + <block type="operator_and"></block> + <block type="operator_or"></block> + <block type="operator_not"></block> + <block type="operator_join"> + <value name="STRING1"> + <shadow type="text"> + <field name="TEXT">hello</field> + </shadow> + </value> + <value name="STRING2"> + <shadow type="text"> + <field name="TEXT">world</field> + </shadow> + </value> + </block> + <block type="operator_letter_of"> + <value name="LETTER"> + <shadow type="math_whole_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="STRING"> + <shadow type="text"> + <field name="TEXT">world</field> + </shadow> + </value> + </block> + <block type="operator_length"> + <value name="STRING"> + <shadow type="text"> + <field name="TEXT">world</field> + </shadow> + </value> + </block> + <block type="operator_mod"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_round"> + <value name="NUM"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_mathop"> + <value name="OPERATOR"> + <shadow type="operator_mathop_menu"></shadow> + </value> + <value name="NUM"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + </category> + <category name="Looks"> + <block type="looks_sayforsecs"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hello!</field> + </shadow> + </value> + <value name="SECS"> + <shadow type="math_number"> + <field name="NUM">2</field> + </shadow> + </value> + </block> + <block type="looks_say"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hello!</field> + </shadow> + </value> + </block> + <block type="looks_thinkforsecs"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hmm...</field> + </shadow> + </value> + <value name="SECS"> + <shadow type="math_number"> + <field name="NUM">2</field> + </shadow> + </value> + </block> + <block type="looks_think"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hmm...</field> + </shadow> + </value> + </block> + <block type="looks_show"></block> + <block type="looks_hide"></block> + <block type="looks_changeeffectby"> + <value name="EFFECT"> + <shadow type="looks_effectmenu"></shadow> + </value> + <value name="CHANGE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_seteffectto"> + <value name="EFFECT"> + <shadow type="looks_effectmenu"></shadow> + </value> + <value name="VALUE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_cleargraphiceffects"></block> + <block type="looks_changesizeby"> + <value name="CHANGE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_setsizeto"> + <value name="SIZE"> + <shadow type="math_number"> + <field name="NUM">100</field> + </shadow> + </value> + </block> + <block type="looks_size"></block> + </category> + <category name="Motion"> <block type="motion_movesteps"> <value name="STEPS"> <shadow type="math_number"> @@ -332,6 +375,29 @@ </shadow> </value> </block> + <block type="motion_goto"> + <value name="TO"> + <shadow type="motion_goto_menu"> + </shadow> + </value> + </block> + <block type="motion_glidesecstoxy"> + <value name="SECS"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> <block type="motion_changexby"> <value name="DX"> <shadow type="math_number"> @@ -360,11 +426,17 @@ </shadow> </value> </block> + <block type="motion_ifonedgebounce"></block> + <block type="motion_setrotationstyle"> + <value name="STYLE"> + <shadow type="motion_setrotationstyle_menu"></shadow> + </value> + </block> <block type="motion_xposition"></block> <block type="motion_yposition"></block> <block type="motion_direction"></block> </category> - </xml> + </xml> <!-- Syntax highlighter --> <script src="../node_modules/highlightjs/highlight.pack.min.js"></script> From 460760bd06cd5893e1a5f710a555e23316bf9e3e Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 6 Jul 2016 13:47:32 -0400 Subject: [PATCH 24/40] Stub "say" and "think" blocks with `console.log` --- src/blocks/scratch3_looks.js | 34 ++++++++++++++++++++++++++++++++++ src/sprites/clone.js | 14 ++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js index b2bad1ef7..33e1ef3bf 100644 --- a/src/blocks/scratch3_looks.js +++ b/src/blocks/scratch3_looks.js @@ -12,6 +12,10 @@ function Scratch3LooksBlocks(runtime) { */ Scratch3LooksBlocks.prototype.getPrimitives = function() { return { + 'looks_say': this.say, + 'looks_sayforsecs': this.sayforsecs, + 'looks_think': this.think, + 'looks_thinkforsecs': this.sayforsecs, 'looks_show': this.show, 'looks_hide': this.hide, 'looks_effectmenu': this.effectMenu, @@ -24,6 +28,36 @@ Scratch3LooksBlocks.prototype.getPrimitives = function() { }; }; +Scratch3LooksBlocks.prototype.say = function (args, util) { + util.target.setSay('say', args.MESSAGE); +}; + +Scratch3LooksBlocks.prototype.sayforsecs = function (args, util) { + util.target.setSay('say', args.MESSAGE); + return new Promise(function(resolve) { + setTimeout(function() { + // Clear say bubble and proceed. + util.target.setSay(); + resolve(); + }, 1000 * args.SECS); + }); +}; + +Scratch3LooksBlocks.prototype.think = function (args, util) { + util.target.setSay('think', args.MESSAGE); +}; + +Scratch3LooksBlocks.prototype.thinkforsecs = function (args, util) { + util.target.setSay('think', args.MESSAGE); + return new Promise(function(resolve) { + setTimeout(function() { + // Clear say bubble and proceed. + util.target.setSay(); + resolve(); + }, 1000 * args.SECS); + }); +}; + Scratch3LooksBlocks.prototype.show = function (args, util) { util.target.setVisible(true); }; diff --git a/src/sprites/clone.js b/src/sprites/clone.js index a1c8b5c8d..1ffca405b 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -53,6 +53,20 @@ Clone.prototype.setDirection = function (direction) { }); }; +/** + * Set a say bubble on this clone. + * @param {?string} type Type of say bubble: "say", "think", or null. + * @param {?string} message Message to put in say bubble. + */ +Clone.prototype.setSay = function (type, message) { + // @todo: Render to stage. + if (!type || !message) { + console.log('Clearing say bubble'); + return; + } + console.log('Setting say bubble:', type, message); +}; + Clone.prototype.setVisible = function (visible) { this.visible = visible; // @todo: Until visibility is implemented in the renderer, use a ghost. From 798368b6c5010426e37f29c4c4002df9c6acccce Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 6 Jul 2016 13:57:58 -0400 Subject: [PATCH 25/40] Add documentation in src/sprites/clone.js --- src/sprites/clone.js | 57 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/sprites/clone.js b/src/sprites/clone.js index 1ffca405b..ce64fda52 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -9,6 +9,9 @@ function Clone(spriteBlocks) { } util.inherits(Clone, Target); +/** + * Create a clone's drawable with the renderer. + */ Clone.prototype.initDrawable = function () { var createPromise = self.renderer.createDrawable(); var instance = this; @@ -17,17 +20,41 @@ Clone.prototype.initDrawable = function () { }); }; -// Clone-level properties +// Clone-level properties. +/** + * Scratch X coordinate. Currently should range from -240 to 240. + * @type {!number} + */ Clone.prototype.x = 0; +/** + * Scratch Y coordinate. Currently should range from -180 to 180. + * @type {!number} + */ Clone.prototype.y = 0; +/** + * Scratch direction. Currently should range from -179 to 180. + * @type {!number} + */ Clone.prototype.direction = 90; +/** + * Whether the clone is currently visible. + * @type {!boolean} + */ Clone.prototype.visible = true; +/** + * Size of clone as a percent of costume size. Ranges from 5% to 535%. + * @type {!number} + */ Clone.prototype.size = 100; +/** + * Map of current graphic effect values. + * @type {!Object.<string, number>} + */ Clone.prototype.effects = { 'color': 0, 'fisheye': 0, @@ -37,7 +64,13 @@ Clone.prototype.effects = { 'brightness': 0, 'ghost': 0 }; +// End clone-level properties. +/** + * Set the X and Y coordinates of a clone. + * @param {!number} x New X coordinate of clone, in Scratch coordinates. + * @param {!number} y New Y coordinate of clone, in Scratch coordinates. + */ Clone.prototype.setXY = function (x, y) { this.x = x; this.y = y; @@ -46,7 +79,12 @@ Clone.prototype.setXY = function (x, y) { }); }; +/** + * Set the direction of a clone. + * @param {!number} direction New direction of clone. + */ Clone.prototype.setDirection = function (direction) { + // Keep direction between -179 and +180. this.direction = MathUtil.wrapClamp(direction, -179, 180); self.renderer.updateDrawableProperties(this.drawableID, { direction: this.direction @@ -67,6 +105,10 @@ Clone.prototype.setSay = function (type, message) { console.log('Setting say bubble:', type, message); }; +/** + * Set visibility of the clone; i.e., whether it's shown or hidden. + * @param {!boolean} visible True if the sprite should be shown. + */ Clone.prototype.setVisible = function (visible) { this.visible = visible; // @todo: Until visibility is implemented in the renderer, use a ghost. @@ -77,13 +119,23 @@ Clone.prototype.setVisible = function (visible) { } }; +/** + * Set size of the clone, as a percentage of the costume size. + * @param {!number} size Size of clone, from 5 to 535. + */ Clone.prototype.setSize = function (size) { + // Keep size between 5% and 535%. this.size = MathUtil.clamp(size, 5, 535); self.renderer.updateDrawableProperties(this.drawableID, { scale: this.size }); }; +/** + * Set a particular graphic effect on this clone. + * @param {!string} effectName Name of effect (see `Clone.prototype.effects`). + * @param {!number} value Numerical magnitude of effect. + */ Clone.prototype.setEffect = function (effectName, value) { this.effects[effectName] = value; var props = {}; @@ -91,6 +143,9 @@ Clone.prototype.setEffect = function (effectName, value) { self.renderer.updateDrawableProperties(this.drawableID, props); }; +/** + * Clear all graphic effects on this clone. + */ Clone.prototype.clearEffects = function () { for (var effectName in this.effects) { this.effects[effectName] = 0; From 2e01caa8a6e5be07ea2354c5ea201b360f98cc28 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 6 Jul 2016 14:04:36 -0400 Subject: [PATCH 26/40] Add documentation for math-util functions. --- src/util/math-util.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/util/math-util.js b/src/util/math-util.js index 2dfd62a6e..b53698ef1 100644 --- a/src/util/math-util.js +++ b/src/util/math-util.js @@ -1,17 +1,45 @@ function MathUtil () {} +/** + * Convert a value from degrees to radians. + * @param {!number} deg Value in degrees. + * @return {!number} Equivalent value in radians. + */ MathUtil.degToRad = function (deg) { return (Math.PI * (90 - deg)) / 180; }; +/** + * Convert a value from radians to degrees. + * @param {!number} rad Value in radians. + * @return {!number} Equivalent value in degrees. + */ MathUtil.radToDeg = function (rad) { return rad * 180 / Math.PI; }; +/** + * Clamp a number between two limits. + * If n < min, return min. If n > max, return max. Else, return n. + * @param {!number} n Number to clamp. + * @param {!number} min Minimum limit. + * @param {!number} max Maximum limit. + * @return {!number} Value of n clamped to min and max. + */ MathUtil.clamp = function (n, min, max) { return Math.min(Math.max(n, min), max); }; +/** + * Keep a number between two limits, wrapping "extra" into the range. + * e.g., wrapClamp(7, 1, 5) == 2 + * wrapClamp(0, 1, 5) == 5 + * wrapClamp(-11, -10, 6) == 6, etc. + * @param {!number} n Number to wrap. + * @param {!number} min Minimum limit. + * @param {!number} max Maximum limit. + * @return {!number} Value of n wrapped between min and max. + */ MathUtil.wrapClamp = function (n, min, max) { var range = (max - min) + 1; return n - Math.floor((n - min) / range) * range; From 7c24bdc612ffca680b2595104d28a75665227a10 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 6 Jul 2016 14:09:06 -0400 Subject: [PATCH 27/40] More documentation in sprite, clone --- src/sprites/clone.js | 5 +++++ src/sprites/sprite.js | 11 ++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/sprites/clone.js b/src/sprites/clone.js index ce64fda52..169d72cc7 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -2,6 +2,11 @@ var util = require('util'); var MathUtil = require('../util/math-util'); var Target = require('../engine/target'); +/** + * Clone (instance) of a sprite. + * @param {!Blocks} spriteBlocks Reference to the sprite's blocks. + * @constructor + */ function Clone(spriteBlocks) { Target.call(this, spriteBlocks); this.drawableID = null; diff --git a/src/sprites/sprite.js b/src/sprites/sprite.js index 1ec25b3ba..b4abdbbb5 100644 --- a/src/sprites/sprite.js +++ b/src/sprites/sprite.js @@ -1,8 +1,13 @@ var Clone = require('./clone'); var Blocks = require('../engine/blocks'); +/** + * Sprite to be used on the Scratch stage. + * All clones of a sprite have shared blocks, shared costumes, shared variables. + * @param {?Blocks} blocks Shared blocks object for all clones of sprite. + * @constructor + */ function Sprite (blocks) { - // Sprites have: shared blocks, shared costumes, shared variables, etc. if (!blocks) { // Shared set of blocks for all clones. blocks = new Blocks(); @@ -11,6 +16,10 @@ function Sprite (blocks) { this.clones = []; } +/** + * Create a clone of this sprite. + * @returns {!Clone} Newly created clone. + */ Sprite.prototype.createClone = function () { var newClone = new Clone(this.blocks); this.clones.push(newClone); From 30dc285a37b78a2a1a27d92ce3ddbb79387d1b12 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 6 Jul 2016 14:13:03 -0400 Subject: [PATCH 28/40] Add implementations for math_positive_number and math_whole_number --- src/blocks/scratch3_operators.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js index c72adf29c..d3a7f1921 100644 --- a/src/blocks/scratch3_operators.js +++ b/src/blocks/scratch3_operators.js @@ -15,6 +15,8 @@ function Scratch3OperatorsBlocks(runtime) { Scratch3OperatorsBlocks.prototype.getPrimitives = function() { return { 'math_number': this.number, + 'math_positive_number': this.number, + 'math_whole_number': this.number, 'text': this.text, 'operator_add': this.add, 'operator_subtract': this.subtract, From d4353458ff2f44d2423a2483466f147b7b35dc0a Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 6 Jul 2016 14:16:44 -0400 Subject: [PATCH 29/40] Don't quit loop when a thread is yielding --- src/engine/sequencer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 342ad8857..175fc68d1 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -62,7 +62,6 @@ Sequencer.prototype.stepThreads = function (threads) { activeThread.status === Thread.STATUS_YIELD_FRAME) { // Yielding thread: do nothing for this step. numYieldingThreads++; - continue; } if (activeThread.stack.length === 0 && activeThread.status === Thread.STATUS_DONE) { From 9c6dca8131ec5fe1d40a93ccd75e07277cd22813 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 7 Jul 2016 19:42:38 -0400 Subject: [PATCH 30/40] Add visual reporting of top-level reporter execution --- playground/playground.js | 3 +++ src/engine/execute.js | 8 ++++++++ src/engine/runtime.js | 15 +++++++++++++++ src/index.js | 6 ++++++ 4 files changed, 32 insertions(+) diff --git a/playground/playground.js b/playground/playground.js index 800894bb9..47631b201 100644 --- a/playground/playground.js +++ b/playground/playground.js @@ -78,6 +78,9 @@ window.onload = function() { vm.on('BLOCK_GLOW_OFF', function(data) { workspace.glowBlock(data.id, false); }); + vm.on('VISUAL_REPORT', function(data) { + workspace.reportValue(data.id, data.value); + }); // Run threads vm.start(); diff --git a/src/engine/execute.js b/src/engine/execute.js index 529990ca0..db111637d 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -96,6 +96,10 @@ var execute = function (sequencer, thread) { primitiveReportedValue.then(function(resolvedValue) { // Promise resolved: the primitive reported a value. thread.pushReportedValue(resolvedValue); + // Report the value visually if necessary. + if (thread.peekStack() === thread.topBlock) { + runtime.visualReport(thread.peekStack(), resolvedValue); + } thread.setStatus(Thread.STATUS_RUNNING); sequencer.proceedThread(thread); }, function(rejectionReason) { @@ -107,6 +111,10 @@ var execute = function (sequencer, thread) { }); } else if (thread.status === Thread.STATUS_RUNNING) { thread.pushReportedValue(primitiveReportedValue); + // Report the value visually if necessary. + if (thread.peekStack() === thread.topBlock) { + runtime.visualReport(thread.peekStack(), primitiveReportedValue); + } } }; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index c51be795d..f92a91db7 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -69,6 +69,12 @@ Runtime.BLOCK_GLOW_ON = 'BLOCK_GLOW_ON'; */ Runtime.BLOCK_GLOW_OFF = 'BLOCK_GLOW_OFF'; +/** + * Event name for visual value report. + * @const {string} + */ +Runtime.VISUAL_REPORT = 'VISUAL_REPORT'; + /** * Inherit from EventEmitter */ @@ -218,6 +224,15 @@ Runtime.prototype.glowBlock = function (blockId, isGlowing) { } }; +/** + * Emit value for reporter to show in the blocks. + * @param {string} blockId ID for the block. + * @param {string} value Value to show associated with the block. + */ +Runtime.prototype.visualReport = function (blockId, value) { + this.emit(Runtime.VISUAL_REPORT, blockId, String(value)); +}; + /** * Return the Target for a particular thread. * @param {!Thread} thread Thread to determine target for. diff --git a/src/index.js b/src/index.js index 15b218071..965c8cd9a 100644 --- a/src/index.js +++ b/src/index.js @@ -53,6 +53,9 @@ function VirtualMachine () { instance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (id) { instance.emit(Runtime.BLOCK_GLOW_OFF, {id: id}); }); + instance.runtime.on(Runtime.VISUAL_REPORT, function (id, value) { + instance.emit(Runtime.VISUAL_REPORT, {id: id, value: value}); + }); } /** @@ -158,6 +161,9 @@ if (ENV_WORKER) { self.vmInstance.runtime.on(Runtime.BLOCK_GLOW_OFF, function (id) { self.postMessage({method: Runtime.BLOCK_GLOW_OFF, id: id}); }); + self.vmInstance.runtime.on(Runtime.VISUAL_REPORT, function (id, value) { + self.postMessage({method: Runtime.VISUAL_REPORT, id: id, value: value}); + }); } /** From 1f19d7a20932d64a08fc8859cc222d022d04b8af Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 7 Jul 2016 19:44:26 -0400 Subject: [PATCH 31/40] Only visually report when an actual value was returned --- src/engine/execute.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine/execute.js b/src/engine/execute.js index db111637d..8d750eee3 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -97,7 +97,8 @@ var execute = function (sequencer, thread) { // Promise resolved: the primitive reported a value. thread.pushReportedValue(resolvedValue); // Report the value visually if necessary. - if (thread.peekStack() === thread.topBlock) { + if (typeof resolvedValue !== 'undefined' && + thread.peekStack() === thread.topBlock) { runtime.visualReport(thread.peekStack(), resolvedValue); } thread.setStatus(Thread.STATUS_RUNNING); @@ -112,7 +113,8 @@ var execute = function (sequencer, thread) { } else if (thread.status === Thread.STATUS_RUNNING) { thread.pushReportedValue(primitiveReportedValue); // Report the value visually if necessary. - if (thread.peekStack() === thread.topBlock) { + if (typeof primitiveReportedValue !== 'undefined' && + thread.peekStack() === thread.topBlock) { runtime.visualReport(thread.peekStack(), primitiveReportedValue); } } From c650de852081d7ebbcb5597e0885cfb775fb345c Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Wed, 13 Jul 2016 16:52:46 -0400 Subject: [PATCH 32/40] Real version of `Clone.prototype.setVisible` --- src/sprites/clone.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/sprites/clone.js b/src/sprites/clone.js index 169d72cc7..6a0f22fee 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -3,7 +3,7 @@ var MathUtil = require('../util/math-util'); var Target = require('../engine/target'); /** - * Clone (instance) of a sprite. + * Clone (instance) of a sprite. * @param {!Blocks} spriteBlocks Reference to the sprite's blocks. * @constructor */ @@ -116,12 +116,9 @@ Clone.prototype.setSay = function (type, message) { */ Clone.prototype.setVisible = function (visible) { this.visible = visible; - // @todo: Until visibility is implemented in the renderer, use a ghost. - if (this.visible) { - this.setEffect('ghost', 0); - } else { - this.setEffect('ghost', 100); - } + self.renderer.updateDrawableProperties(this.drawableID, { + visible: this.visible + }); }; /** From 0465ee2076cbbd494d83dc21f23c8e8b50b8811e Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 14 Jul 2016 13:22:11 -0400 Subject: [PATCH 33/40] Add --host flag and index redirect for playground --- Makefile | 2 +- index.html | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 index.html diff --git a/Makefile b/Makefile index 835c16f79..e2a04c2b6 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ watch: $(WEBPACK) --watch serve: - $(WEBPACK_DEV_SERVER) --content-base ./ + $(WEBPACK_DEV_SERVER) --host 0.0.0.0 --content-base ./ # ------------------------------------------------------------------------------ diff --git a/index.html b/index.html new file mode 100644 index 000000000..42edce430 --- /dev/null +++ b/index.html @@ -0,0 +1,7 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta http-equiv="refresh" content="0; URL='/playground'" /> + <title>Redirect to playground</title> +</head> +</html> From 1098178fc74697de7c663b8e91ff0eb7fc4feac3 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 14 Jul 2016 13:24:39 -0400 Subject: [PATCH 34/40] Add Blockly messages to playground --- playground/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/playground/index.html b/playground/index.html index 8ba16c2c1..28781ebc7 100644 --- a/playground/index.html +++ b/playground/index.html @@ -445,6 +445,7 @@ <script src="../node_modules/scratch-blocks/blockly_compressed_vertical.js"></script> <script src="../node_modules/scratch-blocks/blocks_compressed.js"></script> <script src="../node_modules/scratch-blocks/blocks_compressed_vertical.js"></script> + <script src="../node_modules/scratch-blocks/msg/messages.js"></script> <!-- Renderer --> <script src="../node_modules/scratch-render-webgl/build/render-webgl.js"></script> <!-- VM Worker --> From 30735bc06e8722178ddfe12acb29369d50b875ba Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Thu, 14 Jul 2016 13:25:56 -0400 Subject: [PATCH 35/40] Update toolbox XML July 14 --- playground/index.html | 1094 ++++++++++++++++++++++++++--------------- 1 file changed, 691 insertions(+), 403 deletions(-) diff --git a/playground/index.html b/playground/index.html index 28781ebc7..e38c10793 100644 --- a/playground/index.html +++ b/playground/index.html @@ -33,409 +33,697 @@ <div id="blocks"></div> <xml id="toolbox" style="display: none"> - <category name="Events"> - <block type="event_whenflagclicked"></block> - <block type="event_whenkeypressed"> - <value name="KEY_OPTION"> - <shadow type="event_keyoptions"></shadow> - </value> - </block> - <block type="event_whenbroadcastreceived"> - <value name="BROADCAST_OPTION"> - <shadow type="event_broadcast_menu"></shadow> - </value> - </block> - <block type="event_broadcast"> - <value name="BROADCAST_OPTION"> - <shadow type="event_broadcast_menu"></shadow> - </value> - </block> - <block type="event_broadcastandwait"> - <value name="BROADCAST_OPTION"> - <shadow type="event_broadcast_menu"></shadow> - </value> - </block> - </category> - <category name="Control"> - <block type="control_wait"> - <value name="DURATION"> - <shadow type="math_positive_number"> - <field name="NUM">1</field> - </shadow> - </value> - </block> - <block type="control_repeat"> - <value name="TIMES"> - <shadow type="math_whole_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="control_forever"></block> - <block type="control_if"></block> - <block type="control_if_else"></block> - <block type="control_wait_until"></block> - <block type="control_repeat_until"></block> - <block type="control_stop"> - <value name="STOP_OPTION"> - <shadow type="control_stop_menu"></shadow> - </value> - </block> - <block type="control_start_as_clone"></block> - <block type="control_create_clone_of"> - <value name="CLONE_OPTION"> - <shadow type="control_create_clone_of_menu"></shadow> - </value> - </block> - <block type="control_delete_this_clone"></block> - </category> - <category name="Operators"> - <block type="operator_add"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_subtract"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_multiply"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_divide"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_random"> - <value name="FROM"> - <shadow type="math_number"> - <field name="NUM">1</field> - </shadow> - </value> - <value name="TO"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="operator_lt"> - <value name="OPERAND1"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - <value name="OPERAND2"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - </block> - <block type="operator_equals"> - <value name="OPERAND1"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - <value name="OPERAND2"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - </block> - <block type="operator_gt"> - <value name="OPERAND1"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - <value name="OPERAND2"> - <shadow type="text"> - <field name="TEXT"></field> - </shadow> - </value> - </block> - <block type="operator_and"></block> - <block type="operator_or"></block> - <block type="operator_not"></block> - <block type="operator_join"> - <value name="STRING1"> - <shadow type="text"> - <field name="TEXT">hello</field> - </shadow> - </value> - <value name="STRING2"> - <shadow type="text"> - <field name="TEXT">world</field> - </shadow> - </value> - </block> - <block type="operator_letter_of"> - <value name="LETTER"> - <shadow type="math_whole_number"> - <field name="NUM">1</field> - </shadow> - </value> - <value name="STRING"> - <shadow type="text"> - <field name="TEXT">world</field> - </shadow> - </value> - </block> - <block type="operator_length"> - <value name="STRING"> - <shadow type="text"> - <field name="TEXT">world</field> - </shadow> - </value> - </block> - <block type="operator_mod"> - <value name="NUM1"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - <value name="NUM2"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_round"> - <value name="NUM"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - <block type="operator_mathop"> - <value name="OPERATOR"> - <shadow type="operator_mathop_menu"></shadow> - </value> - <value name="NUM"> - <shadow type="math_number"> - <field name="NUM"></field> - </shadow> - </value> - </block> - </category> - <category name="Looks"> - <block type="looks_sayforsecs"> - <value name="MESSAGE"> - <shadow type="text"> - <field name="TEXT">Hello!</field> - </shadow> - </value> - <value name="SECS"> - <shadow type="math_number"> - <field name="NUM">2</field> - </shadow> - </value> - </block> - <block type="looks_say"> - <value name="MESSAGE"> - <shadow type="text"> - <field name="TEXT">Hello!</field> - </shadow> - </value> - </block> - <block type="looks_thinkforsecs"> - <value name="MESSAGE"> - <shadow type="text"> - <field name="TEXT">Hmm...</field> - </shadow> - </value> - <value name="SECS"> - <shadow type="math_number"> - <field name="NUM">2</field> - </shadow> - </value> - </block> - <block type="looks_think"> - <value name="MESSAGE"> - <shadow type="text"> - <field name="TEXT">Hmm...</field> - </shadow> - </value> - </block> - <block type="looks_show"></block> - <block type="looks_hide"></block> - <block type="looks_changeeffectby"> - <value name="EFFECT"> - <shadow type="looks_effectmenu"></shadow> - </value> - <value name="CHANGE"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="looks_seteffectto"> - <value name="EFFECT"> - <shadow type="looks_effectmenu"></shadow> - </value> - <value name="VALUE"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="looks_cleargraphiceffects"></block> - <block type="looks_changesizeby"> - <value name="CHANGE"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="looks_setsizeto"> - <value name="SIZE"> - <shadow type="math_number"> - <field name="NUM">100</field> - </shadow> - </value> - </block> - <block type="looks_size"></block> - </category> - <category name="Motion"> - <block type="motion_movesteps"> - <value name="STEPS"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="motion_turnright"> - <value name="DEGREES"> - <shadow type="math_number"> - <field name="NUM">15</field> - </shadow> - </value> - </block> - <block type="motion_turnleft"> - <value name="DEGREES"> - <shadow type="math_number"> - <field name="NUM">15</field> - </shadow> - </value> - </block> - <block type="motion_pointindirection"> - <value name="DIRECTION"> - <shadow type="math_number"> - <field name="NUM">90</field> - </shadow> - </value> - </block> - <block type="motion_pointtowards"> - <value name="TOWARDS"> - <shadow type="motion_pointtowards_menu"> - </shadow> - </value> - </block> - <block type="motion_gotoxy"> - <value name="X"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - <value name="Y"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - </block> - <block type="motion_goto"> - <value name="TO"> - <shadow type="motion_goto_menu"> - </shadow> - </value> - </block> - <block type="motion_glidesecstoxy"> - <value name="SECS"> - <shadow type="math_number"> - <field name="NUM">1</field> - </shadow> - </value> - <value name="X"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - <value name="Y"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - </block> - <block type="motion_changexby"> - <value name="DX"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="motion_setx"> - <value name="X"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - </block> - <block type="motion_changeyby"> - <value name="DY"> - <shadow type="math_number"> - <field name="NUM">10</field> - </shadow> - </value> - </block> - <block type="motion_sety"> - <value name="Y"> - <shadow type="math_number"> - <field name="NUM">0</field> - </shadow> - </value> - </block> - <block type="motion_ifonedgebounce"></block> - <block type="motion_setrotationstyle"> - <value name="STYLE"> - <shadow type="motion_setrotationstyle_menu"></shadow> - </value> - </block> - <block type="motion_xposition"></block> - <block type="motion_yposition"></block> - <block type="motion_direction"></block> - </category> + <category name="Motion" colour="#4C97FF"> + <block type="motion_movesteps"> + <value name="STEPS"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_turnright"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_turnleft"> + <value name="DEGREES"> + <shadow type="math_number"> + <field name="NUM">15</field> + </shadow> + </value> + </block> + <block type="motion_pointindirection"> + <value name="DIRECTION"> + <shadow type="math_angle"> + <field name="NUM">90</field> + </shadow> + </value> + </block> + <block type="motion_pointtowards"> + <value name="TOWARDS"> + <shadow type="motion_pointtowards_menu"> + </shadow> + </value> + </block> + <block type="motion_gotoxy"> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="motion_goto"> + <value name="TO"> + <shadow type="motion_goto_menu"> + </shadow> + </value> + </block> + <block type="motion_glidesecstoxy"> + <value name="SECS"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="motion_changexby"> + <value name="DX"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_setx"> + <value name="X"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="motion_changeyby"> + <value name="DY"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="motion_sety"> + <value name="Y"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="motion_ifonedgebounce"></block> + <block type="motion_setrotationstyle"> + <value name="STYLE"> + <shadow type="motion_setrotationstyle_menu"></shadow> + </value> + </block> + <block type="motion_xposition"></block> + <block type="motion_yposition"></block> + <block type="motion_direction"></block> + </category> + <category name="Looks" colour="#9966FF"> + <block type="looks_sayforsecs"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hello!</field> + </shadow> + </value> + <value name="SECS"> + <shadow type="math_number"> + <field name="NUM">2</field> + </shadow> + </value> + </block> + <block type="looks_say"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hello!</field> + </shadow> + </value> + </block> + <block type="looks_thinkforsecs"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hmm...</field> + </shadow> + </value> + <value name="SECS"> + <shadow type="math_number"> + <field name="NUM">2</field> + </shadow> + </value> + </block> + <block type="looks_think"> + <value name="MESSAGE"> + <shadow type="text"> + <field name="TEXT">Hmm...</field> + </shadow> + </value> + </block> + <block type="looks_show"></block> + <block type="looks_hide"></block> + <block type="looks_switchcostumeto"> + <value name="COSTUME"> + <shadow type="looks_costume"></shadow> + </value> + </block> + <block type="looks_nextcostume"></block> + <block type="looks_nextbackdrop"></block> + <block type="looks_switchbackdropto"> + <value name="COSTUME"> + <shadow type="looks_backdrops"></shadow> + </value> + </block> + <block type="looks_switchbackdroptoandwait"> + <value name="COSTUME"> + <shadow type="looks_backdrops"></shadow> + </value> + </block> + <block type="looks_changeeffectby"> + <value name="EFFECT"> + <shadow type="looks_effectmenu"></shadow> + </value> + <value name="CHANGE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_seteffectto"> + <value name="EFFECT"> + <shadow type="looks_effectmenu"></shadow> + </value> + <value name="VALUE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_cleargraphiceffects"></block> + <block type="looks_changesizeby"> + <value name="CHANGE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="looks_setsizeto"> + <value name="SIZE"> + <shadow type="math_number"> + <field name="NUM">100</field> + </shadow> + </value> + </block> + <block type="looks_gotofront"></block> + <block type="looks_gobacklayers"> + <value name="NUM"> + <shadow type="math_integer"> + <field name="NUM">1</field> + </shadow> + </value> + </block> + <block type="looks_costumeorder"></block> + <block type="looks_backdroporder"></block> + <block type="looks_backdropname"></block> + <block type="looks_size"></block> + </category> + <category name="Sound" colour="#D65CD6"> + <block type="sound_play"> + <value name="SOUND_MENU"> + <shadow type="sound_sounds_option"></shadow> + </value> + </block> + <block type="sound_playuntildone"> + <value name="SOUND_MENU"> + <shadow type="sound_sounds_option"></shadow> + </value> + </block> + <block type="sound_stopallsounds"></block> + <block type="sound_playdrumforbeats"> + <value name="DRUMTYPE"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="BEATS"> + <shadow type="math_number"> + <field name="NUM">0.25</field> + </shadow> + </value> + </block> + <block type="sound_restforbeats"> + <value name="BEATS"> + <shadow type="math_number"> + <field name="NUM">0.25</field> + </shadow> + </value> + </block> + <block type="sound_playnoteforbeats"> + <value name="NOTE"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="BEATS"> + <shadow type="math_number"> + <field name="NUM">0.5</field> + </shadow> + </value> + </block> + <block type="sound_setinstrumentto"> + <value name="INSTRUMENT"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + </block> + <block type="sound_changevolumeby"> + <value name="VOLUME"> + <shadow type="math_number"> + <field name="NUM">-10</field> + </shadow> + </value> + </block> + <block type="sound_setvolumeto"> + <value name="VOLUME"> + <shadow type="math_number"> + <field name="NUM">100</field> + </shadow> + </value> + </block> + <block type="sound_volume"></block> + <block type="sound_changetempoby"> + <value name="TEMPO"> + <shadow type="math_number"> + <field name="NUM">20</field> + </shadow> + </value> + </block> + <block type="sound_settempotobpm"> + <value name="TEMPO"> + <shadow type="math_number"> + <field name="NUM">60</field> + </shadow> + </value> + </block> + <block type="sound_tempo"></block> + </category> + <category name="Pen" colour="#00B295"> + <block type="pen_clear"></block> + <block type="pen_stamp"></block> + <block type="pen_pendown"></block> + <block type="pen_penup"></block> + <block type="pen_setpencolortocolor"> + <value name="COLOR"> + <shadow type="colour_picker"> + </shadow> + </value> + </block> + <block type="pen_changepencolorby"> + <value name="COLOR"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="pen_setpencolortonum"> + <value name="COLOR"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="pen_changepenshadeby"> + <value name="SHADE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="pen_setpenshadeto"> + <value name="SHADE"> + <shadow type="math_number"> + <field name="NUM">50</field> + </shadow> + </value> + </block> + <block type="pen_changepensizeby"> + <value name="SIZE"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + </block> + <block type="pen_setpensizeto"> + <value name="SIZE"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + </block> + </category> + <category name="Data" colour="#FF8C1A"> + <block type="data_variable"> + <value name="VARIABLE"> + <shadow type="data_variablemenu"></shadow> + </value> + </block> + <block type="data_setvariableto"> + <value name="VARIABLE"> + <shadow type="data_variablemenu"></shadow> + </value> + <value name="VALUE"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="data_changevariableby"> + <value name="VARIABLE"> + <shadow type="data_variablemenu"></shadow> + </value> + <value name="VALUE"> + <shadow type="math_number"> + <field name="NUM">0</field> + </shadow> + </value> + </block> + <block type="data_showvariable"> + <value name="VARIABLE"> + <shadow type="data_variablemenu"></shadow> + </value> + </block> + <block type="data_hidevariable"> + <value name="VARIABLE"> + <shadow type="data_variablemenu"></shadow> + </value> + </block> + </category> + <category name="Events" colour="#FFD500"> + <block type="event_whenflagclicked"></block> + <block type="event_whenkeypressed"> + <value name="KEY_OPTION"> + <shadow type="event_keyoptions"></shadow> + </value> + </block> + <block type="event_whenthisspriteclicked"></block> + <block type="event_whenbackdropswitchesto"> + <value name="BACKDROP"> + <shadow type="event_backdrops"></shadow> + </value> + </block> + <block type="event_whengreaterthan"> + <value name="WHENGREATERTHANMENU"> + <shadow type="event_whengreaterthanmenu"></shadow> + </value> + <value name="VALUE"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="event_whenbroadcastreceived"> + <value name="BROADCAST_OPTION"> + <shadow type="event_broadcast_menu"></shadow> + </value> + </block> + <block type="event_broadcast"> + <value name="BROADCAST_OPTION"> + <shadow type="event_broadcast_menu"></shadow> + </value> + </block> + <block type="event_broadcastandwait"> + <value name="BROADCAST_OPTION"> + <shadow type="event_broadcast_menu"></shadow> + </value> + </block> + </category> + <category name="Control" colour="#FFAB19"> + <block type="control_wait"> + <value name="DURATION"> + <shadow type="math_positive_number"> + <field name="NUM">1</field> + </shadow> + </value> + </block> + <block type="control_repeat"> + <value name="TIMES"> + <shadow type="math_whole_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="control_forever"></block> + <block type="control_if"></block> + <block type="control_if_else"></block> + <block type="control_wait_until"></block> + <block type="control_repeat_until"></block> + <block type="control_stop"> + <value name="STOP_OPTION"> + <shadow type="control_stop_menu"></shadow> + </value> + </block> + <block type="control_start_as_clone"></block> + <block type="control_create_clone_of"> + <value name="CLONE_OPTION"> + <shadow type="control_create_clone_of_menu"></shadow> + </value> + </block> + <block type="control_delete_this_clone"></block> + </category> + <category name="Sensing" colour="#4CBFE6"> + <block type="sensing_touchingobject"> + <value name="TOUCHINGOBJECTMENU"> + <shadow type="sensing_touchingobjectmenu"></shadow> + </value> + </block> + <block type="sensing_touchingcolor"> + <value name="COLOR"> + <shadow type="colour_picker"></shadow> + </value> + </block> + <block type="sensing_coloristouchingcolor"> + <value name="COLOR"> + <shadow type="colour_picker"></shadow> + </value> + <value name="COLOR2"> + <shadow type="colour_picker"></shadow> + </value> + </block> + <block type="sensing_distanceto"> + <value name="DISTANCETOMENU"> + <shadow type="sensing_distancetomenu"></shadow> + </value> + </block> + <block type="sensing_askandwait"> + <value name="QUESTION"> + <shadow type="text"> + <field name="TEXT">What's your name?</field> + </shadow> + </value> + </block> + <block type="sensing_answer"></block> + <block type="sensing_keypressed"> + <value name="KEY_OPTIONS"> + <shadow type="sensing_keyoptions"></shadow> + </value> + </block> + <block type="sensing_mousedown"></block> + <block type="sensing_mousex"></block> + <block type="sensing_mousey"></block> + <block type="sensing_loudness"></block> + <block type="sensing_videoon"> + <value name="VIDEOONMENU1"> + <shadow type="sensing_videoonmenuone"></shadow> + </value> + <value name="VIDEOONMENU2"> + <shadow type="sensing_videoonmenutwo"></shadow> + </value> + </block> + <block type="sensing_videotoggle"> + <value name="VIDEOTOGGLEMENU"> + <shadow type="sensing_videotogglemenu"></shadow> + </value> + </block> + <block type="sensing_setvideotransparency"> + <value name="TRANSPARENCY"> + <shadow type="math_number"> + <field name="NUM">50</field> + </shadow> + </value> + </block> + <block type="sensing_timer"></block> + <block type="sensing_resettimer"></block> + <block type="sensing_current"> + <value name="CURRENTMENU"> + <shadow type="sensing_currentmenu"></shadow> + </value> + </block> + <block type="sensing_dayssince2000"></block> + <block type="sensing_username"></block> + </category> + <category name="Operators" colour="#40BF4A"> + <block type="operator_add"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_subtract"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_multiply"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_divide"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_random"> + <value name="FROM"> + <shadow type="math_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="TO"> + <shadow type="math_number"> + <field name="NUM">10</field> + </shadow> + </value> + </block> + <block type="operator_lt"> + <value name="OPERAND1"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + <value name="OPERAND2"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + </block> + <block type="operator_equals"> + <value name="OPERAND1"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + <value name="OPERAND2"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + </block> + <block type="operator_gt"> + <value name="OPERAND1"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + <value name="OPERAND2"> + <shadow type="text"> + <field name="TEXT"></field> + </shadow> + </value> + </block> + <block type="operator_and"></block> + <block type="operator_or"></block> + <block type="operator_not"></block> + <block type="operator_join"> + <value name="STRING1"> + <shadow type="text"> + <field name="TEXT">hello</field> + </shadow> + </value> + <value name="STRING2"> + <shadow type="text"> + <field name="TEXT">world</field> + </shadow> + </value> + </block> + <block type="operator_letter_of"> + <value name="LETTER"> + <shadow type="math_whole_number"> + <field name="NUM">1</field> + </shadow> + </value> + <value name="STRING"> + <shadow type="text"> + <field name="TEXT">world</field> + </shadow> + </value> + </block> + <block type="operator_length"> + <value name="STRING"> + <shadow type="text"> + <field name="TEXT">world</field> + </shadow> + </value> + </block> + <block type="operator_mod"> + <value name="NUM1"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + <value name="NUM2"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_round"> + <value name="NUM"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + <block type="operator_mathop"> + <value name="OPERATOR"> + <shadow type="operator_mathop_menu"></shadow> + </value> + <value name="NUM"> + <shadow type="math_number"> + <field name="NUM"></field> + </shadow> + </value> + </block> + </category> + <category name="More Blocks" colour="#FF6680"></category> </xml> <!-- Syntax highlighter --> From c47a061edb3477ec9d7104ec56ab910174536231 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Mon, 8 Aug 2016 15:43:52 -0400 Subject: [PATCH 36/40] Updates for newly released scratch-render repo --- Makefile | 2 +- package.json | 4 ++-- playground/index.html | 2 +- src/index.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index e2a04c2b6..c3614f5ac 100644 --- a/Makefile +++ b/Makefile @@ -31,4 +31,4 @@ coverage: # ------------------------------------------------------------------------------ -.PHONY: build lint test coverage benchmark +.PHONY: build lint test coverage benchmark serve diff --git a/package.json b/package.json index 2b6d7335a..9f28cbd83 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,14 @@ "dependencies": { "htmlparser2": "3.9.0", "memoizee": "0.3.10", - "promise": "7.1.1", - "scratch-render-webgl": "git+https://github.com/LLK/scratch-render-webgl.git" + "promise": "7.1.1" }, "devDependencies": { "eslint": "2.7.0", "highlightjs": "8.7.0", "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", "tap": "5.7.1", "webpack": "1.13.0", "webpack-dev-server": "1.14.1" diff --git a/playground/index.html b/playground/index.html index e38c10793..bcfb4426f 100644 --- a/playground/index.html +++ b/playground/index.html @@ -735,7 +735,7 @@ <script src="../node_modules/scratch-blocks/blocks_compressed_vertical.js"></script> <script src="../node_modules/scratch-blocks/msg/messages.js"></script> <!-- Renderer --> - <script src="../node_modules/scratch-render-webgl/build/render-webgl.js"></script> + <script src="../node_modules/scratch-render/render.js"></script> <!-- VM Worker --> <script src="../vm.worker.js"></script> <!-- Playground --> diff --git a/src/index.js b/src/index.js index 965c8cd9a..db99f1d15 100644 --- a/src/index.js +++ b/src/index.js @@ -108,7 +108,7 @@ VirtualMachine.prototype.animationFrame = function () { */ if (ENV_WORKER) { self.importScripts( - './node_modules/scratch-render-webgl/build/render-webgl-worker.js' + './node_modules/scratch-render/render-worker.js' ); self.renderer = new self.RenderWebGLWorker(); self.vmInstance = new VirtualMachine(); From 0b826c0dc97fb060c08847c208256ad566ee70b6 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Mon, 8 Aug 2016 15:44:55 -0400 Subject: [PATCH 37/40] Remove unused Promise in scratch3_operators --- src/blocks/scratch3_operators.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js index d3a7f1921..6ddae6e9a 100644 --- a/src/blocks/scratch3_operators.js +++ b/src/blocks/scratch3_operators.js @@ -1,5 +1,3 @@ -var Promise = require('promise'); - function Scratch3OperatorsBlocks(runtime) { /** * The runtime instantiating this block package. From a6ad34f0029f6c8abde9c482e3f13d7b68790bfc Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Mon, 8 Aug 2016 16:44:48 -0400 Subject: [PATCH 38/40] Add primitive for `math_angle`, fixing point in direction --- src/blocks/scratch3_operators.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js index 6ddae6e9a..fa43950ae 100644 --- a/src/blocks/scratch3_operators.js +++ b/src/blocks/scratch3_operators.js @@ -15,6 +15,7 @@ Scratch3OperatorsBlocks.prototype.getPrimitives = function() { 'math_number': this.number, 'math_positive_number': this.number, 'math_whole_number': this.number, + 'math_angle': this.number, 'text': this.text, 'operator_add': this.add, 'operator_subtract': this.subtract, From 67c3ceff86623cc56f918f93dc356e4865546970 Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Mon, 8 Aug 2016 16:47:52 -0400 Subject: [PATCH 39/40] Update clone's scale to match renderer update --- src/sprites/clone.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sprites/clone.js b/src/sprites/clone.js index 6a0f22fee..aa168291c 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -129,7 +129,7 @@ Clone.prototype.setSize = function (size) { // Keep size between 5% and 535%. this.size = MathUtil.clamp(size, 5, 535); self.renderer.updateDrawableProperties(this.drawableID, { - scale: this.size + scale: [this.size, this.size] }); }; From 2c031d87f6a24d54c9b107538f5fdbdcad895d0b Mon Sep 17 00:00:00 2001 From: Tim Mickel <tim.mickel@gmail.com> Date: Mon, 8 Aug 2016 18:29:44 -0400 Subject: [PATCH 40/40] Make renderer/self nullable in the Clone Fixes a test issue where renderer is not necessarily defined. --- src/sprites/clone.js | 73 +++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/src/sprites/clone.js b/src/sprites/clone.js index aa168291c..8370ac73e 100644 --- a/src/sprites/clone.js +++ b/src/sprites/clone.js @@ -9,20 +9,37 @@ var Target = require('../engine/target'); */ function Clone(spriteBlocks) { Target.call(this, spriteBlocks); + /** + * Reference to the global renderer for this VM, if one exists. + * @type {?RenderWebGLWorker} + */ + this.renderer = null; + // If this is not true, there is no renderer (e.g., running in a test env). + if (typeof self !== 'undefined' && self.renderer) { + // Pull from `self.renderer`. + this.renderer = self.renderer; + } + /** + * ID of the drawable for this clone returned by the renderer, if rendered. + * @type {?Number} + */ this.drawableID = null; + this.initDrawable(); } util.inherits(Clone, Target); /** - * Create a clone's drawable with the renderer. + * Create a clone's drawable with the this.renderer. */ Clone.prototype.initDrawable = function () { - var createPromise = self.renderer.createDrawable(); - var instance = this; - createPromise.then(function (id) { - instance.drawableID = id; - }); + if (this.renderer) { + var createPromise = this.renderer.createDrawable(); + var instance = this; + createPromise.then(function (id) { + instance.drawableID = id; + }); + } }; // Clone-level properties. @@ -79,9 +96,11 @@ Clone.prototype.effects = { Clone.prototype.setXY = function (x, y) { this.x = x; this.y = y; - self.renderer.updateDrawableProperties(this.drawableID, { - position: [this.x, this.y] - }); + if (this.renderer) { + this.renderer.updateDrawableProperties(this.drawableID, { + position: [this.x, this.y] + }); + } }; /** @@ -91,9 +110,11 @@ Clone.prototype.setXY = function (x, y) { Clone.prototype.setDirection = function (direction) { // Keep direction between -179 and +180. this.direction = MathUtil.wrapClamp(direction, -179, 180); - self.renderer.updateDrawableProperties(this.drawableID, { - direction: this.direction - }); + if (this.renderer) { + this.renderer.updateDrawableProperties(this.drawableID, { + direction: this.direction + }); + } }; /** @@ -116,9 +137,11 @@ Clone.prototype.setSay = function (type, message) { */ Clone.prototype.setVisible = function (visible) { this.visible = visible; - self.renderer.updateDrawableProperties(this.drawableID, { - visible: this.visible - }); + if (this.renderer) { + this.renderer.updateDrawableProperties(this.drawableID, { + visible: this.visible + }); + } }; /** @@ -128,9 +151,11 @@ Clone.prototype.setVisible = function (visible) { Clone.prototype.setSize = function (size) { // Keep size between 5% and 535%. this.size = MathUtil.clamp(size, 5, 535); - self.renderer.updateDrawableProperties(this.drawableID, { - scale: [this.size, this.size] - }); + if (this.renderer) { + this.renderer.updateDrawableProperties(this.drawableID, { + scale: [this.size, this.size] + }); + } }; /** @@ -140,9 +165,11 @@ Clone.prototype.setSize = function (size) { */ Clone.prototype.setEffect = function (effectName, value) { this.effects[effectName] = value; - var props = {}; - props[effectName] = this.effects[effectName]; - self.renderer.updateDrawableProperties(this.drawableID, props); + if (this.renderer) { + var props = {}; + props[effectName] = this.effects[effectName]; + this.renderer.updateDrawableProperties(this.drawableID, props); + } }; /** @@ -152,7 +179,9 @@ Clone.prototype.clearEffects = function () { for (var effectName in this.effects) { this.effects[effectName] = 0; } - self.renderer.updateDrawableProperties(this.drawableID, this.effects); + if (this.renderer) { + this.renderer.updateDrawableProperties(this.drawableID, this.effects); + } }; module.exports = Clone;