mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -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 () {
|
||||
return {
|
||||
looks_size: {isSpriteSpecific: true},
|
||||
looks_costumenumbername: {isSpriteSpecific: true},
|
||||
looks_backdropnumbername: {}
|
||||
looks_size: {
|
||||
isSpriteSpecific: true,
|
||||
getId: targetId => `${targetId}_size`
|
||||
},
|
||||
looks_costumenumbername: {
|
||||
isSpriteSpecific: true,
|
||||
getId: targetId => `${targetId}_costumenumbername`
|
||||
},
|
||||
looks_backdropnumbername: {
|
||||
getId: () => 'backdropnumbername'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -46,9 +46,18 @@ class Scratch3MotionBlocks {
|
|||
|
||||
getMonitored () {
|
||||
return {
|
||||
motion_xposition: {isSpriteSpecific: true},
|
||||
motion_yposition: {isSpriteSpecific: true},
|
||||
motion_direction: {isSpriteSpecific: true}
|
||||
motion_xposition: {
|
||||
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 () {
|
||||
return {
|
||||
sensing_answer: {},
|
||||
sensing_loudness: {},
|
||||
sensing_timer: {},
|
||||
sensing_current: {}
|
||||
sensing_answer: {
|
||||
getId: () => 'answer'
|
||||
},
|
||||
sensing_loudness: {
|
||||
getId: () => 'loudness'
|
||||
},
|
||||
sensing_timer: {
|
||||
getId: () => 'timer'
|
||||
},
|
||||
sensing_current: {
|
||||
getId: (_, param) => `current_${param}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,9 @@ class Scratch3SoundBlocks {
|
|||
|
||||
getMonitored () {
|
||||
return {
|
||||
sound_volume: {}
|
||||
sound_volume: {
|
||||
getId: () => 'volume'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -441,22 +441,34 @@ class Blocks {
|
|||
break;
|
||||
}
|
||||
|
||||
const isSpriteSpecific = optRuntime.monitorBlockInfo.hasOwnProperty(block.opcode) &&
|
||||
optRuntime.monitorBlockInfo[block.opcode].isSpriteSpecific;
|
||||
// Variable blocks may be sprite specific depending on the owner of the variable
|
||||
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;
|
||||
|
||||
if (wasMonitored && !block.isMonitored) {
|
||||
optRuntime.requestRemoveMonitor(block.id);
|
||||
} else if (!wasMonitored && block.isMonitored) {
|
||||
optRuntime.requestAddMonitor(MonitorRecord({
|
||||
// @todo(vm#564) this will collide if multiple sprites use same block
|
||||
id: block.id,
|
||||
targetId: block.targetId,
|
||||
spriteName: block.targetId ? optRuntime.getTargetById(block.targetId).getName() : null,
|
||||
opcode: block.opcode,
|
||||
params: this._getBlockParams(block),
|
||||
// @todo(vm#565) for numerical values with decimals, some countries use comma
|
||||
value: ''
|
||||
value: '',
|
||||
mode: block.opcode === 'data_listcontents' ? 'list' : 'default'
|
||||
}));
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
const {Record} = require('immutable');
|
||||
|
||||
const MonitorRecord = Record({
|
||||
id: null,
|
||||
id: null, // Block Id
|
||||
/** Present only if the monitor is sprite-specific, such as x position */
|
||||
spriteName: null,
|
||||
/** Present only if the monitor is sprite-specific, such as x position */
|
||||
targetId: null,
|
||||
opcode: 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;
|
||||
|
|
|
@ -14,6 +14,7 @@ const uid = require('../util/uid');
|
|||
const StringUtil = require('../util/string-util');
|
||||
const specMap = require('./sb2_specmap');
|
||||
const Variable = require('../engine/variable');
|
||||
const MonitorRecord = require('../engine/monitor-record');
|
||||
|
||||
const {loadCostume} = require('../import/load-costume.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.
|
||||
* TODO: parse the "info" section, especially "savedExtensions"
|
||||
|
@ -220,10 +319,17 @@ const globalBroadcastMsgStateGenerator = (function () {
|
|||
*/
|
||||
const parseScratchObject = function (object, runtime, extensions, topLevel, zip) {
|
||||
if (!object.hasOwnProperty('objName')) {
|
||||
// Watcher/monitor - skip this object until those are implemented in VM.
|
||||
// @todo
|
||||
return Promise.resolve(null);
|
||||
if (object.hasOwnProperty('listName')) {
|
||||
// Shim these objects so they can be processed as monitors
|
||||
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.
|
||||
const blocks = new Blocks();
|
||||
// @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')) {
|
||||
for (let k = 0; k < object.lists.length; k++) {
|
||||
const list = object.lists[k];
|
||||
// @todo: monitor properties.
|
||||
const newVariable = new Variable(
|
||||
getVariableId(list.listName),
|
||||
list.listName,
|
||||
|
@ -455,8 +560,18 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
|
|||
}
|
||||
}
|
||||
let targets = [target];
|
||||
const deferredMonitors = [];
|
||||
for (let n = 0; n < children.length; n++) {
|
||||
targets = targets.concat(children[n]);
|
||||
if (children[n]) {
|
||||
if (children[n].deferredMonitor) {
|
||||
deferredMonitors.push(children[n]);
|
||||
} else {
|
||||
targets = targets.concat(children[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let n = 0; n < deferredMonitors.length; n++) {
|
||||
parseMonitorObject(deferredMonitors[n], runtime, targets, extensions);
|
||||
}
|
||||
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:': {
|
||||
opcode: 'data_setvariableto',
|
||||
argMap: [
|
||||
|
|
Loading…
Reference in a new issue