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. * Parse the assets of a single "Scratch object" and load them. This
* TODO: parse the "info" section, especially "savedExtensions" * 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 {!object} object - From-JSON "Scratch object:" sprite, stage, watcher.
* @param {!Runtime} runtime - Runtime object to load all structures into. * @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 {boolean} topLevel - Whether this is the top-level object (stage).
* @param {?object} zip - Optional zipped assets for local file import * @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('objName')) {
if (object.hasOwnProperty('listName')) { // Skip parsing monitors. Or any other objects missing objName.
// Shim these objects so they can be processed as monitors return 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. const assets = {costumePromises: [], soundPromises: [], children: []};
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. // Costumes from JSON.
const costumePromises = []; const costumePromises = assets.costumePromises;
if (object.hasOwnProperty('costumes')) { if (object.hasOwnProperty('costumes')) {
for (let i = 0; i < object.costumes.length; i++) { for (let i = 0; i < object.costumes.length; i++) {
const costumeSource = object.costumes[i]; const costumeSource = object.costumes[i];
@ -476,7 +457,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel, zip)
} }
} }
// Sounds from JSON // Sounds from JSON
const soundPromises = []; const soundPromises = assets.soundPromises;
if (object.hasOwnProperty('sounds')) { if (object.hasOwnProperty('sounds')) {
for (let s = 0; s < object.sounds.length; s++) { for (let s = 0; s < object.sounds.length; s++) {
const soundSource = object.sounds[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) // the file name of the sound should be the soundID (provided from the project.json)
// followed by the file ext // followed by the file ext
const assetFileName = `${soundSource.soundID}.${ext}`; const assetFileName = `${soundSource.soundID}.${ext}`;
soundPromises.push(deserializeSound(sound, runtime, zip, assetFileName) soundPromises.push(deserializeSound(sound, runtime, zip, assetFileName).then(() => sound));
.then(() => loadSound(sound, runtime, sprite))
);
} }
} }
// 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. // Create the first clone, and load its run-state from JSON.
const target = sprite.createClone(topLevel ? StageLayering.BACKGROUND_LAYER : StageLayering.SPRITE_LAYER); 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 = []; const childrenPromises = [];
if (object.children) { if (object.children) {
for (let m = 0; m < object.children.length; m++) { 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(), extensionIDs: new Set(),
extensionURLs: new Map() 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(reorderParsedTargets)
.then(targets => ({ .then(targets => ({
targets, targets,