Commit ed195cfa authored by Hixie's avatar Hixie

requestPostFrameCallback()

Provide a way to schedule a callback for immediately after the current
frame has been sent to the GPU thread. This is useful for times where
you want to immediately schedule yourself for another build or layout
because, for instance, you just displayed frame zero of an animation and
you want to use the metrics from that layout to set up the actual
animation to begin immediately, such that the very next frame is frame 1.
parent 6fe9446f
......@@ -16,6 +16,13 @@ double timeDilation = 1.0;
/// 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
......@@ -24,37 +31,66 @@ class Scheduler {
}
bool _haveScheduledVisualUpdate = false;
int _nextCallbackId = 1;
int _nextPrivateCallbackId = 0; // negative
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;
/// Called by the engine to produce a new frame.
///
/// This function first calls all the callbacks registered by
/// [requestAnimationFrame] and then calls all the callbacks registered by
/// [addPersistentFrameCallback], which typically drive the rendering pipeline.
void beginFrame(double timeStampMS) {
timeStampMS /= timeDilation;
Duration timeStamp = new Duration(microseconds: (timeStampMS * Duration.MICROSECONDS_PER_MILLISECOND).round());
/// [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(double rawTimeStamp) {
assert(!_inFrame);
_inFrame = true;
rawTimeStamp /= timeDilation;
final Duration timeStamp = new Duration(
microseconds: (rawTimeStamp * Duration.MICROSECONDS_PER_MILLISECOND).round()
);
_haveScheduledVisualUpdate = false;
assert(_postFrameCallbacks.length == 0);
Map<int, SchedulerCallback> callbacks = _transientCallbacks;
_transientCallbacks = new Map<int, SchedulerCallback>();
callbacks.forEach((int id, SchedulerCallback callback) {
if (!_removedIds.contains(id))
callback(timeStamp);
invokeCallback(callback, timeStamp);
});
_removedIds.clear();
for (SchedulerCallback callback in _persistentCallbacks)
invokeCallback(callback, timeStamp);
for (SchedulerCallback callback in _postFrameCallbacks)
invokeCallback(callback, timeStamp);
_postFrameCallbacks.clear();
_inFrame = false;
}
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.
......@@ -72,20 +108,42 @@ class Scheduler {
/// 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) {
int id = _nextCallbackId++;
_transientCallbacks[id] = callback;
_nextCallbackId += 1;
_transientCallbacks[_nextCallbackId] = callback;
ensureVisualUpdate();
return id;
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) {
if (_inFrame) {
_postFrameCallbacks.add(callback);
} else {
_nextPrivateCallbackId -= 1;
_transientCallbacks[_nextPrivateCallbackId] = callback;
ensureVisualUpdate();
}
}
/// Ensure that a frame will be produced after this function is called.
void ensureVisualUpdate() {
if (_haveScheduledVisualUpdate)
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
import 'colors.dart';
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
......
......@@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
import 'icon.dart';
......
......@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:newton/newton.dart';
import 'package:flutter/animation.dart';
......
import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';
import 'package:test/test.dart';
......@@ -75,12 +76,14 @@ void main() {
debugWidgetsExceptionHandler = (String context, dynamic exception, StackTrace stack) {
cachedException = exception;
};
debugSchedulerExceptionHandler = (dynamic exception, StackTrace stack) { throw exception; };
});
tearDown(() {
assert(cachedException == null);
cachedException = null;
debugWidgetsExceptionHandler = null;
debugSchedulerExceptionHandler = null;
});
test('Legal times for setState', () {
......
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