scratch-vm/src/serialization/sb3.js

172 lines
5.8 KiB
JavaScript
Raw Normal View History

2016-12-30 10:19:58 -05:00
/**
* @fileoverview
* Partial implementation of a SB3 serializer and deserializer. Parses provided
* JSON and then generates all needed scratch-vm runtime structures.
*/
2017-04-26 16:50:53 -04:00
const vmPackage = require('../../package.json');
const Blocks = require('../engine/blocks');
const Sprite = require('../sprites/sprite');
const Variable = require('../engine/variable');
const List = require('../engine/list');
2016-12-30 10:19:58 -05:00
const {loadCostume} = require('../import/load-costume.js');
const {loadSound} = require('../import/load-sound.js');
2016-12-30 10:19:58 -05:00
/**
* Serializes the specified VM runtime.
* @param {!Runtime} runtime VM runtime instance to be serialized.
2017-04-27 17:49:57 -04:00
* @return {object} Serialized runtime instance.
2016-12-30 10:19:58 -05:00
*/
2017-04-26 16:50:53 -04:00
const serialize = function (runtime) {
2016-12-30 10:19:58 -05:00
// Fetch targets
2017-04-26 16:50:53 -04:00
const obj = Object.create(null);
2017-05-17 13:07:35 -04:00
obj.targets = runtime.targets.filter(target => target.isOriginal);
2016-12-30 10:19:58 -05:00
// Assemble metadata
2017-04-26 16:50:53 -04:00
const meta = Object.create(null);
2016-12-30 10:19:58 -05:00
meta.semver = '3.0.0';
2017-04-26 16:50:53 -04:00
meta.vm = vmPackage.version;
2016-12-30 10:19:58 -05:00
// Attach full user agent string to metadata if available
meta.agent = null;
if (typeof navigator !== 'undefined') meta.agent = navigator.userAgent;
// Assemble payload and return
obj.meta = meta;
return obj;
};
/**
* Parse a single "Scratch object" and create all its in-memory VM objects.
2017-04-26 16:50:53 -04:00
* @param {!object} object From-JSON "Scratch object:" sprite, stage, watcher.
* @param {!Runtime} runtime Runtime object to load all structures into.
* @return {?Target} Target created (stage or sprite).
*/
2017-04-26 16:50:53 -04:00
const parseScratchObject = function (object, runtime) {
if (!object.hasOwnProperty('name')) {
// Watcher/monitor - skip this object until those are implemented in VM.
// @todo
return;
}
// Blocks container for this object.
2017-04-26 16:50:53 -04:00
const blocks = new Blocks();
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
2017-04-26 16:50:53 -04:00
const sprite = new Sprite(blocks, runtime);
// Sprite/stage name from JSON.
if (object.hasOwnProperty('name')) {
sprite.name = object.name;
}
if (object.hasOwnProperty('blocks')) {
2017-08-26 13:07:47 -04:00
for (const blockId in object.blocks) {
blocks.createBlock(object.blocks[blockId]);
}
2017-04-26 16:50:53 -04:00
// console.log(blocks);
}
// Costumes from JSON.
const costumePromises = (object.costumes || []).map(costumeSource => {
// @todo: Make sure all the relevant metadata is being pulled out.
const costume = {
skinId: null,
name: costumeSource.name,
bitmapResolution: costumeSource.bitmapResolution,
rotationCenterX: costumeSource.rotationCenterX,
rotationCenterY: costumeSource.rotationCenterY
};
const dataFormat =
costumeSource.dataFormat ||
(costumeSource.assetType && costumeSource.assetType.runtimeFormat) || // older format
'png'; // if all else fails, guess that it might be a PNG
const costumeMd5 = `${costumeSource.assetId}.${dataFormat}`;
2017-05-04 12:41:54 -04:00
return loadCostume(costumeMd5, costume, runtime);
});
// Sounds from JSON
const soundPromises = (object.sounds || []).map(soundSource => {
const sound = {
format: soundSource.format,
fileUrl: soundSource.fileUrl,
rate: soundSource.rate,
sampleCount: soundSource.sampleCount,
soundID: soundSource.soundID,
name: soundSource.name,
md5: soundSource.md5,
data: null
};
return loadSound(sound, runtime);
});
// Create the first clone, and load its run-state from JSON.
2017-04-26 16:50:53 -04:00
const target = sprite.createClone();
// Load target properties from JSON.
if (object.hasOwnProperty('variables')) {
2017-08-26 13:07:47 -04:00
for (const j in object.variables) {
2017-04-26 16:50:53 -04:00
const variable = object.variables[j];
const newVariable = new Variable(
variable.id,
variable.name,
variable.value,
variable.isPersistent
);
target.variables[newVariable.id] = newVariable;
}
}
if (object.hasOwnProperty('lists')) {
2017-04-26 16:50:53 -04:00
for (let k = 0; k < object.lists.length; k++) {
const list = object.lists[k];
// @todo: monitor properties.
target.lists[list.listName] = new List(
list.listName,
list.contents
);
}
}
if (object.hasOwnProperty('x')) {
target.x = object.x;
}
if (object.hasOwnProperty('y')) {
target.y = object.y;
}
if (object.hasOwnProperty('direction')) {
target.direction = object.direction;
}
if (object.hasOwnProperty('size')) {
target.size = object.size;
}
if (object.hasOwnProperty('visible')) {
target.visible = object.visible;
}
if (object.hasOwnProperty('currentCostume')) {
target.currentCostume = object.currentCostume;
}
if (object.hasOwnProperty('rotationStyle')) {
target.rotationStyle = object.rotationStyle;
}
if (object.hasOwnProperty('isStage')) {
target.isStage = object.isStage;
}
Promise.all(costumePromises).then(costumes => {
sprite.costumes = costumes;
});
Promise.all(soundPromises).then(sounds => {
sprite.sounds = sounds;
});
return Promise.all(costumePromises.concat(soundPromises)).then(() => target);
};
2016-12-30 10:19:58 -05:00
/**
* Deserializes the specified representation of a VM runtime and loads it into
* the provided runtime instance.
2017-04-27 17:49:57 -04:00
* @param {object} json JSON representation of a VM runtime.
2016-12-30 10:19:58 -05:00
* @param {Runtime} runtime Runtime instance
2017-04-27 17:49:57 -04:00
* @returns {Promise} Promise that resolves to the list of targets after the project is deserialized
2016-12-30 10:19:58 -05:00
*/
2017-04-26 16:50:53 -04:00
const deserialize = function (json, runtime) {
2017-04-27 17:49:57 -04:00
return Promise.all((json.targets || []).map(target => parseScratchObject(target, runtime)));
2016-12-30 10:19:58 -05:00
};
module.exports = {
serialize: serialize,
deserialize: deserialize
};