Commit 61605a9d authored by Ian Hickson's avatar Ian Hickson

Rearrange scheduling library (#3388)

To be more consistent with other parts of the platform:

* put the binding in a binding.dart file.

* rearrange some members of the Scheduler class to be more close to
  execution order.

* factor out Priority class into its own file.

* add more dart docs.
parent e7657b94
...@@ -11,5 +11,6 @@ ...@@ -11,5 +11,6 @@
/// For example, an idle-task is only executed when no animation is running. /// For example, an idle-task is only executed when no animation is running.
library scheduler; library scheduler;
export 'src/scheduler/scheduler.dart'; export 'src/scheduler/binding.dart';
export 'src/scheduler/priority.dart';
export 'src/scheduler/ticker.dart'; export 'src/scheduler/ticker.dart';
...@@ -11,6 +11,8 @@ import 'dart:ui' show VoidCallback; ...@@ -11,6 +11,8 @@ import 'dart:ui' show VoidCallback;
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'priority.dart';
export 'dart:ui' show VoidCallback; export 'dart:ui' show VoidCallback;
/// Slows down animations by this factor to help in development. /// Slows down animations by this factor to help in development.
...@@ -24,11 +26,16 @@ double timeDilation = 1.0; ...@@ -24,11 +26,16 @@ double timeDilation = 1.0;
/// common time base. /// common time base.
typedef void FrameCallback(Duration timeStamp); typedef void FrameCallback(Duration timeStamp);
/// Signature for the [Scheduler.schedulingStrategy] callback. Invoked
/// 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].
typedef bool SchedulingStrategy({ int priority, Scheduler scheduler }); typedef bool SchedulingStrategy({ int priority, Scheduler scheduler });
/// An entry in the scheduler's priority queue.
///
/// Combines the task and its priority.
class _TaskEntry { class _TaskEntry {
const _TaskEntry(this.task, this.priority); const _TaskEntry(this.task, this.priority);
final VoidCallback task; final VoidCallback task;
...@@ -52,52 +59,17 @@ class _FrameCallbackEntry { ...@@ -52,52 +59,17 @@ class _FrameCallbackEntry {
StackTrace stack; StackTrace stack;
} }
class Priority { /// Scheduler for running the following:
static const Priority idle = const Priority._(0);
static const Priority animation = const Priority._(100000);
static const Priority touch = const Priority._(200000);
/// Relative priorities are clamped by this offset.
///
/// It is still possible to have priorities that are offset by more than this
/// amount by repeatedly taking relative offsets, but that's generally
/// discouraged.
static const int kMaxOffset = 10000;
const Priority._(this._value);
int get value => _value;
final int _value;
/// Returns a priority relative to this priority.
///
/// A positive [offset] indicates a higher priority.
///
/// The parameter [offset] is clamped to +/-[kMaxOffset].
Priority operator +(int offset) {
if (offset.abs() > kMaxOffset) {
// Clamp the input offset.
offset = kMaxOffset * offset.sign;
}
return new Priority._(_value + offset);
}
/// Returns a priority relative to this priority.
///
/// A positive offset indicates a lower priority.
///
/// The parameter [offset] is clamped to +/-[kMaxOffset].
Priority operator -(int offset) => this + (-offset);
}
/// Scheduler running tasks with specific priorities.
/// ///
/// Combines the task's priority with remaining time in a frame to decide when /// * _Frame callbacks_, triggered by the system's
/// the task should be run. /// [ui.window.onBeginFrame] callback, for synchronising the
/// application's behavior to the system's display. For example, the
/// rendering layer uses this to drive its rendering pipeline.
/// ///
/// Tasks always run in the idle time after a frame has been committed. /// * Non-rendering tasks, to be run between frames. These are given a
/// priority and are executed in priority order according to a
/// [schedulingStrategy].
abstract class Scheduler extends BindingBase { abstract class Scheduler extends BindingBase {
/// Requires clients to use the [scheduler] singleton
@override @override
void initInstances() { void initInstances() {
...@@ -106,42 +78,61 @@ abstract class Scheduler extends BindingBase { ...@@ -106,42 +78,61 @@ abstract class Scheduler extends BindingBase {
ui.window.onBeginFrame = handleBeginFrame; ui.window.onBeginFrame = handleBeginFrame;
} }
static Scheduler _instance; /// The current [Scheduler], if one has been created.
static Scheduler get instance => _instance; static Scheduler get instance => _instance;
static Scheduler _instance;
/// The strategy to use when deciding whether to run a task or not.
///
/// Defaults to [defaultSchedulingStrategy].
SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy; SchedulingStrategy schedulingStrategy = defaultSchedulingStrategy;
static int _taskSorter (_TaskEntry e1, _TaskEntry e2) { static int _taskSorter (_TaskEntry e1, _TaskEntry e2) {
// Note that we inverse the priority.
return -e1.priority.compareTo(e2.priority); return -e1.priority.compareTo(e2.priority);
} }
final PriorityQueue<_TaskEntry> _taskQueue = new HeapPriorityQueue<_TaskEntry>(_taskSorter); final PriorityQueue<_TaskEntry> _taskQueue = new HeapPriorityQueue<_TaskEntry>(_taskSorter);
/// Whether this scheduler already requested to be called from the event loop. /// Schedules the given `task` with the given `priority`.
bool _hasRequestedAnEventLoopCallback = false; ///
/// Tasks will be executed between frames, in priority order,
/// Whether this scheduler already requested to be called at the beginning of /// excluding tasks that are skipped by the current
/// the next frame. /// [schedulingStrategy]. Tasks should be short (as in, up to a
bool _hasRequestedABeginFrameCallback = false; /// millisecond), so as to not cause the regular frame callbacks to
/// get delayed.
/// Schedules the given [task] with the given [priority].
void scheduleTask(VoidCallback task, Priority priority) { void scheduleTask(VoidCallback task, Priority priority) {
bool isFirstTask = _taskQueue.isEmpty; bool isFirstTask = _taskQueue.isEmpty;
_taskQueue.add(new _TaskEntry(task, priority._value)); _taskQueue.add(new _TaskEntry(task, priority.value));
if (isFirstTask) if (isFirstTask)
_ensureEventLoopCallback(); _ensureEventLoopCallback();
} }
// 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;
}
/// Invoked by the system when there is time to run tasks. /// Invoked by the system when there is time to run tasks.
void handleEventLoopCallback() { void handleEventLoopCallback() {
_hasRequestedAnEventLoopCallback = false; _hasRequestedAnEventLoopCallback = false;
_runTasks(); _runTasks();
} }
// Called when the system wakes up and at the end of each frame.
void _runTasks() { void _runTasks() {
if (_taskQueue.isEmpty) if (_taskQueue.isEmpty)
return; return;
_TaskEntry entry = _taskQueue.first; _TaskEntry entry = _taskQueue.first;
// 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).
if (schedulingStrategy(priority: entry.priority, scheduler: this)) { if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
try { try {
(_taskQueue.removeFirst().task)(); (_taskQueue.removeFirst().task)();
...@@ -152,27 +143,39 @@ abstract class Scheduler extends BindingBase { ...@@ -152,27 +143,39 @@ abstract class Scheduler extends BindingBase {
} else { } else {
// TODO(floitsch): we shouldn't need to request a frame. Just schedule // TODO(floitsch): we shouldn't need to request a frame. Just schedule
// an event-loop callback. // an event-loop callback.
_ensureBeginFrameCallback(); ensureVisualUpdate();
} }
} }
int _nextFrameCallbackId = 0; // positive int _nextFrameCallbackId = 0; // positive
Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{}; Map<int, _FrameCallbackEntry> _transientCallbacks = <int, _FrameCallbackEntry>{};
final Set<int> _removedIds = new HashSet<int>(); final Set<int> _removedIds = new HashSet<int>();
/// The current number of transient frame callbacks scheduled.
///
/// This is reset to zero just before all the currently scheduled
/// transient callbacks are invoked, at the start of a frame.
///
/// 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.
int get transientCallbackCount => _transientCallbacks.length; int get transientCallbackCount => _transientCallbacks.length;
/// Schedules the given frame callback. /// Schedules the given frame callback.
/// ///
/// Adds the given callback to the list of frame-callbacks and ensures that a /// Adds the given callback to the list of frame callbacks and ensures that a
/// frame is scheduled. /// frame is scheduled.
/// ///
/// If `rescheduling` is true, the call must be in the context of a /// If `rescheduling` is true, the call must be in the context of a
/// frame callback, and for debugging purposes the stack trace /// frame callback, and for debugging purposes the stack trace
/// stored for this callback will be the same stack trace as for the /// stored for this callback will be the same stack trace as for the
/// current callback. /// current callback.
///
/// Callbacks registered with this method can be canceled using
/// [cancelFrameCallbackWithId].
int scheduleFrameCallback(FrameCallback callback, { bool rescheduling: false }) { int scheduleFrameCallback(FrameCallback callback, { bool rescheduling: false }) {
_ensureBeginFrameCallback(); ensureVisualUpdate();
return addFrameCallback(callback, rescheduling: rescheduling); return addFrameCallback(callback, rescheduling: rescheduling);
} }
...@@ -181,8 +184,8 @@ abstract class Scheduler extends BindingBase { ...@@ -181,8 +184,8 @@ abstract class Scheduler extends BindingBase {
/// Frame callbacks are executed at the beginning of a frame (see /// Frame callbacks are executed at the beginning of a frame (see
/// [handleBeginFrame]). /// [handleBeginFrame]).
/// ///
/// The registered callbacks are executed in the order in which they have been /// These callbacks are executed in the order in which they have
/// registered. /// been added.
/// ///
/// Callbacks registered with this method will not be invoked until /// Callbacks registered with this method will not be invoked until
/// a frame is requested. To register a callback and ensure that a /// a frame is requested. To register a callback and ensure that a
...@@ -192,6 +195,9 @@ abstract class Scheduler extends BindingBase { ...@@ -192,6 +195,9 @@ abstract class Scheduler extends BindingBase {
/// frame callback, and for debugging purposes the stack trace /// frame callback, and for debugging purposes the stack trace
/// stored for this callback will be the same stack trace as for the /// stored for this callback will be the same stack trace as for the
/// current callback. /// current callback.
///
/// Callbacks registered with this method can be canceled using
/// [cancelFrameCallbackWithId].
int addFrameCallback(FrameCallback callback, { bool rescheduling: false }) { int addFrameCallback(FrameCallback callback, { bool rescheduling: false }) {
_nextFrameCallbackId += 1; _nextFrameCallbackId += 1;
_transientCallbacks[_nextFrameCallbackId] = new _FrameCallbackEntry(callback, rescheduling: rescheduling); _transientCallbacks[_nextFrameCallbackId] = new _FrameCallbackEntry(callback, rescheduling: rescheduling);
...@@ -201,69 +207,127 @@ abstract class Scheduler extends BindingBase { ...@@ -201,69 +207,127 @@ abstract class Scheduler extends BindingBase {
/// Cancels the callback of the given [id]. /// Cancels the callback of the given [id].
/// ///
/// Removes the given callback from the list of frame callbacks. If a frame /// Removes the given callback from the list of frame callbacks. If a frame
/// has been requested does *not* cancel that request. /// has been requested, this does not also cancel that request.
///
/// Frame callbacks are registered using [scheduleFrameCallback] or
/// [addFrameCallback].
void cancelFrameCallbackWithId(int id) { void cancelFrameCallbackWithId(int id) {
assert(id > 0); assert(id > 0);
_transientCallbacks.remove(id); _transientCallbacks.remove(id);
_removedIds.add(id); _removedIds.add(id);
} }
/// Asserts that there are no registered transient callbacks; if
/// there are, prints their locations and throws an exception.
///
/// This is expected to be called at the end of tests (the
/// flutter_test framework does it automatically in normal cases).
///
/// Invoke this method when you expect there to be no transient
/// callbacks registered, in an assert statement with a message that
/// you want printed when a transient callback is registered:
///
/// ```dart
/// assert(Scheduler.instance.debugAssertNoTransientCallbacks(
/// '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) {
FlutterError.reportError(new FlutterErrorDetails(
exception: reason,
library: 'scheduler library',
informationCollector: (StringBuffer information) {
information.writeln(
'There ${ transientCallbackCount == 1 ? "was one transient callback" : "were $transientCallbackCount transient callbacks" } '
'left. The stack traces for when they were registered are as follows:'
);
for (int id in _transientCallbacks.keys) {
_FrameCallbackEntry entry = _transientCallbacks[id];
information.writeln('-- callback $id --');
information.writeln(entry.stack);
}
}
));
}
return true;
});
return true;
}
final List<FrameCallback> _persistentCallbacks = new List<FrameCallback>(); final List<FrameCallback> _persistentCallbacks = new List<FrameCallback>();
/// Adds a persistent frame callback. /// Adds a persistent frame callback.
/// ///
/// Persistent callbacks are invoked after transient (non-persistent) frame /// Persistent callbacks are invoked after transient
/// callbacks. /// (non-persistent) frame callbacks.
/// ///
/// Does *not* request a new frame. Conceptually, persistent /// Does *not* request a new frame. Conceptually, persistent frame
/// frame-callbacks are thus observers of begin-frame events. Since they are /// callbacks are observers of "begin frame" events. Since they are
/// executed after the transient frame-callbacks they can drive the rendering /// executed after the transient frame callbacks they can drive the
/// pipeline. /// rendering pipeline.
void addPersistentFrameCallback(FrameCallback callback) { void addPersistentFrameCallback(FrameCallback callback) {
_persistentCallbacks.add(callback); _persistentCallbacks.add(callback);
} }
final List<FrameCallback> _postFrameCallbacks = new List<FrameCallback>(); final List<FrameCallback> _postFrameCallbacks = new List<FrameCallback>();
/// Schedule a callback for the end of this frame. /// Schedule a callback for the end of this frame.
/// ///
/// Does *not* request a new frame. /// Does *not* request a new frame.
/// ///
/// The callback is run just after the persistent frame-callbacks (which is /// This callback is run during a frame, just after the persistent
/// when the main rendering pipeline has been flushed). If a frame is /// frame callbacks (which is when the main rendering pipeline has
/// in progress, but post frame-callbacks haven't been executed yet, then the /// been flushed). If a frame is in progress and post-frame
/// registered callback is still executed during the frame. Otherwise, /// callbacks haven't been executed yet, then the registered
/// the registered callback is executed during the next frame. /// callback is still executed during the frame. Otherwise, the
/// registered callback is executed during the next frame.
/// ///
/// The registered callbacks are executed in the order in which they have been /// The callbacks are executed in the order in which they have been
/// registered. /// added.
void addPostFrameCallback(FrameCallback callback) { void addPostFrameCallback(FrameCallback callback) {
_postFrameCallbacks.add(callback); _postFrameCallbacks.add(callback);
} }
static bool get debugInFrame => _debugInFrame;
static bool _debugInFrame = false;
void _invokeTransientFrameCallbacks(Duration timeStamp) { // Whether this scheduler already requested to be called at the beginning of
Timeline.startSync('Animate'); // the next frame.
assert(_debugInFrame); bool _hasRequestedABeginFrameCallback = false;
Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
_transientCallbacks = new Map<int, _FrameCallbackEntry>(); /// If necessary, schedules a new frame by calling
callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) { /// [ui.window.scheduleFrame].
if (!_removedIds.contains(id)) ///
invokeFrameCallback(callbackEntry.callback, timeStamp, callbackEntry.stack); /// After this is called, the engine will (eventually) invoke
}); /// [handleBeginFrame]. (This call might be delayed, e.g. if the
_removedIds.clear(); /// device's screen is turned off it will typically be delayed until
Timeline.finishSync(); /// the screen is on and the application is visible.)
void ensureVisualUpdate() {
if (_hasRequestedABeginFrameCallback)
return;
ui.window.scheduleFrame();
_hasRequestedABeginFrameCallback = true;
} }
/// Whether the scheduler is currently handling a "begin frame"
/// callback.
///
/// True while [handleBeginFrame] is running in checked mode. False
/// otherwise.
static bool get debugInFrame => _debugInFrame;
static bool _debugInFrame = false;
/// Called by the engine to produce a new frame. /// Called by the engine to produce a new frame.
/// ///
/// This function first calls all the callbacks registered by /// This function first calls all the callbacks registered by
/// [scheduleFrameCallback]/[addFrameCallback], then calls all the callbacks /// [scheduleFrameCallback]/[addFrameCallback], then calls all the
/// registered by [addPersistentFrameCallback], which typically drive the /// callbacks registered by [addPersistentFrameCallback], which
/// rendering pipeline, and finally calls the callbacks registered by /// typically drive the rendering pipeline, and finally calls the
/// [addPostFrameCallback]. /// callbacks registered by [addPostFrameCallback].
void handleBeginFrame(Duration rawTimeStamp) { void handleBeginFrame(Duration rawTimeStamp) {
Timeline.startSync('Begin frame'); Timeline.startSync('Begin frame');
assert(!_debugInFrame); assert(!_debugInFrame);
...@@ -274,13 +338,13 @@ abstract class Scheduler extends BindingBase { ...@@ -274,13 +338,13 @@ abstract class Scheduler extends BindingBase {
_invokeTransientFrameCallbacks(timeStamp); _invokeTransientFrameCallbacks(timeStamp);
for (FrameCallback callback in _persistentCallbacks) for (FrameCallback callback in _persistentCallbacks)
invokeFrameCallback(callback, timeStamp); _invokeFrameCallback(callback, timeStamp);
List<FrameCallback> localPostFrameCallbacks = List<FrameCallback> localPostFrameCallbacks =
new List<FrameCallback>.from(_postFrameCallbacks); new List<FrameCallback>.from(_postFrameCallbacks);
_postFrameCallbacks.clear(); _postFrameCallbacks.clear();
for (FrameCallback callback in localPostFrameCallbacks) for (FrameCallback callback in localPostFrameCallbacks)
invokeFrameCallback(callback, timeStamp); _invokeFrameCallback(callback, timeStamp);
assert(() { _debugInFrame = false; return true; }); assert(() { _debugInFrame = false; return true; });
Timeline.finishSync(); Timeline.finishSync();
...@@ -289,14 +353,25 @@ abstract class Scheduler extends BindingBase { ...@@ -289,14 +353,25 @@ abstract class Scheduler extends BindingBase {
_runTasks(); _runTasks();
} }
/// Invokes the given [callback] with [timestamp] as argument. void _invokeTransientFrameCallbacks(Duration timeStamp) {
/// Timeline.startSync('Animate');
/// Wraps the callback in a try/catch and forwards any error to assert(_debugInFrame);
/// [debugSchedulerExceptionHandler], if set. If not set, then simply prints Map<int, _FrameCallbackEntry> callbacks = _transientCallbacks;
/// the error. _transientCallbacks = new Map<int, _FrameCallbackEntry>();
/// callbacks.forEach((int id, _FrameCallbackEntry callbackEntry) {
/// Must not be called reentrantly from within a frame callback. if (!_removedIds.contains(id))
void invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace callbackStack ]) { _invokeFrameCallback(callbackEntry.callback, timeStamp, callbackEntry.stack);
});
_removedIds.clear();
Timeline.finishSync();
}
// Invokes the given [callback] with [timestamp] as argument.
//
// 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 ]) {
assert(callback != null); assert(callback != null);
assert(_FrameCallbackEntry.currentCallbackStack == null); assert(_FrameCallbackEntry.currentCallbackStack == null);
assert(() { _FrameCallbackEntry.currentCallbackStack = callbackStack; return true; }); assert(() { _FrameCallbackEntry.currentCallbackStack = callbackStack; return true; });
...@@ -315,75 +390,15 @@ abstract class Scheduler extends BindingBase { ...@@ -315,75 +390,15 @@ abstract class Scheduler extends BindingBase {
} }
assert(() { _FrameCallbackEntry.currentCallbackStack = null; return true; }); assert(() { _FrameCallbackEntry.currentCallbackStack = null; return true; });
} }
/// Asserts that there are no registered transient callbacks; if
/// there are, prints their locations and throws an exception.
///
/// This is expected to be called at the end of tests (the
/// flutter_test framework does it automatically in normal cases).
///
/// To invoke this method, call it, when you expect there to be no
/// transient callbacks registered, in an assert statement with a
/// message that you want printed when a transient callback is
/// registered, as follows:
///
/// ```dart
/// assert(Scheduler.instance.debugAssertNoTransientCallbacks(
/// '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) {
FlutterError.reportError(new FlutterErrorDetails(
exception: reason,
library: 'scheduler library',
informationCollector: (StringBuffer information) {
information.writeln(
'There ${ transientCallbackCount == 1 ? "was one transient callback" : "were $transientCallbackCount transient callbacks" } '
'left. The stack traces for when they were registered are as follows:'
);
for (int id in _transientCallbacks.keys) {
_FrameCallbackEntry entry = _transientCallbacks[id];
information.writeln('-- callback $id --');
information.writeln(entry.stack);
}
}
));
}
return true;
});
return true;
}
/// Ensures that the scheduler is woken by the event loop.
void _ensureEventLoopCallback() {
if (_hasRequestedAnEventLoopCallback)
return;
Timer.run(handleEventLoopCallback);
_hasRequestedAnEventLoopCallback = true;
}
// TODO(floitsch): "ensureVisualUpdate" doesn't really fit into the scheduler.
void ensureVisualUpdate() {
_ensureBeginFrameCallback();
}
/// Schedules a new frame.
void _ensureBeginFrameCallback() {
if (_hasRequestedABeginFrameCallback)
return;
ui.window.scheduleFrame();
_hasRequestedABeginFrameCallback = true;
}
} }
// TODO(floitsch): for now we only expose the priority. It might be interesting /// The default [SchedulingStrategy] for [Scheduler.schedulingStrategy].
// to provide more info (like, how long the task ran the last time). ///
/// If there are any frame callbacks registered, only runs tasks with
/// a [Priority] of [Priority.animation] or higher. Otherwise, runs
/// all tasks.
bool defaultSchedulingStrategy({ int priority, Scheduler scheduler }) { bool defaultSchedulingStrategy({ int priority, Scheduler scheduler }) {
if (scheduler.transientCallbackCount > 0) if (scheduler.transientCallbackCount > 0)
return priority >= Priority.animation._value; return priority >= Priority.animation.value;
return true; return true;
} }
// Copyright 2016 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.
/// A task priority, as passed to [Scheduler.scheduleTask].
class Priority {
const Priority._(this._value);
/// The integer that describes this Priority value.
int get value => _value;
final int _value;
/// A task to run after all other tasks, when no animations are running.
static const Priority idle = const Priority._(0);
/// A task to run even when animations are running.
static const Priority animation = const Priority._(100000);
/// A task to run even when the user is interacting with the device.
static const Priority touch = const Priority._(200000);
/// Maximum offset by which to clamp relative priorities.
///
/// It is still possible to have priorities that are offset by more
/// than this amount by repeatedly taking relative offsets, but that
/// is generally discouraged.
static const int kMaxOffset = 10000;
/// Returns a priority relative to this priority.
///
/// A positive [offset] indicates a higher priority.
///
/// The parameter [offset] is clamped to ±[kMaxOffset].
Priority operator +(int offset) {
if (offset.abs() > kMaxOffset) {
// Clamp the input offset.
offset = kMaxOffset * offset.sign;
}
return new Priority._(_value + offset);
}
/// Returns a priority relative to this priority.
///
/// A positive offset indicates a lower priority.
///
/// The parameter [offset] is clamped to ±[kMaxOffset].
Priority operator -(int offset) => this + (-offset);
}
...@@ -4,14 +4,22 @@ ...@@ -4,14 +4,22 @@
import 'dart:async'; import 'dart:async';
import 'scheduler.dart'; import 'binding.dart';
/// Signature for the [onTick] constructor argument of the [Ticker] class. /// Signature for the [onTick] constructor argument of the [Ticker] class.
///
/// The argument is the time that the object had spent enabled so far
/// at the time of the callback being invoked.
typedef void TickerCallback(Duration elapsed); typedef void TickerCallback(Duration elapsed);
/// Calls its callback once per animation frame. /// Calls its callback once per animation frame.
///
/// When created, a ticker is initially disabled. Call [start] to
/// enable the ticker.
///
/// See also [Scheduler.scheduleFrameCallback].
class Ticker { class Ticker {
/// Constructs a ticker that will call [onTick] once per frame while running. /// Creates a ticker that will call [onTick] once per frame while running.
Ticker(TickerCallback onTick) : _onTick = onTick; Ticker(TickerCallback onTick) : _onTick = onTick;
final TickerCallback _onTick; final TickerCallback _onTick;
...@@ -20,7 +28,11 @@ class Ticker { ...@@ -20,7 +28,11 @@ class Ticker {
int _animationId; int _animationId;
Duration _startTime; Duration _startTime;
/// Starts calling onTick once per animation frame. /// Whether this ticker has scheduled a call to invoke its callback
/// on the next frame.
bool get isTicking => _completer != null;
/// Starts calling the ticker's callback once per animation frame.
/// ///
/// The returned future resolves once the ticker stops ticking. /// The returned future resolves once the ticker stops ticking.
Future<Null> start() { Future<Null> start() {
...@@ -31,7 +43,7 @@ class Ticker { ...@@ -31,7 +43,7 @@ class Ticker {
return _completer.future; return _completer.future;
} }
/// Stops calling onTick. /// Stops calling the ticker's callback.
/// ///
/// Causes the future returned by [start] to resolve. /// Causes the future returned by [start] to resolve.
void stop() { void stop() {
...@@ -54,9 +66,6 @@ class Ticker { ...@@ -54,9 +66,6 @@ class Ticker {
localCompleter.complete(); localCompleter.complete();
} }
/// Whether this ticker has scheduled a call to onTick
bool get isTicking => _completer != null;
void _tick(Duration timeStamp) { void _tick(Duration timeStamp) {
assert(isTicking); assert(isTicking);
assert(_animationId != null); assert(_animationId != null);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment