pixijs/spine

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
uncaught-error-example

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 🙏