kirill-grouchnikov / trident

Java animation library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

NPE in CorePropertyInterpolators

opened this issue · comments

I keep getting a NPE thrown by the CorePropertyInterpolators. So far it is happening intermittently and I haven't found a way to reliably reproduce it, but I figured I would bring it up anyway.

Exception occurred in updating field 'alpha' of object org.jdesktop.jxlayer.JXLayer at timeline position 0.41666666
java.lang.NullPointerException
at org.pushingpixels.trident.interpolator.CorePropertyInterpolators$FloatPropertyInterpolator.interpolate(CorePropertyInterpolators.java:59)
at org.pushingpixels.trident.interpolator.CorePropertyInterpolators$FloatPropertyInterpolator.interpolate(CorePropertyInterpolators.java:50)
at org.pushingpixels.trident.TimelinePropertyBuilder$GenericFieldInfoTo.updateFieldValue(TimelinePropertyBuilder.java:334)
at org.pushingpixels.trident.Timeline$Setter.onTimelineStateChanged(Timeline.java:143)
at org.pushingpixels.trident.Timeline$Chain$1.run(Timeline.java:207)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)

I am getting/setting the value with a PropertyAccessor like this:

PropertyAccessor alphaAccessor = new PropertyAccessor() {

  @Override
  public Float get(Object object, String propertyName) {
    return alpha;
  }

  @Override
  public void set(Object object, String propertyName, Float value) {
    alpha = value;
    layer.repaint(clip);
  }

};

The alpha property being modified is a primitive float value being changed from 0.0 to 1.0. This timeline is played forward or backward to fade in or out a BusyPainter painted on a blurred LockableUI over a JXLayer. I have an application that wraps a bunch of panels in these and when they are busy loading data from a server they lock, blur, and fade in a busy painter animation. During application startup I currently have 7 or 8 panels that load their data simultaneously and it so far that seems to be the only time I have seen this occur. I have tried a bit to simulate the work to create a demo of the problem but haven't managed to recreate the issue though.

This is the entire method:

@OverRide
public Float interpolate(Float from, Float to, float timelinePosition) {
return from + (to - from) * timelinePosition;
}

So the only way it can throw an NPE is that either "from" or "to" are null. Is it possible that you're passing a null Float object when you initialize the timeline?

private float alpha;
public BusyPanel(JComponent view) {
...

// Create the above mentioned PropertyAccessor.

// Create the timeline that fades in/out the busy icon and busyMessage.
fadeIn = new Timeline(layer);
fadeIn.setDuration(300);
fadeIn.addPropertyToInterpolate(Timeline.property("alpha").fromCurrent().to(1.0f).accessWith(alphaAccessor));

...
}

If I change the fadeIn.addPropertyToInterpolate method to this:

fadeIn = new Timeline(layer);
fadeIn.setDuration(300);
fadeIn.addPropertyToInterpolate(Timeline.property("alpha").from(0.0f).to(1.0f));

I then get this exception:

Exception occurred in updating field 'alpha' of object org.jdesktop.jxlayer.JXLayer at timeline position 1.0
java.lang.RuntimeException: Unable to set the value of the field 'alpha'
at org.pushingpixels.trident.TimelinePropertyBuilder$DefaultPropertySetter.set(TimelinePropertyBuilder.java:75)
at org.pushingpixels.trident.TimelinePropertyBuilder$GenericFieldInfo.updateFieldValue(TimelinePropertyBuilder.java:368)
at org.pushingpixels.trident.Timeline$Setter.onTimelineStateChanged(Timeline.java:143)
at org.pushingpixels.trident.Timeline$Chain$1.run(Timeline.java:207)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)
Caused by: java.lang.NullPointerException
at org.pushingpixels.trident.TimelinePropertyBuilder$DefaultPropertySetter.set(TimelinePropertyBuilder.java:73)
... 11 more

So in the first one I'm attempting to access the current value of a primitive type for the from part, and the to part is going to a fixed 1.0f value. The exception doesn't seem to occur at every get or set. For the run I just tested it threw 3 exceptions at timeline position 0.52, 0.0, and 0.52 again. I ran it again without changing anything, and this time it only threw 1 exception at timeline position 0.4166667. And a 3rd time gave me 5 exceptions at timeline positions 0.0 and 0.52.

After changing the add property to interpolate method and starting up the app it throws 2 exceptions at timeline position 0.0 and 0.5233333, on another startup 5 exceptions at 0.0, and 0.52.

Since my interpolated property is a primitive float, I can't set it to null, and it looks like sometimes the timeline makes it part way through before not being able to set the value or encountering a NPE. I don't see anything that would set the interpolated values to null though.

I can only suggest building a local copy of Trident, adding logging statements to see when the different fields (setter, from, to) are set to null.

sounds good, I'm on it. Will report back here whatever I discover. Thanks for your help.

The value that is causing the NPE is the "from" property in the GenericFieldInfoTo inner class in the TimelinePropertyBuilder class. It looks like the onStart method is not being called for some reason.

I'm starting my timelines like this:

private void startAnimation() {
if(startScenario != null) {
startScenario.cancel();
}

fadePause.resetDoneFlag();
fadeIn.resetDoneFlag();

startScenario = new TimelineScenario.RendezvousSequence();
startScenario.addScenarioActor(fadePause);
startScenario.rendezvous();
startScenario.addScenarioActor(fadeIn);
started = true;
startScenario.play();

}

and stopping them like this:

private void stopAnimation() {
started = false;
if(startScenario != null && (startScenario.getState() == TimelineScenarioState.PLAYING || startScenario.getState() == TimelineScenarioState.SUSPENDED)) {
startScenario.cancel();
}

// Start fading out if we are running and not already in the process of fading out.
if(running && fadeOut.getState() != TimelineState.PLAYING_FORWARD) {
  fadeOut.play();
}

}

I tracked this back to the Setter class in Timeline and have thrown in some println's to see what is getting called and what states are being passed. The output I'm getting looks someting like this.

maybe call onStart for timeline: fadeInTimeline when oldState = PLAYING_FORWARD and newState = CANCELLED with durationFraction = 0.10333333 and timelinePosition = 0.10333333

maybe call onStart for timeline: fadeInTimeline when oldState = CANCELLED and newState = IDLE with durationFraction = 0.10333333 and timelinePosition = 0.10333333

Those are the only 2 transitions I'm seeing. I'm not getting a start up of READY to PLAYING_FORWARD before the PLAYING_FORWARD to CANCELLED. I also thought there should be an IDLE to READY after the CANCELLED but I'm not positive thats right.

Anyway, I haven't tracked it back further than that yet, but I thought I'd give an update. It could easily still be due to something I'm doing wrong. I also commented out the stopAnimation call in my code to narrow the problem down to just the animation startup side and still have the NPE.

Ok, I modified the onTimelineStateChanged method in the Chain inner class in the Timeline class to look like this.

public void onTimelineStateChanged(final TimelineState oldState,
final TimelineState newState, final float durationFraction,
final float timelinePosition) {

  if("fadeInTimeline".equals(name)) {
    System.out.println("?????????????????  onTimelineStateChanged: " + oldState + ", " + newState + ", " + durationFraction + ", " + timelinePosition + ", name = " + name);
  }

  if((uiToolkitHandler != null) && !uiToolkitHandler.isInReadyState(mainObject)) {
    if("fadeInTimeline".equals(name)) {
      if(uiToolkitHandler == null)
        System.out.println("uiToolkitHandler was null...BAILING OUT");
      if(!uiToolkitHandler.isInReadyState(mainObject))
        System.out.println("main object was not in ready state...BAILING OUT");
    }
    return;
  }

  for(int i = this.callbacks.size() - 1; i >= 0; i--) {
    final TimelineCallback callback = this.callbacks.get(i);

    if("fadeInTimeline".equals(name))
    System.out.println("callback " + callback.toString());

    // special handling for chained callbacks not running on UI thread
    boolean shouldRunOnUIThread = false;
    Class<?> clazz = callback.getClass();
    while((clazz != null) && !shouldRunOnUIThread) {
      shouldRunOnUIThread = clazz.isAnnotationPresent(RunOnUIThread.class);
      clazz = clazz.getSuperclass();
    }

    if(shouldRunOnUIThread && (Timeline.this.uiToolkitHandler != null)) {
      Timeline.this.uiToolkitHandler.runOnUIThread(mainObject,
          new Runnable() {

            public void run() {
              if("fadeInTimeline".equals(name)) {
                System.out.println("?????????????  running callback.onTimelineStateChanged now on the UI Thread!!!! : " + oldState + ", " + newState + ", " + durationFraction + ", " + timelinePosition + ", name = " + name);
              }
              callback.onTimelineStateChanged(oldState, newState, durationFraction, timelinePosition);
            }

          });
    } else {
      if("fadeInTimeline".equals(name)) {
        System.out.println("?????????????  running callback.onTimelineStateChanged now!!!! : " + oldState + ", " + newState + ", " + durationFraction + ", " + timelinePosition + ", name = " + name);
      }
      callback.onTimelineStateChanged(oldState, newState, durationFraction, timelinePosition);
    }
  }
}

After starting up my app I see this:

????????????????? onTimelineStateChanged: IDLE, READY, 0.0, 0.0, name = fadeInTimeline
main object was not in ready state...BAILING OUT
????????????????? onTimelineStateChanged: READY, PLAYING_FORWARD, 0.0, 0.0, name = fadeInTimeline
main object was not in ready state...BAILING OUT

... some other stuff ...

????????????????? onTimelineStateChanged: IDLE, READY, 0.0, 0.0, name = fadeInTimeline
main object was not in ready state...BAILING OUT
Load Initial Layout called...
????????????????? onTimelineStateChanged: READY, CANCELLED, 0.0, 0.0, name = fadeInTimeline
callback org.pushingpixels.trident.Timeline$UISetter@7f9e04
????????????????? onTimelineStateChanged: CANCELLED, IDLE, 0.0, 0.0, name = fadeInTimeline
callback org.pushingpixels.trident.Timeline$UISetter@7f9e04
????????????????? onTimelineStateChanged: PLAYING_FORWARD, CANCELLED, 0.41666666, 0.41666666, name = fadeInTimeline
callback org.pushingpixels.trident.Timeline$UISetter@5f39b0
????????????????? onTimelineStateChanged: CANCELLED, IDLE, 0.41666666, 0.41666666, name = fadeInTimeline
callback org.pushingpixels.trident.Timeline$UISetter@5f39b0

Then the callbacks hit...

????????????? running callback.onTimelineStateChanged now on the UI Thread!!!! : READY, CANCELLED, 0.0, 0.0, name = fadeInTimeline
????????????? running callback.onTimelineStateChanged now on the UI Thread!!!! : CANCELLED, IDLE, 0.0, 0.0, name = fadeInTimeline
????????????? running callback.onTimelineStateChanged now on the UI Thread!!!! : PLAYING_FORWARD, CANCELLED, 0.26, 0.26, name = fadeInTimeline
maybe call onStart for timeline: fadeInTimeline when oldState = PLAYING_FORWARD and newState = CANCELLED with durationFraction = 0.41666666 and timelinePosition = 0.41666666
???????? from = null, to = 1.0, timelinePosition = 0.41666666, for fieldName = alpha on object org.jdesktop.jxlayer.JXLayer[stuff...]
Exception occurred in updating field 'alpha' of object org.jdesktop.jxlayer.JXLayer at timeline position 0.41666666
java.lang.NullPointerException
...

At this point I've hit the NPE. This seems to have happened because no callback with the state IDLE to READY was ever run as the main object was not in the ready state, which means that the onStart() method that initializes the "from" value didn't run, which leaves it with a null value.

So it seems the NPE is being caused by my timelines being started before the components are done being displayed on the screen. Since it's my fault after all I'll close this issue.