diff --git a/package.json b/package.json index ed0e2d185..0308aa6f7 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,9 @@ "lodash.defaultsdeep": "4.6.0", "minilog": "3.1.0", "promise": "7.1.1", - "scratch-audio": "latest", - "scratch-blocks": "latest", - "scratch-render": "latest", + "scratch-audio": "^0.1.0-prerelease.0", + "scratch-blocks": "^0.1.0-prerelease.0", + "scratch-render": "^0.1.0-prerelease.0", "scratch-storage": "^0.1.0", "script-loader": "0.7.0", "stats.js": "^0.17.0", diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 0c90d1a64..9e4a4c9ed 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -194,7 +194,7 @@ class Blocks { // UI event: clicked scripts toggle in the runtime. if (e.element === 'stackclick') { if (optRuntime) { - optRuntime.toggleScript(e.blockId); + optRuntime.toggleScript(e.blockId, {showVisualReport: true}); } return; } @@ -273,16 +273,22 @@ class Blocks { */ changeBlock (args) { // Validate - if (args.element !== 'field' && args.element !== 'mutation') return; + if (['field', 'mutation', 'checkbox'].indexOf(args.element) === -1) return; const block = this._blocks[args.id]; if (typeof block === 'undefined') return; - if (args.element === 'field') { + switch (args.element) { + case 'field': // Update block value if (!block.fields[args.name]) return; block.fields[args.name].value = args.value; - } else if (args.element === 'mutation') { + break; + case 'mutation': block.mutation = mutationAdapter(args.value); + break; + case 'checkbox': + block.isMonitored = args.value; + break; } } @@ -342,6 +348,20 @@ class Blocks { } } + + /** + * Block management: run all blocks. + * @param {!object} runtime Runtime to run all blocks in. + */ + runAllMonitored (runtime) { + Object.keys(this._blocks).forEach(blockId => { + if (this.getBlock(blockId).isMonitored) { + // @todo handle specific targets (e.g. apple x position) + runtime.toggleScript(blockId); + } + }); + } + /** * Block management: delete blocks and their associated scripts. * @param {!object} e Blockly delete event to be processed. diff --git a/src/engine/execute.js b/src/engine/execute.js index 707859551..2b66c5e73 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -85,7 +85,7 @@ const execute = function (sequencer, thread) { } else { // In a non-hat, report the value visually if necessary if // at the top of the thread stack. - if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) { + if (typeof resolvedValue !== 'undefined' && thread.showVisualReport && thread.atStackTop()) { runtime.visualReport(currentBlockId, resolvedValue); } // Finished any yields. diff --git a/src/engine/runtime.js b/src/engine/runtime.js index f57a0e9f9..34c008a1e 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -52,6 +52,13 @@ class Runtime extends EventEmitter { */ this.flyoutBlocks = new Blocks(); + /** + * Storage container for monitor blocks. + * These will execute on a target maybe + * @type {!Blocks} + */ + this.monitorBlocks = new Blocks(); + /** * Currently known editing target for the VM. * @type {?Target} @@ -362,11 +369,13 @@ class Runtime extends EventEmitter { * Create a thread and push it to the list of threads. * @param {!string} id ID of block that starts the stack. * @param {!Target} target Target to run thread on. + * @param {?boolean} optShowVisualReport true if the script should show speech bubble for its value * @return {!Thread} The newly created thread. */ - _pushThread (id, target) { + _pushThread (id, target, optShowVisualReport) { const thread = new Thread(id); thread.target = target; + thread.showVisualReport = optShowVisualReport; thread.pushStack(id); this.threads.push(thread); return thread; @@ -416,8 +425,11 @@ class Runtime extends EventEmitter { /** * Toggle a script. * @param {!string} topBlockId ID of block that starts the script. + * @param {?object} opts optional arguments to toggle script + * @param {?string} opts.target target ID for target to run script on. If not supplied, uses editing target. + * @param {?boolean} opts.showVisualReport true if the speech bubble should pop up on the block, false if not. */ - toggleScript (topBlockId) { + toggleScript (topBlockId, opts) { // Remove any existing thread. for (let i = 0; i < this.threads.length; i++) { if (this.threads[i].topBlock === topBlockId) { @@ -426,7 +438,10 @@ class Runtime extends EventEmitter { } } // Otherwise add it. - this._pushThread(topBlockId, this._editingTarget); + this._pushThread( + topBlockId, + opts && opts.target ? opts.target : this._editingTarget, + opts ? opts.showVisualReport : false); } /** @@ -632,6 +647,7 @@ class Runtime extends EventEmitter { } } this.redrawRequested = false; + this._pushMonitors(); const doneThreads = this.sequencer.stepThreads(); this._updateGlows(doneThreads); this._setThreadCount(this.threads.length + doneThreads.length); @@ -641,6 +657,13 @@ class Runtime extends EventEmitter { } } + /** + * Queue monitor blocks to sequencer to be run. + */ + _pushMonitors () { + this.monitorBlocks.runAllMonitored(this); + } + /** * Set the current editing target known by the runtime. * @param {!Target} editingTarget New editing target. diff --git a/src/engine/target.js b/src/engine/target.js index d72937f65..c4f690aa9 100644 --- a/src/engine/target.js +++ b/src/engine/target.js @@ -20,7 +20,7 @@ class Target extends EventEmitter { super(); if (!blocks) { - blocks = new Blocks(this); + blocks = new Blocks(); } /** * A unique ID for this target. diff --git a/src/playground/playground.js b/src/playground/playground.js index fe06b4144..c1b655a58 100644 --- a/src/playground/playground.js +++ b/src/playground/playground.js @@ -92,6 +92,7 @@ window.onload = function () { workspace.addChangeListener(vm.blockListener); const flyoutWorkspace = workspace.getFlyout().getWorkspace(); flyoutWorkspace.addChangeListener(vm.flyoutBlockListener); + flyoutWorkspace.addChangeListener(vm.monitorBlockListener); // Create FPS counter. const stats = new window.Stats(); diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index 694d78612..1ffc00a1e 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -696,6 +696,7 @@ class RenderedTarget extends Target { newClone.effects = JSON.parse(JSON.stringify(this.effects)); newClone.variables = JSON.parse(JSON.stringify(this.variables)); newClone.lists = JSON.parse(JSON.stringify(this.lists)); + newClone._customState = JSON.parse(JSON.stringify(this._customState)); newClone.initDrawable(); newClone.updateAllDrawableProperties(); // Place behind the current target. diff --git a/src/virtual-machine.js b/src/virtual-machine.js index f85caf4b7..719bf6cc7 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -59,6 +59,7 @@ class VirtualMachine extends EventEmitter { this.blockListener = this.blockListener.bind(this); this.flyoutBlockListener = this.flyoutBlockListener.bind(this); + this.monitorBlockListener = this.monitorBlockListener.bind(this); } /** @@ -413,6 +414,18 @@ class VirtualMachine extends EventEmitter { this.runtime.flyoutBlocks.blocklyListen(e, this.runtime); } + /** + * Handle a Blockly event for the flyout to be passed to the monitor container. + * @param {!Blockly.Event} e Any Blockly event. + */ + monitorBlockListener (e) { + // Filter events by type, since monitor blocks only need to listen to these events. + // Monitor blocks shouldn't be destroyed when flyout blocks are deleted. + if (['create', 'change'].indexOf(e.type) !== -1) { + this.runtime.monitorBlocks.blocklyListen(e, this.runtime); + } + } + /** * Set an editing target. An editor UI can use this function to switch * between editing different targets, sprites, etc.