mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Merge pull request #1468 from gnarf/serialization-util
export deserializeBlocks method
This commit is contained in:
commit
6f9367a8e2
2 changed files with 85 additions and 25 deletions
|
@ -18,6 +18,8 @@ const {loadCostume} = require('../import/load-costume.js');
|
||||||
const {loadSound} = require('../import/load-sound.js');
|
const {loadSound} = require('../import/load-sound.js');
|
||||||
const {deserializeCostume, deserializeSound} = require('./deserialize-assets.js');
|
const {deserializeCostume, deserializeSound} = require('./deserialize-assets.js');
|
||||||
|
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} ImportedProject
|
* @typedef {object} ImportedProject
|
||||||
* @property {Array.<Target>} targets - the imported Scratch 3.0 target objects.
|
* @property {Array.<Target>} targets - the imported Scratch 3.0 target objects.
|
||||||
|
@ -99,7 +101,7 @@ const primitiveOpcodeInfoMap = {
|
||||||
const serializePrimitiveBlock = function (block) {
|
const serializePrimitiveBlock = function (block) {
|
||||||
// Returns an array represeting a primitive block or null if not one of
|
// Returns an array represeting a primitive block or null if not one of
|
||||||
// the primitive types above
|
// the primitive types above
|
||||||
if (primitiveOpcodeInfoMap.hasOwnProperty(block.opcode)) {
|
if (hasOwnProperty.call(primitiveOpcodeInfoMap, block.opcode)) {
|
||||||
const primitiveInfo = primitiveOpcodeInfoMap[block.opcode];
|
const primitiveInfo = primitiveOpcodeInfoMap[block.opcode];
|
||||||
const primitiveConstant = primitiveInfo[0];
|
const primitiveConstant = primitiveInfo[0];
|
||||||
const fieldName = primitiveInfo[1];
|
const fieldName = primitiveInfo[1];
|
||||||
|
@ -132,7 +134,7 @@ const serializePrimitiveBlock = function (block) {
|
||||||
const serializeInputs = function (inputs) {
|
const serializeInputs = function (inputs) {
|
||||||
const obj = Object.create(null);
|
const obj = Object.create(null);
|
||||||
for (const inputName in inputs) {
|
for (const inputName in inputs) {
|
||||||
if (!inputs.hasOwnProperty(inputName)) continue;
|
if (!hasOwnProperty.call(inputs, inputName)) continue;
|
||||||
// if block and shadow refer to the same block, only serialize one
|
// if block and shadow refer to the same block, only serialize one
|
||||||
if (inputs[inputName].block === inputs[inputName].shadow) {
|
if (inputs[inputName].block === inputs[inputName].shadow) {
|
||||||
// has block and shadow, and they are the same
|
// has block and shadow, and they are the same
|
||||||
|
@ -166,7 +168,7 @@ const serializeInputs = function (inputs) {
|
||||||
const serializeFields = function (fields) {
|
const serializeFields = function (fields) {
|
||||||
const obj = Object.create(null);
|
const obj = Object.create(null);
|
||||||
for (const fieldName in fields) {
|
for (const fieldName in fields) {
|
||||||
if (!fields.hasOwnProperty(fieldName)) continue;
|
if (!hasOwnProperty.call(fields, fieldName)) continue;
|
||||||
obj[fieldName] = [fields[fieldName].value];
|
obj[fieldName] = [fields[fieldName].value];
|
||||||
if (fields[fieldName].hasOwnProperty('id')) {
|
if (fields[fieldName].hasOwnProperty('id')) {
|
||||||
obj[fieldName].push(fields[fieldName].id);
|
obj[fieldName].push(fields[fieldName].id);
|
||||||
|
@ -688,8 +690,10 @@ const deserializeInputs = function (inputs, parentId, blocks) {
|
||||||
// because we call prototype functions later in the vm
|
// because we call prototype functions later in the vm
|
||||||
const obj = {};
|
const obj = {};
|
||||||
for (const inputName in inputs) {
|
for (const inputName in inputs) {
|
||||||
if (!inputs.hasOwnProperty(inputName)) continue;
|
if (!hasOwnProperty.call(inputs, inputName)) continue;
|
||||||
const inputDescArr = inputs[inputName];
|
const inputDescArr = inputs[inputName];
|
||||||
|
// If this block has already been deserialized (it's not an array) skip it
|
||||||
|
if (!Array.isArray(inputDescArr)) continue;
|
||||||
let block = null;
|
let block = null;
|
||||||
let shadow = null;
|
let shadow = null;
|
||||||
const blockShadowInfo = inputDescArr[0];
|
const blockShadowInfo = inputDescArr[0];
|
||||||
|
@ -721,8 +725,10 @@ const deserializeFields = function (fields) {
|
||||||
// because we call prototype functions later in the vm
|
// because we call prototype functions later in the vm
|
||||||
const obj = {};
|
const obj = {};
|
||||||
for (const fieldName in fields) {
|
for (const fieldName in fields) {
|
||||||
if (!fields.hasOwnProperty(fieldName)) continue;
|
if (!hasOwnProperty.call(fields, fieldName)) continue;
|
||||||
const fieldDescArr = fields[fieldName];
|
const fieldDescArr = fields[fieldName];
|
||||||
|
// If this block has already been deserialized (it's not an array) skip it
|
||||||
|
if (!Array.isArray(fieldDescArr)) continue;
|
||||||
obj[fieldName] = {
|
obj[fieldName] = {
|
||||||
name: fieldName,
|
name: fieldName,
|
||||||
value: fieldDescArr[0]
|
value: fieldDescArr[0]
|
||||||
|
@ -741,6 +747,37 @@ const deserializeFields = function (fields) {
|
||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Covnert serialized INPUT and FIELD primitives back to hydrated block templates.
|
||||||
|
* Should be able to deserialize a format that has already been deserialized. The only
|
||||||
|
* "east" path to adding new targets/code requires going through deserialize, so it should
|
||||||
|
* work with pre-parsed deserialized blocks.
|
||||||
|
*
|
||||||
|
* @param {object} blocks Serialized SB3 "blocks" property of a target. Will be mutated.
|
||||||
|
* @return {object} input is modified and returned
|
||||||
|
*/
|
||||||
|
const deserializeBlocks = function (blocks) {
|
||||||
|
for (const blockId in blocks) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(blocks, blockId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const block = blocks[blockId];
|
||||||
|
if (Array.isArray(block)) {
|
||||||
|
// this is one of the primitives
|
||||||
|
// delete the old entry in object.blocks and replace it w/the
|
||||||
|
// deserialized object
|
||||||
|
delete block[blockId];
|
||||||
|
deserializeInputDesc(block, null, false, blocks);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
block.id = blockId; // add id back to block since it wasn't serialized
|
||||||
|
block.inputs = deserializeInputs(block.inputs, blockId, blocks);
|
||||||
|
block.fields = deserializeFields(block.fields);
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
* @param {!object} object From-JSON "Scratch object:" sprite, stage, watcher.
|
* @param {!object} object From-JSON "Scratch object:" sprite, stage, watcher.
|
||||||
|
@ -766,25 +803,7 @@ const parseScratchObject = function (object, runtime, extensions, zip) {
|
||||||
sprite.name = object.name;
|
sprite.name = object.name;
|
||||||
}
|
}
|
||||||
if (object.hasOwnProperty('blocks')) {
|
if (object.hasOwnProperty('blocks')) {
|
||||||
for (const blockId in object.blocks) {
|
deserializeBlocks(object.blocks);
|
||||||
if (!object.blocks.hasOwnProperty(blockId)) continue;
|
|
||||||
const blockJSON = object.blocks[blockId];
|
|
||||||
if (Array.isArray(blockJSON)) {
|
|
||||||
// this is one of the primitives
|
|
||||||
// delete the old entry in object.blocks and replace it w/the
|
|
||||||
// deserialized object
|
|
||||||
delete object.blocks[blockId];
|
|
||||||
deserializeInputDesc(blockJSON, null, false, object.blocks);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
blockJSON.id = blockId; // add id back to block since it wasn't serialized
|
|
||||||
const serializedInputs = blockJSON.inputs;
|
|
||||||
const deserializedInputs = deserializeInputs(serializedInputs, blockId, object.blocks);
|
|
||||||
blockJSON.inputs = deserializedInputs;
|
|
||||||
const serializedFields = blockJSON.fields;
|
|
||||||
const deserializedFields = deserializeFields(serializedFields);
|
|
||||||
blockJSON.fields = deserializedFields;
|
|
||||||
}
|
|
||||||
// Take a second pass to create objects and add extensions
|
// Take a second pass to create objects and add extensions
|
||||||
for (const blockId in object.blocks) {
|
for (const blockId in object.blocks) {
|
||||||
if (!object.blocks.hasOwnProperty(blockId)) continue;
|
if (!object.blocks.hasOwnProperty(blockId)) continue;
|
||||||
|
@ -1008,5 +1027,7 @@ const deserialize = function (json, runtime, zip, isSingleSprite) {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
serialize: serialize,
|
serialize: serialize,
|
||||||
deserialize: deserialize
|
deserialize: deserialize,
|
||||||
|
deserializeBlocks: deserializeBlocks,
|
||||||
|
serializeBlocks: serializeBlocks
|
||||||
};
|
};
|
||||||
|
|
|
@ -182,3 +182,42 @@ test('serialize sb3 preserves sprite layer order', t => {
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('serializeBlocks', t => {
|
||||||
|
const vm = new VirtualMachine();
|
||||||
|
vm.loadProject(readFileToBuffer(commentsSB3ProjectPath))
|
||||||
|
.then(() => {
|
||||||
|
const blocks = vm.runtime.targets[1].blocks._blocks;
|
||||||
|
const result = sb3.serializeBlocks(blocks);
|
||||||
|
// @todo Analyze
|
||||||
|
t.type(result[0], 'object');
|
||||||
|
t.ok(Object.keys(result[0]).length < Object.keys(blocks).length, 'less blocks in serialized format');
|
||||||
|
t.ok(Array.isArray(result[1]));
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deserializeBlocks', t => {
|
||||||
|
const vm = new VirtualMachine();
|
||||||
|
vm.loadProject(readFileToBuffer(commentsSB3ProjectPath))
|
||||||
|
.then(() => {
|
||||||
|
const blocks = vm.runtime.targets[1].blocks._blocks;
|
||||||
|
const serialized = sb3.serializeBlocks(blocks)[0];
|
||||||
|
const deserialized = sb3.deserializeBlocks(serialized);
|
||||||
|
t.equal(Object.keys(deserialized).length, Object.keys(blocks).length, 'same number of blocks');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deserializeBlocks on already deserialized input', t => {
|
||||||
|
const vm = new VirtualMachine();
|
||||||
|
vm.loadProject(readFileToBuffer(commentsSB3ProjectPath))
|
||||||
|
.then(() => {
|
||||||
|
const blocks = vm.runtime.targets[1].blocks._blocks;
|
||||||
|
const serialized = sb3.serializeBlocks(blocks)[0];
|
||||||
|
const deserialized = sb3.deserializeBlocks(serialized);
|
||||||
|
const deserializedAgain = sb3.deserializeBlocks(deserialized);
|
||||||
|
t.deepEqual(deserialized, deserializedAgain, 'no change from second pass of deserialize');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in a new issue