Merge pull request #1947 from mzgoddard/sb2-load-assets-first

Deserialize sb2 assets before other sprite data and blocks
This commit is contained in:
Michael "Z" Goddard 2019-03-04 12:50:09 -05:00 committed by GitHub
commit 9350c2a9c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -391,47 +391,28 @@ const parseMonitorObject = (object, runtime, targets, extensions) => {
};
/**
* Parse a single "Scratch object" and create all its in-memory VM objects.
* TODO: parse the "info" section, especially "savedExtensions"
* Parse the assets of a single "Scratch object" and load them. This
* preprocesses objects to support loading the data for those assets over a
* network while the objects are further processed into Blocks, Sprites, and a
* list of needed Extensions.
* @param {!object} object - From-JSON "Scratch object:" sprite, stage, watcher.
* @param {!Runtime} runtime - Runtime object to load all structures into.
* @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here.
* @param {boolean} topLevel - Whether this is the top-level object (stage).
* @param {?object} zip - Optional zipped assets for local file import
* @return {!Promise.<Array.<Target>>} Promise for the loaded targets when ready, or null for unsupported objects.
* @return {?{costumePromises:Array.<Promise>,soundPromises:Array.<Promise>,children:object}}
* Object of arrays of promises and child objects for asset objects used in
* Sprites. null for unsupported objects.
*/
const parseScratchObject = function (object, runtime, extensions, topLevel, zip) {
const parseScratchAssets = function (object, runtime, topLevel, zip) {
if (!object.hasOwnProperty('objName')) {
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);
// Skip parsing monitors. Or any other objects missing objName.
return null;
}
// Blocks container for this object.
const blocks = new Blocks(runtime);
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
const sprite = new Sprite(blocks, runtime);
// Sprite/stage name from JSON.
if (object.hasOwnProperty('objName')) {
if (topLevel && object.objName !== 'Stage') {
for (const child of object.children) {
if (!child.hasOwnProperty('objName') && child.target === object.objName) {
child.target = 'Stage';
}
}
object.objName = 'Stage';
}
const assets = {costumePromises: [], soundPromises: [], children: []};
sprite.name = object.objName;
}
// Costumes from JSON.
const costumePromises = [];
const costumePromises = assets.costumePromises;
if (object.hasOwnProperty('costumes')) {
for (let i = 0; i < object.costumes.length; i++) {
const costumeSource = object.costumes[i];
@ -476,7 +457,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
}
}
// Sounds from JSON
const soundPromises = [];
const soundPromises = assets.soundPromises;
if (object.hasOwnProperty('sounds')) {
for (let s = 0; s < object.sounds.length; s++) {
const soundSource = object.sounds[s];
@ -505,12 +486,72 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
// the file name of the sound should be the soundID (provided from the project.json)
// followed by the file ext
const assetFileName = `${soundSource.soundID}.${ext}`;
soundPromises.push(deserializeSound(sound, runtime, zip, assetFileName)
.then(() => loadSound(sound, runtime, sprite))
);
soundPromises.push(deserializeSound(sound, runtime, zip, assetFileName).then(() => sound));
}
}
// The stage will have child objects; recursively process them.
const childrenAssets = assets.children;
if (object.children) {
for (let m = 0; m < object.children.length; m++) {
childrenAssets.push(parseScratchAssets(object.children[m], runtime, false, zip));
}
}
return assets;
};
/**
* Parse a single "Scratch object" and create all its in-memory VM objects.
* TODO: parse the "info" section, especially "savedExtensions"
* @param {!object} object - From-JSON "Scratch object:" sprite, stage, watcher.
* @param {!Runtime} runtime - Runtime object to load all structures into.
* @param {ImportedExtensionsInfo} extensions - (in/out) parsed extension information will be stored here.
* @param {boolean} topLevel - Whether this is the top-level object (stage).
* @param {?object} zip - Optional zipped assets for local file import
* @param {object} assets - Promises for assets of this scratch object grouped
* into costumes and sounds
* @return {!Promise.<Array.<Target>>} Promise for the loaded targets when ready, or null for unsupported objects.
*/
const parseScratchObject = function (object, runtime, extensions, topLevel, zip, assets) {
if (!object.hasOwnProperty('objName')) {
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(runtime);
// @todo: For now, load all Scratch objects (stage/sprites) as a Sprite.
const sprite = new Sprite(blocks, runtime);
// Sprite/stage name from JSON.
if (object.hasOwnProperty('objName')) {
if (topLevel && object.objName !== 'Stage') {
for (const child of object.children) {
if (!child.hasOwnProperty('objName') && child.target === object.objName) {
child.target = 'Stage';
}
}
object.objName = 'Stage';
}
sprite.name = object.objName;
}
// Costumes from JSON.
const costumePromises = assets.costumePromises;
// Sounds from JSON
const soundPromises = assets.soundPromises;
for (let s = 0; s < soundPromises.length; s++) {
soundPromises[s] = soundPromises[s]
.then(sound => loadSound(sound, runtime, sprite));
}
// Create the first clone, and load its run-state from JSON.
const target = sprite.createClone(topLevel ? StageLayering.BACKGROUND_LAYER : StageLayering.SPRITE_LAYER);
@ -703,7 +744,9 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
const childrenPromises = [];
if (object.children) {
for (let m = 0; m < object.children.length; m++) {
childrenPromises.push(parseScratchObject(object.children[m], runtime, extensions, false, zip));
childrenPromises.push(
parseScratchObject(object.children[m], runtime, extensions, false, zip, assets.children[m])
);
}
}
@ -810,7 +853,13 @@ const sb2import = function (json, runtime, optForceSprite, zip) {
extensionIDs: new Set(),
extensionURLs: new Map()
};
return parseScratchObject(json, runtime, extensions, !optForceSprite, zip)
return Promise.resolve(parseScratchAssets(json, runtime, !optForceSprite, zip))
// Force this promise to wait for the next loop in the js tick. Let
// storage have some time to send off asset requests.
.then(assets => Promise.resolve(assets))
.then(assets => (
parseScratchObject(json, runtime, extensions, !optForceSprite, zip, assets)
))
.then(reorderParsedTargets)
.then(targets => ({
targets,