Animation doesn't work in Promise's `then()` method before every `then()` is completed

Intro

Hey everybody! I have been working on performance issues and the goal is to minimize “black screen scene loading time” when some assets has been already loaded but not rendered due to the fact that the scene didn’t finish its loading state (I hope you understand).

Problem Description

And I have a .gltf skybox model which is loading using ImportMeshAsync() method. I chain it with then() method in which I try to invoke animation using beginAnimation() to smoothly animate visibility of my skybox mesh. And after that again I do chain with then() method loading another meshes etc. (The goal is to display skybox as fast as possible because I have custom preloader which manipulates DOM while babylonjs’ chunk is still loading).

The trouble is the animation is not invoked until every single resource in the chain has complete its loading state.

Variants of fix

I can fix that by saving result of beginAnimation() to any variable and then await it using await variable.waitAsync() in then method. But in that case my next resources are not starting to load till my animation is complete (despite it’s just a function, not a file which I load from any server, etc.).

I fixed this using beforeRenderObservable-like animation and I pretty like this solution but I would highly appreciate for explanations for animations if it’s possible. Maybe I am wrong somewhere.

Minimal reproduction

In the playground linked below I did a minimal reproduction of my issue. (You can use throttling if needed to lower loading speed of models in playground).

Playground

P.S. The real code if interested

Production-like code
. . .
SceneLoader.ImportMeshAsync(
      "",
      "./models/",
      "nebula_skybox_test.gltf",
      scene,
      onProgress,
      ".gltf"
    )
      .then(({ meshes }) => {
        console.log("NEBULA HERE");
        const rootMesh = meshes[0];
        this.createParticleSystem(rootMesh, scene);
        rootMesh.scaling.scaleInPlace(500);

        const nebulaSkyboxMesh = meshes.find(
          ({ name }) => name === "Nebula Skybox_Material_0"
        );
        nebulaSkyboxMesh.parent = null;

        console.log(nebulaSkyboxMesh);

        nebulaSkyboxMesh.visibility = 0;

        // TODO: Animation doesn't work while loading is not completely finished. Need to make a topic on forum.

        // const nebulaVisibilityAnimation = new Animation(
        //   "nebula-visibility",
        //   "visibility",
        //   120,
        //   Animation.ANIMATIONTYPE_FLOAT,
        //   Animation.ANIMATIONLOOPMODE_CONSTANT,
        //   false
        // );

        // const keyFrames: IAnimationKey[] = [];
        // keyFrames.push({
        //   frame: 0,
        //   value: 0,
        // });
        // keyFrames.push({
        //   frame: 120,
        //   value: 1,
        // });

        // nebulaVisibilityAnimation.setKeys(keyFrames);

        // nebulaSkyboxMesh.animations = [];
        // nebulaSkyboxMesh.animations.push(nebulaVisibilityAnimation);

        // const anim = scene.beginAnimation(nebulaSkyboxMesh, 0, 120);

        // const test = await anim.waitAsync();
        // console.log(test);

        const observer = this.scene.onBeforeRenderObservable.add(() => {
          console.log(nebulaSkyboxMesh.visibility);
          const deltaTime = this.scene.getEngine().getDeltaTime() / 1000;

          if (nebulaSkyboxMesh.visibility < 1) {
            nebulaSkyboxMesh.visibility += 0.5 * deltaTime;
          } else {
            nebulaSkyboxMesh.visibility = 1;
            this.scene.onBeforeRenderObservable.remove(observer);
          }
        });
      })
      .then(() => {
         . . .

Maybe you can test if a mesh is ready by calling mesh.isReady(true) and start the animation when it is?

Something like:

const checkReady = (mesh, callback) => {
    if (mesh.isReady(true)) {
        callback();
    } else {
        window.setTimeout(() => {
            checkReady(mesh, callback);
        }, 16);
    }
};

checkReady(mesh, () => {
    // start the animation
});

The key is that we know when mesh was loaded because we are using Promise’s chain structure.
In then() method we do have access to meshes array using destructuring and therefore able to operate with its properties etc.

Still I tried your code in playground, the problem remains unsolved.

The reason is the scene ready mechanism. Here is a (hacky) way to fix it:

Questionable animation | Babylon.js Playground (babylonjs.com)

The idea is this - if you load a model right after the scene is ready, the scene will not be ready and will not render. So loading the model on AFTER render will make it work as you (probably?) expect.

I am trying to think of a good solution for that. Assets container might be a good solution, or making sure you wait one frame before running the next load.

2 Likes

Okay, that was an interesting “revelation” for me! Thank you for the answer.