1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6import 'dart:collection';
7import 'dart:developer' show Flow, Timeline, TimelineTask;
8import 'dart:ui' show AppLifecycleState, DartPerformanceMode, FramePhase, FrameTiming, PlatformDispatcher, TimingsCallback;
9
10import 'package:collection/collection.dart' show HeapPriorityQueue, PriorityQueue;
11import 'package:flutter/foundation.dart';
12
13import 'debug.dart';
14import 'priority.dart';
15import 'service_extensions.dart';
16
17export 'dart:ui' show AppLifecycleState, FrameTiming, TimingsCallback;
18
19export 'priority.dart' show Priority;
20
21/// Slows down animations by this factor to help in development.
22double get timeDilation => _timeDilation;
23double _timeDilation = 1.0;
24/// If the [SchedulerBinding] has been initialized, setting the time dilation
25/// automatically calls [SchedulerBinding.resetEpoch] to ensure that time stamps
26/// seen by consumers of the scheduler binding are always increasing.
27///
28/// It is safe to set this before initializing the binding.
29set timeDilation(double value) {
30 assert(value > 0.0);
31 if (_timeDilation == value) {
32 return;
33 }
34 // If the binding has been created, we need to resetEpoch first so that we
35 // capture start of the epoch with the current time dilation.
36 SchedulerBinding._instance?.resetEpoch();
37 _timeDilation = value;
38}
39
40/// Signature for frame-related callbacks from the scheduler.
41///
42/// The `timeStamp` is the number of milliseconds since the beginning of the
43/// scheduler's epoch. Use timeStamp to determine how far to advance animation
44/// timelines so that all the animations in the system are synchronized to a
45/// common time base.
46typedef FrameCallback = void Function(Duration timeStamp);
47
48/// Signature for [SchedulerBinding.scheduleTask] callbacks.
49///
50/// The type argument `T` is the task's return value. Consider `void` if the
51/// task does not return a value.
52typedef TaskCallback<T> = FutureOr<T> Function();
53
54/// Signature for the [SchedulerBinding.schedulingStrategy] callback. Called
55/// whenever the system needs to decide whether a task at a given
56/// priority needs to be run.
57///
58/// Return true if a task with the given priority should be executed at this
59/// time, false otherwise.
60///
61/// See also:
62///
63/// * [defaultSchedulingStrategy], the default [SchedulingStrategy] for [SchedulerBinding.schedulingStrategy].
64typedef SchedulingStrategy = bool Function({ required int priority, required SchedulerBinding scheduler });
65
66class _TaskEntry<T> {
67 _TaskEntry(this.task, this.priority, this.debugLabel, this.flow) {
68 assert(() {
69 debugStack = StackTrace.current;
70 return true;
71 }());
72 }
73 final TaskCallback<T> task;
74 final int priority;
75 final String? debugLabel;
76 final Flow? flow;
77
78 late StackTrace debugStack;
79 final Completer<T> completer = Completer<T>();
80
81 void run() {
82 if (!kReleaseMode) {
83 Timeline.timeSync(
84 debugLabel ?? 'Scheduled Task',
85 () {
86 completer.complete(task());
87 },
88 flow: flow != null ? Flow.step(flow!.id) : null,
89 );
90 } else {
91 completer.complete(task());
92 }
93 }
94}
95
96class _FrameCallbackEntry {
97 _FrameCallbackEntry(this.callback, { bool rescheduling = false }) {
98 assert(() {
99 if (rescheduling) {
100 assert(() {
101 if (debugCurrentCallbackStack == null) {
102 throw FlutterError.fromParts(<DiagnosticsNode>[
103 ErrorSummary('scheduleFrameCallback called with rescheduling true, but no callback is in scope.'),
104 ErrorDescription(
105 'The "rescheduling" argument should only be set to true if the '
106 'callback is being reregistered from within the callback itself, '
107 'and only then if the callback itself is entirely synchronous.',
108 ),
109 ErrorHint(
110 'If this is the initial registration of the callback, or if the '
111 'callback is asynchronous, then do not use the "rescheduling" '
112 'argument.',
113 ),
114 ]);
115 }
116 return true;
117 }());
118 debugStack = debugCurrentCallbackStack;
119 } else {
120 // TODO(ianh): trim the frames from this library, so that the call to scheduleFrameCallback is the top one
121 debugStack = StackTrace.current;
122 }
123 return true;
124 }());
125 }
126
127 final FrameCallback callback;
128
129 static StackTrace? debugCurrentCallbackStack;
130 StackTrace? debugStack;
131}
132
133/// The various phases that a [SchedulerBinding] goes through during
134/// [SchedulerBinding.handleBeginFrame].
135///
136/// This is exposed by [SchedulerBinding.schedulerPhase].
137///
138/// The values of this enum are ordered in the same order as the phases occur,
139/// so their relative index values can be compared to each other.
140///
141/// See also:
142///
143/// * [WidgetsBinding.drawFrame], which pumps the build and rendering pipeline
144/// to generate a frame.
145enum SchedulerPhase {
146 /// No frame is being processed. Tasks (scheduled by
147 /// [SchedulerBinding.scheduleTask]), microtasks (scheduled by
148 /// [scheduleMicrotask]), [Timer] callbacks, event handlers (e.g. from user
149 /// input), and other callbacks (e.g. from [Future]s, [Stream]s, and the like)
150 /// may be executing.
151 idle,
152
153 /// The transient callbacks (scheduled by
154 /// [SchedulerBinding.scheduleFrameCallback]) are currently executing.
155 ///
156 /// Typically, these callbacks handle updating objects to new animation
157 /// states.
158 ///
159 /// See [SchedulerBinding.handleBeginFrame].
160 transientCallbacks,
161
162 /// Microtasks scheduled during the processing of transient callbacks are
163 /// current executing.
164 ///
165 /// This may include, for instance, callbacks from futures resolved during the
166 /// [transientCallbacks] phase.
167 midFrameMicrotasks,
168
169 /// The persistent callbacks (scheduled by
170 /// [SchedulerBinding.addPersistentFrameCallback]) are currently executing.
171 ///
172 /// Typically, this is the build/layout/paint pipeline. See
173 /// [WidgetsBinding.drawFrame] and [SchedulerBinding.handleDrawFrame].
174 persistentCallbacks,
175
176 /// The post-frame callbacks (scheduled by
177 /// [SchedulerBinding.addPostFrameCallback]) are currently executing.
178 ///
179 /// Typically, these callbacks handle cleanup and scheduling of work for the
180 /// next frame.
181 ///
182 /// See [SchedulerBinding.handleDrawFrame].
183 postFrameCallbacks,
184}
185
186/// This callback is invoked when a request for [DartPerformanceMode] is disposed.
187///
188/// See also:
189///
190/// * [PerformanceModeRequestHandle] for more information on the lifecycle of the handle.
191typedef _PerformanceModeCleanupCallback = VoidCallback;
192
193/// An opaque handle that keeps a request for [DartPerformanceMode] active until
194/// disposed.
195///
196/// To create a [PerformanceModeRequestHandle], use [SchedulerBinding.requestPerformanceMode].
197/// The component that makes the request is responsible for disposing the handle.
198class PerformanceModeRequestHandle {
199 PerformanceModeRequestHandle._(_PerformanceModeCleanupCallback this._cleanup) {
200 // TODO(polina-c): stop duplicating code across disposables
201 // https://github.com/flutter/flutter/issues/137435
202 if (kFlutterMemoryAllocationsEnabled) {
203 FlutterMemoryAllocations.instance.dispatchObjectCreated(
204 library: 'package:flutter/scheduler.dart',
205 className: '$PerformanceModeRequestHandle',
206 object: this,
207 );
208 }
209 }
210
211 _PerformanceModeCleanupCallback? _cleanup;
212
213 /// Call this method to signal to [SchedulerBinding] that a request for a [DartPerformanceMode]
214 /// is no longer needed.
215 ///
216 /// This method must only be called once per object.
217 void dispose() {
218 assert(_cleanup != null);
219 // TODO(polina-c): stop duplicating code across disposables
220 // https://github.com/flutter/flutter/issues/137435
221 if (kFlutterMemoryAllocationsEnabled) {
222 FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
223 }
224 _cleanup!();
225 _cleanup = null;
226 }
227}
228
229/// Scheduler for running the following:
230///
231/// * _Transient callbacks_, triggered by the system's
232/// [dart:ui.PlatformDispatcher.onBeginFrame] callback, for synchronizing the
233/// application's behavior to the system's display. For example, [Ticker]s and
234/// [AnimationController]s trigger from these.
235///
236/// * _Persistent callbacks_, triggered by the system's
237/// [dart:ui.PlatformDispatcher.onDrawFrame] callback, for updating the
238/// system's display after transient callbacks have executed. For example, the
239/// rendering layer uses this to drive its rendering pipeline.
240///
241/// * _Post-frame callbacks_, which are run after persistent callbacks, just
242/// before returning from the [dart:ui.PlatformDispatcher.onDrawFrame] callback.
243///
244/// * Non-rendering tasks, to be run between frames. These are given a
245/// priority and are executed in priority order according to a
246/// [schedulingStrategy].
247mixin SchedulerBinding on BindingBase {
248 @override
249 void initInstances() {
250 super.initInstances();
251 _instance = this;
252
253 if (!kReleaseMode) {
254 addTimingsCallback((List<FrameTiming> timings) {
255 timings.forEach(_profileFramePostEvent);
256 });
257 }
258 }
259
260 /// The current [SchedulerBinding], if one has been created.
261 ///
262 /// Provides access to the features exposed by this mixin. The binding must
263 /// be initialized before using this getter; this is typically done by calling
264 /// [runApp] or [WidgetsFlutterBinding.ensureInitialized].
265 static SchedulerBinding get instance => BindingBase.checkInstance(_instance);
266 static SchedulerBinding? _instance;
267
268 final List<TimingsCallback> _timingsCallbacks = <TimingsCallback>[];
269
270 /// Add a [TimingsCallback] that receives [FrameTiming] sent from
271 /// the engine.
272 ///
273 /// This API enables applications to monitor their graphics
274 /// performance. Data from the engine is batched into lists of
275 /// [FrameTiming] objects which are reported approximately once a
276 /// second in release mode and approximately once every 100ms in
277 /// debug and profile builds. The list is sorted in ascending
278 /// chronological order (earliest frame first). The timing of the
279 /// first frame is sent immediately without batching.
280 ///
281 /// The data returned can be used to catch missed frames (by seeing
282 /// if [FrameTiming.buildDuration] or [FrameTiming.rasterDuration]
283 /// exceed the frame budget, e.g. 16ms at 60Hz), and to catch high
284 /// latency (by seeing if [FrameTiming.totalSpan] exceeds the frame
285 /// budget). It is possible for no frames to be missed but for the
286 /// latency to be more than one frame in the case where the Flutter
287 /// engine is pipelining the graphics updates, e.g. because the sum
288 /// of the [FrameTiming.buildDuration] and the
289 /// [FrameTiming.rasterDuration] together exceed the frame budget.
290 /// In those cases, animations will be smooth but touch input will
291 /// feel more sluggish.
292 ///
293 /// Using [addTimingsCallback] is preferred over using
294 /// [dart:ui.PlatformDispatcher.onReportTimings] directly because the
295 /// [dart:ui.PlatformDispatcher.onReportTimings] API only allows one callback,
296 /// which prevents multiple libraries from registering listeners
297 /// simultaneously, while this API allows multiple callbacks to be registered
298 /// independently.
299 ///
300 /// This API is implemented in terms of
301 /// [dart:ui.PlatformDispatcher.onReportTimings]. In release builds, when no
302 /// libraries have registered with this API, the
303 /// [dart:ui.PlatformDispatcher.onReportTimings] callback is not set, which
304 /// disables the performance tracking and reduces the runtime overhead to
305 /// approximately zero. The performance overhead of the performance tracking
306 /// when one or more callbacks are registered (i.e. when it is enabled) is
307 /// very approximately 0.01% CPU usage per second (measured on an iPhone 6s).
308 ///
309 /// In debug and profile builds, the [SchedulerBinding] itself
310 /// registers a timings callback to update the [Timeline].
311 ///
312 /// If the same callback is added twice, it will be executed twice.
313 ///
314 /// See also:
315 ///
316 /// * [removeTimingsCallback], which can be used to remove a callback
317 /// added using this method.
318 void addTimingsCallback(TimingsCallback callback) {
319 _timingsCallbacks.add(callback);
320 if (_timingsCallbacks.length == 1) {
321 assert(platformDispatcher.onReportTimings == null);
322 platformDispatcher.onReportTimings = _executeTimingsCallbacks;
323 }
324 assert(platformDispatcher.onReportTimings == _executeTimingsCallbacks);
325 }
326
327 /// Removes a callback that was earlier added by [addTimingsCallback].
328 void removeTimingsCallback(TimingsCallback callback) {
329 assert(_timingsCallbacks.contains(callback));
330 _timingsCallbacks.remove(callback);
331 if (_timingsCallbacks.isEmpty) {
332 platformDispatcher.onReportTimings = null;
333 }
334 }
335
336 @pragma('vm:notify-debugger-on-exception')
337 void _executeTimingsCallbacks(List<FrameTiming> timings) {
338 final List<TimingsCallback> clonedCallbacks =
339 List<TimingsCallback>.of(_timingsCallbacks);
340 for (final TimingsCallback callback in clonedCallbacks) {
341 try {
342 if (_timingsCallbacks.contains(callback)) {
343 callback(timings);
344 }
345 } catch (exception, stack) {
346 InformationCollector? collector;
347 assert(() {
348 collector = () => <DiagnosticsNode>[
349 DiagnosticsProperty<TimingsCallback>(
350 'The TimingsCallback that gets executed was',
351 callback,
352 style: DiagnosticsTreeStyle.errorProperty,
353 ),
354 ];
355 return true;
356 }());
357 FlutterError.reportError(FlutterErrorDetails(
358 exception: exception,
359 stack: stack,
360 context: ErrorDescription('while executing callbacks for FrameTiming'),
361 informationCollector: collector,
362 ));
363 }
364 }
365 }
366
367 @override
368 void initServiceExtensions() {
369 super.initServiceExtensions();
370
371 if (!kReleaseMode) {
372 registerNumericServiceExtension(
373 name: SchedulerServiceExtensions.timeDilation.name,
374 getter: () async => timeDilation,
375 setter: (double value) async {
376 timeDilation = value;
377 },
378 );
379 }
380 }
381
382 /// Whether the application is visible, and if so, whether it is currently
383 /// interactive.
384 ///
385 /// This is set by [handleAppLifecycleStateChanged] when the
386 /// [SystemChannels.lifecycle] notification is dispatched.
387 ///
388 /// The preferred ways to watch for changes to this value are using
389 /// [WidgetsBindingObserver.didChangeAppLifecycleState], or through an
390 /// [AppLifecycleListener] object.
391 AppLifecycleState? get lifecycleState => _lifecycleState;
392 AppLifecycleState? _lifecycleState;
393
394 /// Allows the test framework to reset the lifecycle state back to its
395 /// initial value.
396 @visibleForTesting
397 void resetLifecycleState() {
398 _lifecycleState = null;
399 }
400
401 /// Called when the application lifecycle state changes.
402 ///
403 /// Notifies all the observers using
404 /// [WidgetsBindingObserver.didChangeAppLifecycleState].
405 ///
406 /// This method exposes notifications from [SystemChannels.lifecycle].
407 @protected
408 @mustCallSuper
409 void handleAppLifecycleStateChanged(AppLifecycleState state) {
410 if (lifecycleState == state) {
411 return;
412 }
413 _lifecycleState = state;
414 switch (state) {
415 case AppLifecycleState.resumed:
416 case AppLifecycleState.inactive:
417 _setFramesEnabledState(true);
418 case AppLifecycleState.hidden:
419 case AppLifecycleState.paused:
420 case AppLifecycleState.detached:
421 _setFramesEnabledState(false);
422 }
423 }
424
425 /// The strategy to use when deciding whether to run a task or not.
426 ///
427 /// Defaults to [defaultSchedulingStrategy].
428 SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy;
429
430 static int _taskSorter (_TaskEntry<dynamic> e1, _TaskEntry<dynamic> e2) {
431 return -e1.priority.compareTo(e2.priority);
432 }
433 final PriorityQueue<_TaskEntry<dynamic>> _taskQueue = HeapPriorityQueue<_TaskEntry<dynamic>>(_taskSorter);
434
435 /// Schedules the given `task` with the given `priority`.
436 ///
437 /// If `task` returns a future, the future returned by [scheduleTask] will
438 /// complete after the former future has been scheduled to completion.
439 /// Otherwise, the returned future for [scheduleTask] will complete with the
440 /// same value returned by `task` after it has been scheduled.
441 ///
442 /// The `debugLabel` and `flow` are used to report the task to the [Timeline],
443 /// for use when profiling.
444 ///
445 /// ## Processing model
446 ///
447 /// Tasks will be executed between frames, in priority order,
448 /// excluding tasks that are skipped by the current
449 /// [schedulingStrategy]. Tasks should be short (as in, up to a
450 /// millisecond), so as to not cause the regular frame callbacks to
451 /// get delayed.
452 ///
453 /// If an animation is running, including, for instance, a [ProgressIndicator]
454 /// indicating that there are pending tasks, then tasks with a priority below
455 /// [Priority.animation] won't run (at least, not with the
456 /// [defaultSchedulingStrategy]; this can be configured using
457 /// [schedulingStrategy]).
458 Future<T> scheduleTask<T>(
459 TaskCallback<T> task,
460 Priority priority, {
461 String? debugLabel,
462 Flow? flow,
463 }) {
464 final bool isFirstTask = _taskQueue.isEmpty;
465 final _TaskEntry<T> entry = _TaskEntry<T>(
466 task,
467 priority.value,
468 debugLabel,
469 flow,
470 );
471 _taskQueue.add(entry);
472 if (isFirstTask && !locked) {
473 _ensureEventLoopCallback();
474 }
475 return entry.completer.future;
476 }
477
478 @override
479 void unlocked() {
480 super.unlocked();
481 if (_taskQueue.isNotEmpty) {
482 _ensureEventLoopCallback();
483 }
484 }
485
486 // Whether this scheduler already requested to be called from the event loop.
487 bool _hasRequestedAnEventLoopCallback = false;
488
489 // Ensures that the scheduler services a task scheduled by
490 // [SchedulerBinding.scheduleTask].
491 void _ensureEventLoopCallback() {
492 assert(!locked);
493 assert(_taskQueue.isNotEmpty);
494 if (_hasRequestedAnEventLoopCallback) {
495 return;
496 }
497 _hasRequestedAnEventLoopCallback = true;
498 Timer.run(_runTasks);
499 }
500
501 // Scheduled by _ensureEventLoopCallback.
502 void _runTasks() {
503 _hasRequestedAnEventLoopCallback = false;
504 if (handleEventLoopCallback()) {
505 _ensureEventLoopCallback();
506 } // runs next task when there's time
507 }
508
509 /// Execute the highest-priority task, if it is of a high enough priority.
510 ///
511 /// Returns true if a task was executed and there are other tasks remaining
512 /// (even if they are not high-enough priority).
513 ///
514 /// Returns false if no task was executed, which can occur if there are no
515 /// tasks scheduled, if the scheduler is [locked], or if the highest-priority
516 /// task is of too low a priority given the current [schedulingStrategy].
517 ///
518 /// Also returns false if there are no tasks remaining.
519 @visibleForTesting
520 @pragma('vm:notify-debugger-on-exception')
521 bool handleEventLoopCallback() {
522 if (_taskQueue.isEmpty || locked) {
523 return false;
524 }
525 final _TaskEntry<dynamic> entry = _taskQueue.first;
526 if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
527 try {
528 _taskQueue.removeFirst();
529 entry.run();
530 } catch (exception, exceptionStack) {
531 StackTrace? callbackStack;
532 assert(() {
533 callbackStack = entry.debugStack;
534 return true;
535 }());
536 FlutterError.reportError(FlutterErrorDetails(
537 exception: exception,
538 stack: exceptionStack,
539 library: 'scheduler library',
540 context: ErrorDescription('during a task callback'),
541 informationCollector: (callbackStack == null) ? null : () {
542 return <DiagnosticsNode>[
543 DiagnosticsStackTrace(
544 '\nThis exception was thrown in the context of a scheduler callback. '
545 'When the scheduler callback was _registered_ (as opposed to when the '
546 'exception was thrown), this was the stack',
547 callbackStack,
548 ),
549 ];
550 },
551 ));
552 }
553 return _taskQueue.isNotEmpty;
554 }
555 return false;
556 }
557
558 int _nextFrameCallbackId = 0; // positive
559 Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
560 final Set<int> _removedIds = HashSet<int>();
561
562 /// The current number of transient frame callbacks scheduled.
563 ///
564 /// This is reset to zero just before all the currently scheduled
565 /// transient callbacks are called, at the start of a frame.
566 ///
567 /// This number is primarily exposed so that tests can verify that
568 /// there are no unexpected transient callbacks still registered
569 /// after a test's resources have been gracefully disposed.
570 int get transientCallbackCount => _transientCallbacks.length;
571
572 /// Schedules the given transient frame callback.
573 ///
574 /// Adds the given callback to the list of frame callbacks and ensures that a
575 /// frame is scheduled.
576 ///
577 /// If this is called during the frame's animation phase (when transient frame
578 /// callbacks are still being invoked), a new frame will be scheduled, and
579 /// `callback` will be called in the newly scheduled frame, not in the current
580 /// frame.
581 ///
582 /// If this is a one-off registration, ignore the `rescheduling` argument.
583 ///
584 /// If this is a callback that will be re-registered each time it fires, then
585 /// when you re-register the callback, set the `rescheduling` argument to
586 /// true. This has no effect in release builds, but in debug builds, it
587 /// ensures that the stack trace that is stored for this callback is the
588 /// original stack trace for when the callback was _first_ registered, rather
589 /// than the stack trace for when the callback is re-registered. This makes it
590 /// easier to track down the original reason that a particular callback was
591 /// called. If `rescheduling` is true, the call must be in the context of a
592 /// frame callback.
593 ///
594 /// Callbacks registered with this method can be canceled using
595 /// [cancelFrameCallbackWithId].
596 ///
597 /// See also:
598 ///
599 /// * [WidgetsBinding.drawFrame], which explains the phases of each frame
600 /// for those apps that use Flutter widgets (and where transient frame
601 /// callbacks fit into those phases).
602 int scheduleFrameCallback(FrameCallback callback, { bool rescheduling = false }) {
603 scheduleFrame();
604 _nextFrameCallbackId += 1;
605 _transientCallbacks[_nextFrameCallbackId] = _FrameCallbackEntry(callback, rescheduling: rescheduling);
606 return _nextFrameCallbackId;
607 }
608
609 /// Cancels the transient frame callback with the given [id].
610 ///
611 /// Removes the given callback from the list of frame callbacks. If a frame
612 /// has been requested, this does not also cancel that request.
613 ///
614 /// Transient frame callbacks are those registered using
615 /// [scheduleFrameCallback].
616 void cancelFrameCallbackWithId(int id) {
617 assert(id > 0);
618 _transientCallbacks.remove(id);
619 _removedIds.add(id);
620 }
621
622 /// Asserts that there are no registered transient callbacks; if
623 /// there are, prints their locations and throws an exception.
624 ///
625 /// A transient frame callback is one that was registered with
626 /// [scheduleFrameCallback].
627 ///
628 /// This is expected to be called at the end of tests (the
629 /// flutter_test framework does it automatically in normal cases).
630 ///
631 /// Call this method when you expect there to be no transient
632 /// callbacks registered, in an assert statement with a message that
633 /// you want printed when a transient callback is registered:
634 ///
635 /// ```dart
636 /// assert(SchedulerBinding.instance.debugAssertNoTransientCallbacks(
637 /// 'A leak of transient callbacks was detected while doing foo.'
638 /// ));
639 /// ```
640 ///
641 /// Does nothing if asserts are disabled. Always returns true.
642 bool debugAssertNoTransientCallbacks(String reason) {
643 assert(() {
644 if (transientCallbackCount > 0) {
645 // We cache the values so that we can produce them later
646 // even if the information collector is called after
647 // the problem has been resolved.
648 final int count = transientCallbackCount;
649 final Map<int, _FrameCallbackEntry> callbacks = Map<int, _FrameCallbackEntry>.of(_transientCallbacks);
650 FlutterError.reportError(FlutterErrorDetails(
651 exception: reason,
652 library: 'scheduler library',
653 informationCollector: () => <DiagnosticsNode>[
654 if (count == 1)
655 // TODO(jacobr): I have added an extra line break in this case.
656 ErrorDescription(
657 'There was one transient callback left. '
658 'The stack trace for when it was registered is as follows:',
659 )
660 else
661 ErrorDescription(
662 'There were $count transient callbacks left. '
663 'The stack traces for when they were registered are as follows:',
664 ),
665 for (final int id in callbacks.keys)
666 DiagnosticsStackTrace('── callback $id ──', callbacks[id]!.debugStack, showSeparator: false),
667 ],
668 ));
669 }
670 return true;
671 }());
672 return true;
673 }
674
675 /// Asserts that there are no pending performance mode requests in debug mode.
676 ///
677 /// Throws a [FlutterError] if there are pending performance mode requests,
678 /// as this indicates a potential memory leak.
679 bool debugAssertNoPendingPerformanceModeRequests(String reason) {
680 assert(() {
681 if (_performanceMode != null) {
682 throw FlutterError(reason);
683 }
684 return true;
685 }());
686 return true;
687 }
688
689 /// Asserts that there is no artificial time dilation in debug mode.
690 ///
691 /// Throws a [FlutterError] if there are such dilation, as this will make
692 /// subsequent tests see dilation and thus flaky.
693 bool debugAssertNoTimeDilation(String reason) {
694 assert(() {
695 if (timeDilation != 1.0) {
696 throw FlutterError(reason);
697 }
698 return true;
699 }());
700 return true;
701 }
702
703 /// Prints the stack for where the current transient callback was registered.
704 ///
705 /// A transient frame callback is one that was registered with
706 /// [scheduleFrameCallback].
707 ///
708 /// When called in debug more and in the context of a transient callback, this
709 /// function prints the stack trace from where the current transient callback
710 /// was registered (i.e. where it first called [scheduleFrameCallback]).
711 ///
712 /// When called in debug mode in other contexts, it prints a message saying
713 /// that this function was not called in the context a transient callback.
714 ///
715 /// In release mode, this function does nothing.
716 ///
717 /// To call this function, use the following code:
718 ///
719 /// ```dart
720 /// SchedulerBinding.debugPrintTransientCallbackRegistrationStack();
721 /// ```
722 static void debugPrintTransientCallbackRegistrationStack() {
723 assert(() {
724 if (_FrameCallbackEntry.debugCurrentCallbackStack != null) {
725 debugPrint('When the current transient callback was registered, this was the stack:');
726 debugPrint(
727 FlutterError.defaultStackFilter(
728 FlutterError.demangleStackTrace(
729 _FrameCallbackEntry.debugCurrentCallbackStack!,
730 ).toString().trimRight().split('\n'),
731 ).join('\n'),
732 );
733 } else {
734 debugPrint('No transient callback is currently executing.');
735 }
736 return true;
737 }());
738 }
739
740 final List<FrameCallback> _persistentCallbacks = <FrameCallback>[];
741
742 /// Adds a persistent frame callback.
743 ///
744 /// Persistent callbacks are called after transient
745 /// (non-persistent) frame callbacks.
746 ///
747 /// Does *not* request a new frame. Conceptually, persistent frame
748 /// callbacks are observers of "begin frame" events. Since they are
749 /// executed after the transient frame callbacks they can drive the
750 /// rendering pipeline.
751 ///
752 /// Persistent frame callbacks cannot be unregistered. Once registered, they
753 /// are called for every frame for the lifetime of the application.
754 ///
755 /// See also:
756 ///
757 /// * [WidgetsBinding.drawFrame], which explains the phases of each frame
758 /// for those apps that use Flutter widgets (and where persistent frame
759 /// callbacks fit into those phases).
760 void addPersistentFrameCallback(FrameCallback callback) {
761 _persistentCallbacks.add(callback);
762 }
763
764 final List<FrameCallback> _postFrameCallbacks = <FrameCallback>[];
765
766 /// Schedule a callback for the end of this frame.
767 ///
768 /// The provided callback is run immediately after a frame, just after the
769 /// persistent frame callbacks (which is when the main rendering pipeline has
770 /// been flushed).
771 ///
772 /// This method does *not* request a new frame. If a frame is already in
773 /// progress and the execution of post-frame callbacks has not yet begun, then
774 /// the registered callback is executed at the end of the current frame.
775 /// Otherwise, the registered callback is executed after the next frame
776 /// (whenever that may be, if ever).
777 ///
778 /// The callbacks are executed in the order in which they have been
779 /// added.
780 ///
781 /// Post-frame callbacks cannot be unregistered. They are called exactly once.
782 ///
783 /// In debug mode, if [debugTracePostFrameCallbacks] is set to true, then the
784 /// registered callback will show up in the timeline events chart, which can
785 /// be viewed in [DevTools](https://docs.flutter.dev/tools/devtools/overview).
786 /// In that case, the `debugLabel` argument specifies the name of the callback
787 /// as it will appear in the timeline. In profile and release builds,
788 /// post-frame are never traced, and the `debugLabel` argument is ignored.
789 ///
790 /// See also:
791 ///
792 /// * [scheduleFrameCallback], which registers a callback for the start of
793 /// the next frame.
794 /// * [WidgetsBinding.drawFrame], which explains the phases of each frame
795 /// for those apps that use Flutter widgets (and where post frame
796 /// callbacks fit into those phases).
797 void addPostFrameCallback(FrameCallback callback, {String debugLabel = 'callback'}) {
798 assert(() {
799 if (debugTracePostFrameCallbacks) {
800 final FrameCallback originalCallback = callback;
801 callback = (Duration timeStamp) {
802 Timeline.startSync(debugLabel);
803 try {
804 originalCallback(timeStamp);
805 } finally {
806 Timeline.finishSync();
807 }
808 };
809 }
810 return true;
811 }());
812 _postFrameCallbacks.add(callback);
813 }
814
815 Completer<void>? _nextFrameCompleter;
816
817 /// Returns a Future that completes after the frame completes.
818 ///
819 /// If this is called between frames, a frame is immediately scheduled if
820 /// necessary. If this is called during a frame, the Future completes after
821 /// the current frame.
822 ///
823 /// If the device's screen is currently turned off, this may wait a very long
824 /// time, since frames are not scheduled while the device's screen is turned
825 /// off.
826 Future<void> get endOfFrame {
827 if (_nextFrameCompleter == null) {
828 if (schedulerPhase == SchedulerPhase.idle) {
829 scheduleFrame();
830 }
831 _nextFrameCompleter = Completer<void>();
832 addPostFrameCallback((Duration timeStamp) {
833 _nextFrameCompleter!.complete();
834 _nextFrameCompleter = null;
835 }, debugLabel: 'SchedulerBinding.completeFrame');
836 }
837 return _nextFrameCompleter!.future;
838 }
839
840 /// Whether this scheduler has requested that [handleBeginFrame] be called soon.
841 bool get hasScheduledFrame => _hasScheduledFrame;
842 bool _hasScheduledFrame = false;
843
844 /// The phase that the scheduler is currently operating under.
845 SchedulerPhase get schedulerPhase => _schedulerPhase;
846 SchedulerPhase _schedulerPhase = SchedulerPhase.idle;
847
848 /// Whether frames are currently being scheduled when [scheduleFrame] is called.
849 ///
850 /// This value depends on the value of the [lifecycleState].
851 bool get framesEnabled => _framesEnabled;
852
853 bool _framesEnabled = true;
854 void _setFramesEnabledState(bool enabled) {
855 if (_framesEnabled == enabled) {
856 return;
857 }
858 _framesEnabled = enabled;
859 if (enabled) {
860 scheduleFrame();
861 }
862 }
863
864 /// Ensures callbacks for [PlatformDispatcher.onBeginFrame] and
865 /// [PlatformDispatcher.onDrawFrame] are registered.
866 @protected
867 void ensureFrameCallbacksRegistered() {
868 platformDispatcher.onBeginFrame ??= _handleBeginFrame;
869 platformDispatcher.onDrawFrame ??= _handleDrawFrame;
870 }
871
872 /// Schedules a new frame using [scheduleFrame] if this object is not
873 /// currently producing a frame.
874 ///
875 /// Calling this method ensures that [handleDrawFrame] will eventually be
876 /// called, unless it's already in progress.
877 ///
878 /// This has no effect if [schedulerPhase] is
879 /// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
880 /// (because a frame is already being prepared in that case), or
881 /// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
882 /// rendered in that case). It will schedule a frame if the [schedulerPhase]
883 /// is [SchedulerPhase.idle] (in between frames) or
884 /// [SchedulerPhase.postFrameCallbacks] (after a frame).
885 void ensureVisualUpdate() {
886 switch (schedulerPhase) {
887 case SchedulerPhase.idle:
888 case SchedulerPhase.postFrameCallbacks:
889 scheduleFrame();
890 return;
891 case SchedulerPhase.transientCallbacks:
892 case SchedulerPhase.midFrameMicrotasks:
893 case SchedulerPhase.persistentCallbacks:
894 return;
895 }
896 }
897
898 /// If necessary, schedules a new frame by calling
899 /// [dart:ui.PlatformDispatcher.scheduleFrame].
900 ///
901 /// After this is called, the engine will (eventually) call
902 /// [handleBeginFrame]. (This call might be delayed, e.g. if the device's
903 /// screen is turned off it will typically be delayed until the screen is on
904 /// and the application is visible.) Calling this during a frame forces
905 /// another frame to be scheduled, even if the current frame has not yet
906 /// completed.
907 ///
908 /// Scheduled frames are serviced when triggered by a "Vsync" signal provided
909 /// by the operating system. The "Vsync" signal, or vertical synchronization
910 /// signal, was historically related to the display refresh, at a time when
911 /// hardware physically moved a beam of electrons vertically between updates
912 /// of the display. The operation of contemporary hardware is somewhat more
913 /// subtle and complicated, but the conceptual "Vsync" refresh signal continue
914 /// to be used to indicate when applications should update their rendering.
915 ///
916 /// To have a stack trace printed to the console any time this function
917 /// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
918 ///
919 /// See also:
920 ///
921 /// * [scheduleForcedFrame], which ignores the [lifecycleState] when
922 /// scheduling a frame.
923 /// * [scheduleWarmUpFrame], which ignores the "Vsync" signal entirely and
924 /// triggers a frame immediately.
925 void scheduleFrame() {
926 if (_hasScheduledFrame || !framesEnabled) {
927 return;
928 }
929 assert(() {
930 if (debugPrintScheduleFrameStacks) {
931 debugPrintStack(label: 'scheduleFrame() called. Current phase is $schedulerPhase.');
932 }
933 return true;
934 }());
935 ensureFrameCallbacksRegistered();
936 platformDispatcher.scheduleFrame();
937 _hasScheduledFrame = true;
938 }
939
940 /// Schedules a new frame by calling
941 /// [dart:ui.PlatformDispatcher.scheduleFrame].
942 ///
943 /// After this is called, the engine will call [handleBeginFrame], even if
944 /// frames would normally not be scheduled by [scheduleFrame] (e.g. even if
945 /// the device's screen is turned off).
946 ///
947 /// The framework uses this to force a frame to be rendered at the correct
948 /// size when the phone is rotated, so that a correctly-sized rendering is
949 /// available when the screen is turned back on.
950 ///
951 /// To have a stack trace printed to the console any time this function
952 /// schedules a frame, set [debugPrintScheduleFrameStacks] to true.
953 ///
954 /// Prefer using [scheduleFrame] unless it is imperative that a frame be
955 /// scheduled immediately, since using [scheduleForcedFrame] will cause
956 /// significantly higher battery usage when the device should be idle.
957 ///
958 /// Consider using [scheduleWarmUpFrame] instead if the goal is to update the
959 /// rendering as soon as possible (e.g. at application startup).
960 void scheduleForcedFrame() {
961 if (_hasScheduledFrame) {
962 return;
963 }
964 assert(() {
965 if (debugPrintScheduleFrameStacks) {
966 debugPrintStack(label: 'scheduleForcedFrame() called. Current phase is $schedulerPhase.');
967 }
968 return true;
969 }());
970 ensureFrameCallbacksRegistered();
971 platformDispatcher.scheduleFrame();
972 _hasScheduledFrame = true;
973 }
974
975 bool _warmUpFrame = false;
976
977 /// Schedule a frame to run as soon as possible, rather than waiting for
978 /// the engine to request a frame in response to a system "Vsync" signal.
979 ///
980 /// This is used during application startup so that the first frame (which is
981 /// likely to be quite expensive) gets a few extra milliseconds to run.
982 ///
983 /// Locks events dispatching until the scheduled frame has completed.
984 ///
985 /// If a frame has already been scheduled with [scheduleFrame] or
986 /// [scheduleForcedFrame], this call may delay that frame.
987 ///
988 /// If any scheduled frame has already begun or if another
989 /// [scheduleWarmUpFrame] was already called, this call will be ignored.
990 ///
991 /// Prefer [scheduleFrame] to update the display in normal operation.
992 ///
993 /// ## Design discussion
994 ///
995 /// The Flutter engine prompts the framework to generate frames when it
996 /// receives a request from the operating system (known for historical reasons
997 /// as a vsync). However, this may not happen for several milliseconds after
998 /// the app starts (or after a hot reload). To make use of the time between
999 /// when the widget tree is first configured and when the engine requests an
1000 /// update, the framework schedules a _warm-up frame_.
1001 ///
1002 /// A warm-up frame may never actually render (as the engine did not request
1003 /// it and therefore does not have a valid context in which to paint), but it
1004 /// will cause the framework to go through the steps of building, laying out,
1005 /// and painting, which can together take several milliseconds. Thus, when the
1006 /// engine requests a real frame, much of the work will already have been
1007 /// completed, and the framework can generate the frame with minimal
1008 /// additional effort.
1009 ///
1010 /// Warm-up frames are scheduled by [runApp] on startup, and by
1011 /// [RendererBinding.performReassemble] during a hot reload.
1012 ///
1013 /// Warm-up frames are also scheduled when the framework is unblocked by a
1014 /// call to [RendererBinding.allowFirstFrame] (corresponding to a call to
1015 /// [RendererBinding.deferFirstFrame] that blocked the rendering).
1016 void scheduleWarmUpFrame() {
1017 if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle) {
1018 return;
1019 }
1020
1021 _warmUpFrame = true;
1022 TimelineTask? debugTimelineTask;
1023 if (!kReleaseMode) {
1024 debugTimelineTask = TimelineTask()..start('Warm-up frame');
1025 }
1026 final bool hadScheduledFrame = _hasScheduledFrame;
1027 // We use timers here to ensure that microtasks flush in between.
1028 Timer.run(() {
1029 assert(_warmUpFrame);
1030 handleBeginFrame(null);
1031 });
1032 Timer.run(() {
1033 assert(_warmUpFrame);
1034 handleDrawFrame();
1035 // We call resetEpoch after this frame so that, in the hot reload case,
1036 // the very next frame pretends to have occurred immediately after this
1037 // warm-up frame. The warm-up frame's timestamp will typically be far in
1038 // the past (the time of the last real frame), so if we didn't reset the
1039 // epoch we would see a sudden jump from the old time in the warm-up frame
1040 // to the new time in the "real" frame. The biggest problem with this is
1041 // that implicit animations end up being triggered at the old time and
1042 // then skipping every frame and finishing in the new time.
1043 resetEpoch();
1044 _warmUpFrame = false;
1045 if (hadScheduledFrame) {
1046 scheduleFrame();
1047 }
1048 });
1049
1050 // Lock events so touch events etc don't insert themselves until the
1051 // scheduled frame has finished.
1052 lockEvents(() async {
1053 await endOfFrame;
1054 if (!kReleaseMode) {
1055 debugTimelineTask!.finish();
1056 }
1057 });
1058 }
1059
1060 Duration? _firstRawTimeStampInEpoch;
1061 Duration _epochStart = Duration.zero;
1062 Duration _lastRawTimeStamp = Duration.zero;
1063
1064 /// Prepares the scheduler for a non-monotonic change to how time stamps are
1065 /// calculated.
1066 ///
1067 /// Callbacks received from the scheduler assume that their time stamps are
1068 /// monotonically increasing. The raw time stamp passed to [handleBeginFrame]
1069 /// is monotonic, but the scheduler might adjust those time stamps to provide
1070 /// [timeDilation]. Without careful handling, these adjusts could cause time
1071 /// to appear to run backwards.
1072 ///
1073 /// The [resetEpoch] function ensures that the time stamps are monotonic by
1074 /// resetting the base time stamp used for future time stamp adjustments to the
1075 /// current value. For example, if the [timeDilation] decreases, rather than
1076 /// scaling down the [Duration] since the beginning of time, [resetEpoch] will
1077 /// ensure that we only scale down the duration since [resetEpoch] was called.
1078 ///
1079 /// Setting [timeDilation] calls [resetEpoch] automatically. You don't need to
1080 /// call [resetEpoch] yourself.
1081 void resetEpoch() {
1082 _epochStart = _adjustForEpoch(_lastRawTimeStamp);
1083 _firstRawTimeStampInEpoch = null;
1084 }
1085
1086 /// Adjusts the given time stamp into the current epoch.
1087 ///
1088 /// This both offsets the time stamp to account for when the epoch started
1089 /// (both in raw time and in the epoch's own time line) and scales the time
1090 /// stamp to reflect the time dilation in the current epoch.
1091 ///
1092 /// These mechanisms together combine to ensure that the durations we give
1093 /// during frame callbacks are monotonically increasing.
1094 Duration _adjustForEpoch(Duration rawTimeStamp) {
1095 final Duration rawDurationSinceEpoch = _firstRawTimeStampInEpoch == null ? Duration.zero : rawTimeStamp - _firstRawTimeStampInEpoch!;
1096 return Duration(microseconds: (rawDurationSinceEpoch.inMicroseconds / timeDilation).round() + _epochStart.inMicroseconds);
1097 }
1098
1099 /// The time stamp for the frame currently being processed.
1100 ///
1101 /// This is only valid while between the start of [handleBeginFrame] and the
1102 /// end of the corresponding [handleDrawFrame], i.e. while a frame is being
1103 /// produced.
1104 Duration get currentFrameTimeStamp {
1105 assert(_currentFrameTimeStamp != null);
1106 return _currentFrameTimeStamp!;
1107 }
1108 Duration? _currentFrameTimeStamp;
1109
1110 /// The raw time stamp as provided by the engine to
1111 /// [dart:ui.PlatformDispatcher.onBeginFrame] for the frame currently being
1112 /// processed.
1113 ///
1114 /// Unlike [currentFrameTimeStamp], this time stamp is neither adjusted to
1115 /// offset when the epoch started nor scaled to reflect the [timeDilation] in
1116 /// the current epoch.
1117 ///
1118 /// On most platforms, this is a more or less arbitrary value, and should
1119 /// generally be ignored. On Fuchsia, this corresponds to the system-provided
1120 /// presentation time, and can be used to ensure that animations running in
1121 /// different processes are synchronized.
1122 Duration get currentSystemFrameTimeStamp {
1123 return _lastRawTimeStamp;
1124 }
1125
1126 int _debugFrameNumber = 0;
1127 String? _debugBanner;
1128
1129 // Whether the current engine frame needs to be postponed till after the
1130 // warm-up frame.
1131 //
1132 // Engine may begin a frame in the middle of the warm-up frame because the
1133 // warm-up frame is scheduled by timers while the engine frame is scheduled
1134 // by platform specific frame scheduler (e.g. `requestAnimationFrame` on the
1135 // web). When this happens, we let the warm-up frame finish, and postpone the
1136 // engine frame.
1137 bool _rescheduleAfterWarmUpFrame = false;
1138
1139 void _handleBeginFrame(Duration rawTimeStamp) {
1140 if (_warmUpFrame) {
1141 // "begin frame" and "draw frame" must strictly alternate. Therefore
1142 // _rescheduleAfterWarmUpFrame cannot possibly be true here as it is
1143 // reset by _handleDrawFrame.
1144 assert(!_rescheduleAfterWarmUpFrame);
1145 _rescheduleAfterWarmUpFrame = true;
1146 return;
1147 }
1148 handleBeginFrame(rawTimeStamp);
1149 }
1150
1151 void _handleDrawFrame() {
1152 if (_rescheduleAfterWarmUpFrame) {
1153 _rescheduleAfterWarmUpFrame = false;
1154 // Reschedule in a post-frame callback to allow the draw-frame phase of
1155 // the warm-up frame to finish.
1156 addPostFrameCallback((Duration timeStamp) {
1157 // Force an engine frame.
1158 //
1159 // We need to reset _hasScheduledFrame here because we cancelled the
1160 // original engine frame, and therefore did not run handleBeginFrame
1161 // who is responsible for resetting it. So if a frame callback set this
1162 // to true in the "begin frame" part of the warm-up frame, it will
1163 // still be true here and cause us to skip scheduling an engine frame.
1164 _hasScheduledFrame = false;
1165 scheduleFrame();
1166 }, debugLabel: 'SchedulerBinding.scheduleFrame');
1167 return;
1168 }
1169 handleDrawFrame();
1170 }
1171
1172 final TimelineTask? _frameTimelineTask = kReleaseMode ? null : TimelineTask();
1173
1174 /// Called by the engine to prepare the framework to produce a new frame.
1175 ///
1176 /// This function calls all the transient frame callbacks registered by
1177 /// [scheduleFrameCallback]. It then returns, any scheduled microtasks are run
1178 /// (e.g. handlers for any [Future]s resolved by transient frame callbacks),
1179 /// and [handleDrawFrame] is called to continue the frame.
1180 ///
1181 /// If the given time stamp is null, the time stamp from the last frame is
1182 /// reused.
1183 ///
1184 /// To have a banner shown at the start of every frame in debug mode, set
1185 /// [debugPrintBeginFrameBanner] to true. The banner will be printed to the
1186 /// console using [debugPrint] and will contain the frame number (which
1187 /// increments by one for each frame), and the time stamp of the frame. If the
1188 /// given time stamp was null, then the string "warm-up frame" is shown
1189 /// instead of the time stamp. This allows frames eagerly pushed by the
1190 /// framework to be distinguished from those requested by the engine in
1191 /// response to the "Vsync" signal from the operating system.
1192 ///
1193 /// You can also show a banner at the end of every frame by setting
1194 /// [debugPrintEndFrameBanner] to true. This allows you to distinguish log
1195 /// statements printed during a frame from those printed between frames (e.g.
1196 /// in response to events or timers).
1197 void handleBeginFrame(Duration? rawTimeStamp) {
1198 _frameTimelineTask?.start('Frame');
1199 _firstRawTimeStampInEpoch ??= rawTimeStamp;
1200 _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
1201 if (rawTimeStamp != null) {
1202 _lastRawTimeStamp = rawTimeStamp;
1203 }
1204
1205 assert(() {
1206 _debugFrameNumber += 1;
1207
1208 if (debugPrintBeginFrameBanner || debugPrintEndFrameBanner) {
1209 final StringBuffer frameTimeStampDescription = StringBuffer();
1210 if (rawTimeStamp != null) {
1211 _debugDescribeTimeStamp(_currentFrameTimeStamp!, frameTimeStampDescription);
1212 } else {
1213 frameTimeStampDescription.write('(warm-up frame)');
1214 }
1215 _debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_debugFrameNumber.toString().padRight(7)} ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
1216 if (debugPrintBeginFrameBanner) {
1217 debugPrint(_debugBanner);
1218 }
1219 }
1220 return true;
1221 }());
1222
1223 assert(schedulerPhase == SchedulerPhase.idle);
1224 _hasScheduledFrame = false;
1225 try {
1226 // TRANSIENT FRAME CALLBACKS
1227 _frameTimelineTask?.start('Animate');
1228 _schedulerPhase = SchedulerPhase.transientCallbacks;
1229 final Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
1230 _transientCallbacks = <int, _FrameCallbackEntry>{};
1231 callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
1232 if (!_removedIds.contains(id)) {
1233 _invokeFrameCallback(callbackEntry.callback, _currentFrameTimeStamp!, callbackEntry.debugStack);
1234 }
1235 });
1236 _removedIds.clear();
1237 } finally {
1238 _schedulerPhase = SchedulerPhase.midFrameMicrotasks;
1239 }
1240 }
1241
1242 DartPerformanceMode? _performanceMode;
1243 int _numPerformanceModeRequests = 0;
1244
1245 /// Request a specific [DartPerformanceMode].
1246 ///
1247 /// Returns `null` if the request was not successful due to conflicting performance mode requests.
1248 /// Two requests are said to be in conflict if they are not of the same [DartPerformanceMode] type,
1249 /// and an explicit request for a performance mode has been made prior.
1250 ///
1251 /// Requestor is responsible for calling [PerformanceModeRequestHandle.dispose] when it no longer
1252 /// requires the performance mode.
1253 PerformanceModeRequestHandle? requestPerformanceMode(DartPerformanceMode mode) {
1254 // conflicting requests are not allowed.
1255 if (_performanceMode != null && _performanceMode != mode) {
1256 return null;
1257 }
1258
1259 if (_performanceMode == mode) {
1260 assert(_numPerformanceModeRequests > 0);
1261 _numPerformanceModeRequests++;
1262 } else if (_performanceMode == null) {
1263 assert(_numPerformanceModeRequests == 0);
1264 _performanceMode = mode;
1265 _numPerformanceModeRequests = 1;
1266 }
1267
1268 return PerformanceModeRequestHandle._(_disposePerformanceModeRequest);
1269 }
1270
1271 /// Remove a request for a specific [DartPerformanceMode].
1272 ///
1273 /// If all the pending requests have been disposed, the engine will revert to the
1274 /// [DartPerformanceMode.balanced] performance mode.
1275 void _disposePerformanceModeRequest() {
1276 _numPerformanceModeRequests--;
1277 if (_numPerformanceModeRequests == 0) {
1278 _performanceMode = null;
1279 PlatformDispatcher.instance.requestDartPerformanceMode(DartPerformanceMode.balanced);
1280 }
1281 }
1282
1283 /// Returns the current [DartPerformanceMode] requested or `null` if no requests have
1284 /// been made.
1285 ///
1286 /// This is only supported in debug and profile modes, returns `null` in release mode.
1287 DartPerformanceMode? debugGetRequestedPerformanceMode() {
1288 if (!(kDebugMode || kProfileMode)) {
1289 return null;
1290 } else {
1291 return _performanceMode;
1292 }
1293 }
1294
1295 /// Called by the engine to produce a new frame.
1296 ///
1297 /// This method is called immediately after [handleBeginFrame]. It calls all
1298 /// the callbacks registered by [addPersistentFrameCallback], which typically
1299 /// drive the rendering pipeline, and then calls the callbacks registered by
1300 /// [addPostFrameCallback].
1301 ///
1302 /// See [handleBeginFrame] for a discussion about debugging hooks that may be
1303 /// useful when working with frame callbacks.
1304 void handleDrawFrame() {
1305 assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
1306 _frameTimelineTask?.finish(); // end the "Animate" phase
1307 try {
1308 // PERSISTENT FRAME CALLBACKS
1309 _schedulerPhase = SchedulerPhase.persistentCallbacks;
1310 for (final FrameCallback callback in List<FrameCallback>.of(_persistentCallbacks)) {
1311 _invokeFrameCallback(callback, _currentFrameTimeStamp!);
1312 }
1313
1314 // POST-FRAME CALLBACKS
1315 _schedulerPhase = SchedulerPhase.postFrameCallbacks;
1316 final List<FrameCallback> localPostFrameCallbacks =
1317 List<FrameCallback>.of(_postFrameCallbacks);
1318 _postFrameCallbacks.clear();
1319 Timeline.startSync('POST_FRAME');
1320 try {
1321 for (final FrameCallback callback in localPostFrameCallbacks) {
1322 _invokeFrameCallback(callback, _currentFrameTimeStamp!);
1323 }
1324 } finally {
1325 Timeline.finishSync();
1326 }
1327 } finally {
1328 _schedulerPhase = SchedulerPhase.idle;
1329 _frameTimelineTask?.finish(); // end the Frame
1330 assert(() {
1331 if (debugPrintEndFrameBanner) {
1332 debugPrint('▀' * _debugBanner!.length);
1333 }
1334 _debugBanner = null;
1335 return true;
1336 }());
1337 _currentFrameTimeStamp = null;
1338 }
1339 }
1340
1341 void _profileFramePostEvent(FrameTiming frameTiming) {
1342 postEvent('Flutter.Frame', <String, dynamic>{
1343 'number': frameTiming.frameNumber,
1344 'startTime': frameTiming.timestampInMicroseconds(FramePhase.buildStart),
1345 'elapsed': frameTiming.totalSpan.inMicroseconds,
1346 'build': frameTiming.buildDuration.inMicroseconds,
1347 'raster': frameTiming.rasterDuration.inMicroseconds,
1348 'vsyncOverhead': frameTiming.vsyncOverhead.inMicroseconds,
1349 });
1350 }
1351
1352 static void _debugDescribeTimeStamp(Duration timeStamp, StringBuffer buffer) {
1353 if (timeStamp.inDays > 0) {
1354 buffer.write('${timeStamp.inDays}d ');
1355 }
1356 if (timeStamp.inHours > 0) {
1357 buffer.write('${timeStamp.inHours - timeStamp.inDays * Duration.hoursPerDay}h ');
1358 }
1359 if (timeStamp.inMinutes > 0) {
1360 buffer.write('${timeStamp.inMinutes - timeStamp.inHours * Duration.minutesPerHour}m ');
1361 }
1362 if (timeStamp.inSeconds > 0) {
1363 buffer.write('${timeStamp.inSeconds - timeStamp.inMinutes * Duration.secondsPerMinute}s ');
1364 }
1365 buffer.write('${timeStamp.inMilliseconds - timeStamp.inSeconds * Duration.millisecondsPerSecond}');
1366 final int microseconds = timeStamp.inMicroseconds - timeStamp.inMilliseconds * Duration.microsecondsPerMillisecond;
1367 if (microseconds > 0) {
1368 buffer.write('.${microseconds.toString().padLeft(3, "0")}');
1369 }
1370 buffer.write('ms');
1371 }
1372
1373 // Calls the given [callback] with [timestamp] as argument.
1374 //
1375 // Wraps the callback in a try/catch and forwards any error to
1376 // [debugSchedulerExceptionHandler], if set. If not set, prints
1377 // the error.
1378 @pragma('vm:notify-debugger-on-exception')
1379 void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
1380 assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
1381 assert(() {
1382 _FrameCallbackEntry.debugCurrentCallbackStack = callbackStack;
1383 return true;
1384 }());
1385 try {
1386 callback(timeStamp);
1387 } catch (exception, exceptionStack) {
1388 FlutterError.reportError(FlutterErrorDetails(
1389 exception: exception,
1390 stack: exceptionStack,
1391 library: 'scheduler library',
1392 context: ErrorDescription('during a scheduler callback'),
1393 informationCollector: (callbackStack == null) ? null : () {
1394 return <DiagnosticsNode>[
1395 DiagnosticsStackTrace(
1396 '\nThis exception was thrown in the context of a scheduler callback. '
1397 'When the scheduler callback was _registered_ (as opposed to when the '
1398 'exception was thrown), this was the stack',
1399 callbackStack,
1400 ),
1401 ];
1402 },
1403 ));
1404 }
1405 assert(() {
1406 _FrameCallbackEntry.debugCurrentCallbackStack = null;
1407 return true;
1408 }());
1409 }
1410}
1411
1412/// The default [SchedulingStrategy] for [SchedulerBinding.schedulingStrategy].
1413///
1414/// If there are any frame callbacks registered, only runs tasks with
1415/// a [Priority] of [Priority.animation] or higher. Otherwise, runs
1416/// all tasks.
1417bool defaultSchedulingStrategy({ required int priority, required SchedulerBinding scheduler }) {
1418 if (scheduler.transientCallbackCount > 0) {
1419 return priority >= Priority.animation.value;
1420 }
1421 return true;
1422}
1423