scheduler.dart 5.79 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:collection';
6
import 'dart:developer';
7
import 'dart:ui' as ui;
8 9 10 11 12 13 14 15 16 17

/// Slows down animations by this factor to help in development.
double timeDilation = 1.0;

/// A 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.
18
typedef void SchedulerCallback(Duration timeStamp);
19

Hixie's avatar
Hixie committed
20 21 22 23 24 25 26
typedef void SchedulerExceptionHandler(dynamic exception, StackTrace stack);
/// This callback is invoked whenever an exception is caught by the scheduler.
/// The 'exception' argument contains the object that was thrown, and the
/// 'stack' argument contains the stack trace. If the callback is set, it is
/// invoked instead of printing the information to the console.
SchedulerExceptionHandler debugSchedulerExceptionHandler;

27 28 29 30
/// Schedules callbacks to run in concert with the engine's animation system
class Scheduler {
  /// Requires clients to use the [scheduler] singleton
  Scheduler._() {
31
    ui.window.onBeginFrame = beginFrame;
32 33 34
  }

  bool _haveScheduledVisualUpdate = false;
Hixie's avatar
Hixie committed
35
  int _nextCallbackId = 0; // positive
36 37 38 39

  final List<SchedulerCallback> _persistentCallbacks = new List<SchedulerCallback>();
  Map<int, SchedulerCallback> _transientCallbacks = new LinkedHashMap<int, SchedulerCallback>();
  final Set<int> _removedIds = new Set<int>();
Hixie's avatar
Hixie committed
40 41 42
  final List<SchedulerCallback> _postFrameCallbacks = new List<SchedulerCallback>();

  bool _inFrame = false;
43

44 45
  int get transientCallbackCount => _transientCallbacks.length;

46 47 48 49 50 51 52 53 54 55 56 57 58
  void _invokeAnimationCallbacks(Duration timeStamp) {
    Timeline.startSync('Animate');
    assert(_inFrame);
    Map<int, SchedulerCallback> callbacks = _transientCallbacks;
    _transientCallbacks = new Map<int, SchedulerCallback>();
    callbacks.forEach((int id, SchedulerCallback callback) {
      if (!_removedIds.contains(id))
        invokeCallback(callback, timeStamp);
    });
    _removedIds.clear();
    Timeline.finishSync();
  }

59 60 61
  /// Called by the engine to produce a new frame.
  ///
  /// This function first calls all the callbacks registered by
Hixie's avatar
Hixie committed
62 63 64
  /// [requestAnimationFrame], then calls all the callbacks registered by
  /// [addPersistentFrameCallback], which typically drive the rendering pipeline,
  /// and finally calls the callbacks registered by [requestPostFrameCallback].
65
  void beginFrame(Duration rawTimeStamp) {
66
    Timeline.startSync('Begin frame');
Hixie's avatar
Hixie committed
67 68
    assert(!_inFrame);
    _inFrame = true;
69 70
    Duration timeStamp = new Duration(
        microseconds: (rawTimeStamp.inMicroseconds / timeDilation).round());
71
    _haveScheduledVisualUpdate = false;
72
    _invokeAnimationCallbacks(timeStamp);
73 74

    for (SchedulerCallback callback in _persistentCallbacks)
Hixie's avatar
Hixie committed
75 76
      invokeCallback(callback, timeStamp);

77 78
    List<SchedulerCallback> localPostFrameCallbacks =
        new List<SchedulerCallback>.from(_postFrameCallbacks);
Hixie's avatar
Hixie committed
79
    _postFrameCallbacks.clear();
80 81
    for (SchedulerCallback callback in localPostFrameCallbacks)
      invokeCallback(callback, timeStamp);
Hixie's avatar
Hixie committed
82 83

    _inFrame = false;
84
    Timeline.finishSync();
Hixie's avatar
Hixie committed
85 86 87 88 89
  }

  void invokeCallback(SchedulerCallback callback, Duration timeStamp) {
    assert(callback != null);
    try {
90
      callback(timeStamp);
Hixie's avatar
Hixie committed
91 92 93 94 95 96 97 98 99 100
    } catch (exception, stack) {
      if (debugSchedulerExceptionHandler != null) {
        debugSchedulerExceptionHandler(exception, stack);
      } else {
        print('-- EXCEPTION IN SCHEDULER CALLBACK --');
        print('$exception');
        print('Stack trace:');
        print('$stack');
      }
    }
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
  }

  /// Call callback every frame.
  void addPersistentFrameCallback(SchedulerCallback callback) {
    _persistentCallbacks.add(callback);
  }

  /// Schedule a callback for the next frame.
  ///
  /// The callback will be run prior to flushing the main rendering pipeline.
  /// Typically, requestAnimationFrame is used to throttle writes into the
  /// rendering pipeline until the system is ready to accept a new frame. For
  /// example, if you wanted to tick through an animation, you should use
  /// requestAnimation frame to determine when to tick the animation. The callback
  /// is passed a timeStamp that you can use to determine how far along the
  /// timeline to advance your animation.
  ///
Hixie's avatar
Hixie committed
118 119
  /// Callbacks in invoked in an arbitrary order.
  ///
120 121
  /// Returns an id that can be used to unschedule this callback.
  int requestAnimationFrame(SchedulerCallback callback) {
Hixie's avatar
Hixie committed
122 123
    _nextCallbackId += 1;
    _transientCallbacks[_nextCallbackId] = callback;
124
    ensureVisualUpdate();
Hixie's avatar
Hixie committed
125
    return _nextCallbackId;
126 127 128 129
  }

  /// Cancel the callback identified by id.
  void cancelAnimationFrame(int id) {
Hixie's avatar
Hixie committed
130
    assert(id > 0);
131 132 133 134
    _transientCallbacks.remove(id);
    _removedIds.add(id);
  }

Hixie's avatar
Hixie committed
135 136 137 138 139 140 141 142 143 144
  /// Schedule a callback for the end of this frame.
  ///
  /// If a frame is in progress, the callback will be run just after the main
  /// rendering pipeline has been flushed. In this case, order is preserved (the
  /// callbacks are run in registration order).
  ///
  /// If no frame is in progress, it will be called at the start of the next
  /// frame. In this case, the registration order is not preserved. Callbacks
  /// are called in an arbitrary order.
  void requestPostFrameCallback(SchedulerCallback callback) {
145
    _postFrameCallbacks.add(callback);
Hixie's avatar
Hixie committed
146 147
  }

148 149 150 151
  /// Ensure that a frame will be produced after this function is called.
  void ensureVisualUpdate() {
    if (_haveScheduledVisualUpdate)
      return;
152
    ui.window.scheduleFrame();
153 154 155 156 157 158
    _haveScheduledVisualUpdate = true;
  }
}

/// A singleton instance of Scheduler to coordinate all the callbacks.
final Scheduler scheduler = new Scheduler._();