@@ -514,9 +514,9 @@ class VirtualMachine extends EventEmitter {
514514 }
515515
516516 /**
517- * @returns {JSZip } JSZip zip object representing the sb3.
517+ * @returns {Promise< JSZip> } JSZip zip object representing the sb3.
518518 */
519- _saveProjectZip ( ) {
519+ async _saveProjectZip ( ) {
520520 const projectJson = this . toJSON ( ) ;
521521
522522 // TODO want to eventually move zip creation out of here, and perhaps
@@ -525,7 +525,7 @@ class VirtualMachine extends EventEmitter {
525525
526526 // Put everything in a zip file
527527 zip . file ( 'project.json' , projectJson ) ;
528- this . _addFileDescsToZip ( this . serializeAssets ( ) , zip ) ;
528+ this . _addFileDescsToZip ( await this . serializeAssets ( ) , zip ) ;
529529
530530 // Use a fixed modification date for the files in the zip instead of letting JSZip use the
531531 // current time to avoid a very small metadata leak and make zipping deterministic. The magic
@@ -543,8 +543,9 @@ class VirtualMachine extends EventEmitter {
543543 * @param {JSZip.OutputType } [type] JSZip output type. Defaults to 'blob' for Scratch compatibility.
544544 * @returns {Promise<unknown> } Compressed sb3 file in a type determined by the type argument.
545545 */
546- saveProjectSb3 ( type ) {
547- return this . _saveProjectZip ( ) . generateAsync ( {
546+ async saveProjectSb3 ( type ) {
547+ const zip = await this . _saveProjectZip ( ) ;
548+ return zip . generateAsync ( {
548549 type : type || 'blob' ,
549550 mimeType : 'application/x.scratch.sb3' ,
550551 compression : 'DEFLATE'
@@ -553,11 +554,12 @@ class VirtualMachine extends EventEmitter {
553554
554555 /**
555556 * @param {JSZip.OutputType } [type] JSZip output type. Defaults to 'arraybuffer'.
556- * @returns {StreamHelper } JSZip StreamHelper object generating the compressed sb3.
557+ * @returns {Promise< StreamHelper> } JSZip StreamHelper object generating the compressed sb3.
557558 * See: https://stuk.github.io/jszip/documentation/api_streamhelper.html
558559 */
559- saveProjectSb3Stream ( type ) {
560- return this . _saveProjectZip ( ) . generateInternalStream ( {
560+ async saveProjectSb3Stream ( type ) {
561+ const zip = await this . _saveProjectZip ( ) ;
562+ return zip . generateInternalStream ( {
561563 type : type || 'arraybuffer' ,
562564 mimeType : 'application/x.scratch.sb3' ,
563565 compression : 'DEFLATE'
@@ -601,19 +603,34 @@ class VirtualMachine extends EventEmitter {
601603
602604 /**
603605 * @param {string } targetId Optional ID of target to export
604- * @returns {Array<{fileName: string; fileContent: Uint8Array;} } list of file descs
606+ * @returns {Promise< Array<{fileName: string; fileContent: Uint8Array;}> } list of file descs
605607 */
606- serializeAssets ( targetId ) {
607- const costumeDescs = serializeCostumes ( this . runtime , targetId ) ;
608- const soundDescs = serializeSounds ( this . runtime , targetId ) ;
608+ async serializeAssets ( targetId ) {
609+ // This will include non-lazy sprites and loaded lazy sprites.
610+ const loadedCostumeDescs = serializeCostumes ( this . runtime , targetId ) ;
611+ const loadedSoundDescs = serializeSounds ( this . runtime , targetId ) ;
612+
613+ // Assume every target needs all fonts.
609614 const fontDescs = this . runtime . fontManager . serializeAssets ( ) . map ( asset => ( {
610615 fileName : `${ asset . assetId } .${ asset . dataFormat } ` ,
611616 fileContent : asset . data
612617 } ) ) ;
618+
619+ // Fetch assets used by lazy sprites.
620+ const unloadedSprites = this . runtime . lazySprites . filter ( i => i . clones . length === 0 ) ;
621+ const unloadedSpriteDescs = await Promise . all ( unloadedSprites . map ( s => s . serializeAssets ( ) ) ) ;
622+ const flattenedUnloadedSpriteDescs = [ ] ;
623+ for ( const descs of unloadedSpriteDescs ) {
624+ for ( const desc of descs ) {
625+ flattenedUnloadedSpriteDescs . push ( desc ) ;
626+ }
627+ }
628+
613629 return [
614- ...costumeDescs ,
615- ...soundDescs ,
616- ...fontDescs
630+ ...loadedCostumeDescs ,
631+ ...loadedSoundDescs ,
632+ ...fontDescs ,
633+ ...flattenedUnloadedSpriteDescs
617634 ] ;
618635 }
619636
@@ -637,12 +654,12 @@ class VirtualMachine extends EventEmitter {
637654 * @return {object } A generated zip of the sprite and its assets in the format
638655 * specified by optZipType or blob by default.
639656 */
640- exportSprite ( targetId , optZipType ) {
657+ async exportSprite ( targetId , optZipType ) {
641658 const spriteJson = this . toJSON ( targetId ) ;
642659
643660 const zip = new JSZip ( ) ;
644661 zip . file ( 'sprite.json' , spriteJson ) ;
645- this . _addFileDescsToZip ( this . serializeAssets ( targetId ) , zip ) ;
662+ this . _addFileDescsToZip ( await this . serializeAssets ( targetId ) , zip ) ;
646663
647664 return zip . generateAsync ( {
648665 type : typeof optZipType === 'string' ? optZipType : 'blob' ,
0 commit comments