[Solved] Calling reset on an AnimChannel in the onAnimCycleDone causes a nullptr exception

I have this code for the onAnimCycleDone method which is also in a control :

@Override
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
    Animations anim = Animations.looseValueOf(animName);
    switch (anim) {
        case ATTACK:
        case HURT:
            channel.reset(false);
    }
}

I have three animation channels. One for each of the following animations : hurt, idle and attack. Yes I know that I should use channels with bones in mind instead of animations in mind. However, I have a setup, more specifically an insertion sort that sorts the channels in the AnimControl so that the most prioritary channels are at the beginning of the list. It works atually.

My problem is that calling reset will then result in an nullptr exception in the channel’s update method.

void update(float tpf, TempVars vars) {
    if (animation == null)
        return;

    if (blendFrom != null && blendAmount != 1.0f){
        // The blendFrom anim is set, the actual animation
        // playing will be set 
//      blendFrom.setTime(timeBlendFrom, 1f, control, this, vars);
        blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars);
        
        timeBlendFrom += tpf * speedBlendFrom;
        timeBlendFrom = AnimationUtils.clampWrapTime(timeBlendFrom,
                                      blendFrom.getLength(),
                                      loopModeBlendFrom);
        if (timeBlendFrom < 0){
            timeBlendFrom = -timeBlendFrom;
            speedBlendFrom = -speedBlendFrom;
        }

        blendAmount += tpf * blendRate;
        if (blendAmount > 1f){
            blendAmount = 1f;
            blendFrom = null;
        }
    }
    
    animation.setTime(time, blendAmount, control, this, vars);
    time += tpf * speed;      
    if (animation.getLength() > 0){
        if (!notified && (time >= animation.getLength() || time < 0)) {
            if (loopMode == LoopMode.DontLoop) {
                // Note that this flag has to be set before calling the notify
                // since the notify may start a new animation and then unset
                // the flag.
                notified = true;
            }
            control.notifyAnimCycleDone(this, animation.getName());
        } 
    }
    // HERE IS THE BUG! ANIMATION == NULL 
    time = AnimationUtils.clampWrapTime(time, animation.getLength(), loopMode);
    if (time < 0){
        // Negative time indicates that speed should be inverted
        // (for cycle loop mode only)
        time = -time;
        speed = -speed;
    }
}

At the line

time = AnimationUtils.clampWrapTime(time, animation.getLength(), loopMode);

animation is null even though there was a check at the beginning of the method. Does that mean I have a multithreading problem here? How can I set the animation to null (I am debugging something and I need to the animation to null)?

You’ve left out all of the useful information from the NullPointerException.

99.999999% of the useful information is the stack trace. So you’ve essentially not even mentioned it at all.

IntelliJ’s exported threads’ stack traces.

“main@1” prio=5 tid=0x1 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at com.jme3.animation.AnimChannel.update(AnimChannel.java:357)
at com.jme3.animation.AnimControl.controlUpdate(AnimControl.java:404)
at com.jme3.scene.control.AbstractControl.update(AbstractControl.java:128)
at com.jme3.scene.Spatial.runControlUpdate(Spatial.java:737)
at com.jme3.scene.Spatial.updateLogicalState(Spatial.java:880)
at com.jme3.scene.Node.updateLogicalState(Node.java:230)
at com.jme3.scene.Node.updateLogicalState(Node.java:241)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:242)
at com.jme3.system.lwjgl.LwjglWindow.runLoop(LwjglWindow.java:367)
at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:450)
at com.jme3.system.lwjgl.LwjglWindow.create(LwjglWindow.java:295)
at com.jme3.app.LegacyApplication.start(LegacyApplication.java:463)
at com.jme3.app.LegacyApplication.start(LegacyApplication.java:424)
at com.jme3.app.SimpleApplication.start(SimpleApplication.java:125)
at com.chevreuilgames.retroflashyrpg.Game_Main.main(Game_Main.java:41)

“TimerQueue@2656” daemon prio=5 tid=0x18 nid=NA waiting
java.lang.Thread.State: WAITING
at jdk.internal.misc.Unsafe.park(Unsafe.java:-1)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:194)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2062)
at java.util.concurrent.DelayQueue.take(DelayQueue.java:217)
at javax.swing.TimerQueue.run(TimerQueue.java:171)
at java.lang.Thread.run(Thread.java:844)

“Common-Cleaner@3944” daemon prio=8 tid=0xb nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at jdk.internal.ref.CleanerImpl.run(CleanerImpl.java:148)
at java.lang.Thread.run(Thread.java:844)
at jdk.internal.misc.InnocuousThread.run(InnocuousThread.java:122)

“jME3 Audio Decoder@3580” daemon prio=6 tid=0x19 nid=NA sleeping
java.lang.Thread.State: TIMED_WAITING
at java.lang.Thread.sleep(Thread.java:-1)
at com.jme3.audio.openal.ALAudioRenderer.run(ALAudioRenderer.java:261)
at java.lang.Thread.run(Thread.java:844)

“AWT-Windows@1342” daemon prio=6 tid=0x13 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at sun.awt.windows.WToolkit.eventLoop(WToolkit.java:-1)
at sun.awt.windows.WToolkit.run(WToolkit.java:297)
at java.lang.Thread.run(Thread.java:844)

“Java2D Disposer@1337” daemon prio=10 tid=0x11 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:172)
at sun.java2d.Disposer.run(Disposer.java:144)
at java.lang.Thread.run(Thread.java:844)

“Finalizer@3947” daemon prio=8 tid=0x3 nid=NA waiting
java.lang.Thread.State: WAITING
at java.lang.Object.wait(Object.java:-1)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:172)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

“Reference Handler@3948” daemon prio=10 tid=0x2 nid=NA runnable
java.lang.Thread.State: RUNNABLE
at java.lang.ref.Reference.waitForReferencePendingList(Reference.java:-1)
at java.lang.ref.Reference.processPendingReferences(Reference.java:174)
at java.lang.ref.Reference.access$000(Reference.java:44)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:138)

“Attach Listener@3945” daemon prio=5 tid=0x5 nid=NA runnable
java.lang.Thread.State: RUNNABLE

“Signal Dispatcher@3946” daemon prio=9 tid=0x4 nid=NA runnable
java.lang.Thread.State: RUNNABLE

No, the stack trace of the null pointer exception.

Where did you see the null pointer exception that it didn’t have a stack trace?!?

Oops sorry :

GRAVE: Uncaught exception thrown in Thread[main,5,main]
java.lang.NullPointerException
at com.jme3.animation.AnimChannel.update(AnimChannel.java:357)
at com.jme3.animation.AnimControl.controlUpdate(AnimControl.java:404)
at com.jme3.scene.control.AbstractControl.update(AbstractControl.java:128)
at com.jme3.scene.Spatial.runControlUpdate(Spatial.java:737)
at com.jme3.scene.Spatial.updateLogicalState(Spatial.java:880)
at com.jme3.scene.Node.updateLogicalState(Node.java:230)
at com.jme3.scene.Node.updateLogicalState(Node.java:241)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:242)
at com.jme3.system.lwjgl.LwjglWindow.runLoop(LwjglWindow.java:367)
at com.jme3.system.lwjgl.LwjglWindow.run(LwjglWindow.java:450)
at com.jme3.system.lwjgl.LwjglWindow.create(LwjglWindow.java:295)
at com.jme3.app.LegacyApplication.start(LegacyApplication.java:463)
at com.jme3.app.LegacyApplication.start(LegacyApplication.java:424)
at com.jme3.app.SimpleApplication.start(SimpleApplication.java:125)
at com.chevreuilgames.retroflashyrpg.Game_Main.main(Game_Main.java:41)

After testing to use a boolean and then call reset in the update method instead of directly calling it in the onAnimCycleDone, I found out that it fixed my problem.

However, conceptually, I need to find an alternative to my concept of bone priority.

@Override
public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
    Animations anim = Animations.looseValueOf(animName);
    switch (anim) {
        case ATTACK:
        case HURT:
            test = true;
        //    channel.reset(false);
            break;
    }
}

@Override
public void update(float tpf) {
    super.update(tpf);
    detectAndPlayAnimation();

    if (test) {
        getAnimChannel(Animations.HURT.getBonePriority()).reset(false);
        getAnimChannel(Animations.ATTACK.getBonePriority()).reset(false);
        test = false;
    }
}