From 6c46fcbbc6b9fefb7625a6cbebd7832e0127d6d6 Mon Sep 17 00:00:00 2001 From: Davide Tantillo Date: Tue, 8 Jul 2025 16:41:17 +0200 Subject: [PATCH] [ts][webcomponents] Sharable cache for AssetManager. --- spine-ts/spine-core/src/AssetManagerBase.ts | 86 +++++++++++++------ .../src/SpineWebComponentOverlay.ts | 10 ++- spine-ts/spine-webgl/src/AssetManager.ts | 1 - spine-ts/spine-webgl/src/WebGL.ts | 2 +- 4 files changed, 66 insertions(+), 33 deletions(-) diff --git a/spine-ts/spine-core/src/AssetManagerBase.ts b/spine-ts/spine-core/src/AssetManagerBase.ts index c2df0606f..69431c560 100644 --- a/spine-ts/spine-core/src/AssetManagerBase.ts +++ b/spine-ts/spine-core/src/AssetManagerBase.ts @@ -35,17 +35,16 @@ export class AssetManagerBase implements Disposable { private pathPrefix: string = ""; private textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture; private downloader: Downloader; - private assets: StringMap = {}; - private assetsRefCount: StringMap = {}; - private assetsLoaded: StringMap> = {}; + private cache: AssetCache; private errors: StringMap = {}; private toLoad = 0; private loaded = 0; - constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader: Downloader = new Downloader()) { + constructor (textureLoader: (image: HTMLImageElement | ImageBitmap) => Texture, pathPrefix: string = "", downloader = new Downloader(), cache = new AssetCache()) { this.textureLoader = textureLoader; this.pathPrefix = pathPrefix; this.downloader = downloader; + this.cache = cache; } private start (path: string): string { @@ -56,8 +55,8 @@ export class AssetManagerBase implements Disposable { private success (callback: (path: string, data: any) => void, path: string, asset: any) { this.toLoad--; this.loaded++; - this.assets[path] = asset; - this.assetsRefCount[path] = (this.assetsRefCount[path] || 0) + 1; + this.cache.assets[path] = asset; + this.cache.assetsRefCount[path] = (this.cache.assetsRefCount[path] || 0) + 1; if (callback) callback(path, asset); } @@ -94,7 +93,7 @@ export class AssetManagerBase implements Disposable { if (this.reuseAssets(path, success, error)) return; - this.assetsLoaded[path] = new Promise((resolve, reject) => { + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { this.downloader.downloadBinary(path, (data: Uint8Array): void => { this.success(success, path, data); resolve(data); @@ -125,7 +124,7 @@ export class AssetManagerBase implements Disposable { if (this.reuseAssets(path, success, error)) return; - this.assetsLoaded[path] = new Promise((resolve, reject) => { + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { this.downloader.downloadJson(path, (data: object): void => { this.success(success, path, data); resolve(data); @@ -140,11 +139,17 @@ export class AssetManagerBase implements Disposable { reuseAssets (path: string, success: (path: string, data: any) => void = () => { }, error: (path: string, message: string) => void = () => { }) { - const loadedStatus = this.assetsLoaded[path]; + const loadedStatus = this.cache.assetsLoaded[path]; const alreadyExistsOrLoading = loadedStatus !== undefined; if (alreadyExistsOrLoading) { loadedStatus - .then(data => this.success(success, path, data)) + .then(data => { + if (data instanceof Image) { + data = this.textureLoader(data); + this.cache.assetsLoaded[path] = Promise.resolve(data); + } + return this.success(success, path, data) + }) .catch(errorMsg => this.error(error, path, errorMsg)); } return alreadyExistsOrLoading; @@ -158,7 +163,7 @@ export class AssetManagerBase implements Disposable { if (this.reuseAssets(path, success, error)) return; - this.assetsLoaded[path] = new Promise((resolve, reject) => { + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { let isBrowser = !!(typeof window !== 'undefined' && typeof navigator !== 'undefined' && window.document); let isWebWorker = !isBrowser; // && typeof importScripts !== 'undefined'; if (isWebWorker) { @@ -171,7 +176,7 @@ export class AssetManagerBase implements Disposable { return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null; }).then((bitmap) => { if (bitmap) { - const texture = this.textureLoader(bitmap) + const texture = this.textureLoader(bitmap); this.success(success, path, texture); resolve(texture); }; @@ -180,7 +185,7 @@ export class AssetManagerBase implements Disposable { let image = new Image(); image.crossOrigin = "anonymous"; image.onload = () => { - const texture = this.textureLoader(image) + const texture = this.textureLoader(image); this.success(success, path, texture); resolve(texture); }; @@ -206,7 +211,7 @@ export class AssetManagerBase implements Disposable { if (this.reuseAssets(path, success, error)) return; - this.assetsLoaded[path] = new Promise((resolve, reject) => { + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { this.downloader.downloadText(path, (atlasText: string): void => { try { let atlas = new TextureAtlas(atlasText); @@ -224,7 +229,7 @@ export class AssetManagerBase implements Disposable { }, (imagePath: string, message: string) => { if (!abort) { - const errorMsg = `Couldn't load texture atlas ${path} page image: ${imagePath}`; + const errorMsg = `Couldn't load texture ${path} page image: ${imagePath}`; this.error(error, path, errorMsg); reject(errorMsg); } @@ -254,7 +259,7 @@ export class AssetManagerBase implements Disposable { if (this.reuseAssets(path, success, error)) return; - this.assetsLoaded[path] = new Promise((resolve, reject) => { + this.cache.assetsLoaded[path] = new Promise((resolve, reject) => { this.downloader.downloadText(path, (atlasText: string): void => { try { const atlas = new TextureAtlas(atlasText); @@ -319,13 +324,17 @@ export class AssetManagerBase implements Disposable { }); } + setCache (cache: AssetCache) { + this.cache = cache; + } + get (path: string) { - return this.assets[this.pathPrefix + path]; + return this.cache.assets[this.pathPrefix + path]; } require (path: string) { path = this.pathPrefix + path; - let asset = this.assets[path]; + let asset = this.cache.assets[path]; if (asset) return asset; let error = this.errors[path]; throw Error("Asset not found: " + path + (error ? "\n" + error : "")); @@ -333,22 +342,22 @@ export class AssetManagerBase implements Disposable { remove (path: string) { path = this.pathPrefix + path; - let asset = this.assets[path]; + let asset = this.cache.assets[path]; if (asset.dispose) asset.dispose(); - delete this.assets[path]; - delete this.assetsRefCount[path]; - delete this.assetsLoaded[path]; + delete this.cache.assets[path]; + delete this.cache.assetsRefCount[path]; + delete this.cache.assetsLoaded[path]; return asset; } removeAll () { - for (let path in this.assets) { - let asset = this.assets[path]; + for (let path in this.cache.assets) { + let asset = this.cache.assets[path]; if (asset.dispose) asset.dispose(); } - this.assets = {}; - this.assetsLoaded = {}; - this.assetsRefCount = {}; + this.cache.assets = {}; + this.cache.assetsLoaded = {}; + this.cache.assetsRefCount = {}; } isLoadingComplete (): boolean { @@ -369,7 +378,7 @@ export class AssetManagerBase implements Disposable { // dispose asset only if it's not used by others disposeAsset (path: string) { - if (--this.assetsRefCount[path] === 0) { + if (--this.cache.assetsRefCount[path] === 0) { this.remove(path) } } @@ -383,6 +392,27 @@ export class AssetManagerBase implements Disposable { } } +export class AssetCache { + public assets: StringMap = {}; + public assetsRefCount: StringMap = {}; + public assetsLoaded: StringMap> = {}; + + static AVAILABLE_CACHES = new Map(); + static getCache (id: string) { + const cache = AssetCache.AVAILABLE_CACHES.get(id); + if (cache) return cache; + + const newCache = new AssetCache(); + AssetCache.AVAILABLE_CACHES.set(id, newCache); + return newCache; + } + + async addAsset(path: string, asset: any) { + this.assetsLoaded[path] = Promise.resolve(asset); + this.assets[path] = await asset; + } +} + export class Downloader { private callbacks: StringMap> = {}; rawDataUris: StringMap = {}; diff --git a/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts b/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts index 992d8c055..3161d1e84 100644 --- a/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts +++ b/spine-ts/spine-webcomponents/src/SpineWebComponentOverlay.ts @@ -27,7 +27,7 @@ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import { AssetManager, Color, Disposable, Input, LoadingScreen, ManagedWebGLRenderingContext, Physics, SceneRenderer, TimeKeeper, Vector2, Vector3 } from "@esotericsoftware/spine-webgl" +import { AssetCache, AssetManager, Color, Disposable, Input, LoadingScreen, ManagedWebGLRenderingContext, Physics, SceneRenderer, TimeKeeper, Vector2, Vector3 } from "@esotericsoftware/spine-webgl" import { SpineWebComponentSkeleton } from "./SpineWebComponentSkeleton.js" import { AttributeTypes, castValue, Point, Rectangle } from "./wcUtils.js" @@ -48,10 +48,11 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr * @internal */ static getOrCreateOverlay (overlayId: string | null): SpineWebComponentOverlay { - let overlay = SpineWebComponentOverlay.OVERLAY_LIST.get(overlayId || SpineWebComponentOverlay.OVERLAY_ID); + const id = overlayId || SpineWebComponentOverlay.OVERLAY_ID; + let overlay = SpineWebComponentOverlay.OVERLAY_LIST.get(id); if (!overlay) { overlay = document.createElement('spine-overlay') as SpineWebComponentOverlay; - overlay.setAttribute('overlay-id', SpineWebComponentOverlay.OVERLAY_ID); + overlay.setAttribute('overlay-id', id); document.body.appendChild(overlay); } return overlay; @@ -232,6 +233,9 @@ export class SpineWebComponentOverlay extends HTMLElement implements OverlayAttr overlayId = SpineWebComponentOverlay.OVERLAY_ID; this.setAttribute('overlay-id', overlayId); } + + this.assetManager.setCache(AssetCache.getCache(overlayId)); + const existingOverlay = SpineWebComponentOverlay.OVERLAY_LIST.get(overlayId); if (existingOverlay && existingOverlay !== this) { throw new Error(`"SpineWebComponentOverlay - You cannot have two spine-overlay with the same overlay-id: ${overlayId}"`); diff --git a/spine-ts/spine-webgl/src/AssetManager.ts b/spine-ts/spine-webgl/src/AssetManager.ts index 7cb7b394b..ce9329191 100644 --- a/spine-ts/spine-webgl/src/AssetManager.ts +++ b/spine-ts/spine-webgl/src/AssetManager.ts @@ -31,7 +31,6 @@ import { AssetManagerBase, Downloader } from "@esotericsoftware/spine-core" import { ManagedWebGLRenderingContext } from "./WebGL.js"; import { GLTexture } from "./GLTexture.js"; - export class AssetManager extends AssetManagerBase { constructor (context: ManagedWebGLRenderingContext | WebGLRenderingContext, pathPrefix: string = "", downloader: Downloader = new Downloader()) { super((image: HTMLImageElement | ImageBitmap) => { diff --git a/spine-ts/spine-webgl/src/WebGL.ts b/spine-ts/spine-webgl/src/WebGL.ts index 1fc42d3e9..2103c61e6 100644 --- a/spine-ts/spine-webgl/src/WebGL.ts +++ b/spine-ts/spine-webgl/src/WebGL.ts @@ -27,7 +27,7 @@ * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -import { Restorable, BlendMode } from "@esotericsoftware/spine-core"; +import { Restorable } from "@esotericsoftware/spine-core"; export class ManagedWebGLRenderingContext { public canvas: HTMLCanvasElement | OffscreenCanvas;