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