What is the proper way to handle atlas image loading failures?
vovaslotmill opened this issue · 1 comments
Hi!
Problem
It is not possible to catch spine loading error if an image, defined in .atlas
file, has failed to load (network error, absence of asset itself, etc.).
Dependencies:
spine: 4.1.24
pixi-spine: 4.0.4
pixi.js: 7.3.2
Example
Few assumptions first:
assets
folder is available and serve files correctly.
faultySpine
consists of 3 files: faultySpine.json
, faultySpine.atlas
and faultySpine.png
.
To prevent faultySpine.png
file from being loaded - it has been blocked, through Chrome Dev Tools.
Here is the code example, itself:
import { Application, Assets, Sprite } from "pixi.js";
import { Spine } from "pixi-spine";
const worldWidth = 1920;
const worldHeight = 1080;
(async () => {
const app = new Application({
backgroundColor: 0x000000,
width: worldWidth,
height: worldHeight,
});
document.body.appendChild(app.view as HTMLCanvasElement);
// setup faulty spine
try {
Assets.add({
alias: "faultySpine",
src: "./assets/faultySpine.json",
data: {
metadata: {
spineAtlasFile: `./assets/faultySpine.atlas`,
},
},
});
const spineAsset = await Assets.load("faultySpine");
const spine = new Spine(spineAsset.spineData);
spine.x = app.renderer.width / 2;
spine.y = app.renderer.height / 2;
spine.state.setAnimation(0, "some_animation", true);
app.stage.addChild(spine);
} catch (e) {
console.error(e); // (1) will not be triggered if `faultySpine.png` failed to load
}
// setup bunny sprite
try {
const texture = await Assets.load("./assets/bunny.png");
const bunny = new Sprite(texture);
bunny.x = app.renderer.width / 2;
bunny.y = app.renderer.height / 2;
bunny.anchor.x = 0.5;
bunny.anchor.y = 0.5;
app.stage.addChild(bunny);
app.ticker.add(() => {
bunny.rotation += 0.01;
});
} catch (e) {
console.error(e); // (2) will be triggered if `bunny.png` failed to load
}
})();
Trying to figure out why an error for Assets.load('faultySpine.json')
has not been trigged, brought up the following part to attention:
export const makeSpineTextureAtlasLoaderFunctionFromPixiLoaderObject = (loader: Loader, atlasBasePath: string, imageMetadata: any) => {
return async (pageName: string, textureLoadedCallback: (tex: BaseTexture) => any): Promise<void> => {
// const url = utils.path.join(...atlasBasePath.split(utils.path.sep), pageName); // Broken in upstream
const url = utils.path.normalize([...atlasBasePath.split(utils.path.sep), pageName].join(utils.path.sep));
const texture = await loader.load<Texture>({ src: url, data: imageMetadata });
textureLoadedCallback(texture.baseTexture);
};
};
https://github.com/pixijs/spine/blob/master/packages/loader-base/src/atlasLoader.ts#L98-L108
If const texture = await loader.load<Texture>({ src: url, data: imageMetadata });
will throw an error -textureLoadedCallback
will never be called, therefore the following rejection will never happen in this case, thus error will never be captured and will be an instance of Uncaught error
:
if (!newAtlas) {
reject('Something went terribly wrong loading a spine .atlas file\nMost likely your texture failed to load.');
}
https://github.com/pixijs/spine/blob/master/packages/loader-base/src/atlasLoader.ts#L59-L61
Solution (?)
Call textureLoadedCallback
with null
, if texture loading has failed, to prevent TextureAtlas setup and trigger an error that will be captured in (1)
i've prepared PR to address this issue: #544
if texture loading has failed for any reason - an appropriate error will be populated by loader as expected.
@miltoncandelero if an assumption that there IS a problem is wrong - please, leave comment here, so PR and issue can be closed 🙏