2d-inc/Flare-Flutter

Animation to specific frame consuming too much CPU

ricsmania opened this issue · 8 comments

I'm creating a progress percentage animation, where I have an animation with a total of 100 frames at 60 FPS, each frame corresponding to 1%, so I want to animate until a specific frame and then stop.

I got this working by doing this:

class ProgressCircleState extends State<ProgressCircle>
    implements FlareController {
  ActorAnimation _animation;

  @override
  Widget build(BuildContext context) {
    return FlareActor(
      ...
      controller: this,
    );
  }

  @override
  bool advance(FlutterActorArtboard artboard, double elapsed) {
    _animation.apply(widget.progress / 60, artboard, .05);
    return true;
  }

  @override
  void initialize(FlutterActorArtboard artboard) {
    _animation = artboard.getAnimation("progress");
  }

  @override
  void setViewTransform(Mat2D viewTransform) {}
}

It works, but the problem is that even after the animation reaches the frame that I need, it continues running the code at every frame and that is consuming a lot of CPU on the device.

I have noticed that if I only set the animation property when creating the actor, and let it run to 100%, it doesn't consume any CPU after it's done.

Is there a more efficient way of doing that instead of doing it frame by frame? Or maybe detect that it's completed so that I can pause it?

Hi @ricsmania, you can return false in the advance function when you are done advancing. We need to document that and also add a way to start it back up, but for now that should allow you to save some CPU cycles :) let me know if that works!

Hi @luigi-rosso I'm using a workaround that involves returning false, but I create a timer that sets a variable after 2 seconds, and if that variable is true I return false. I was looking for a good way to detect that the animation has finished without the timer, do you have a suggestion? I thought that checking if the elapsed is >= the duration would do that, but it doesn't seem to work.

Hey @ricsmania!

The easiest way to do this is to use a local variable for your animation, and increment it by elapsed every time advance() is called.

Here's what I mean:

// Local field in your controller.
double animationTime = 0.0;

@override
bool advance(FlutterActorArtboard artboard, double elapsed) {
  animationTime += elapsed;
  if(animationTime >= _animation.duration) {
    return false;
  }
  _animation.apply(animationTime, artboard, .05);
  return true;
}

Another thing you could do is to just set the animation to not loop in Flare

Lastly, if you have any other doubts regarding Controllers, you can check out our tutorial here!

Hi @umberto-sonnino, thanks for your help!

Unfortunately it didn't do it for me. I tried using the elapsed time, but the animation always stops a few frames short of finishing. I think it's the mix value that is causing that, but I couldn't find a combination that has a smooth animation and stops on the correct frame.

I also tried counting the frames, but I noticed that it's not always 1:1, because there are a few dropped frames.

Also the animation is already set to not loop in Flare, but I guess that because I'm using a custom controller that has no effect.

What I did for now was using the total elapsed time check, but instead of getting the duration I get the duration * 2. It's not ideal but I guess it's better than using a timer.

The logic above will actually miss the last apply, try something like this instead and use a full mix value. Is there a reason you were using a mix of .05? That will barely mix the animation in.

@override
bool advance(FlutterActorArtboard artboard, double elapsed) {
  animationTime += elapsed;
  _animation.apply(animationTime, artboard, 1.0);
  return animationTime < _animation.duration;
}

@luigi-rosso that seems to work. I have several animations, I tried with the simplest one and it worked. I'll try with the most complex ones, and if it works I'll close the issue.

But now I see that I misunderstood the advance method. On the first code sample I send the widget.progress variable is always the same, so I'm actually always applying the same value. That was kind of trial and error to get it working, that's why I chose the 0.05 mix, but somehow it worked. I wasn't even using the elapsed time, so I guess by applying a very small mix several times it will eventually get to the final value.

I got to this point by basically trying to reverse engineer the examples and many hours of trial and error. Flare is great and so is the Flutter implementation, the only thing that I find lacking is the documentation, especially on custom controllers.

Anyway, thank you both so much for your help so far.

It worked, now I can stop exactly on the correct frame and stop executing after that. I'm closing the issue. Thanks again!

You're right, we definitely need better documentation around how applying and mixing animations works.

And yes, you're right the mix operation is cumulative because every frame it interpolates the current values to the next target ones which you are applying. So by keeping the animation running for a long time, you'll eventually get there (or at least very very close). Think of mix as a way to interpolate your animations. It's really just another layer of interpolation on top of the current (on your actor hierarchy) values and target (computed key values in your animation) values.