Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tickers, Timers, Tests, and FPS #131357

Open
matthew-carroll opened this issue Jul 26, 2023 · 3 comments
Open

Tickers, Timers, Tests, and FPS #131357

matthew-carroll opened this issue Jul 26, 2023 · 3 comments
Labels
a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. P2 Important issues not at the top of the work list team-framework Owned by Framework team triaged-framework Triaged by Framework team

Comments

@matthew-carroll
Copy link
Contributor

In Flutter widgets, Timers are a problem because they aren't controlled by Flutter's test runner. Instead, Flutter provides Tickers, which hook into the Flutter pipeline, and therefore they can be controlled in tests. Generally speaking, any Timer behavior will produce unpredictable test results and therefore shouldn't be used in widgets.

However, there seems to be an issue with relying on Tickers for time tracking, too. Namely, Tickers seem to continuously schedule new frames, and force Flutter into a full FPS execution, which, at best, is a big waste of device energy.

Here's a real-world example. In Super Editor, we flash a text caret on and off every 0.5 seconds. To do this, we could use a Timer, but as mentioned above, Timers don't play well with tests. Therefore, we register a Ticker and observe the passage of time. When we see 0.5 seconds pass, we show or hide the caret.

void _onTick(Duration elapsedTime) {
    if (elapsedTime - _lastBlinkTime >= _flashPeriod) {
      _blink();
      _lastBlinkTime = elapsedTime;
    }
  }

Apparently, by using a Ticker, Flutter constantly schedules frames, yielding 58 unnecessary frames per second. Moreover, this constant frame scheduling prevents mobile devices from entering low-frame-rate mode.

Is there a canonical solution to this Ticker problem? If not, I think Flutter needs a way to handle the passage of time, which integrates with tests, and also doesn't schedule unnecessary frames.

@jonahwilliams
Copy link
Member

In Flutter widgets, Timers are a problem because they aren't controlled by Flutter's test runner.

This is sort of true, but in theory in unit tests the fake_async should provide some level of control over times. Though I admit its been a while since I looked at this.

The framework blinking cursors use Timers to avoid the frame scheduling.

@darshankawar darshankawar added in triage Presently being triaged by the triage team a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. team-framework Owned by Framework team and removed in triage Presently being triaged by the triage team labels Jul 27, 2023
@matthew-carroll
Copy link
Contributor Author

@jonahwilliams - I can't tell if that comment is meant to be actionable or not. Is there something I'm supposed to do to make a Timer work for these tests? And what's a "unit test"? Are you using that term to refer to widget tests?

@esDotDev
Copy link

esDotDev commented Sep 14, 2023

This problem also manifests when using a long-running animation that has delays built into it. This is very easy to trigger using something like flutter_animate, the following code will turn the app into an immediate mode gui ticking every 60fps forever:

 Widget build(BuildContext context) => MaterialApp(
        home: Center(
          child: FlutterLogo(size: 200)
              .animate( onPlay: (c) => c.repeat())
              .shimmer(delay: 2.seconds, duration: 1.seconds),
        ),
      );

Despite the animation being inactive 2/3s of the time, it is always rendering:

267720253-2e2f5674-6f28-4b26-ae59-d7bff78f6188.mp4

We're playing with the idea of creating an optimized Ticker that can switch between using a Timer or the SchedulerBinding to schedule ticks. This way the TickerProvider could set .shouldScheduleFrame to false when wanting to avoid wasteful renders. This allows you to use an AnimationController and the various downstream builders, but avoid re-rendering when you know nothing is changing.

This would be relatively easy except that the _tick method inside of Ticker is marked as private so there is no way to call it from a sub-class. Would the team be open to marking _tick as @protected to enable this sort of extension? As-is, the only option appears to be to re-implement the entire class :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: tests "flutter test", flutter_test, or one of our tests framework flutter/packages/flutter repository. See also f: labels. P2 Important issues not at the top of the work list team-framework Owned by Framework team triaged-framework Triaged by Framework team
Projects
None yet
Development

No branches or pull requests

5 participants