Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/engine/monitor-record.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const MonitorRecord = Record({
mode: 'default',
sliderMin: 0,
sliderMax: 100,
x: 0,
y: 0,
x: null, // (x: null, y: null) Indicates that the monitor should be auto-positioned
y: null,
width: 0,
height: 0,
visible: true
Expand Down
8 changes: 6 additions & 2 deletions src/engine/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -2098,9 +2102,9 @@ class Runtime extends EventEmitter {
const block = categoryInfo.blocks.find(b => b.info.opcode === opcode);
if (!block) return;

// TODO: should this use some other category? Also, we may want to format the label in a locale-specific way.
// TODO: we may want to format the label in a locale-specific way.
return {
category: 'data',
category: 'extension', // This assumes that all extensions have the same monitor color.
label: `${categoryInfo.name}: ${block.info.text}`
};
}
Expand Down
121 changes: 119 additions & 2 deletions src/serialization/sb3.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -1015,6 +1038,94 @@ const parseScratchObject = function (object, runtime, extensions, zip) {
return Promise.all(costumePromises.concat(soundPromises)).then(() => target);
};

const deserializeMonitor = function (monitorData, runtime, targets, extensions) {
// If the serialized monitor has spriteName defined, look up the sprite
// by name in the given list of targets and update the monitor's targetId
// to match the sprite's id.
if (monitorData.spriteName) {
const filteredTargets = targets.filter(t => t.sprite.name === monitorData.spriteName);
if (filteredTargets && filteredTargets.length > 0) {
monitorData.targetId = filteredTargets[0].id;
} else {
log.warn(`Tried to deserialize sprite specific monitor ${
monitorData.opcode} but could not find sprite ${monitorData.spriteName}.`);
}
}

// Get information about this monitor, if it exists, given the monitor's opcode.
// This will be undefined for extension blocks
const monitorBlockInfo = runtime.monitorBlockInfo[monitorData.opcode];

// Convert the serialized monitorData params into the block fields structure
const fields = {};
for (const paramKey in monitorData.params) {
const field = {
name: paramKey,
value: monitorData.params[paramKey]
};
fields[paramKey] = field;
}

// Variables, lists, and non-sprite-specific monitors, including any extension
// monitors should already have the correct monitor ID serialized in the monitorData,
// find the correct id for all other monitors.
if (monitorData.opcode !== 'data_variable' && monitorData.opcode !== 'data_listcontents' &&
monitorBlockInfo && monitorBlockInfo.isSpriteSpecific) {
monitorData.id = monitorBlockInfo.getId(
monitorData.targetId, fields);
}

// If the runtime already has a monitor block for this monitor's id,
// update the existing block with the relevant monitor information.
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).
existingMonitorBlock.isMonitored = monitorData.visible;
existingMonitorBlock.targetId = monitorData.targetId;
} else {
// If a monitor block doesn't already exist for this monitor,
// construct a monitor block to add to the monitor blocks container
const monitorBlock = {
id: monitorData.id,
opcode: monitorData.opcode,
inputs: {}, // Assuming that monitor blocks don't have droppable fields
fields: fields,
topLevel: true,
next: null,
parent: null,
shadow: false,
x: 0,
y: 0,
isMonitored: monitorData.visible,
targetId: monitorData.targetId
};

// Variables and lists have additional properties
// stored in their fields, update this info in the
// monitor block fields
if (monitorData.opcode === 'data_variable') {
const field = monitorBlock.fields.VARIABLE;
field.id = monitorData.id;
field.variableType = Variable.SCALAR_TYPE;
} else if (monitorData.opcode === 'data_listcontents') {
const field = monitorBlock.fields.LIST;
field.id = monitorData.id;
field.variableType = Variable.LIST_TYPE;
}

runtime.monitorBlocks.createBlock(monitorBlock);

// If the block is from an extension, record it.
const extensionID = getExtensionIdForOpcode(monitorBlock.opcode);
if (extensionID) {
extensions.extensionIDs.add(extensionID);
}
}

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.
Expand All @@ -1037,6 +1148,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))
Expand All @@ -1050,6 +1163,10 @@ const deserialize = function (json, runtime, zip, isSingleSprite) {
delete t.layerOrder;
return t;
}))
.then(targets => {
monitorObjects.map(monitorDesc => deserializeMonitor(monitorDesc, runtime, targets, extensions));
return targets;
})
.then(targets => ({
targets,
extensions
Expand Down
Binary file added test/fixtures/monitors.sb3
Binary file not shown.
File renamed without changes.
Loading