binding.dart 26.9 KB
Newer Older
1 2 3 4 5
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
6
import 'dart:collection';
7
import 'dart:developer';
8 9
import 'dart:ui' as ui show window;
import 'dart:ui' show VoidCallback;
10

11
import 'package:collection/collection.dart';
12
import 'package:flutter/foundation.dart';
13

14
import 'debug.dart';
15 16
import 'priority.dart';

17 18
export 'dart:ui' show VoidCallback;

19
/// Slows down animations by this factor to help in development.
20 21
double get timeDilation => _timeDilation;
double _timeDilation = 1.0;
22 23 24
/// Setting the time dilation automatically calls [SchedulerBinding.resetEpoch]
/// to ensure that time stamps seen by consumers of the scheduler binding are
/// always increasing.
25
set timeDilation(double value) {
26
  assert(value > 0.0);
27 28 29 30 31 32 33
  if (_timeDilation == value)
    return;
  // We need to resetEpoch first so that we capture start of the epoch with the
  // current time dilation.
  SchedulerBinding.instance?.resetEpoch();
  _timeDilation = value;
}
34 35 36 37 38 39 40

/// A frame-related callback from the scheduler.
///
/// The timeStamp is the number of milliseconds since the beginning of the
/// scheduler's epoch. Use timeStamp to determine how far to advance animation
/// timelines so that all the animations in the system are synchronized to a
/// common time base.
41
typedef void FrameCallback(Duration timeStamp);
42

43
/// Signature for the [SchedulerBinding.schedulingStrategy] callback. Called
44 45 46 47 48 49 50
/// whenever the system needs to decide whether a task at a given
/// priority needs to be run.
///
/// Return true if a task with the given priority should be executed
/// at this time, false otherwise.
///
/// See also [defaultSchedulingStrategy].
51
typedef bool SchedulingStrategy({ int priority, SchedulerBinding scheduler });
Ian Hickson's avatar
Ian Hickson committed
52

53
class _TaskEntry {
54
  const _TaskEntry(this.task, this.priority);
55
  final VoidCallback task;
56
  final int priority;
57
}
58

59 60 61 62
class _FrameCallbackEntry {
  _FrameCallbackEntry(this.callback, { bool rescheduling: false }) {
    assert(() {
      if (rescheduling) {
63
        assert(() {
64
          if (debugCurrentCallbackStack == null) {
65 66 67 68 69 70 71 72 73 74 75 76
            throw new FlutterError(
              'addFrameCallback or scheduleFrameCallback called with rescheduling true, but no callback is in scope.\n'
              'The "rescheduling" argument should only be set to true if the '
              'callback is being reregistered from within the callback itself, '
              'and only then if the callback itself is entirely synchronous. '
              'If this is the initial registration of the callback, or if the '
              'callback is asynchronous, then do not use the "rescheduling" '
              'argument.'
            );
          }
          return true;
        });
77
        debugStack = debugCurrentCallbackStack;
78
      } else {
79
        // TODO(ianh): trim the frames from this library, so that the call to scheduleFrameCallback is the top one
80
        debugStack = StackTrace.current;
81 82 83 84
      }
      return true;
    });
  }
85

86
  final FrameCallback callback;
87 88 89 90

  // debug-mode fields
  static StackTrace debugCurrentCallbackStack;
  StackTrace debugStack;
91 92
}

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
/// The various phases that a [SchedulerBinding] goes through during
/// [SchedulerBinding.handleBeginFrame].
///
/// This is exposed by [SchedulerBinding.schedulerPhase].
///
/// The values of this enum are ordered in the same order as the phases occur,
/// so their relative index values can be compared to each other.
///
/// See also [WidgetsBinding.beginFrame].
enum SchedulerPhase {
  /// No frame is being processed. Tasks (scheduled by
  /// [WidgetsBinding.scheduleTask]), microtasks (scheduled by
  /// [scheduleMicrotask]), [Timer] callbacks, event handlers (e.g. from user
  /// input), and other callbacks (e.g. from [Future]s, [Stream]s, and the like)
  /// may be executing.
  idle,

  /// The transient callbacks (scheduled by
  /// [WidgetsBinding.scheduleFrameCallback] and
  /// [WidgetsBinding.addFrameCallback]) are currently executing.
  ///
  /// Typically, these callbacks handle updating objects to new animation states.
  transientCallbacks,

  /// The persistent callbacks (scheduled by
  /// [WidgetsBinding.addPersistentFrameCallback]) are currently executing.
  ///
  /// Typically, this is the build/layout/paint pipeline. See
  /// [WidgetsBinding.beginFrame].
  persistentCallbacks,

  /// The post-frame callbacks (scheduled by
  /// [WidgetsBinding.addPostFrameCallback]) are currently executing.
  ///
  /// Typically, these callbacks handle cleanup and scheduling of work for the
  /// next frame.
  postFrameCallbacks,
}

132
/// Scheduler for running the following:
133
///
134
/// * _Frame callbacks_, triggered by the system's
135
///   [ui.window.onBeginFrame] callback, for synchronizing the
136 137
///   application's behavior to the system's display. For example, the
///   rendering layer uses this to drive its rendering pipeline.
138
///
139 140 141
/// * Non-rendering tasks, to be run between frames. These are given a
///   priority and are executed in priority order according to a
///   [schedulingStrategy].
142
abstract class SchedulerBinding extends BindingBase {
Ian Hickson's avatar
Ian Hickson committed
143

144
  @override
Ian Hickson's avatar
Ian Hickson committed
145 146 147
  void initInstances() {
    super.initInstances();
    _instance = this;
148
    ui.window.onBeginFrame = handleBeginFrame;
149
  }
150

151 152 153
  /// The current [SchedulerBinding], if one has been created.
  static SchedulerBinding get instance => _instance;
  static SchedulerBinding _instance;
Ian Hickson's avatar
Ian Hickson committed
154

155 156 157 158
  @override
  void initServiceExtensions() {
    super.initServiceExtensions();
    registerNumericServiceExtension(
159
      name: 'timeDilation',
160 161 162 163 164 165 166
      getter: () => timeDilation,
      setter: (double value) {
        timeDilation = value;
      }
    );
  }

167
  /// The strategy to use when deciding whether to run a task or not.
168
  ///
169
  /// Defaults to [defaultSchedulingStrategy].
Ian Hickson's avatar
Ian Hickson committed
170
  SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy;
171

Hixie's avatar
Hixie committed
172 173 174
  static int _taskSorter (_TaskEntry e1, _TaskEntry e2) {
    return -e1.priority.compareTo(e2.priority);
  }
175
  final PriorityQueue<_TaskEntry> _taskQueue = new HeapPriorityQueue<_TaskEntry>(_taskSorter);
176

177 178 179 180 181 182 183
  /// Schedules the given `task` with the given `priority`.
  ///
  /// Tasks will be executed between frames, in priority order,
  /// excluding tasks that are skipped by the current
  /// [schedulingStrategy]. Tasks should be short (as in, up to a
  /// millisecond), so as to not cause the regular frame callbacks to
  /// get delayed.
184
  void scheduleTask(VoidCallback task, Priority priority) {
185
    bool isFirstTask = _taskQueue.isEmpty;
186
    _taskQueue.add(new _TaskEntry(task, priority.value));
187
    if (isFirstTask)
188
      _ensureEventLoopCallback();
189 190
  }

191 192 193 194 195 196 197 198 199 200 201
  // Whether this scheduler already requested to be called from the event loop.
  bool _hasRequestedAnEventLoopCallback = false;

  // Ensures that the scheduler is awakened by the event loop.
  void _ensureEventLoopCallback() {
    if (_hasRequestedAnEventLoopCallback)
      return;
    Timer.run(handleEventLoopCallback);
    _hasRequestedAnEventLoopCallback = true;
  }

202
  /// Called by the system when there is time to run tasks.
203 204 205 206 207
  void handleEventLoopCallback() {
    _hasRequestedAnEventLoopCallback = false;
    _runTasks();
  }

208
  // Called when the system wakes up and at the end of each frame.
209 210
  void _runTasks() {
    if (_taskQueue.isEmpty)
211
      return;
212
    _TaskEntry entry = _taskQueue.first;
213 214 215
    // TODO(floitsch): for now we only expose the priority. It might
    // be interesting to provide more info (like, how long the task
    // ran the last time, or how long is left in this frame).
Ian Hickson's avatar
Ian Hickson committed
216
    if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
217
      try {
218
        (_taskQueue.removeFirst().task)();
219
      } finally {
220 221
        if (_taskQueue.isNotEmpty)
          _ensureEventLoopCallback();
222 223
      }
    } else {
224 225
      // TODO(floitsch): we shouldn't need to request a frame. Just schedule
      // an event-loop callback.
226
      scheduleFrame();
227 228 229
    }
  }

230
  int _nextFrameCallbackId = 0; // positive
231
  Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
232
  final Set<int> _removedIds = new HashSet<int>();
233

234 235 236
  /// The current number of transient frame callbacks scheduled.
  ///
  /// This is reset to zero just before all the currently scheduled
237
  /// transient callbacks are called, at the start of a frame.
238 239 240 241
  ///
  /// This number is primarily exposed so that tests can verify that
  /// there are no unexpected transient callbacks still registered
  /// after a test's resources have been gracefully disposed.
242 243
  int get transientCallbackCount => _transientCallbacks.length;

244
  /// Schedules the given transient frame callback.
245
  ///
246
  /// Adds the given callback to the list of frame callbacks and ensures that a
247
  /// frame is scheduled.
248
  ///
249 250 251 252 253 254 255 256
  /// If this is a one-off registration, ignore the `rescheduling` argument.
  ///
  /// If this is a callback that will be reregistered each time it fires, then
  /// when you reregister the callback, set the `rescheduling` argument to true.
  /// This has no effect in release builds, but in debug builds, it ensures that
  /// the stack trace that is stored for this callback is the original stack
  /// trace for when the callback was _first_ registered, rather than the stack
  /// trace for when the callback is reregistered. This makes it easier to track
257
  /// down the original reason that a particular callback was called. If
258 259
  /// `rescheduling` is true, the call must be in the context of a frame
  /// callback.
260 261 262
  ///
  /// Callbacks registered with this method can be canceled using
  /// [cancelFrameCallbackWithId].
263
  int scheduleFrameCallback(FrameCallback callback, { bool rescheduling: false }) {
264
    scheduleFrame();
265
    return addFrameCallback(callback, rescheduling: rescheduling);
266 267
  }

268
  /// Adds a transient frame callback.
269 270 271 272
  ///
  /// Frame callbacks are executed at the beginning of a frame (see
  /// [handleBeginFrame]).
  ///
273 274
  /// These callbacks are executed in the order in which they have
  /// been added.
275
  ///
276
  /// Callbacks registered with this method will not be called until
277 278 279
  /// a frame is requested. To register a callback and ensure that a
  /// frame is immediately scheduled, use [scheduleFrameCallback].
  ///
280 281 282 283 284 285 286 287
  /// If this is a one-off registration, ignore the `rescheduling` argument.
  ///
  /// If this is a callback that will be reregistered each time it fires, then
  /// when you reregister the callback, set the `rescheduling` argument to true.
  /// This has no effect in release builds, but in debug builds, it ensures that
  /// the stack trace that is stored for this callback is the original stack
  /// trace for when the callback was _first_ registered, rather than the stack
  /// trace for when the callback is reregistered. This makes it easier to track
288
  /// down the original reason that a particular callback was called. If
289 290
  /// `rescheduling` is true, the call must be in the context of a frame
  /// callback.
291 292 293
  ///
  /// Callbacks registered with this method can be canceled using
  /// [cancelFrameCallbackWithId].
294
  int addFrameCallback(FrameCallback callback, { bool rescheduling: false }) {
295
    _nextFrameCallbackId += 1;
296
    _transientCallbacks[_nextFrameCallbackId] = new _FrameCallbackEntry(callback, rescheduling: rescheduling);
297 298 299
    return _nextFrameCallbackId;
  }

300
  /// Cancels the transient frame callback with the given [id].
301 302
  ///
  /// Removes the given callback from the list of frame callbacks. If a frame
303 304
  /// has been requested, this does not also cancel that request.
  ///
305 306
  /// Transient frame callbacks are those registered using
  /// [scheduleFrameCallback] or [addFrameCallback].
307
  void cancelFrameCallbackWithId(int id) {
308 309 310 311 312
    assert(id > 0);
    _transientCallbacks.remove(id);
    _removedIds.add(id);
  }

313 314 315
  /// Asserts that there are no registered transient callbacks; if
  /// there are, prints their locations and throws an exception.
  ///
316 317 318
  /// A transient frame callback is one that was registered with
  /// [scheduleFrameCallback] or [addFrameCallback].
  ///
319 320 321
  /// This is expected to be called at the end of tests (the
  /// flutter_test framework does it automatically in normal cases).
  ///
322
  /// Call this method when you expect there to be no transient
323 324 325 326
  /// callbacks registered, in an assert statement with a message that
  /// you want printed when a transient callback is registered:
  ///
  /// ```dart
327
  /// assert(SchedulerBinding.instance.debugAssertNoTransientCallbacks(
328 329 330 331 332 333 334 335
  ///   'A leak of transient callbacks was detected while doing foo.'
  /// ));
  /// ```
  ///
  /// Does nothing if asserts are disabled. Always returns true.
  bool debugAssertNoTransientCallbacks(String reason) {
    assert(() {
      if (transientCallbackCount > 0) {
336 337 338 339 340
        // We cache the values so that we can produce them later
        // even if the information collector is called after
        // the problem has been resolved.
        final int count = transientCallbackCount;
        final Map<int, _FrameCallbackEntry> callbacks = new Map<int, _FrameCallbackEntry>.from(_transientCallbacks);
341 342 343 344
        FlutterError.reportError(new FlutterErrorDetails(
          exception: reason,
          library: 'scheduler library',
          informationCollector: (StringBuffer information) {
345
            if (count == 1) {
346 347
              information.writeln(
                'There was one transient callback left. '
348
                'The stack trace for when it was registered is as follows:'
349 350 351
              );
            } else {
              information.writeln(
352
                'There were $count transient callbacks left. '
353 354 355
                'The stack traces for when they were registered are as follows:'
              );
            }
356 357 358
            for (int id in callbacks.keys) {
              _FrameCallbackEntry entry = callbacks[id];
              information.writeln('── callback $id ──');
359
              FlutterError.defaultStackFilter(entry.debugStack.toString().trimRight().split('\n')).forEach(information.writeln);
360 361 362 363 364 365 366 367 368
            }
          }
        ));
      }
      return true;
    });
    return true;
  }

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
  /// Prints the stack for where the current transient callback was registered.
  ///
  /// A transient frame callback is one that was registered with
  /// [scheduleFrameCallback] or [addFrameCallback].
  ///
  /// When called in debug more and in the context of a transient callback, this
  /// function prints the stack trace from where the current transient callback
  /// was registered (i.e. where it first called addFrameCallback or
  /// scheduleFrameCallback).
  ///
  /// When called in debug mode in other contexts, it prints a message saying
  /// that this function was not called in the context a transient callback.
  ///
  /// In release mode, this function does nothing.
  ///
  /// To call this function, use the following code:
  ///
  /// ```dart
  ///   SchedulerBinding.debugPrintTransientCallbackRegistrationStack();
  /// ```
  static void debugPrintTransientCallbackRegistrationStack() {
    assert(() {
      if (_FrameCallbackEntry.debugCurrentCallbackStack != null) {
        debugPrint('When the current transient callback was registered, this was the stack:');
        debugPrint(
          FlutterError.defaultStackFilter(
            _FrameCallbackEntry.debugCurrentCallbackStack.toString().trimRight().split('\n')
          ).join('\n')
        );
      } else {
        debugPrint('No transient callback is currently executing.');
      }
      return true;
    });
  }

405
  final List<FrameCallback> _persistentCallbacks = new List<FrameCallback>();
406

407 408
  /// Adds a persistent frame callback.
  ///
409
  /// Persistent callbacks are called after transient
410
  /// (non-persistent) frame callbacks.
411
  ///
412 413 414 415
  /// Does *not* request a new frame. Conceptually, persistent frame
  /// callbacks are observers of "begin frame" events. Since they are
  /// executed after the transient frame callbacks they can drive the
  /// rendering pipeline.
416 417 418
  ///
  /// Persistent frame callbacks cannot be unregistered. Once registered, they
  /// are called for every frame for the lifetime of the application.
419
  void addPersistentFrameCallback(FrameCallback callback) {
420 421 422
    _persistentCallbacks.add(callback);
  }

423
  final List<FrameCallback> _postFrameCallbacks = new List<FrameCallback>();
424 425 426

  /// Schedule a callback for the end of this frame.
  ///
427
  /// Does *not* request a new frame.
428
  ///
429 430 431 432 433 434
  /// This callback is run during a frame, just after the persistent
  /// frame callbacks (which is when the main rendering pipeline has
  /// been flushed). If a frame is in progress and post-frame
  /// callbacks haven't been executed yet, then the registered
  /// callback is still executed during the frame. Otherwise, the
  /// registered callback is executed during the next frame.
435
  ///
436 437
  /// The callbacks are executed in the order in which they have been
  /// added.
438 439
  ///
  /// Post-frame callbacks cannot be unregistered. They are called exactly once.
440
  void addPostFrameCallback(FrameCallback callback) {
441 442 443
    _postFrameCallbacks.add(callback);
  }

Hans Muller's avatar
Hans Muller committed
444
  /// Whether this scheduler has requested that handleBeginFrame be called soon.
445
  bool get hasScheduledFrame => _hasScheduledFrame;
446
  bool _hasScheduledFrame = false;
447

448 449 450
  /// The phase that the scheduler is currently operating under.
  SchedulerPhase get schedulerPhase => _schedulerPhase;
  SchedulerPhase _schedulerPhase = SchedulerPhase.idle;
451

452 453
  /// Schedules a new frame using [scheduleFrame] if this object is not
  /// currently producing a frame.
454
  ///
455 456
  /// After this is called, the framework ensures that the end of the
  /// [handleBeginFrame] function will (eventually) be reached.
457
  void ensureVisualUpdate() {
458
    if (schedulerPhase != SchedulerPhase.idle)
459
      return;
460
    scheduleFrame();
461
  }
462

463 464 465 466 467 468 469
  /// If necessary, schedules a new frame by calling
  /// [ui.window.scheduleFrame].
  ///
  /// After this is called, the engine will (eventually) call
  /// [handleBeginFrame]. (This call might be delayed, e.g. if the
  /// device's screen is turned off it will typically be delayed until
  /// the screen is on and the application is visible.)
470 471 472 473 474 475
  void scheduleFrame() {
    if (_hasScheduledFrame)
      return;
    ui.window.scheduleFrame();
    _hasScheduledFrame = true;
  }
476

477 478 479 480
  Duration _firstRawTimeStampInEpoch;
  Duration _epochStart = Duration.ZERO;
  Duration _lastRawTimeStamp = Duration.ZERO;

481 482
  /// Prepares the scheduler for a non-monotonic change to how time stamps are
  /// calcuated.
483 484 485 486 487 488 489 490
  ///
  /// Callbacks received from the scheduler assume that their time stamps are
  /// monotonically increasing. The raw time stamp passed to [handleBeginFrame]
  /// is monotonic, but the scheduler might adjust those time stamps to provide
  /// [timeDilation]. Without careful handling, these adjusts could cause time
  /// to appear to run backwards.
  ///
  /// The [resetEpoch] function ensures that the time stamps are monotonic by
491
  /// resetting the base time stamp used for future time stamp adjustments to the
492 493 494 495
  /// current value. For example, if the [timeDilation] decreases, rather than
  /// scaling down the [Duration] since the beginning of time, [resetEpoch] will
  /// ensure that we only scale down the duration since [resetEpoch] was called.
  ///
496 497
  /// Setting [timeDilation] calls [resetEpoch] automatically. You don't need to
  /// call [resetEpoch] yourself.
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
  void resetEpoch() {
    _epochStart = _adjustForEpoch(_lastRawTimeStamp);
    _firstRawTimeStampInEpoch = null;
  }

  /// Adjusts the given time stamp into the current epoch.
  ///
  /// This both offsets the time stamp to account for when the epoch started
  /// (both in raw time and in the epoch's own time line) and scales the time
  /// stamp to reflect the time dilation in the current epoch.
  ///
  /// These mechanisms together combine to ensure that the durations we give
  /// during frame callbacks are monotonically increasing.
  Duration _adjustForEpoch(Duration rawTimeStamp) {
    Duration rawDurationSinceEpoch = _firstRawTimeStampInEpoch == null ? Duration.ZERO : rawTimeStamp - _firstRawTimeStampInEpoch;
    return new Duration(microseconds: (rawDurationSinceEpoch.inMicroseconds / timeDilation).round() + _epochStart.inMicroseconds);
  }

516 517 518 519 520 521 522 523 524 525 526 527
  /// The time stamp for the frame currently being processed.
  ///
  /// This is only valid while [handleBeginFrame] is running, i.e. while a frame
  /// is being produced.
  Duration get currentFrameTimeStamp {
    assert(_currentFrameTimeStamp != null);
    return _currentFrameTimeStamp;
  }
  Duration _currentFrameTimeStamp;

  int _debugFrameNumber = 0;

528 529 530
  /// Called by the engine to produce a new frame.
  ///
  /// This function first calls all the callbacks registered by
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
  /// [scheduleFrameCallback]/[addFrameCallback], then calls all the callbacks
  /// registered by [addPersistentFrameCallback], which typically drive the
  /// rendering pipeline, and finally calls the callbacks registered by
  /// [addPostFrameCallback].
  ///
  /// If the given time stamp is null, the time stamp from the last frame is
  /// reused.
  ///
  /// To have a banner shown at the start of every frame in debug mode, set
  /// [debugPrintBeginFrameBanner] to true. The banner will be printed to the
  /// console using [debugPrint] and will contain the frame number (which
  /// increments by one for each frame), and the time stamp of the frame. If the
  /// given time stamp was null, then the string "warm-up frame" is shown
  /// instead of the time stamp. This allows you to distinguish frames eagerly
  /// pushed by the framework from those requested by the engine in response to
  /// the vsync signal from the operating system.
  ///
  /// You can also show a banner at the end of every frame by setting
  /// [debugPrintEndFrameBanner] to true. This allows you to distinguish log
  /// statements printed during a frame from those printed between frames (e.g.
  /// in response to events or timers).
552
  void handleBeginFrame(Duration rawTimeStamp) {
553
    Timeline.startSync('Frame');
554
    _firstRawTimeStampInEpoch ??= rawTimeStamp;
555 556 557 558 559
    _currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
    if (rawTimeStamp != null)
      _lastRawTimeStamp = rawTimeStamp;

    String debugBanner;
560
    assert(() {
561 562 563 564 565 566 567 568 569 570 571 572
      _debugFrameNumber += 1;
      if (debugPrintBeginFrameBanner || debugPrintEndFrameBanner) {
        StringBuffer frameTimeStampDescription = new StringBuffer();
        if (rawTimeStamp != null) {
          _debugDescribeTimeStamp(_currentFrameTimeStamp, frameTimeStampDescription);
        } else {
          frameTimeStampDescription.write('(warm-up frame)');
        }
        debugBanner = '▄▄▄▄▄▄▄▄ Frame ${_debugFrameNumber.toString().padRight(7)}   ${frameTimeStampDescription.toString().padLeft(18)} ▄▄▄▄▄▄▄▄';
        if (debugPrintBeginFrameBanner)
          debugPrint(debugBanner);
      }
573 574
      return true;
    });
575

576
    assert(schedulerPhase == SchedulerPhase.idle);
577
    _hasScheduledFrame = false;
578
    try {
579

580
      // TRANSIENT FRAME CALLBACKS
581
      _schedulerPhase = SchedulerPhase.transientCallbacks;
582 583 584
      _invokeTransientFrameCallbacks(_currentFrameTimeStamp);

      // PERSISTENT FRAME CALLBACKS
585
      _schedulerPhase = SchedulerPhase.persistentCallbacks;
586 587 588 589
      for (FrameCallback callback in _persistentCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

      // POST-FRAME CALLBACKS
590
      _schedulerPhase = SchedulerPhase.postFrameCallbacks;
591 592 593 594 595 596 597
      List<FrameCallback> localPostFrameCallbacks =
          new List<FrameCallback>.from(_postFrameCallbacks);
      _postFrameCallbacks.clear();
      for (FrameCallback callback in localPostFrameCallbacks)
        _invokeFrameCallback(callback, _currentFrameTimeStamp);

    } finally {
598
      _schedulerPhase = SchedulerPhase.idle;
599 600 601 602 603 604 605 606
      _currentFrameTimeStamp = null;
      Timeline.finishSync();
      assert(() {
        if (debugPrintEndFrameBanner)
          debugPrint('▀' * debugBanner.length);
        return true;
      });
    }
607

608
    // All frame-related callbacks have been executed. Run lower-priority tasks.
609
    _runTasks();
610 611
  }

612 613
  void _invokeTransientFrameCallbacks(Duration timeStamp) {
    Timeline.startSync('Animate');
614
    assert(schedulerPhase == SchedulerPhase.transientCallbacks);
615 616 617 618
    Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
    _transientCallbacks = new Map<int, _FrameCallbackEntry>();
    callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
      if (!_removedIds.contains(id))
619
        _invokeFrameCallback(callbackEntry.callback, timeStamp, callbackEntry.debugStack);
620 621 622 623 624
    });
    _removedIds.clear();
    Timeline.finishSync();
  }

625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
  static void _debugDescribeTimeStamp(Duration timeStamp, StringBuffer buffer) {
    if (timeStamp.inDays > 0)
      buffer.write('${timeStamp.inDays}d ');
    if (timeStamp.inHours > 0)
      buffer.write('${timeStamp.inHours - timeStamp.inDays * Duration.HOURS_PER_DAY}h ');
    if (timeStamp.inMinutes > 0)
      buffer.write('${timeStamp.inMinutes - timeStamp.inHours * Duration.MINUTES_PER_HOUR}m ');
    if (timeStamp.inSeconds > 0)
      buffer.write('${timeStamp.inSeconds - timeStamp.inMinutes * Duration.SECONDS_PER_MINUTE}s ');
    buffer.write('${timeStamp.inMilliseconds - timeStamp.inSeconds * Duration.MILLISECONDS_PER_SECOND}');
    int microseconds = timeStamp.inMicroseconds - timeStamp.inMilliseconds * Duration.MICROSECONDS_PER_MILLISECOND;
    if (microseconds > 0)
      buffer.write('.${microseconds.toString().padLeft(3, "0")}');
    buffer.write('ms');
  }

641
  // Calls the given [callback] with [timestamp] as argument.
642 643 644 645 646
  //
  // Wraps the callback in a try/catch and forwards any error to
  // [debugSchedulerExceptionHandler], if set. If not set, then simply prints
  // the error.
  void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace callbackStack ]) {
647
    assert(callback != null);
648
    assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
649
    // TODO(ianh): Consider using a Zone instead to track the current callback registration stack
650
    assert(() { _FrameCallbackEntry.debugCurrentCallbackStack = callbackStack; return true; });
651 652
    try {
      callback(timeStamp);
653
    } catch (exception, exceptionStack) {
654 655
      FlutterError.reportError(new FlutterErrorDetails(
        exception: exception,
656
        stack: exceptionStack,
657
        library: 'scheduler library',
658
        context: 'during a scheduler callback',
659
        informationCollector: (callbackStack == null) ? null : (StringBuffer information) {
660 661 662 663 664 665
          information.writeln(
            '\nThis exception was thrown in the context of a scheduler callback. '
            'When the scheduler callback was _registered_ (as opposed to when the '
            'exception was thrown), this was the stack:'
          );
          FlutterError.defaultStackFilter(callbackStack.toString().trimRight().split('\n')).forEach(information.writeln);
666
        }
667
      ));
668
    }
669
    assert(() { _FrameCallbackEntry.debugCurrentCallbackStack = null; return true; });
670
  }
671 672
}

673
/// The default [SchedulingStrategy] for [SchedulerBinding.schedulingStrategy].
674 675 676 677
///
/// If there are any frame callbacks registered, only runs tasks with
/// a [Priority] of [Priority.animation] or higher. Otherwise, runs
/// all tasks.
678
bool defaultSchedulingStrategy({ int priority, SchedulerBinding scheduler }) {
Ian Hickson's avatar
Ian Hickson committed
679
  if (scheduler.transientCallbackCount > 0)
680
    return priority >= Priority.animation.value;
Ian Hickson's avatar
Ian Hickson committed
681
  return true;
682
}