mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 23:12:24 -05:00
Import monitors from sb2 files.
Paired with @kchadha on all of this.
This commit is contained in:
parent
784705d46e
commit
4713f47fb7
8 changed files with 196 additions and 22 deletions
|
@ -258,9 +258,17 @@ class Scratch3LooksBlocks {
|
||||||
|
|
||||||
getMonitored () {
|
getMonitored () {
|
||||||
return {
|
return {
|
||||||
looks_size: {isSpriteSpecific: true},
|
looks_size: {
|
||||||
looks_costumenumbername: {isSpriteSpecific: true},
|
isSpriteSpecific: true,
|
||||||
looks_backdropnumbername: {}
|
getId: targetId => `${targetId}_size`
|
||||||
|
},
|
||||||
|
looks_costumenumbername: {
|
||||||
|
isSpriteSpecific: true,
|
||||||
|
getId: targetId => `${targetId}_costumenumbername`
|
||||||
|
},
|
||||||
|
looks_backdropnumbername: {
|
||||||
|
getId: () => 'backdropnumbername'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,9 +46,18 @@ class Scratch3MotionBlocks {
|
||||||
|
|
||||||
getMonitored () {
|
getMonitored () {
|
||||||
return {
|
return {
|
||||||
motion_xposition: {isSpriteSpecific: true},
|
motion_xposition: {
|
||||||
motion_yposition: {isSpriteSpecific: true},
|
isSpriteSpecific: true,
|
||||||
motion_direction: {isSpriteSpecific: true}
|
getId: targetId => `${targetId}_xposition`
|
||||||
|
},
|
||||||
|
motion_yposition: {
|
||||||
|
isSpriteSpecific: true,
|
||||||
|
getId: targetId => `${targetId}_yposition`
|
||||||
|
},
|
||||||
|
motion_direction: {
|
||||||
|
isSpriteSpecific: true,
|
||||||
|
getId: targetId => `${targetId}_direction`
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,10 +74,18 @@ class Scratch3SensingBlocks {
|
||||||
|
|
||||||
getMonitored () {
|
getMonitored () {
|
||||||
return {
|
return {
|
||||||
sensing_answer: {},
|
sensing_answer: {
|
||||||
sensing_loudness: {},
|
getId: () => 'answer'
|
||||||
sensing_timer: {},
|
},
|
||||||
sensing_current: {}
|
sensing_loudness: {
|
||||||
|
getId: () => 'loudness'
|
||||||
|
},
|
||||||
|
sensing_timer: {
|
||||||
|
getId: () => 'timer'
|
||||||
|
},
|
||||||
|
sensing_current: {
|
||||||
|
getId: (_, param) => `current_${param}`
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,9 @@ class Scratch3SoundBlocks {
|
||||||
|
|
||||||
getMonitored () {
|
getMonitored () {
|
||||||
return {
|
return {
|
||||||
sound_volume: {}
|
sound_volume: {
|
||||||
|
getId: () => 'volume'
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -441,22 +441,34 @@ class Blocks {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSpriteSpecific = optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) &&
|
// Variable blocks may be sprite specific depending on the owner of the variable
|
||||||
optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific;
|
let isSpriteLocalVariable = false;
|
||||||
|
if (block.opcode === 'data_variable') {
|
||||||
|
isSpriteLocalVariable = !optRuntime.getEditingTarget().isStage &&
|
||||||
|
optRuntime.getEditingTarget().variables[block.fields.VARIABLE.id];
|
||||||
|
} else if (block.opcode === 'data_listcontents') {
|
||||||
|
isSpriteLocalVariable = !optRuntime.getEditingTarget().isStage &&
|
||||||
|
optRuntime.getEditingTarget().variables[block.fields.LIST.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const isSpriteSpecific = isSpriteLocalVariable ||
|
||||||
|
(optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) &&
|
||||||
|
optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific);
|
||||||
block.targetId = isSpriteSpecific ? optRuntime.getEditingTarget().id : null;
|
block.targetId = isSpriteSpecific ? optRuntime.getEditingTarget().id : null;
|
||||||
|
|
||||||
if (wasMonitored && !block.isMonitored) {
|
if (wasMonitored && !block.isMonitored) {
|
||||||
optRuntime.requestRemoveMonitor(block.id);
|
optRuntime.requestRemoveMonitor(block.id);
|
||||||
} else if (!wasMonitored && block.isMonitored) {
|
} else if (!wasMonitored && block.isMonitored) {
|
||||||
optRuntime.requestAddMonitor(MonitorRecord({
|
optRuntime.requestAddMonitor(MonitorRecord({
|
||||||
// @todo(vm#564) this will collide if multiple sprites use same block
|
|
||||||
id: block.id,
|
id: block.id,
|
||||||
targetId: block.targetId,
|
targetId: block.targetId,
|
||||||
spriteName: block.targetId ? optRuntime.getTargetById(block.targetId).getName() : null,
|
spriteName: block.targetId ? optRuntime.getTargetById(block.targetId).getName() : null,
|
||||||
opcode: block.opcode,
|
opcode: block.opcode,
|
||||||
params: this._getBlockParams(block),
|
params: this._getBlockParams(block),
|
||||||
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
||||||
value: ''
|
value: '',
|
||||||
|
mode: block.opcode === 'data_listcontents' ? 'list' : 'default'
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
const {Record} = require('immutable');
|
const {Record} = require('immutable');
|
||||||
|
|
||||||
const MonitorRecord = Record({
|
const MonitorRecord = Record({
|
||||||
id: null,
|
id: null, // Block Id
|
||||||
/** Present only if the monitor is sprite-specific, such as x position */
|
/** Present only if the monitor is sprite-specific, such as x position */
|
||||||
spriteName: null,
|
spriteName: null,
|
||||||
/** Present only if the monitor is sprite-specific, such as x position */
|
/** Present only if the monitor is sprite-specific, such as x position */
|
||||||
targetId: null,
|
targetId: null,
|
||||||
opcode: null,
|
opcode: null,
|
||||||
value: null,
|
value: null,
|
||||||
params: null
|
params: null,
|
||||||
|
mode: 1, // 1=default, 2=big, 3=slider
|
||||||
|
sliderMin: 0,
|
||||||
|
sliderMax: 100,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
visible: true
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = MonitorRecord;
|
module.exports = MonitorRecord;
|
||||||
|
|
|
@ -14,6 +14,7 @@ const uid = require('../util/uid');
|
||||||
const StringUtil = require('../util/string-util');
|
const StringUtil = require('../util/string-util');
|
||||||
const specMap = require('./sb2_specmap');
|
const specMap = require('./sb2_specmap');
|
||||||
const Variable = require('../engine/variable');
|
const Variable = require('../engine/variable');
|
||||||
|
const MonitorRecord = require('../engine/monitor-record');
|
||||||
|
|
||||||
const {loadCostume} = require('../import/load-costume.js');
|
const {loadCostume} = require('../import/load-costume.js');
|
||||||
const {loadSound} = require('../import/load-sound.js');
|
const {loadSound} = require('../import/load-sound.js');
|
||||||
|
@ -208,6 +209,104 @@ const globalBroadcastMsgStateGenerator = (function () {
|
||||||
};
|
};
|
||||||
}());
|
}());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a single monitor object and create all its in-memory VM objects.
|
||||||
|
* @param {!object} object - From-JSON "Scratch object"
|
||||||
|
* @param {!Runtime} runtime - (in/out) Runtime object to load monitor info into.
|
||||||
|
* @param {!Array.<Target>} targets - Targets have already been parsed.
|
||||||
|
* @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here.
|
||||||
|
*/
|
||||||
|
const parseMonitorObject = (object, runtime, targets, extensions) => {
|
||||||
|
let target = null;
|
||||||
|
// List blocks don't come in with their target name set.
|
||||||
|
// Find the target by searching for a target with matching variable name/type.
|
||||||
|
if (!object.hasOwnProperty('target')) {
|
||||||
|
for (let i = 0; i < targets.length; i++) {
|
||||||
|
const currTarget = targets[i];
|
||||||
|
const listVariables = Object.keys(currTarget.variables).filter(key => {
|
||||||
|
const variable = currTarget.variables[key];
|
||||||
|
return variable.type === Variable.LIST_TYPE && variable.name === object.listName;
|
||||||
|
});
|
||||||
|
if (listVariables.length > 0) {
|
||||||
|
target = currTarget; // Keep this target for later use
|
||||||
|
object.target = currTarget.getName(); // Set target name to normalize with other monitors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a block for the monitor blocks container
|
||||||
|
target = target || targets.filter(t => t.getName() === object.target)[0];
|
||||||
|
if (!target) throw new Error('Cannot create monitor for target that cannot be found by name');
|
||||||
|
|
||||||
|
// Create var id getter to make block naming/parsing easier, variables already created.
|
||||||
|
const getVariableId = generateVariableIdGetter(target.id, false);
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
const block = parseBlock(
|
||||||
|
[object.cmd, object.param], // Scratch 2 monitor blocks only have one param.
|
||||||
|
null, // `addBroadcastMsg`, not needed for monitor blocks.
|
||||||
|
getVariableId,
|
||||||
|
extensions
|
||||||
|
);
|
||||||
|
|
||||||
|
let isSpriteLocalVariable;
|
||||||
|
if (object.cmd === 'getVar:' || object.cmd === 'contentsOfList:') {
|
||||||
|
// These monitors are sprite-specific if they are not targetting the stage.
|
||||||
|
isSpriteLocalVariable = object.target.isStage;
|
||||||
|
// Variable getters have special block IDs for the toolbox that match the variable ID.
|
||||||
|
block.id = getVariableId(object.param);
|
||||||
|
}
|
||||||
|
|
||||||
|
block.id = runtime.monitorBlockInfo.hasOwnProperty(block.opcode) ?
|
||||||
|
runtime.monitorBlockInfo[block.opcode].getId(target.id, object.param) : block.id;
|
||||||
|
|
||||||
|
// Block needs a targetId if it is sprite specific or a local variable.
|
||||||
|
// Consult the monitorBlockInfo in the runtime for sprite-specificity.
|
||||||
|
const isSpriteSpecific = isSpriteLocalVariable ||
|
||||||
|
(runtime.monitorBlockInfo.hasOwnProperty(block.opcode) &&
|
||||||
|
runtime.monitorBlockInfo[block.opcode].isSpriteSpecific);
|
||||||
|
block.targetId = isSpriteSpecific ? target.id : null;
|
||||||
|
|
||||||
|
// Property required for running monitored blocks.
|
||||||
|
block.isMonitored = object.visible;
|
||||||
|
|
||||||
|
// Blocks can be created with children, flatten and add to monitorBlocks.
|
||||||
|
const newBlocks = flatten([block]);
|
||||||
|
for (let i = 0; i < newBlocks.length; i++) {
|
||||||
|
runtime.monitorBlocks.createBlock(newBlocks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert numbered mode into strings for better understandability.
|
||||||
|
switch (object.mode) {
|
||||||
|
case 1:
|
||||||
|
object.mode = 'default';
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
object.mode = 'large';
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
object.mode = 'slider';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a monitor record for the runtime's monitorState
|
||||||
|
runtime.requestAddMonitor(MonitorRecord({
|
||||||
|
id: block.id,
|
||||||
|
targetId: block.targetId,
|
||||||
|
spriteName: block.targetId ? object.target : null,
|
||||||
|
opcode: block.opcode,
|
||||||
|
params: runtime.monitorBlocks._getBlockParams(block),
|
||||||
|
value: '',
|
||||||
|
mode: object.mode,
|
||||||
|
sliderMin: object.sliderMin,
|
||||||
|
sliderMax: object.sliderMax,
|
||||||
|
x: object.x,
|
||||||
|
y: object.y,
|
||||||
|
width: object.width,
|
||||||
|
height: object.height,
|
||||||
|
visible: object.visible
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a single "Scratch object" and create all its in-memory VM objects.
|
* Parse a single "Scratch object" and create all its in-memory VM objects.
|
||||||
* TODO: parse the "info" section, especially "savedExtensions"
|
* TODO: parse the "info" section, especially "savedExtensions"
|
||||||
|
@ -220,10 +319,17 @@ const globalBroadcastMsgStateGenerator = (function () {
|
||||||
*/
|
*/
|
||||||
const parseScratchObject = function (object, runtime, extensions, topLevel, zip) {
|
const parseScratchObject = function (object, runtime, extensions, topLevel, zip) {
|
||||||
if (!object.hasOwnProperty('objName')) {
|
if (!object.hasOwnProperty('objName')) {
|
||||||
// Watcher/monitor - skip this object until those are implemented in VM.
|
if (object.hasOwnProperty('listName')) {
|
||||||
// @todo
|
// Shim these objects so they can be processed as monitors
|
||||||
return Promise.resolve(null);
|
object.cmd = 'contentsOfList:';
|
||||||
|
object.param = object.listName;
|
||||||
|
object.mode = 'list';
|
||||||
}
|
}
|
||||||
|
// Defer parsing monitors until targets are all parsed
|
||||||
|
object.deferredMonitor = true;
|
||||||
|
return Promise.resolve(object);
|
||||||
|
}
|
||||||
|
|
||||||
// Blocks container for this object.
|
// Blocks container for this object.
|
||||||
const blocks = new Blocks();
|
const blocks = new Blocks();
|
||||||
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
|
||||||
|
@ -332,7 +438,6 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
|
||||||
if (object.hasOwnProperty('lists')) {
|
if (object.hasOwnProperty('lists')) {
|
||||||
for (let k = 0; k < object.lists.length; k++) {
|
for (let k = 0; k < object.lists.length; k++) {
|
||||||
const list = object.lists[k];
|
const list = object.lists[k];
|
||||||
// @todo: monitor properties.
|
|
||||||
const newVariable = new Variable(
|
const newVariable = new Variable(
|
||||||
getVariableId(list.listName),
|
getVariableId(list.listName),
|
||||||
list.listName,
|
list.listName,
|
||||||
|
@ -455,9 +560,19 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let targets = [target];
|
let targets = [target];
|
||||||
|
const deferredMonitors = [];
|
||||||
for (let n = 0; n < children.length; n++) {
|
for (let n = 0; n < children.length; n++) {
|
||||||
|
if (children[n]) {
|
||||||
|
if (children[n].deferredMonitor) {
|
||||||
|
deferredMonitors.push(children[n]);
|
||||||
|
} else {
|
||||||
targets = targets.concat(children[n]);
|
targets = targets.concat(children[n]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let n = 0; n < deferredMonitors.length; n++) {
|
||||||
|
parseMonitorObject(deferredMonitors[n], runtime, targets, extensions);
|
||||||
|
}
|
||||||
return targets;
|
return targets;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -1370,6 +1370,18 @@ const specMap = {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
// Scratch 2 uses this alternative variable getter opcode only in monitors,
|
||||||
|
// blocks use the `readVariable` opcode above.
|
||||||
|
'getVar:': {
|
||||||
|
opcode: 'data_variable',
|
||||||
|
argMap: [
|
||||||
|
{
|
||||||
|
type: 'field',
|
||||||
|
fieldName: 'VARIABLE',
|
||||||
|
variableType: Variable.SCALAR_TYPE
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
'setVar:to:': {
|
'setVar:to:': {
|
||||||
opcode: 'data_setvariableto',
|
opcode: 'data_setvariableto',
|
||||||
argMap: [
|
argMap: [
|
||||||
|
|
Loading…
Reference in a new issue