diff --git a/packages/flutter/lib/src/animation/scheduler.dart b/packages/flutter/lib/src/animation/scheduler.dart index 980eda485e2..9f2f79990b1 100644 --- a/packages/flutter/lib/src/animation/scheduler.dart +++ b/packages/flutter/lib/src/animation/scheduler.dart @@ -2,157 +2,4 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:collection'; -import 'dart:developer'; -import 'dart:ui' as ui; - -/// 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. -typedef void SchedulerCallback(Duration timeStamp); - -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; - -/// Schedules callbacks to run in concert with the engine's animation system -class Scheduler { - /// Requires clients to use the [scheduler] singleton - Scheduler._() { - ui.window.onBeginFrame = beginFrame; - } - - bool _haveScheduledVisualUpdate = false; - int _nextCallbackId = 0; // positive - - final List _persistentCallbacks = new List(); - Map _transientCallbacks = new LinkedHashMap(); - final Set _removedIds = new Set(); - final List _postFrameCallbacks = new List(); - - bool _inFrame = false; - - int get transientCallbackCount => _transientCallbacks.length; - - void _invokeAnimationCallbacks(Duration timeStamp) { - Timeline.startSync('Animate'); - assert(_inFrame); - Map callbacks = _transientCallbacks; - _transientCallbacks = new Map(); - callbacks.forEach((int id, SchedulerCallback callback) { - if (!_removedIds.contains(id)) - invokeCallback(callback, timeStamp); - }); - _removedIds.clear(); - Timeline.finishSync(); - } - - /// Called by the engine to produce a new frame. - /// - /// This function first calls all the callbacks registered by - /// [requestAnimationFrame], then calls all the callbacks registered by - /// [addPersistentFrameCallback], which typically drive the rendering pipeline, - /// and finally calls the callbacks registered by [requestPostFrameCallback]. - void beginFrame(Duration rawTimeStamp) { - Timeline.startSync('Begin frame'); - assert(!_inFrame); - _inFrame = true; - Duration timeStamp = new Duration( - microseconds: (rawTimeStamp.inMicroseconds / timeDilation).round()); - _haveScheduledVisualUpdate = false; - _invokeAnimationCallbacks(timeStamp); - - for (SchedulerCallback callback in _persistentCallbacks) - invokeCallback(callback, timeStamp); - - List localPostFrameCallbacks = - new List.from(_postFrameCallbacks); - _postFrameCallbacks.clear(); - for (SchedulerCallback callback in localPostFrameCallbacks) - invokeCallback(callback, timeStamp); - - _inFrame = false; - Timeline.finishSync(); - } - - void invokeCallback(SchedulerCallback callback, Duration timeStamp) { - assert(callback != null); - try { - callback(timeStamp); - } catch (exception, stack) { - if (debugSchedulerExceptionHandler != null) { - debugSchedulerExceptionHandler(exception, stack); - } else { - print('-- EXCEPTION IN SCHEDULER CALLBACK --'); - print('$exception'); - print('Stack trace:'); - print('$stack'); - } - } - } - - /// 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. - /// - /// Callbacks in invoked in an arbitrary order. - /// - /// Returns an id that can be used to unschedule this callback. - int requestAnimationFrame(SchedulerCallback callback) { - _nextCallbackId += 1; - _transientCallbacks[_nextCallbackId] = callback; - ensureVisualUpdate(); - return _nextCallbackId; - } - - /// Cancel the callback identified by id. - void cancelAnimationFrame(int id) { - assert(id > 0); - _transientCallbacks.remove(id); - _removedIds.add(id); - } - - /// 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) { - _postFrameCallbacks.add(callback); - } - - /// Ensure that a frame will be produced after this function is called. - void ensureVisualUpdate() { - if (_haveScheduledVisualUpdate) - return; - ui.window.scheduleFrame(); - _haveScheduledVisualUpdate = true; - } -} - -/// A singleton instance of Scheduler to coordinate all the callbacks. -final Scheduler scheduler = new Scheduler._(); +export '../scheduler/scheduler.dart'; diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index 8523558de4f..a63bfb5cf36 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -48,7 +48,7 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { if (onTapCancel != null) onTapCancel(); _reset(); - } + } super.resolve(disposition); } diff --git a/packages/flutter/lib/src/scheduler/scheduler.dart b/packages/flutter/lib/src/scheduler/scheduler.dart index b05aa12d8fd..70a1fc5c273 100644 --- a/packages/flutter/lib/src/scheduler/scheduler.dart +++ b/packages/flutter/lib/src/scheduler/scheduler.dart @@ -3,16 +3,35 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:ui' show VoidCallback; +import 'dart:developer'; +import 'dart:ui' as ui; import 'package:collection/priority_queue.dart'; import 'package:flutter/animation.dart' as animation show scheduler; +/// Slows down animations by this factor to help in development. +double timeDilation = 1.0; + +/// 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. +typedef void SchedulerCallback(Duration timeStamp); + +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; + /// An entry in the scheduler's priority queue. /// /// Combines the task and its priority. class _SchedulerEntry { - final VoidCallback task; + final ui.VoidCallback task; final int priority; const _SchedulerEntry(this.task, this.priority); @@ -62,8 +81,11 @@ class Priority { /// the task should be run. /// /// Tasks always run in the idle time after a frame has been committed. -class TaskScheduler { - TaskScheduler._(); +class Scheduler { + /// Requires clients to use the [scheduler] singleton + Scheduler._() { + ui.window.onBeginFrame = beginFrame; + } SchedulingStrategy schedulingStrategy = new DefaultSchedulingStrategy(); @@ -82,7 +104,7 @@ class TaskScheduler { bool _wakingNextFrame = false; /// Schedules the given [task] with the given [priority]. - void schedule(VoidCallback task, Priority priority) { + void scheduleTask(ui.VoidCallback task, Priority priority) { bool isFirstTask = _queue.isEmpty; _queue.add(new _SchedulerEntry(task, priority._value)); if (isFirstTask) @@ -106,6 +128,110 @@ class TaskScheduler { } } + int _nextFrameCallbackId = 0; // positive + Map _transientCallbacks = {}; + final Set _removedIds = new Set(); + + int get transientCallbackCount => _transientCallbacks.length; + + /// Schedules the given frame callback. + int requestAnimationFrame(SchedulerCallback callback) { + _nextFrameCallbackId += 1; + _transientCallbacks[_nextFrameCallbackId] = callback; + _wakeNextFrame(); + return _nextFrameCallbackId; + } + + /// Cancels the callback of the given [id]. + void cancelAnimationFrame(int id) { + assert(id > 0); + _transientCallbacks.remove(id); + _removedIds.add(id); + } + + final List _persistentCallbacks = new List(); + + void addPersistentFrameCallback(SchedulerCallback callback) { + _persistentCallbacks.add(callback); + } + + final List _postFrameCallbacks = new List(); + + /// 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) { + _postFrameCallbacks.add(callback); + } + + bool _inFrame = false; + + void _invokeAnimationCallbacks(Duration timeStamp) { + Timeline.startSync('Animate'); + assert(_inFrame); + Map callbacks = _transientCallbacks; + _transientCallbacks = new Map(); + callbacks.forEach((int id, SchedulerCallback callback) { + if (!_removedIds.contains(id)) + invokeCallback(callback, timeStamp); + }); + _removedIds.clear(); + Timeline.finishSync(); + } + + /// Called by the engine to produce a new frame. + /// + /// This function first calls all the callbacks registered by + /// [requestAnimationFrame], then calls all the callbacks registered by + /// [addPersistentFrameCallback], which typically drive the rendering pipeline, + /// and finally calls the callbacks registered by [requestPostFrameCallback]. + void beginFrame(Duration rawTimeStamp) { + Timeline.startSync('Begin frame'); + assert(!_inFrame); + _inFrame = true; + Duration timeStamp = new Duration( + microseconds: (rawTimeStamp.inMicroseconds / timeDilation).round()); + _wakingNextFrame = false; + _invokeAnimationCallbacks(timeStamp); + + for (SchedulerCallback callback in _persistentCallbacks) + invokeCallback(callback, timeStamp); + + List localPostFrameCallbacks = + new List.from(_postFrameCallbacks); + _postFrameCallbacks.clear(); + for (SchedulerCallback callback in localPostFrameCallbacks) + invokeCallback(callback, timeStamp); + + _inFrame = false; + Timeline.finishSync(); + + // All frame-related callbacks have been executed. Run lower-priority tasks. + tick(); + } + + void invokeCallback(SchedulerCallback callback, Duration timeStamp) { + assert(callback != null); + try { + callback(timeStamp); + } catch (exception, stack) { + if (debugSchedulerExceptionHandler != null) { + debugSchedulerExceptionHandler(exception, stack); + } else { + print('-- EXCEPTION IN SCHEDULER CALLBACK --'); + print('$exception'); + print('Stack trace:'); + print('$stack'); + } + } + } + /// Tells the system that the scheduler is awake and should be called as /// soon a there is time. void _wakeNow() { @@ -118,23 +244,20 @@ class TaskScheduler { }); } - /// Tells the system that the scheduler needs to run again (ideally next - /// frame). + void ensureVisualUpdate() { + _wakeNextFrame(); + } + + /// Schedules a new frame. void _wakeNextFrame() { if (_wakingNextFrame) return; _wakingNextFrame = true; - animation.scheduler.requestAnimationFrame((_) { - _wakingNextFrame = false; - // RequestAnimationFrame calls back at the beginning of a frame. We want - // to run in the idle-phase of an animation. We therefore request to be - // woken up as soon as possible. - _wakeNow(); - }); + ui.window.scheduleFrame(); } } -final TaskScheduler tasks = new TaskScheduler._(); +final Scheduler scheduler = new Scheduler._(); abstract class SchedulingStrategy { bool shouldRunTaskWithPriority(int priority); diff --git a/packages/unit/test/scheduler/scheduler_test.dart b/packages/unit/test/scheduler/scheduler_test.dart index e863ee23f94..b8099fecebf 100644 --- a/packages/unit/test/scheduler/scheduler_test.dart +++ b/packages/unit/test/scheduler/scheduler_test.dart @@ -16,29 +16,29 @@ class TestStrategy implements SchedulingStrategy { void main() { test("Tasks are executed in the right order", () { var strategy = new TestStrategy(); - tasks.schedulingStrategy = strategy; + scheduler.schedulingStrategy = strategy; List input = [2, 23, 23, 11, 0, 80, 3]; List executedTasks = []; void scheduleAddingTask(int x) { - tasks.schedule(() { executedTasks.add(x); }, Priority.idle + x); + scheduler.scheduleTask(() { executedTasks.add(x); }, Priority.idle + x); } for (int x in input) { scheduleAddingTask(x); } strategy.allowedPriority = 100; - for (int i = 0; i < 3; i++) tasks.tick(); + for (int i = 0; i < 3; i++) scheduler.tick(); expect(executedTasks.isEmpty, isTrue); strategy.allowedPriority = 50; - for (int i = 0; i < 3; i++) tasks.tick(); + for (int i = 0; i < 3; i++) scheduler.tick(); expect(executedTasks.length, equals(1)); expect(executedTasks.single, equals(80)); executedTasks.clear(); strategy.allowedPriority = 20; - for (int i = 0; i < 3; i++) tasks.tick(); + for (int i = 0; i < 3; i++) scheduler.tick(); expect(executedTasks.length, equals(2)); expect(executedTasks[0], equals(23)); expect(executedTasks[1], equals(23)); @@ -48,21 +48,21 @@ void main() { scheduleAddingTask(19); scheduleAddingTask(5); scheduleAddingTask(97); - for (int i = 0; i < 3; i++) tasks.tick(); + for (int i = 0; i < 3; i++) scheduler.tick(); expect(executedTasks.length, equals(2)); expect(executedTasks[0], equals(99)); expect(executedTasks[1], equals(97)); executedTasks.clear(); strategy.allowedPriority = 10; - for (int i = 0; i < 3; i++) tasks.tick(); + for (int i = 0; i < 3; i++) scheduler.tick(); expect(executedTasks.length, equals(2)); expect(executedTasks[0], equals(19)); expect(executedTasks[1], equals(11)); executedTasks.clear(); strategy.allowedPriority = 1; - for (int i = 0; i < 4; i++) tasks.tick(); + for (int i = 0; i < 4; i++) scheduler.tick(); expect(executedTasks.length, equals(3)); expect(executedTasks[0], equals(5)); expect(executedTasks[1], equals(3)); @@ -70,7 +70,7 @@ void main() { executedTasks.clear(); strategy.allowedPriority = 0; - tasks.tick(); + scheduler.tick(); expect(executedTasks.length, equals(1)); expect(executedTasks[0], equals(0)); });