Serialize and deserialize monitors. Obtain monitor block information from runtime. Fix issue where stage monitors weren't getting loaded correctly.

This commit is contained in:
Karishma Chadha 2018-10-21 00:22:39 -04:00
parent 3050f496b2
commit 1a4f0a75f2
4 changed files with 87 additions and 4 deletions

View file

@ -282,8 +282,10 @@ class Blocks {
* runtime interface.
* @param {object} e Blockly "block" or "variable" event
* @param {?Runtime} optRuntime Optional runtime to forward click events to.
* @param {boolean=} forMonitors Whether the event being listened to pertains to a
* monitor block.
*/
blocklyListen (e, optRuntime) {
blocklyListen (e, optRuntime, forMonitors) {
// Validate event
if (typeof e !== 'object') return;
if (typeof e.blockId !== 'string' && typeof e.varId !== 'string' &&
@ -305,6 +307,21 @@ class Blocks {
switch (e.type) {
case 'create': {
const newBlocks = adapter(e);
if (forMonitors) {
// If this is the monitor block container,
// add the appropriate info to the monitorBlock looking it up
// in the runtime monitorState
const topLevelBlock = newBlocks[0];
if (optRuntime && optRuntime.getMonitorState().has(topLevelBlock.id)) {
const monitorData = optRuntime.getMonitorState().get(topLevelBlock.id);
topLevelBlock.isMonitored = monitorData.visible;
topLevelBlock.targetId = monitorData.targetId;
}
}
// A create event can create many blocks. Add them all.
for (let i = 0; i < newBlocks.length; i++) {
this.createBlock(newBlocks[i]);

View file

@ -659,6 +659,10 @@ class Runtime extends EventEmitter {
}
}
getMonitorState () {
return this._monitorState;
}
/**
* Generate an extension-specific menu ID.
* @param {string} menuName - the name of the menu.

View file

@ -9,6 +9,7 @@ const Blocks = require('../engine/blocks');
const Sprite = require('../sprites/sprite');
const Variable = require('../engine/variable');
const Comment = require('../engine/comment');
const MonitorRecord = require('../engine/monitor-record');
const StageLayering = require('../engine/stage-layering');
const log = require('../util/log');
const uid = require('../util/uid');
@ -480,6 +481,29 @@ const getSimplifiedLayerOrdering = function (targets) {
return MathUtil.reducedSortOrdering(layerOrders);
};
const serializeMonitors = function (monitors) {
return monitors.valueSeq().map(monitorData => {
const serializedMonitor = {
id: monitorData.id,
mode: monitorData.mode,
opcode: monitorData.opcode,
params: monitorData.params,
spriteName: monitorData.spriteName,
value: monitorData.value,
width: monitorData.width,
height: monitorData.height,
x: monitorData.x,
y: monitorData.y,
visible: monitorData.visible
};
if (monitorData.mode !== 'list') {
serializedMonitor.min = monitorData.sliderMin;
serializedMonitor.max = monitorData.sliderMax;
}
return serializedMonitor;
});
};
/**
* Serializes the specified VM runtime.
* @param {!Runtime} runtime VM runtime instance to be serialized.
@ -516,8 +540,7 @@ const serialize = function (runtime, targetId) {
obj.targets = serializedTargets;
// TODO Serialize monitors
obj.monitors = serializeMonitors(runtime.getMonitorState());
// Assemble extension list
obj.extensions = Array.from(extensions);
@ -1015,6 +1038,39 @@ const parseScratchObject = function (object, runtime, extensions, zip) {
return Promise.all(costumePromises.concat(soundPromises)).then(() => target);
};
const deserializeMonitor = function (monitorData, runtime, targets) {
const monitorBlockInfo = runtime.monitorBlockInfo[monitorData.opcode];
if (monitorData.spriteName) {
const filteredTargets = targets.filter(t => t.sprite.name === monitorData.spriteName);
if (!filteredTargets || filteredTargets.length !== 1) {
log.error(`Could not deserialize monitor ${monitorData.opcode} for sprite ${
monitorData.spriteName} because no such sprite could be found.`);
}
monitorData.targetId = filteredTargets[0].id;
}
if (monitorData.opcode !== 'data_variable' && monitorData.opcode !== 'data_listcontents') {
// Variables and lists already have their ID serialized in the monitorData,
// find the correct id for all other monitors. monitorBlockInfo.getId should
// ignore the given parameters if the monitor in question is not target specific.
monitorData.id = monitorBlockInfo.getId(
monitorData.targetId, Object.values(monitorData.params)[0]);
}
const existingMonitorBlock = runtime.monitorBlocks._blocks[monitorData.id];
if (existingMonitorBlock) {
// A monitor block already exists if the toolbox has been loaded and
// the monitor block is not target specific (because the block gets recycled).
// Update the existing block with the relevant monitor information.
existingMonitorBlock.isMonitored = monitorData.visible;
existingMonitorBlock.targetId = monitorData.targetId;
}
// Otherwise, the monitor block will get created when the toolbox updates
// after the target has been installed.
runtime.requestAddMonitor(MonitorRecord(monitorData));
};
/**
* Deserialize the specified representation of a VM runtime and loads it into the provided runtime instance.
* @param {object} json - JSON representation of a VM runtime.
@ -1037,6 +1093,8 @@ const deserialize = function (json, runtime, zip, isSingleSprite) {
.map((t, i) => Object.assign(t, {targetPaneOrder: i}))
.sort((a, b) => a.layerOrder - b.layerOrder);
const monitorObjects = json.monitors || [];
return Promise.all(
targetObjects.map(target =>
parseScratchObject(target, runtime, extensions, zip))
@ -1050,6 +1108,10 @@ const deserialize = function (json, runtime, zip, isSingleSprite) {
delete t.layerOrder;
return t;
}))
.then(targets => {
monitorObjects.map(monitorDesc => deserializeMonitor(monitorDesc, runtime, targets));
return targets;
})
.then(targets => ({
targets,
extensions

View file

@ -1060,7 +1060,7 @@ class VirtualMachine extends EventEmitter {
// 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);
this.runtime.monitorBlocks.blocklyListen(e, this.runtime, true);
}
}