Commit 43e1dc9d authored by Florian Loitsch's avatar Florian Loitsch

Merge pull request #662 from floitschG/scheduler.merge_schedulers

Merge the two schedulers.
parents 3fe160d8 10c80f2e
...@@ -2,157 +2,4 @@ ...@@ -2,157 +2,4 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:collection'; export '../scheduler/scheduler.dart';
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<SchedulerCallback> _persistentCallbacks = new List<SchedulerCallback>();
Map<int, SchedulerCallback> _transientCallbacks = new LinkedHashMap<int, SchedulerCallback>();
final Set<int> _removedIds = new Set<int>();
final List<SchedulerCallback> _postFrameCallbacks = new List<SchedulerCallback>();
bool _inFrame = false;
int get transientCallbackCount => _transientCallbacks.length;
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();
}
/// 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<SchedulerCallback> localPostFrameCallbacks =
new List<SchedulerCallback>.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._();
...@@ -3,16 +3,35 @@ ...@@ -3,16 +3,35 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:ui' show VoidCallback; import 'dart:developer';
import 'dart:ui' as ui;
import 'package:collection/priority_queue.dart'; import 'package:collection/priority_queue.dart';
import 'package:flutter/animation.dart' as animation show scheduler; 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. /// An entry in the scheduler's priority queue.
/// ///
/// Combines the task and its priority. /// Combines the task and its priority.
class _SchedulerEntry { class _SchedulerEntry {
final VoidCallback task; final ui.VoidCallback task;
final int priority; final int priority;
const _SchedulerEntry(this.task, this.priority); const _SchedulerEntry(this.task, this.priority);
...@@ -62,8 +81,11 @@ class Priority { ...@@ -62,8 +81,11 @@ class Priority {
/// the task should be run. /// the task should be run.
/// ///
/// Tasks always run in the idle time after a frame has been committed. /// Tasks always run in the idle time after a frame has been committed.
class TaskScheduler { class Scheduler {
TaskScheduler._(); /// Requires clients to use the [scheduler] singleton
Scheduler._() {
ui.window.onBeginFrame = beginFrame;
}
SchedulingStrategy schedulingStrategy = new DefaultSchedulingStrategy(); SchedulingStrategy schedulingStrategy = new DefaultSchedulingStrategy();
...@@ -82,7 +104,7 @@ class TaskScheduler { ...@@ -82,7 +104,7 @@ class TaskScheduler {
bool _wakingNextFrame = false; bool _wakingNextFrame = false;
/// Schedules the given [task] with the given [priority]. /// 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; bool isFirstTask = _queue.isEmpty;
_queue.add(new _SchedulerEntry(task, priority._value)); _queue.add(new _SchedulerEntry(task, priority._value));
if (isFirstTask) if (isFirstTask)
...@@ -106,6 +128,110 @@ class TaskScheduler { ...@@ -106,6 +128,110 @@ class TaskScheduler {
} }
} }
int _nextFrameCallbackId = 0; // positive
Map<int, SchedulerCallback> _transientCallbacks = <int, SchedulerCallback>{};
final Set<int> _removedIds = new Set<int>();
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<SchedulerCallback> _persistentCallbacks = new List<SchedulerCallback>();
void addPersistentFrameCallback(SchedulerCallback callback) {
_persistentCallbacks.add(callback);
}
final List<SchedulerCallback> _postFrameCallbacks = new List<SchedulerCallback>();
/// 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<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();
}
/// 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<SchedulerCallback> localPostFrameCallbacks =
new List<SchedulerCallback>.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 /// Tells the system that the scheduler is awake and should be called as
/// soon a there is time. /// soon a there is time.
void _wakeNow() { void _wakeNow() {
...@@ -118,23 +244,20 @@ class TaskScheduler { ...@@ -118,23 +244,20 @@ class TaskScheduler {
}); });
} }
/// Tells the system that the scheduler needs to run again (ideally next void ensureVisualUpdate() {
/// frame). _wakeNextFrame();
}
/// Schedules a new frame.
void _wakeNextFrame() { void _wakeNextFrame() {
if (_wakingNextFrame) if (_wakingNextFrame)
return; return;
_wakingNextFrame = true; _wakingNextFrame = true;
animation.scheduler.requestAnimationFrame((_) { ui.window.scheduleFrame();
_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();
});
} }
} }
final TaskScheduler tasks = new TaskScheduler._(); final Scheduler scheduler = new Scheduler._();
abstract class SchedulingStrategy { abstract class SchedulingStrategy {
bool shouldRunTaskWithPriority(int priority); bool shouldRunTaskWithPriority(int priority);
......
...@@ -16,29 +16,29 @@ class TestStrategy implements SchedulingStrategy { ...@@ -16,29 +16,29 @@ class TestStrategy implements SchedulingStrategy {
void main() { void main() {
test("Tasks are executed in the right order", () { test("Tasks are executed in the right order", () {
var strategy = new TestStrategy(); var strategy = new TestStrategy();
tasks.schedulingStrategy = strategy; scheduler.schedulingStrategy = strategy;
List input = [2, 23, 23, 11, 0, 80, 3]; List input = [2, 23, 23, 11, 0, 80, 3];
List executedTasks = []; List executedTasks = [];
void scheduleAddingTask(int x) { 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) { for (int x in input) {
scheduleAddingTask(x); scheduleAddingTask(x);
} }
strategy.allowedPriority = 100; 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); expect(executedTasks.isEmpty, isTrue);
strategy.allowedPriority = 50; 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.length, equals(1));
expect(executedTasks.single, equals(80)); expect(executedTasks.single, equals(80));
executedTasks.clear(); executedTasks.clear();
strategy.allowedPriority = 20; 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.length, equals(2));
expect(executedTasks[0], equals(23)); expect(executedTasks[0], equals(23));
expect(executedTasks[1], equals(23)); expect(executedTasks[1], equals(23));
...@@ -48,21 +48,21 @@ void main() { ...@@ -48,21 +48,21 @@ void main() {
scheduleAddingTask(19); scheduleAddingTask(19);
scheduleAddingTask(5); scheduleAddingTask(5);
scheduleAddingTask(97); 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.length, equals(2));
expect(executedTasks[0], equals(99)); expect(executedTasks[0], equals(99));
expect(executedTasks[1], equals(97)); expect(executedTasks[1], equals(97));
executedTasks.clear(); executedTasks.clear();
strategy.allowedPriority = 10; 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.length, equals(2));
expect(executedTasks[0], equals(19)); expect(executedTasks[0], equals(19));
expect(executedTasks[1], equals(11)); expect(executedTasks[1], equals(11));
executedTasks.clear(); executedTasks.clear();
strategy.allowedPriority = 1; 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.length, equals(3));
expect(executedTasks[0], equals(5)); expect(executedTasks[0], equals(5));
expect(executedTasks[1], equals(3)); expect(executedTasks[1], equals(3));
...@@ -70,7 +70,7 @@ void main() { ...@@ -70,7 +70,7 @@ void main() {
executedTasks.clear(); executedTasks.clear();
strategy.allowedPriority = 0; strategy.allowedPriority = 0;
tasks.tick(); scheduler.tick();
expect(executedTasks.length, equals(1)); expect(executedTasks.length, equals(1));
expect(executedTasks[0], equals(0)); expect(executedTasks[0], equals(0));
}); });
......
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