@@ -32,15 +32,18 @@ import { C3TextureEditor, C3TextureRuntime } from "./C3Texture";
3232
3333
3434interface CacheEntry < T > {
35- data : T ;
35+ data ?: T ;
36+ promise : Promise < T > ;
3637 refCount : number ;
3738}
3839
40+ type ResourceCache < T > = Map < string , CacheEntry < T > > ;
41+
3942export class AssetLoader {
4043
41- private static CacheSkeleton = new Map < string , CacheEntry < SkeletonData > > ( ) ;
42- private static CacheAtlas = new Map < string , CacheEntry < TextureAtlas > > ( ) ;
43- private static CacheTexture = new Map < string , CacheEntry < C3TextureRuntime > > ( ) ;
44+ private static CacheSkeleton : ResourceCache < SkeletonData > = new Map ( ) ;
45+ private static CacheAtlas : ResourceCache < TextureAtlas > = new Map ( ) ;
46+ private static CacheTexture : ResourceCache < C3TextureRuntime > = new Map ( ) ;
4447
4548 public async loadSkeletonEditor ( sid : number , textureAtlas : TextureAtlas , scale = 1 , instance : SDK . IWorldInstance ) {
4649 const projectFile = instance . GetProject ( ) . GetProjectFileBySID ( sid ) ;
@@ -96,102 +99,94 @@ export class AssetLoader {
9699 }
97100
98101 public async loadSkeletonRuntime ( path : string , textureAtlas : TextureAtlas , scale = 1 , instance : IRuntime ) {
99- const cacheKey = `${ path } |scale${ scale } ` ;
100-
101- const fileInCache = this . getFromCache ( AssetLoader . CacheSkeleton , cacheKey ) ;
102- if ( fileInCache ) return fileInCache ;
103-
104- const fullPath = await instance . assets . getProjectFileUrl ( path ) ;
105- if ( ! fullPath ) return null ;
106-
107- const atlasLoader = new AtlasAttachmentLoader ( textureAtlas ) ;
108-
109- let skeletonData : SkeletonData ;
110- const isBinary = path . endsWith ( ".skel" ) ;
111- if ( isBinary ) {
112- const content = await instance . assets . fetchArrayBuffer ( fullPath ) ;
113- if ( ! content ) return null ;
114- const skeletonLoader = new SkeletonBinary ( atlasLoader ) ;
115- skeletonLoader . scale = scale ;
116- skeletonData = skeletonLoader . readSkeletonData ( content ) ;
117- } else {
118- const content = await instance . assets . fetchJson ( fullPath ) ;
119- if ( ! content ) return null ;
120- const skeletonLoader = new SkeletonJson ( atlasLoader ) ;
121- skeletonLoader . scale = scale ;
122- skeletonData = skeletonLoader . readSkeletonData ( content ) ;
123- }
124-
125- AssetLoader . CacheSkeleton . set ( cacheKey , { data : skeletonData , refCount : 1 } ) ;
102+ const loadPromise = ( async ( ) => {
103+ const fullPath = await instance . assets . getProjectFileUrl ( path ) ;
104+ if ( ! fullPath ) throw new Error ( `Cannot find project file url for: ${ path } ` ) ;
105+
106+ const atlasLoader = new AtlasAttachmentLoader ( textureAtlas ) ;
107+
108+ let skeletonData : SkeletonData ;
109+ const isBinary = path . endsWith ( ".skel" ) ;
110+ if ( isBinary ) {
111+ const content = await instance . assets . fetchArrayBuffer ( fullPath ) ;
112+ if ( ! content ) throw new Error ( `Cannot fetch array buffer for: ${ fullPath } ` ) ;
113+
114+ const skeletonLoader = new SkeletonBinary ( atlasLoader ) ;
115+ skeletonLoader . scale = scale ;
116+ skeletonData = skeletonLoader . readSkeletonData ( content ) ;
117+ } else {
118+ const content = await instance . assets . fetchJson ( fullPath ) ;
119+ if ( ! content ) throw new Error ( `Cannot fetch json for: ${ fullPath } ` ) ;
120+
121+ const skeletonLoader = new SkeletonJson ( atlasLoader ) ;
122+ skeletonLoader . scale = scale ;
123+ skeletonData = skeletonLoader . readSkeletonData ( content ) ;
124+ }
125+ return skeletonData ;
126+ } ) ;
126127
127- return skeletonData ;
128+ return this . loadRuntimeResource ( ` ${ path } |scale ${ scale } ` , AssetLoader . CacheSkeleton , loadPromise ) ;
128129 }
129130
130131 public async loadAtlasRuntime ( path : string , instance : IRuntime , renderer : IRenderer ) {
131- const cacheKey = path ;
132-
133- const fileInCache = this . getFromCache ( AssetLoader . CacheAtlas , cacheKey ) ;
134- if ( fileInCache ) return fileInCache ;
135-
136- const fullPath = await instance . assets . getProjectFileUrl ( path ) ;
137- if ( ! fullPath ) return null ;
132+ const loadPromise = ( async ( ) => {
133+ const fullPath = await instance . assets . getProjectFileUrl ( path ) ;
134+ if ( ! fullPath ) throw new Error ( `Cannot find project file url for: ${ path } ` ) ;
138135
139- const content = await instance . assets . fetchText ( fullPath ) ;
140- if ( ! content ) return null ;
136+ const content = await instance . assets . fetchText ( fullPath ) ;
137+ if ( ! content ) throw new Error ( `Cannot fetch text for: ${ fullPath } ` ) ;
141138
142- const basePath = path . substring ( 0 , path . lastIndexOf ( "/" ) + 1 ) ;
143- const textureAtlas = new TextureAtlas ( content ) ;
144- await Promise . all ( textureAtlas . pages . map ( async page => {
145- const texture = await this . loadSpineTextureRuntime ( basePath , page , instance , renderer ) ;
146- if ( texture ) page . setTexture ( texture ) ;
147- return texture ;
148- } ) ) ;
139+ const basePath = path . substring ( 0 , path . lastIndexOf ( "/" ) + 1 ) ;
140+ const textureAtlas = new TextureAtlas ( content ) ;
141+ await Promise . all ( textureAtlas . pages . map ( async page => {
142+ const texture = await this . loadSpineTextureRuntime ( basePath , page , instance , renderer ) ;
143+ if ( texture ) page . setTexture ( texture ) ;
144+ return texture ;
145+ } ) ) ;
149146
150- AssetLoader . CacheAtlas . set ( cacheKey , { data : textureAtlas , refCount : 1 } ) ;
147+ return textureAtlas ;
148+ } ) ;
151149
152- return textureAtlas ;
150+ return this . loadRuntimeResource ( path , AssetLoader . CacheAtlas , loadPromise ) ;
153151 }
154152
155153 public async loadSpineTextureRuntime ( basePath : string , page : TextureAtlasPage , instance : IRuntime , renderer : IRenderer ) {
156154 const cacheKey = basePath + page . name ;
157155
158- const fileInCache = this . getFromCache ( AssetLoader . CacheTexture , cacheKey ) ;
159- if ( fileInCache ) return fileInCache ;
160-
161- const fullPath = await instance . assets . getProjectFileUrl ( cacheKey ) ;
162- if ( ! fullPath ) return null ;
156+ const loadPromise = ( async ( ) => {
157+ const fullPath = await instance . assets . getProjectFileUrl ( cacheKey ) ;
158+ if ( ! fullPath ) throw new Error ( `Cannot find project file url for: ${ cacheKey } ` ) ;
163159
164- const content = await instance . assets . fetchBlob ( fullPath ) ;
165- if ( ! content ) return null ;
160+ const content = await instance . assets . fetchBlob ( fullPath ) ;
161+ if ( ! content ) throw new Error ( `Cannot fetch blob for: ${ fullPath } ` ) ;
166162
167- const image = await AssetLoader . createImageBitmapFromBlob ( content , page . pma ) ;
168- if ( ! image ) return null ;
163+ const image = await AssetLoader . createImageBitmapFromBlob ( content , page . pma ) ;
164+ if ( ! image ) throw new Error ( `Cannot create image bitmap for: ${ fullPath } ` ) ;
169165
170- const spineTexture = new C3TextureRuntime ( image , renderer , page ) ;
166+ return new C3TextureRuntime ( image , renderer , page ) ;
167+ } ) ;
171168
172- this . addToCache ( AssetLoader . CacheTexture , cacheKey , spineTexture ) ;
173-
174- return spineTexture ;
169+ return this . loadRuntimeResource ( cacheKey , AssetLoader . CacheTexture , loadPromise ) ;
175170 }
176171
177172 public releaseInstanceResources ( skeletonPath : string , atlasPath : string , loaderScale : number ) {
178173 this . releaseResource ( AssetLoader . CacheSkeleton , `${ skeletonPath } |scale${ loaderScale } ` ) ;
179174
180175 const atlasEntry = AssetLoader . CacheAtlas . get ( atlasPath ) ;
181176 if ( atlasEntry ) {
182- this . releaseResource ( AssetLoader . CacheAtlas , atlasPath , ( ) => {
177+ this . releaseResource ( AssetLoader . CacheAtlas , atlasPath , async ( ) => {
183178 const basePath = atlasPath . substring ( 0 , atlasPath . lastIndexOf ( "/" ) + 1 ) ;
184- for ( const page of atlasEntry . data . pages ) {
179+ for ( const page of ( await atlasEntry . promise ) . pages ) {
185180 const textureKey = basePath + page . name ;
186181 this . releaseResource ( AssetLoader . CacheTexture , textureKey , ( texture ) => {
187- texture . dispose ( ) ;
182+ texture ? .dispose ( ) ;
188183 } ) ;
189184 }
190185 } ) ;
191186 }
192187 }
193188
194- private releaseResource < T > ( cache : Map < string , CacheEntry < T > > , key : string , disposer ?: ( data : T ) => void ) {
189+ private releaseResource < T > ( cache : ResourceCache < T > , key : string , disposer ?: ( data ? : T ) => void ) {
195190 const entry = cache . get ( key ) ;
196191 if ( ! entry ) return ;
197192
@@ -203,24 +198,34 @@ export class AssetLoader {
203198 }
204199 }
205200
206- private addToCache < T > ( cache : Map < string , CacheEntry < T > > , cacheKey : string , data : T ) {
207- cache . set ( cacheKey , { data, refCount : 1 } ) ;
201+ private async loadRuntimeResource < T > ( cacheKey : string , resourceCache : ResourceCache < T > , loader : ( ) => Promise < T > ) : Promise < T > {
202+ const entry = this . getFromCache ( resourceCache , cacheKey ) ;
203+ if ( entry ) return entry . promise ;
204+ const result = loader ( ) ;
205+ this . addToCache ( resourceCache , cacheKey , result ) ;
206+ return result ;
207+ }
208+
209+ private async addToCache < T > ( cache : ResourceCache < T > , cacheKey : string , promise : Promise < T > ) {
210+ const cachEntry : CacheEntry < T > = { promise, refCount : 1 } ;
211+ cache . set ( cacheKey , cachEntry ) ;
212+ cachEntry . data = await promise ;
208213 }
209214
210- private getFromCache < T > ( cache : Map < string , CacheEntry < T > > , cacheKey : string ) {
211- const fileInCache = cache . get ( cacheKey ) ;
212- if ( ! fileInCache ) return undefined ;
215+ private getFromCache < T > ( cache : ResourceCache < T > , cacheKey : string ) {
216+ const entry = cache . get ( cacheKey ) ;
217+ if ( ! entry ) return undefined ;
213218
214- fileInCache . refCount ++ ;
215- return fileInCache . data ;
219+ entry . refCount ++ ;
220+ return entry ;
216221 }
217222
218223 static async createImageBitmapFromBlob ( blob : Blob , pma : boolean ) : Promise < ImageBitmap | null > {
219224 try {
220225 return createImageBitmap ( blob , { premultiplyAlpha : pma ? "none" : "premultiply" } ) ;
221226 } catch ( e ) {
222227 console . error ( "Failed to create ImageBitmap from blob:" , e ) ;
223- return null ;
228+ throw e ;
224229 }
225230 }
226231
0 commit comments