• Runtimes
  • [spine-c] 3.5 - animation jerking

Related Discussions
...

Start animation with setAnimation. Have callback spineAnimStateHandler and on SP_ANIMATION_COMPLETE I call new setAnimation. I expect to start new animation from setupPose with applying first frame from new timelines. BUT, I can see setupPose first and only after update I can see first frame of a new animation.

Is it by design? Or bug? How can I fix it on my side? I need to start new animation from inside StateHandler ...

Do you have the latest? We committed a possibly related fix minutes ago.

Yeah, tested latest.

When you call setAnimation it changes the AnimationState. The Skeleton is not affected until the next time the AnimationState is applied to the skeleton. Maybe I misunderstand the issue? Can you modify one of the examples to show the problem?

I have following order:
spSkeleton_update
spAnimationState_update
spAnimationState_apply

Here apply calls _spEventQueue_drain and listener ... on my side on listener I call spAnimationState_setAnimationByName

Now my Update() loop completed and I Draw() everything ...

So, should I call spAnimationState_apply again after I set animation?

Yes, that is correct if you do not want to wait until the next frame for your changes to the AnimationState to be applied to the skeleton. We have to fire skeleton events in apply, since we don't know what skeletons the AnimationState will be applied to in AnimationState update.

4 jours plus tard

Ok, now another bug, I have single "idle" animation with 0 frames (ie same as setup pose), call order is:

spSkeleton_update
spAnimationState_update
spAnimationState_apply ->
  _spEventQueue_drain -> 
    callback hook -> 
      spAnimationState_setAnimationByName( set same "idle" animation )
      spAnimationState_apply 

at _spAnimationState_applyMixingFrom I get stack overflow on this line "if (from->mixingFrom) _spAnimationState_applyMixingFrom(self, from, skeleton);"

What callback are you using? If your setAnimationByName triggers the callback then it's doing what you asked until it runs out of stack space.

I use onComplete, want to make loop (I need to control it manually). As I said recursion happens at _spAnimationState_applyMixingFrom after spAnimationState_apply. I call it only once.

Can you modify one of the samples to reproduce the issue?

Ok, add this code into MemoryTestFixture


spSkeleton* skeleton = nullptr;

static void   spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event)
{
    if (type == SP_ANIMATION_COMPLETE)
    {
        spAnimationState_setAnimationByName(state, 0, "walk", false);
        spAnimationState_apply(state, skeleton);
    }
}

void MemoryTestFixture::reproduceIssue_Loop()
{
    spAtlas* atlas = nullptr;
    spSkeletonData* skeletonData = nullptr;
    spAnimationStateData* stateData = nullptr;
    spAnimationState* state = nullptr;

//////////////////////////////////////////////////////////////////////////
// Initialize Animations
LoadSpineboyExample(atlas, skeletonData, stateData, skeleton, state);

///////////////////////////////////////////////////////////////////////////

if (state)
    state->listener = (spAnimationStateListener)&spineAnimStateHandler;

spAnimationState_setAnimationByName(state, 0, "walk", false);

// run normal update
for (int i = 0; i < 50; ++i) {
    const float timeSlice = 1.0f / 60.0f;
    spSkeleton_update(skeleton, timeSlice);
    spAnimationState_update(state, timeSlice);
    spAnimationState_apply(state, skeleton);
}
}

Also, don't forget to modify header:
TEST_CASE(reproduceIssue_Loop);
and
void reproduceIssue_Loop();

Sorry, I mislead you when I said you should call apply. If it makes you feel better, badlogic and I suffered to figure out why. 😉 When you call apply, by design it does not have any side effects (though your callbacks may). This allows you to call update, then call apply on any number of skeletons. If an animation has completed, each apply will trigger a complete event. The fix for your stack overflow then is to call update to advance the AnimationState internals so they know that the complete has already happened:

spAnimationState_setAnimationByName(state, 0, "walk", false);
spAnimationState_update(state, 0);
spAnimationState_apply(state, skeleton);

Actually it looks like a hack ... this magic sequence of calls.


I think it should be done on API side, I just want to set new animation without any knowledge of your internals with update/apply logic.

No, not at all. You change the animation, but you want the skeleton modified right away, rather than wait until the next frame. This means you want to apply the animation right after changing it. To do that you advance the AnimationState to the next frame then you apply it. No hacks here.


These aren't internals. The update and applying of the AnimationState are public API and your responsibility. The only way we could remove the need to call update would be to give apply side effects. This would mean it could only be applied to a single skeleton, a second apply with no update between would not fire the complete event. It's not super common, but it can be useful to have a single AnimationState applied to many skeletons. Imagine a marching army of 100 skeletons, an AnimationState for each one may be overkill or otherwise eat too much CPU. You could use just a few AnimationStates to animate them all, randomly assigned so it's hard to notice some have the same pose.

It's true this may be a kind of API surprise, but at least you know right away (stack overflow) and it makes sense when you consider how update and apply work (which you should).

Ok, with update - I get exactly same stack overflow. Any ideas? This happens on animation with zero frames (on my side only now).

Ah, in that case think what happens


every time you apply a zero duration animation, complete will fire. You may want to deregister your listener before setting a new animation, or set the listener on a specific TrackEntry rather than the whole AnimationState.

Just edit Spineboy.json and add "empty": {} inside "animation".


Changed code to looks like this, same stack overflow, any ideas?

static void   spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event)
{
    if (!handlerEnabled) return;
    handlerEnabled = false;
    if (type == SP_ANIMATION_COMPLETE)
    {
        spAnimationState_setAnimationByName(state, 0, "empty", false);
        spAnimationState_update(state, 0);
        spAnimationState_apply(state, skeleton);
    }
    handlerEnabled = true;
}

As far as I can tell, it's working correctly. You've set a listener on the AnimationState which will be notified every time any animation event happens. With a zero duration animation, your listener gets a complete event every time the animation is applied. That happens when you call apply every frame, not just when you call apply from the listener. So, every frame you are getting a complete event and calling setAnimationByName. This builds up a long linked list of mixingFrom entries. When you get enough of those, applying the mixing from animations runs out of stack space and crashes.

See TrackEntry listener. This way you can set a listener that handles the complete on an animation but is not notified for any other animations. Or, deregister your listener using AnimationState removeListener before setting a new animation.

Eg, to play run then when complete happens, play empty:

static void spineAnimStateHandler(spAnimationState* state, int type, spTrackEntry* entry, spEvent* event) {
    if (type != SP_ANIMATION_COMPLETE) return;
    spAnimationState_setAnimationByName(state, 0, "empty", false);
    spAnimationState_update(state, 0);
    spAnimationState_apply(state, skeleton);
}
...
TrackEntry* entry = spAnimationState_setAnimationByName(state, 0, "run", true);
entry.listener = (spAnimationStateListener)&spineAnimStateHandler;

Of course for such a simple example you would use AnimationState addAnimation:

spAnimationState_setAnimationByName(state, 0, "run", true);
spAnimationState_addAnimationByName(state, 0, "empty", true, 0);

Hm ... I need to get events for all animations, so I can't use trackentry listener. And it's hard to unregister/register handler on my side, as I use complex wrapper around Spine API ... so, looks like it almost impossible to change animation from inside listener ... need to think.

Anyway, it works for non-empty animation, but for empty - crash ...