[ts] Fixed AssetManager dispose invalidating textures too early. Close #2905.

This commit is contained in:
Davide Tantillo 2025-09-29 18:18:29 +02:00
parent aded292be3
commit e899a99cbb
2 changed files with 46 additions and 17 deletions

View File

@ -176,7 +176,7 @@ export class AssetManagerBase implements Disposable {
return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null; return blob ? createImageBitmap(blob, { premultiplyAlpha: "none", colorSpaceConversion: "none" }) : null;
}).then((bitmap) => { }).then((bitmap) => {
if (bitmap) { if (bitmap) {
const texture = this.textureLoader(bitmap); const texture = this.createTexture(path, bitmap);
this.success(success, path, texture); this.success(success, path, texture);
resolve(texture); resolve(texture);
}; };
@ -185,7 +185,7 @@ export class AssetManagerBase implements Disposable {
let image = new Image(); let image = new Image();
image.crossOrigin = "anonymous"; image.crossOrigin = "anonymous";
image.onload = () => { image.onload = () => {
const texture = this.textureLoader(image); const texture = this.createTexture(path, image);
this.success(success, path, texture); this.success(success, path, texture);
resolve(texture); resolve(texture);
}; };
@ -214,7 +214,7 @@ export class AssetManagerBase implements Disposable {
this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => { this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => {
this.downloader.downloadText(path, (atlasText: string): void => { this.downloader.downloadText(path, (atlasText: string): void => {
try { try {
let atlas = new TextureAtlas(atlasText); const atlas = this.createTextureAtlas(path, atlasText);
let toLoad = atlas.pages.length, abort = false; let toLoad = atlas.pages.length, abort = false;
for (let page of atlas.pages) { for (let page of atlas.pages) {
this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name!], this.loadTexture(!fileAlias ? parent + page.name : fileAlias[page.name!],
@ -262,7 +262,7 @@ export class AssetManagerBase implements Disposable {
this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => { this.cache.assetsLoaded[path] = new Promise<any>((resolve, reject) => {
this.downloader.downloadText(path, (atlasText: string): void => { this.downloader.downloadText(path, (atlasText: string): void => {
try { try {
const atlas = new TextureAtlas(atlasText); const atlas = this.createTextureAtlas(path, atlasText);
this.success(success, path, atlas); this.success(success, path, atlas);
resolve(atlas); resolve(atlas);
} catch (e) { } catch (e) {
@ -378,9 +378,12 @@ export class AssetManagerBase implements Disposable {
// dispose asset only if it's not used by others // dispose asset only if it's not used by others
disposeAsset (path: string) { disposeAsset (path: string) {
if (--this.cache.assetsRefCount[path] === 0) { const asset = this.cache.assets[path];
this.remove(path) if (asset instanceof TextureAtlas) {
asset.dispose();
return;
} }
this.disposeAssetInternal(path);
} }
hasErrors () { hasErrors () {
@ -390,6 +393,33 @@ export class AssetManagerBase implements Disposable {
getErrors () { getErrors () {
return this.errors; return this.errors;
} }
private disposeAssetInternal (path: string) {
if (this.cache.assetsRefCount[path] > 0 && --this.cache.assetsRefCount[path] === 0) {
return this.remove(path);
}
}
private createTextureAtlas (path: string, atlasText: string): TextureAtlas {
const atlas = new TextureAtlas(atlasText);
atlas.dispose = () => {
if (this.cache.assetsRefCount[path] <= 0) return;
this.disposeAssetInternal(path);
for (const page of atlas.pages) {
page.texture?.dispose();
}
}
return atlas;
}
private createTexture (path: string, image: HTMLImageElement | ImageBitmap): Texture {
const texture = this.textureLoader(image);
const textureDispose = texture.dispose.bind(texture);
texture.dispose = () => {
if (this.disposeAssetInternal(path)) textureDispose();
}
return texture;
}
} }
export class AssetCache { export class AssetCache {

View File

@ -32,27 +32,27 @@ import {
AnimationState, AnimationState,
AnimationStateData, AnimationStateData,
AtlasAttachmentLoader, AtlasAttachmentLoader,
Bone,
Disposable, Disposable,
LoadingScreen, LoadingScreen,
MeshAttachment,
MixBlend, MixBlend,
MixDirection, MixDirection,
NumberArrayLike,
Physics, Physics,
RegionAttachment,
Skeleton,
SkeletonBinary, SkeletonBinary,
SkeletonData, SkeletonData,
SkeletonJson, SkeletonJson,
Skeleton,
TextureAtlas,
Vector2,
Utils,
NumberArrayLike,
Slot,
RegionAttachment,
MeshAttachment,
Bone,
Skin, Skin,
Slot,
TextureAtlas,
Utils,
Vector2,
} from "@esotericsoftware/spine-webgl"; } from "@esotericsoftware/spine-webgl";
import { AttributeTypes, castValue, isBase64, Rectangle } from "./wcUtils.js";
import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js"; import { SpineWebComponentOverlay } from "./SpineWebComponentOverlay.js";
import { AttributeTypes, castValue, isBase64, Rectangle } from "./wcUtils.js";
type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void; type UpdateSpineWidgetFunction = (delta: number, skeleton: Skeleton, state: AnimationState) => void;
@ -1318,7 +1318,6 @@ export class SpineWebComponentSkeleton extends HTMLElement implements Disposable
const { assetManager } = this.overlay; const { assetManager } = this.overlay;
if (this.lastAtlasPath) assetManager.disposeAsset(this.lastAtlasPath); if (this.lastAtlasPath) assetManager.disposeAsset(this.lastAtlasPath);
if (this.lastSkelPath) assetManager.disposeAsset(this.lastSkelPath); if (this.lastSkelPath) assetManager.disposeAsset(this.lastSkelPath);
for (const texturePath of this.lastTexturePaths) assetManager.disposeAsset(texturePath);
} }
} }