Is AnimationEvent 100% reliable?

Hello everybody,

The new version of my card game is tested since a few months now and I have the first bugs to solve :wink:
I am using an AnimationEvent to change the texture of the score table ‘on the fly’ to use it to prompt players with different game actions.

This is the correct behaviour:

I was reported this bug by a friend who is used to have a look at the js console when something weird is happening to capture errors

There was no error recorded, so as the texture change is happening on the frame 10 of an animation, triggered on an animation event, I was wondering, if… it could happen that a frame is skipped and the attached event not triggered… ?

2 Likes

Can you repro? (Beautiful game btw, this is so rad!)

thanks ! And of course, I cannot reproduce, this is so rare, that my question is to see if I have to investigate more this direction or if I should look somewhere else :slight_smile:

Looking into the code I am not seeing a lot of possibilities on the animations to go wrong. It might be smthg different.

okay, if for instance the cpu/gpu is kind of too busy, isn’t it possible that a frame is skipped to keep the animation synced ?

I was thinking I could add a second event on frame 15 that would check a flag to see if the frame 10 event happened, but if it has nothing to do with this :stuck_out_tongue:

In your case it might be simpler to rely on time event then ? @RaananW just introduced a nice addition for those :slight_smile:

time event, you mean this BABYLON.setTimerAndStart ?

@RaananW any pointers to your new time observable ?

Fully documented here - Handle Events with Observables - Babylon.js Documentation , if anything is not working or missing, please let me know

1 Like

Test #1

  1. Create an Animation
  2. Reg AnimationEvents in descending order at 10s 9s 8s … 1s
  3. Block runtime for 3.5s

https://playground.babylonjs.com/#WUCYZ9

Ev happens in this order
ev at 3s is called
ev at 2s is called
ev at 1s is called
ev at 4s is called
ev at 5s is called
ev at 6s is called
ev at 7s is called
ev at 8s is called
ev at 9s is called
ev at 10s is called

(I believe ev get called in chronological order, but not really, see test #2)

Test #2

After opening the same PG, immediately switch to other browser tab,
after 10s, switch it back, open console.

The ordering is somehting like that

a

1 Like

Super interesting :slight_smile:

The reason this happens is the way we add new events to the list and iterate them later. Here is a very quick fix, using unshift instead of push, make sure they are processed in the right order:

https://playground.babylonjs.com/#WUCYZ9#2

image

I think it’s worth pushing this to the framework, thou it is more of a nice-to-have fix. I’ll make sure to update that on my next PR.

I don’t quite understand the fix?

As @ycw wrotes the PG with a top down loop, using unshift instead of push simply reverse the order of the elements in the array.

You would achieve the same thing by doing a bottom up loop (without unshift):

    for (let i = 0; i < 10; --i) {
      const ev = new BABYLON.AnimationEvent(i, (f) => {
        console.log(`ev ${i} is called`);
      }, true);
      anim.addEvent(ev);
    }

I think the outcome to this is that the order in which the events are pushed in the array should not impact the runtime behaviour. At least, on a user point of view, it’s not easily understandable.

[EDIT] If the animation code expects that the animation event array is sorted by increasing frame number, I think it should either be stated in the doc (if not already) or the array should be sorted automatically when new actions are added [/EDIT]

Well this is expected as the code checking if the event needs to be run is doing it by checking if:

  • event was not run yet
  • we are over the event.frame

Here is the relevant code:

 const events = this._events;
        if (range > 0 && this.currentFrame > currentFrame ||
            range < 0 && this.currentFrame < currentFrame) {
            this._onLoop();

            // Need to reset animation events
            if (events.length) {
                for (var index = 0; index < events.length; index++) {
                    if (!events[index].onlyOnce) {
                        // reset event, the animation is looping
                        events[index].isDone = false;
                    }
                }
            }
        }

from: Babylon.js/runtimeAnimation.ts at master · BabylonJS/Babylon.js · GitHub

so if you block the thread, the animation frame will continue and when the thread will get back to life it will run a lot of events in a row

Maybe we can explain that more in the doc I agree (cc @PirateJC)

1 Like

I think that this is ok, if indeed you jumped from frame 4 to frame 15 (for whatever reason) between two calls to the animation engine.

What is less obvious is that the actions you may have attached to frames 5…14 will not be executed in this order (as the PG demonstrates when the actions are pushed in reverse order in the action array). Instead, you could have action of frame 7 called before action of frame 5, which could be a problem if you expected your actions to be called in the frame ascending order.

1 Like

In fact, I realize the animation code is not working as I expect / I’m used to, so my comments above may not applied…

What I expected/was used to is:

  • frame 4 has been handled at time t
  • then there’s a delay of 1s (for whatever reason)
  • so the next time the animation code is called (time t+1s) it determines it must handle frame 15
    • then all actions registered for frames 5 to 15 (if any) are called in sequence and in order without any delay. Frames 5 to 14 have not been able to be displayed because of the stuttering, but the actions linked to them should still be executed. As we were not able to do it before frame 15, we do it when handling frame 15.

Well by reading what you expect I think this is correct. What is not done the way you think it was?

The order of the calls are 3/2/1 instead of being 1/2/3 after the stuttering simulation (the events are indeed all called at once, I missed that previously).

That’s because the events are processed in the order they have been pushed in the event array.

As you said, an update of the doc would be nice, and maybe adding a flag on an animation that would sort the event array each time a new event is added/modified could be worthwhile?

In short, i expected that when

anim.addEvent(new AnimationEvent(frame, ..)),

Babylon will sort underlying events list by {frame}. Such that pending frames will eventually get called in ascending order rather than in chronological order.

I’m fine with that!
Do you mind sending a PR?

Ignore my post, I missed something crucial :slight_smile: Will support any method of event ordering

1 Like