mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge pull request #1947 from mzgoddard/sb2-load-assets-first
Deserialize sb2 assets before other sprite data and blocks
This commit is contained in:
commit
9350c2a9c2
1 changed files with 86 additions and 37 deletions
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue