add async and scheduler binding.
This commit is contained in:
Родитель
b7c15be96a
Коммит
ab477fe987
|
@ -6,6 +6,7 @@ using Unity.UIWidgets.async;
|
|||
using Unity.UIWidgets.foundation;
|
||||
using Unity.UIWidgets.material;
|
||||
using Unity.UIWidgets.scheduler;
|
||||
using Unity.UIWidgets.scheduler2;
|
||||
using Unity.UIWidgets.ui;
|
||||
using Unity.UIWidgets.widgets;
|
||||
using UnityEngine;
|
||||
|
@ -63,7 +64,7 @@ namespace UIWidgetsGallery.gallery {
|
|||
this._options = new GalleryOptions(
|
||||
theme: GalleryTheme.kLightGalleryTheme,
|
||||
textScaleFactor: GalleryTextScaleValue.kAllGalleryTextScaleValues[0],
|
||||
timeDilation: SchedulerBinding.instance.timeDilation,
|
||||
timeDilation: scheduler_.timeDilation,
|
||||
platform: Application.platform,
|
||||
showPerformanceOverlay: this.widget.enablePerformanceOverlay
|
||||
);
|
||||
|
@ -82,9 +83,9 @@ namespace UIWidgetsGallery.gallery {
|
|||
this._timeDilationTimer = null;
|
||||
if (newOptions.timeDilation > 1.0f) {
|
||||
this._timeDilationTimer = Window.instance.run(new TimeSpan(0, 0, 0, 0, 150),
|
||||
() => { SchedulerBinding.instance.timeDilation = newOptions.timeDilation; });
|
||||
() => { scheduler_.timeDilation = newOptions.timeDilation; });
|
||||
} else {
|
||||
SchedulerBinding.instance.timeDilation = newOptions.timeDilation;
|
||||
scheduler_.timeDilation = newOptions.timeDilation;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,24 @@ namespace Unity.UIWidgets.async {
|
|||
get { return this._data.Count; }
|
||||
}
|
||||
|
||||
public bool isEmpty {
|
||||
get { return count == 0; }
|
||||
}
|
||||
|
||||
public bool isNotEmpty {
|
||||
get { return count != 0; }
|
||||
}
|
||||
|
||||
public T first {
|
||||
get {
|
||||
return peek();
|
||||
}
|
||||
}
|
||||
|
||||
public T removeFirst() {
|
||||
return dequeue();
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
string s = "";
|
||||
for (int i = 0; i < this._data.Count; ++i) {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0dd4bb15542f4ad9a7bef43d7defab5f
|
||||
timeCreated: 1599458064
|
|
@ -0,0 +1,360 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.UIWidgets.async2 {
|
||||
public struct FutureOr {
|
||||
public object value;
|
||||
public Future future;
|
||||
|
||||
public bool isFuture => future != null;
|
||||
|
||||
public static FutureOr withValue(object value) {
|
||||
return new FutureOr {value = value};
|
||||
}
|
||||
|
||||
public static FutureOr withFuture(Future future) {
|
||||
return new FutureOr {future = future};
|
||||
}
|
||||
|
||||
public static readonly FutureOr nullValue = withValue(null);
|
||||
|
||||
public static readonly FutureOr trueValue = withValue(true);
|
||||
|
||||
public static readonly FutureOr falseValue = withValue(false);
|
||||
}
|
||||
|
||||
public abstract class Future {
|
||||
static readonly _Future _nullFuture = _Future.zoneValue(null, Zone.root);
|
||||
|
||||
static readonly _Future _falseFuture = _Future.zoneValue(false, Zone.root);
|
||||
|
||||
public static Future create(Func<FutureOr> computation) {
|
||||
_Future result = new _Future();
|
||||
Timer.run(() => {
|
||||
try {
|
||||
result._complete(computation());
|
||||
}
|
||||
catch (Exception e) {
|
||||
async_._completeWithErrorCallback(result, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Future microtask(Func<FutureOr> computation) {
|
||||
_Future result = new _Future();
|
||||
async_.scheduleMicrotask(() => {
|
||||
try {
|
||||
result._complete(computation());
|
||||
}
|
||||
catch (Exception e) {
|
||||
async_._completeWithErrorCallback(result, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Future sync(Func<FutureOr> computation) {
|
||||
try {
|
||||
var result = computation();
|
||||
if (result.isFuture) {
|
||||
return result.future;
|
||||
}
|
||||
else {
|
||||
return _Future.value(result);
|
||||
}
|
||||
}
|
||||
catch (Exception error) {
|
||||
var future = new _Future();
|
||||
AsyncError replacement = Zone.current.errorCallback(error);
|
||||
if (replacement != null) {
|
||||
future._asyncCompleteError(async_._nonNullError(replacement.InnerException));
|
||||
}
|
||||
else {
|
||||
future._asyncCompleteError(error);
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
public static Future value(FutureOr value = default) {
|
||||
return _Future.immediate(value);
|
||||
}
|
||||
|
||||
public static Future error(Exception error) {
|
||||
if (error == null)
|
||||
throw new ArgumentNullException(nameof(error));
|
||||
|
||||
if (!ReferenceEquals(Zone.current, async_._rootZone)) {
|
||||
AsyncError replacement = Zone.current.errorCallback(error);
|
||||
if (replacement != null) {
|
||||
error = async_._nonNullError(replacement.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
return _Future.immediateError(error);
|
||||
}
|
||||
|
||||
public static Future delayed(TimeSpan duration, Func<FutureOr> computation = null) {
|
||||
_Future result = new _Future();
|
||||
Timer.create(duration, () => {
|
||||
if (computation == null) {
|
||||
result._complete(FutureOr.nullValue);
|
||||
}
|
||||
else {
|
||||
try {
|
||||
result._complete(computation());
|
||||
}
|
||||
catch (Exception e) {
|
||||
async_._completeWithErrorCallback(result, e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Future wait<T>(IEnumerable<Future> futures, bool eagerError = false, Action<T> cleanUp = null) {
|
||||
_Future result = new _Future();
|
||||
List<T> values = null; // Collects the values. Set to null on error.
|
||||
int remaining = 0; // How many futures are we waiting for.
|
||||
Exception error = null; // The first error from a future.
|
||||
|
||||
Func<Exception, FutureOr> handleError = (Exception theError) => {
|
||||
remaining--;
|
||||
if (values != null) {
|
||||
if (cleanUp != null) {
|
||||
foreach (var value in values) {
|
||||
if (value != null) {
|
||||
// Ensure errors from cleanUp are uncaught.
|
||||
Future.sync(() => {
|
||||
cleanUp(value);
|
||||
return FutureOr.nullValue;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
values = null;
|
||||
if (remaining == 0 || eagerError) {
|
||||
result._completeError(theError);
|
||||
}
|
||||
else {
|
||||
error = theError;
|
||||
}
|
||||
}
|
||||
else if (remaining == 0 && !eagerError) {
|
||||
result._completeError(error);
|
||||
}
|
||||
|
||||
return FutureOr.nullValue;
|
||||
};
|
||||
|
||||
try {
|
||||
// As each future completes, put its value into the corresponding
|
||||
// position in the list of values.
|
||||
foreach (var future in futures) {
|
||||
int pos = remaining;
|
||||
future.then((object value) => {
|
||||
remaining--;
|
||||
if (values != null) {
|
||||
values[pos] = (T) value;
|
||||
if (remaining == 0) {
|
||||
result._completeWithValue(values);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (cleanUp != null && value != null) {
|
||||
// Ensure errors from cleanUp are uncaught.
|
||||
Future.sync(() => {
|
||||
cleanUp((T) value);
|
||||
return FutureOr.nullValue;
|
||||
});
|
||||
}
|
||||
|
||||
if (remaining == 0 && !eagerError) {
|
||||
result._completeError(error);
|
||||
}
|
||||
}
|
||||
|
||||
return FutureOr.nullValue;
|
||||
}, onError: handleError);
|
||||
// Increment the 'remaining' after the call to 'then'.
|
||||
// If that call throws, we don't expect any future callback from
|
||||
// the future, and we also don't increment remaining.
|
||||
remaining++;
|
||||
}
|
||||
|
||||
if (remaining == 0) {
|
||||
return Future.value(FutureOr.withValue(new List<T>()));
|
||||
}
|
||||
|
||||
values = new List<T>(remaining);
|
||||
}
|
||||
catch (Exception e) {
|
||||
// The error must have been thrown while iterating over the futures
|
||||
// list, or while installing a callback handler on the future.
|
||||
if (remaining == 0 || eagerError) {
|
||||
// Throw a new Future.error.
|
||||
// Don't just call `result._completeError` since that would propagate
|
||||
// the error too eagerly, not giving the callers time to install
|
||||
// error handlers.
|
||||
// Also, don't use `_asyncCompleteError` since that one doesn't give
|
||||
// zones the chance to intercept the error.
|
||||
return Future.error(e);
|
||||
}
|
||||
else {
|
||||
// Don't allocate a list for values, thus indicating that there was an
|
||||
// error.
|
||||
// Set error to the caught exception.
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Future any(IEnumerable<Future> futures) {
|
||||
var completer = Completer.sync();
|
||||
Func<object, FutureOr> onValue = (object value) => {
|
||||
if (!completer.isCompleted) completer.complete(FutureOr.withValue(value));
|
||||
return FutureOr.nullValue;
|
||||
};
|
||||
|
||||
Func<Exception, FutureOr> onError = (Exception error) => {
|
||||
if (!completer.isCompleted) completer.completeError(error);
|
||||
return FutureOr.nullValue;
|
||||
};
|
||||
|
||||
foreach (var future in futures) {
|
||||
future.then(onValue, onError: onError);
|
||||
}
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
public static Future forEach<T>(IEnumerable<T> elements, Func<T, FutureOr> action) {
|
||||
var iterator = elements.GetEnumerator();
|
||||
return doWhile(() => {
|
||||
if (!iterator.MoveNext()) return FutureOr.falseValue;
|
||||
|
||||
var result = action(iterator.Current);
|
||||
if (result.isFuture) return FutureOr.withFuture(result.future.then(_kTrue));
|
||||
return FutureOr.trueValue;
|
||||
});
|
||||
}
|
||||
|
||||
static readonly Func<object, FutureOr> _kTrue = (_) => FutureOr.trueValue;
|
||||
|
||||
public static Future doWhile(Func<FutureOr> action) {
|
||||
_Future doneSignal = new _Future();
|
||||
ZoneUnaryCallback nextIteration = null;
|
||||
// Bind this callback explicitly so that each iteration isn't bound in the
|
||||
// context of all the previous iterations' callbacks.
|
||||
// This avoids, e.g., deeply nested stack traces from the stack trace
|
||||
// package.
|
||||
nextIteration = Zone.current.bindUnaryCallbackGuarded((object keepGoingObj) => {
|
||||
bool keepGoing = (bool) keepGoingObj;
|
||||
while (keepGoing) {
|
||||
FutureOr result;
|
||||
try {
|
||||
result = action();
|
||||
}
|
||||
catch (Exception error) {
|
||||
// Cannot use _completeWithErrorCallback because it completes
|
||||
// the future synchronously.
|
||||
async_._asyncCompleteWithErrorCallback(doneSignal, error);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (result.isFuture) {
|
||||
result.future.then((value) => {
|
||||
nextIteration((bool) value);
|
||||
return FutureOr.nullValue;
|
||||
}, onError: error => {
|
||||
doneSignal._completeError(error);
|
||||
return FutureOr.nullValue;
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
keepGoing = (bool) result.value;
|
||||
}
|
||||
|
||||
doneSignal._complete(FutureOr.nullValue);
|
||||
return null;
|
||||
});
|
||||
|
||||
nextIteration(true);
|
||||
return doneSignal;
|
||||
}
|
||||
|
||||
public abstract Future then(Func<object, FutureOr> onValue, Func<Exception, FutureOr> onError = null);
|
||||
|
||||
public abstract Future catchError(Func<Exception, FutureOr> onError, Func<Exception, bool> test = null);
|
||||
|
||||
public abstract Future whenComplete(Func<object> action);
|
||||
|
||||
// public abstract Stream asStream();
|
||||
|
||||
public abstract Future timeout(TimeSpan timeLimit, Func<FutureOr> onTimeout = null);
|
||||
}
|
||||
|
||||
|
||||
public class TimeoutException : Exception {
|
||||
public readonly TimeSpan? duration;
|
||||
|
||||
public TimeoutException(string message, TimeSpan? duration = null) : base(message) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public override string ToString() {
|
||||
string result = "TimeoutException";
|
||||
if (duration != null) result = $"TimeoutException after {duration}";
|
||||
if (Message != null) result = $"result: {Message}";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Completer {
|
||||
public static Completer create() => new _AsyncCompleter();
|
||||
|
||||
public static Completer sync() => new _SyncCompleter();
|
||||
|
||||
public abstract Future future { get; }
|
||||
|
||||
public abstract void complete(FutureOr value = default);
|
||||
|
||||
public abstract void completeError(Exception error);
|
||||
public abstract bool isCompleted { get; }
|
||||
}
|
||||
|
||||
public static partial class async_ {
|
||||
internal static void _completeWithErrorCallback(_Future result, Exception error) {
|
||||
AsyncError replacement = Zone.current.errorCallback(error);
|
||||
if (replacement != null) {
|
||||
error = _nonNullError(replacement.InnerException);
|
||||
}
|
||||
|
||||
result._completeError(error);
|
||||
}
|
||||
|
||||
internal static void _asyncCompleteWithErrorCallback(_Future result, Exception error) {
|
||||
AsyncError replacement = Zone.current.errorCallback(error);
|
||||
if (replacement != null) {
|
||||
error = _nonNullError(replacement.InnerException);
|
||||
}
|
||||
|
||||
result._asyncCompleteError(error);
|
||||
}
|
||||
|
||||
internal static Exception _nonNullError(Exception error) =>
|
||||
error ?? new Exception("Throw of null.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bdf10194e91e4558bc97ebad8b81d5a0
|
||||
timeCreated: 1599458114
|
|
@ -0,0 +1,812 @@
|
|||
using System;
|
||||
using Unity.UIWidgets.foundation;
|
||||
|
||||
namespace Unity.UIWidgets.async2 {
|
||||
using _FutureOnValue = Func<object, FutureOr>;
|
||||
using _FutureErrorTest = Func<Exception, bool>;
|
||||
using _FutureAction = Func<object>;
|
||||
|
||||
abstract class _Completer : Completer {
|
||||
protected readonly _Future _future = new _Future();
|
||||
public override Future future => _future;
|
||||
|
||||
public override void completeError(Exception error) {
|
||||
if (error == null)
|
||||
throw new ArgumentNullException(nameof(error));
|
||||
|
||||
if (!_future._mayComplete) throw new Exception("Future already completed");
|
||||
AsyncError replacement = Zone.current.errorCallback(error);
|
||||
if (replacement != null) {
|
||||
error = async_._nonNullError(replacement.InnerException);
|
||||
}
|
||||
|
||||
_completeError(error);
|
||||
}
|
||||
|
||||
protected abstract void _completeError(Exception error);
|
||||
|
||||
public override bool isCompleted => !_future._mayComplete;
|
||||
}
|
||||
|
||||
class _AsyncCompleter : _Completer {
|
||||
public override void complete(FutureOr value = default) {
|
||||
if (!_future._mayComplete) throw new Exception("Future already completed");
|
||||
_future._asyncComplete(value);
|
||||
}
|
||||
|
||||
protected override void _completeError(Exception error) {
|
||||
_future._asyncCompleteError(error);
|
||||
}
|
||||
}
|
||||
|
||||
class _SyncCompleter : _Completer {
|
||||
public override void complete(FutureOr value = default) {
|
||||
if (!_future._mayComplete) throw new Exception("Future already completed");
|
||||
_future._complete(value);
|
||||
}
|
||||
|
||||
protected override void _completeError(Exception error) {
|
||||
_future._completeError(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _FutureListener {
|
||||
public const int maskValue = 1;
|
||||
public const int maskError = 2;
|
||||
public const int maskTestError = 4;
|
||||
public const int maskWhencomplete = 8;
|
||||
|
||||
public const int stateChain = 0;
|
||||
public const int stateThen = maskValue;
|
||||
public const int stateThenOnerror = maskValue | maskError;
|
||||
public const int stateCatcherror = maskError;
|
||||
public const int stateCatcherrorTest = maskError | maskTestError;
|
||||
public const int stateWhencomplete = maskWhencomplete;
|
||||
public const int maskType = maskValue | maskError | maskTestError | maskWhencomplete;
|
||||
public const int stateIsAwait = 16;
|
||||
|
||||
internal _FutureListener _nextListener;
|
||||
|
||||
public readonly _Future result;
|
||||
public readonly int state;
|
||||
public readonly Delegate callback;
|
||||
public readonly Func<Exception, FutureOr> errorCallback;
|
||||
|
||||
_FutureListener(_Future result, Delegate callback, Func<Exception, FutureOr> errorCallback, int state) {
|
||||
this.result = result;
|
||||
this.state = state;
|
||||
this.callback = callback;
|
||||
this.errorCallback = errorCallback;
|
||||
}
|
||||
|
||||
public static _FutureListener then(
|
||||
_Future result, _FutureOnValue onValue, Func<Exception, FutureOr> errorCallback) {
|
||||
return new _FutureListener(
|
||||
result, onValue, errorCallback,
|
||||
(errorCallback == null) ? stateThen : stateThenOnerror
|
||||
);
|
||||
}
|
||||
|
||||
public static _FutureListener thenAwait(
|
||||
_Future result, _FutureOnValue onValue, Func<Exception, FutureOr> errorCallback) {
|
||||
return new _FutureListener(
|
||||
result, onValue, errorCallback,
|
||||
((errorCallback == null) ? stateThen : stateThenOnerror) | stateIsAwait
|
||||
);
|
||||
}
|
||||
|
||||
public static _FutureListener catchError(_Future result, Func<Exception, FutureOr> errorCallback,
|
||||
_FutureErrorTest callback) {
|
||||
return new _FutureListener(
|
||||
result, callback, errorCallback,
|
||||
(callback == null) ? stateCatcherror : stateCatcherrorTest
|
||||
);
|
||||
}
|
||||
|
||||
public static _FutureListener whenComplete(_Future result, _FutureAction callback) {
|
||||
return new _FutureListener(
|
||||
result, callback, null,
|
||||
stateWhencomplete
|
||||
);
|
||||
}
|
||||
|
||||
internal Zone _zone => result._zone;
|
||||
|
||||
public bool handlesValue => (state & maskValue) != 0;
|
||||
public bool handlesError => (state & maskError) != 0;
|
||||
public bool hasErrorTest => (state & maskType) == stateCatcherrorTest;
|
||||
public bool handlesComplete => (state & maskType) == stateWhencomplete;
|
||||
|
||||
public bool isAwait => (state & stateIsAwait) != 0;
|
||||
|
||||
internal _FutureOnValue _onValue {
|
||||
get {
|
||||
D.assert(handlesValue);
|
||||
return (_FutureOnValue) callback;
|
||||
}
|
||||
}
|
||||
|
||||
internal Func<Exception, FutureOr> _onError => errorCallback;
|
||||
|
||||
internal _FutureErrorTest _errorTest {
|
||||
get {
|
||||
D.assert(hasErrorTest);
|
||||
return (_FutureErrorTest) callback;
|
||||
}
|
||||
}
|
||||
|
||||
internal _FutureAction _whenCompleteAction {
|
||||
get {
|
||||
D.assert(handlesComplete);
|
||||
return (_FutureAction) callback;
|
||||
}
|
||||
}
|
||||
|
||||
public bool hasErrorCallback {
|
||||
get {
|
||||
D.assert(handlesError);
|
||||
return _onError != null;
|
||||
}
|
||||
}
|
||||
|
||||
public FutureOr handleValue(object sourceResult) {
|
||||
return (FutureOr) _zone.runUnary(arg => _onValue(arg), sourceResult);
|
||||
}
|
||||
|
||||
public bool matchesErrorTest(AsyncError asyncError) {
|
||||
if (!hasErrorTest) return true;
|
||||
return (bool) _zone.runUnary(arg => _errorTest((Exception) arg), asyncError.InnerException);
|
||||
}
|
||||
|
||||
public FutureOr handleError(AsyncError asyncError) {
|
||||
D.assert(handlesError && hasErrorCallback);
|
||||
|
||||
var errorCallback = this.errorCallback;
|
||||
return (FutureOr) _zone.runUnary(arg => errorCallback((Exception) arg), asyncError.InnerException);
|
||||
}
|
||||
|
||||
public object handleWhenComplete() {
|
||||
D.assert(!handlesError);
|
||||
return _zone.run(() => _whenCompleteAction());
|
||||
}
|
||||
}
|
||||
|
||||
public class _Future : Future {
|
||||
internal const int _stateIncomplete = 0;
|
||||
internal const int _statePendingComplete = 1;
|
||||
internal const int _stateChained = 2;
|
||||
internal const int _stateValue = 4;
|
||||
internal const int _stateError = 8;
|
||||
|
||||
internal int _state = _stateIncomplete;
|
||||
|
||||
internal readonly Zone _zone;
|
||||
|
||||
internal object _resultOrListeners;
|
||||
|
||||
internal _Future() {
|
||||
_zone = Zone.current;
|
||||
}
|
||||
|
||||
internal _Future(Zone zone) {
|
||||
_zone = zone;
|
||||
}
|
||||
|
||||
internal static _Future immediate(object result) {
|
||||
var future = new _Future(Zone.current);
|
||||
future._asyncComplete(result);
|
||||
return future;
|
||||
}
|
||||
|
||||
internal static _Future zoneValue(object value, Zone zone) {
|
||||
var future = new _Future(zone);
|
||||
future._setValue(value);
|
||||
return future;
|
||||
}
|
||||
|
||||
internal static _Future immediateError(Exception error) {
|
||||
var future = new _Future(Zone.current);
|
||||
future._asyncCompleteError(error);
|
||||
return future;
|
||||
}
|
||||
|
||||
internal static _Future value(object value) {
|
||||
return zoneValue(value, Zone.current);
|
||||
}
|
||||
|
||||
internal bool _mayComplete => _state == _stateIncomplete;
|
||||
internal bool _isPendingComplete => _state == _statePendingComplete;
|
||||
internal bool _mayAddListener => _state <= _statePendingComplete;
|
||||
internal bool _isChained => _state == _stateChained;
|
||||
internal bool _isComplete => _state >= _stateValue;
|
||||
internal bool _hasError => _state == _stateError;
|
||||
|
||||
internal void _setChained(_Future source) {
|
||||
D.assert(_mayAddListener);
|
||||
_state = _stateChained;
|
||||
_resultOrListeners = source;
|
||||
}
|
||||
|
||||
public override Future then(Func<object, FutureOr> f, Func<Exception, FutureOr> onError = null) {
|
||||
Zone currentZone = Zone.current;
|
||||
if (!ReferenceEquals(currentZone, async_._rootZone)) {
|
||||
f = async_._registerUnaryHandler(f, currentZone);
|
||||
if (onError != null) {
|
||||
onError = async_._registerErrorHandler(onError, currentZone);
|
||||
}
|
||||
}
|
||||
|
||||
_Future result = new _Future();
|
||||
_addListener(_FutureListener.then(result, f, onError));
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Future catchError(Func<Exception, FutureOr> onError, Func<Exception, bool> test = null) {
|
||||
_Future result = new _Future();
|
||||
if (!ReferenceEquals(result._zone, async_._rootZone)) {
|
||||
onError = async_._registerErrorHandler(onError, result._zone);
|
||||
if (test != null) {
|
||||
test = async_._registerUnaryHandler(test, result._zone);
|
||||
}
|
||||
}
|
||||
|
||||
_addListener(_FutureListener.catchError(result, onError, test));
|
||||
return result;
|
||||
}
|
||||
|
||||
public override Future whenComplete(Func<object> action) {
|
||||
_Future result = new _Future();
|
||||
if (!ReferenceEquals(result._zone, async_._rootZone)) {
|
||||
action = async_._registerHandler(action, result._zone);
|
||||
}
|
||||
|
||||
_addListener(_FutureListener.whenComplete(result, action));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Stream<T> asStream() => new Stream<T>.fromFuture(this);
|
||||
|
||||
internal void _setPendingComplete() {
|
||||
D.assert(_mayComplete);
|
||||
_state = _statePendingComplete;
|
||||
}
|
||||
|
||||
internal void _clearPendingComplete() {
|
||||
D.assert(_isPendingComplete);
|
||||
_state = _stateIncomplete;
|
||||
}
|
||||
|
||||
internal AsyncError _error {
|
||||
get {
|
||||
D.assert(_hasError);
|
||||
return (AsyncError) _resultOrListeners;
|
||||
}
|
||||
}
|
||||
|
||||
internal _Future _chainSource {
|
||||
get {
|
||||
D.assert(_isChained);
|
||||
return (_Future) _resultOrListeners;
|
||||
}
|
||||
}
|
||||
|
||||
internal void _setValue(object value) {
|
||||
D.assert(!_isComplete); // But may have a completion pending.
|
||||
_state = _stateValue;
|
||||
_resultOrListeners = value;
|
||||
}
|
||||
|
||||
internal void _setErrorObject(AsyncError error) {
|
||||
D.assert(!_isComplete); // But may have a completion pending.
|
||||
_state = _stateError;
|
||||
_resultOrListeners = error;
|
||||
}
|
||||
|
||||
internal void _setError(Exception error) {
|
||||
_setErrorObject(new AsyncError(error));
|
||||
}
|
||||
|
||||
internal void _cloneResult(_Future source) {
|
||||
D.assert(!_isComplete);
|
||||
D.assert(source._isComplete);
|
||||
_state = source._state;
|
||||
_resultOrListeners = source._resultOrListeners;
|
||||
}
|
||||
|
||||
internal void _addListener(_FutureListener listener) {
|
||||
D.assert(listener._nextListener == null);
|
||||
if (_mayAddListener) {
|
||||
listener._nextListener = (_FutureListener) _resultOrListeners;
|
||||
_resultOrListeners = listener;
|
||||
}
|
||||
else {
|
||||
if (_isChained) {
|
||||
// Delegate listeners to chained source future.
|
||||
// If the source is complete, instead copy its values and
|
||||
// drop the chaining.
|
||||
_Future source = _chainSource;
|
||||
if (!source._isComplete) {
|
||||
source._addListener(listener);
|
||||
return;
|
||||
}
|
||||
|
||||
_cloneResult(source);
|
||||
}
|
||||
|
||||
D.assert(_isComplete);
|
||||
// Handle late listeners asynchronously.
|
||||
_zone.scheduleMicrotask(() => {
|
||||
_propagateToListeners(this, listener);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _prependListeners(_FutureListener listeners) {
|
||||
if (listeners == null) return;
|
||||
if (_mayAddListener) {
|
||||
_FutureListener existingListeners = (_FutureListener) _resultOrListeners;
|
||||
_resultOrListeners = listeners;
|
||||
if (existingListeners != null) {
|
||||
_FutureListener cursor = listeners;
|
||||
while (cursor._nextListener != null) {
|
||||
cursor = cursor._nextListener;
|
||||
}
|
||||
|
||||
cursor._nextListener = existingListeners;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (_isChained) {
|
||||
// Delegate listeners to chained source future.
|
||||
// If the source is complete, instead copy its values and
|
||||
// drop the chaining.
|
||||
_Future source = _chainSource;
|
||||
if (!source._isComplete) {
|
||||
source._prependListeners(listeners);
|
||||
return;
|
||||
}
|
||||
|
||||
_cloneResult(source);
|
||||
}
|
||||
|
||||
D.assert(_isComplete);
|
||||
listeners = _reverseListeners(listeners);
|
||||
_zone.scheduleMicrotask(() => {
|
||||
_propagateToListeners(this, listeners);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_FutureListener _removeListeners() {
|
||||
// Reverse listeners before returning them, so the resulting list is in
|
||||
// subscription order.
|
||||
D.assert(!_isComplete);
|
||||
_FutureListener current = (_FutureListener) _resultOrListeners;
|
||||
_resultOrListeners = null;
|
||||
return _reverseListeners(current);
|
||||
}
|
||||
|
||||
_FutureListener _reverseListeners(_FutureListener listeners) {
|
||||
_FutureListener prev = null;
|
||||
_FutureListener current = listeners;
|
||||
while (current != null) {
|
||||
_FutureListener next = current._nextListener;
|
||||
current._nextListener = prev;
|
||||
prev = current;
|
||||
current = next;
|
||||
}
|
||||
|
||||
return prev;
|
||||
}
|
||||
|
||||
static void _chainForeignFuture(Future source, _Future target) {
|
||||
D.assert(!target._isComplete);
|
||||
D.assert(!(source is _Future));
|
||||
|
||||
// Mark the target as chained (and as such half-completed).
|
||||
target._setPendingComplete();
|
||||
try {
|
||||
source.then((value) => {
|
||||
D.assert(target._isPendingComplete);
|
||||
// The "value" may be another future if the foreign future
|
||||
// implementation is mis-behaving,
|
||||
// so use _complete instead of _completeWithValue.
|
||||
target._clearPendingComplete(); // Clear this first, it's set again.
|
||||
target._complete(FutureOr.withValue(value));
|
||||
return new FutureOr();
|
||||
},
|
||||
onError: (Exception error) => {
|
||||
D.assert(target._isPendingComplete);
|
||||
target._completeError(error);
|
||||
return new FutureOr();
|
||||
});
|
||||
}
|
||||
catch (Exception e) {
|
||||
// This only happens if the `then` call threw synchronously when given
|
||||
// valid arguments.
|
||||
// That requires a non-conforming implementation of the Future interface,
|
||||
// which should, hopefully, never happen.
|
||||
async_.scheduleMicrotask(() => {
|
||||
target._completeError(e);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void _chainCoreFuture(_Future source, _Future target) {
|
||||
D.assert(target._mayAddListener); // Not completed, not already chained.
|
||||
while (source._isChained) {
|
||||
source = source._chainSource;
|
||||
}
|
||||
|
||||
if (source._isComplete) {
|
||||
_FutureListener listeners = target._removeListeners();
|
||||
target._cloneResult(source);
|
||||
_propagateToListeners(target, listeners);
|
||||
}
|
||||
else {
|
||||
_FutureListener listeners = (_FutureListener) target._resultOrListeners;
|
||||
target._setChained(source);
|
||||
source._prependListeners(listeners);
|
||||
}
|
||||
}
|
||||
|
||||
internal void _complete(FutureOr value) {
|
||||
D.assert(!_isComplete);
|
||||
if (value.isFuture) {
|
||||
if (value.future is _Future coreFuture) {
|
||||
_chainCoreFuture(coreFuture, this);
|
||||
}
|
||||
else {
|
||||
_chainForeignFuture(value.future, this);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_FutureListener listeners = _removeListeners();
|
||||
_setValue(value);
|
||||
_propagateToListeners(this, listeners);
|
||||
}
|
||||
}
|
||||
|
||||
internal void _completeWithValue(object value) {
|
||||
D.assert(!_isComplete);
|
||||
D.assert(!(value is Future));
|
||||
|
||||
_FutureListener listeners = _removeListeners();
|
||||
_setValue(value);
|
||||
_propagateToListeners(this, listeners);
|
||||
}
|
||||
|
||||
internal void _completeError(Exception error) {
|
||||
D.assert(!_isComplete);
|
||||
|
||||
_FutureListener listeners = _removeListeners();
|
||||
_setError(error);
|
||||
_propagateToListeners(this, listeners);
|
||||
}
|
||||
|
||||
internal void _asyncComplete(object value) {
|
||||
D.assert(!_isComplete);
|
||||
// Two corner cases if the value is a future:
|
||||
// 1. the future is already completed and an error.
|
||||
// 2. the future is not yet completed but might become an error.
|
||||
// The first case means that we must not immediately complete the Future,
|
||||
// as our code would immediately start propagating the error without
|
||||
// giving the time to install error-handlers.
|
||||
// However the second case requires us to deal with the value immediately.
|
||||
// Otherwise the value could complete with an error and report an
|
||||
// unhandled error, even though we know we are already going to listen to
|
||||
// it.
|
||||
|
||||
if (value is Future future) {
|
||||
_chainFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
_setPendingComplete();
|
||||
_zone.scheduleMicrotask(() => {
|
||||
_completeWithValue(value);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
internal void _chainFuture(Future value) {
|
||||
if (value is _Future future) {
|
||||
if (future._hasError) {
|
||||
// Delay completion to allow the user to register callbacks.
|
||||
_setPendingComplete();
|
||||
_zone.scheduleMicrotask(() => {
|
||||
_chainCoreFuture(future, this);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
else {
|
||||
_chainCoreFuture(future, this);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Just listen on the foreign future. This guarantees an async delay.
|
||||
_chainForeignFuture(value, this);
|
||||
}
|
||||
|
||||
|
||||
internal void _asyncCompleteError(Exception error) {
|
||||
D.assert(!_isComplete);
|
||||
|
||||
_setPendingComplete();
|
||||
_zone.scheduleMicrotask(() => {
|
||||
_completeError(error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static void _propagateToListeners(_Future source, _FutureListener listeners) {
|
||||
while (true) {
|
||||
D.assert(source._isComplete);
|
||||
bool hasError = source._hasError;
|
||||
if (listeners == null) {
|
||||
if (hasError) {
|
||||
AsyncError asyncError = source._error;
|
||||
source._zone.handleUncaughtError(asyncError);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Usually futures only have one listener. If they have several, we
|
||||
// call handle them separately in recursive calls, continuing
|
||||
// here only when there is only one listener left.
|
||||
while (listeners._nextListener != null) {
|
||||
_FutureListener currentListener = listeners;
|
||||
listeners = currentListener._nextListener;
|
||||
currentListener._nextListener = null;
|
||||
_propagateToListeners(source, currentListener);
|
||||
}
|
||||
|
||||
_FutureListener listener = listeners;
|
||||
var sourceResult = source._resultOrListeners;
|
||||
|
||||
// Do the actual propagation.
|
||||
// Set initial state of listenerHasError and listenerValueOrError. These
|
||||
// variables are updated with the outcome of potential callbacks.
|
||||
// Non-error results, including futures, are stored in
|
||||
// listenerValueOrError and listenerHasError is set to false. Errors
|
||||
// are stored in listenerValueOrError as an [AsyncError] and
|
||||
// listenerHasError is set to true.
|
||||
bool listenerHasError = hasError;
|
||||
var listenerValueOrError = sourceResult;
|
||||
|
||||
// Only if we either have an error or callbacks, go into this, somewhat
|
||||
// expensive, branch. Here we'll enter/leave the zone. Many futures
|
||||
// don't have callbacks, so this is a significant optimization.
|
||||
if (hasError || listener.handlesValue || listener.handlesComplete) {
|
||||
Zone zone = listener._zone;
|
||||
if (hasError && !source._zone.inSameErrorZone(zone)) {
|
||||
// Don't cross zone boundaries with errors.
|
||||
AsyncError asyncError = source._error;
|
||||
source._zone.handleUncaughtError(asyncError);
|
||||
return;
|
||||
}
|
||||
|
||||
Zone oldZone = null;
|
||||
if (!ReferenceEquals(Zone.current, zone)) {
|
||||
// Change zone if it's not current.
|
||||
oldZone = Zone._enter(zone);
|
||||
}
|
||||
|
||||
// These callbacks are abstracted to isolate the try/catch blocks
|
||||
// from the rest of the code to work around a V8 glass jaw.
|
||||
Action handleWhenCompleteCallback = () => {
|
||||
// The whenComplete-handler is not combined with normal value/error
|
||||
// handling. This means at most one handleX method is called per
|
||||
// listener.
|
||||
D.assert(!listener.handlesValue);
|
||||
D.assert(!listener.handlesError);
|
||||
object completeResult = null;
|
||||
try {
|
||||
completeResult = listener.handleWhenComplete();
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (hasError && ReferenceEquals(source._error.InnerException, e)) {
|
||||
listenerValueOrError = source._error;
|
||||
}
|
||||
else {
|
||||
listenerValueOrError = new AsyncError(e);
|
||||
}
|
||||
|
||||
listenerHasError = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (completeResult is Future completeResultFuture) {
|
||||
if (completeResult is _Future completeResultCoreFuture &&
|
||||
completeResultCoreFuture._isComplete) {
|
||||
if (completeResultCoreFuture._hasError) {
|
||||
listenerValueOrError = completeResultCoreFuture._error;
|
||||
listenerHasError = true;
|
||||
}
|
||||
|
||||
// Otherwise use the existing result of source.
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to wait for the completeResult future to complete
|
||||
// before knowing if it's an error or we should use the result
|
||||
// of source.
|
||||
var originalSource = source;
|
||||
listenerValueOrError =
|
||||
completeResultFuture.then((_) => FutureOr.withFuture(originalSource));
|
||||
listenerHasError = false;
|
||||
}
|
||||
};
|
||||
|
||||
Action handleValueCallback = () => {
|
||||
try {
|
||||
listenerValueOrError = listener.handleValue(sourceResult);
|
||||
}
|
||||
catch (Exception e) {
|
||||
listenerValueOrError = new AsyncError(e);
|
||||
listenerHasError = true;
|
||||
}
|
||||
};
|
||||
|
||||
Action handleError = () => {
|
||||
try {
|
||||
AsyncError asyncError = source._error;
|
||||
if (listener.matchesErrorTest(asyncError) &&
|
||||
listener.hasErrorCallback) {
|
||||
listenerValueOrError = listener.handleError(asyncError);
|
||||
listenerHasError = false;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (ReferenceEquals(source._error.InnerException, e)) {
|
||||
listenerValueOrError = source._error;
|
||||
}
|
||||
else {
|
||||
listenerValueOrError = new AsyncError(e);
|
||||
}
|
||||
|
||||
listenerHasError = true;
|
||||
}
|
||||
};
|
||||
|
||||
if (listener.handlesComplete) {
|
||||
handleWhenCompleteCallback();
|
||||
}
|
||||
else if (!hasError) {
|
||||
if (listener.handlesValue) {
|
||||
handleValueCallback();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (listener.handlesError) {
|
||||
handleError();
|
||||
}
|
||||
}
|
||||
|
||||
// If we changed zone, oldZone will not be null.
|
||||
if (oldZone != null) Zone._leave(oldZone);
|
||||
|
||||
// If the listener's value is a future we need to chain it. Note that
|
||||
// this can only happen if there is a callback.
|
||||
if (listenerValueOrError is Future chainSource) {
|
||||
// Shortcut if the chain-source is already completed. Just continue
|
||||
// the loop.
|
||||
_Future listenerResult = listener.result;
|
||||
if (chainSource is _Future chainSourceCore) {
|
||||
if (chainSourceCore._isComplete) {
|
||||
listeners = listenerResult._removeListeners();
|
||||
listenerResult._cloneResult(chainSourceCore);
|
||||
source = chainSourceCore;
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
_chainCoreFuture(chainSourceCore, listenerResult);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_chainForeignFuture(chainSource, listenerResult);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_Future result = listener.result;
|
||||
listeners = result._removeListeners();
|
||||
if (!listenerHasError) {
|
||||
result._setValue(listenerValueOrError);
|
||||
}
|
||||
else {
|
||||
AsyncError asyncError = (AsyncError) listenerValueOrError;
|
||||
result._setErrorObject(asyncError);
|
||||
}
|
||||
|
||||
// Prepare for next round.
|
||||
source = result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override Future timeout(TimeSpan timeLimit, Func<FutureOr> onTimeout = null) {
|
||||
if (_isComplete) return _Future.immediate(this);
|
||||
|
||||
_Future result = new _Future();
|
||||
Timer timer;
|
||||
if (onTimeout == null) {
|
||||
timer = Timer.create(timeLimit, () => {
|
||||
result._completeError(
|
||||
new TimeoutException("Future not completed", timeLimit));
|
||||
return null;
|
||||
});
|
||||
}
|
||||
else {
|
||||
Zone zone = Zone.current;
|
||||
onTimeout = async_._registerHandler(onTimeout, zone);
|
||||
|
||||
timer = Timer.create(timeLimit, () => {
|
||||
try {
|
||||
result._complete((FutureOr) zone.run(() => onTimeout()));
|
||||
}
|
||||
catch (Exception e) {
|
||||
result._completeError(e);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
then(v => {
|
||||
if (timer.isActive) {
|
||||
timer.cancel();
|
||||
result._completeWithValue(v);
|
||||
}
|
||||
|
||||
return FutureOr.nullValue;
|
||||
}, onError: e => {
|
||||
if (timer.isActive) {
|
||||
timer.cancel();
|
||||
result._completeError(e);
|
||||
}
|
||||
|
||||
return FutureOr.nullValue;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class async_ {
|
||||
internal static Func<object> _registerHandler(Func<object> handler, Zone zone) {
|
||||
var callback = zone.registerCallback(() => handler());
|
||||
return () => callback();
|
||||
}
|
||||
|
||||
internal static Func<FutureOr> _registerHandler(Func<FutureOr> handler, Zone zone) {
|
||||
var callback = zone.registerCallback(() => handler());
|
||||
return () => (FutureOr) callback();
|
||||
}
|
||||
|
||||
internal static Func<object, FutureOr> _registerUnaryHandler(Func<object, FutureOr> handler, Zone zone) {
|
||||
var callback = zone.registerUnaryCallback(arg => handler(arg));
|
||||
return arg => (FutureOr) callback(arg);
|
||||
}
|
||||
|
||||
internal static Func<Exception, bool> _registerUnaryHandler(Func<Exception, bool> handler, Zone zone) {
|
||||
var callback = zone.registerUnaryCallback(arg => handler((Exception) arg));
|
||||
return arg => (bool) callback(arg);
|
||||
}
|
||||
|
||||
internal static Func<Exception, FutureOr> _registerErrorHandler(Func<Exception, FutureOr> errorHandler,
|
||||
Zone zone) {
|
||||
var callback = zone.registerUnaryCallback(arg => errorHandler((Exception) arg));
|
||||
return arg => (FutureOr) callback(arg);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4072406cc46448f99159c137b458841e
|
||||
timeCreated: 1599458114
|
|
@ -0,0 +1,129 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using AOT;
|
||||
using Unity.UIWidgets.ui2;
|
||||
|
||||
namespace Unity.UIWidgets.async2 {
|
||||
class _AsyncCallbackEntry {
|
||||
public readonly ZoneCallback callback;
|
||||
public _AsyncCallbackEntry next;
|
||||
|
||||
internal _AsyncCallbackEntry(ZoneCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class async_ {
|
||||
static _AsyncCallbackEntry _nextCallback;
|
||||
static _AsyncCallbackEntry _lastCallback;
|
||||
static _AsyncCallbackEntry _lastPriorityCallback;
|
||||
|
||||
static bool _isInCallbackLoop = false;
|
||||
|
||||
static void _microtaskLoop() {
|
||||
while (_nextCallback != null) {
|
||||
_lastPriorityCallback = null;
|
||||
_AsyncCallbackEntry entry = _nextCallback;
|
||||
_nextCallback = entry.next;
|
||||
if (_nextCallback == null) _lastCallback = null;
|
||||
entry.callback();
|
||||
}
|
||||
}
|
||||
|
||||
static object _startMicrotaskLoop() {
|
||||
_isInCallbackLoop = true;
|
||||
try {
|
||||
// Moved to separate function because try-finally prevents
|
||||
// good optimization.
|
||||
_microtaskLoop();
|
||||
}
|
||||
finally {
|
||||
_lastPriorityCallback = null;
|
||||
_isInCallbackLoop = false;
|
||||
if (_nextCallback != null) {
|
||||
_AsyncRun._scheduleImmediate(_startMicrotaskLoop);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void _scheduleAsyncCallback(ZoneCallback callback) {
|
||||
_AsyncCallbackEntry newEntry = new _AsyncCallbackEntry(callback);
|
||||
if (_nextCallback == null) {
|
||||
_nextCallback = _lastCallback = newEntry;
|
||||
if (!_isInCallbackLoop) {
|
||||
_AsyncRun._scheduleImmediate(_startMicrotaskLoop);
|
||||
}
|
||||
}
|
||||
else {
|
||||
_lastCallback.next = newEntry;
|
||||
_lastCallback = newEntry;
|
||||
}
|
||||
}
|
||||
|
||||
static void _schedulePriorityAsyncCallback(ZoneCallback callback) {
|
||||
if (_nextCallback == null) {
|
||||
_scheduleAsyncCallback(callback);
|
||||
_lastPriorityCallback = _lastCallback;
|
||||
return;
|
||||
}
|
||||
|
||||
_AsyncCallbackEntry entry = new _AsyncCallbackEntry(callback);
|
||||
if (_lastPriorityCallback == null) {
|
||||
entry.next = _nextCallback;
|
||||
_nextCallback = _lastPriorityCallback = entry;
|
||||
}
|
||||
else {
|
||||
entry.next = _lastPriorityCallback.next;
|
||||
_lastPriorityCallback.next = entry;
|
||||
_lastPriorityCallback = entry;
|
||||
if (entry.next == null) {
|
||||
_lastCallback = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void scheduleMicrotask(ZoneCallback callback) {
|
||||
_Zone currentZone = (_Zone) Zone.current;
|
||||
if (ReferenceEquals(_rootZone, currentZone)) {
|
||||
// No need to bind the callback. We know that the root's scheduleMicrotask
|
||||
// will be invoked in the root zone.
|
||||
_rootScheduleMicrotask(null, null, _rootZone, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
_ZoneFunction<ScheduleMicrotaskHandler> implementation = currentZone._scheduleMicrotask;
|
||||
if (ReferenceEquals(_rootZone, implementation.zone) &&
|
||||
_rootZone.inSameErrorZone(currentZone)) {
|
||||
_rootScheduleMicrotask(
|
||||
null, null, currentZone, currentZone.registerCallback(callback));
|
||||
return;
|
||||
}
|
||||
|
||||
Zone.current.scheduleMicrotask(Zone.current.bindCallbackGuarded(callback));
|
||||
}
|
||||
}
|
||||
|
||||
class _AsyncRun {
|
||||
internal static void _scheduleImmediate(ZoneCallback callback) {
|
||||
GCHandle callabackHandle = GCHandle.Alloc(callback);
|
||||
UIMonoState_scheduleMicrotask(_scheduleMicrotask, (IntPtr) callabackHandle);
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(UIMonoState_scheduleMicrotaskCallback))]
|
||||
static void _scheduleMicrotask(IntPtr callbackHandle) {
|
||||
GCHandle handle = (GCHandle) callbackHandle;
|
||||
var callback = (ZoneCallback) handle.Target;
|
||||
handle.Free();
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
delegate void UIMonoState_scheduleMicrotaskCallback(IntPtr callbackHandle);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern void UIMonoState_scheduleMicrotask(UIMonoState_scheduleMicrotaskCallback callback,
|
||||
IntPtr callbackHandle);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e9d7bdadaa9c46e0b96ba19f4261185a
|
||||
timeCreated: 1599458114
|
|
@ -0,0 +1,142 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using AOT;
|
||||
using Unity.UIWidgets.ui2;
|
||||
|
||||
namespace Unity.UIWidgets.async2 {
|
||||
public abstract class Timer : IDisposable {
|
||||
public static Timer create(TimeSpan duration, ZoneCallback callback) {
|
||||
if (Zone.current == Zone.root) {
|
||||
return Zone.current.createTimer(duration, callback);
|
||||
}
|
||||
|
||||
return Zone.current
|
||||
.createTimer(duration, Zone.current.bindCallbackGuarded(callback));
|
||||
}
|
||||
|
||||
public static Timer periodic(TimeSpan duration, ZoneUnaryCallback callback) {
|
||||
if (Zone.current == Zone.root) {
|
||||
return Zone.current.createPeriodicTimer(duration, callback);
|
||||
}
|
||||
|
||||
var boundCallback = Zone.current.bindUnaryCallbackGuarded(callback);
|
||||
return Zone.current.createPeriodicTimer(duration, boundCallback);
|
||||
}
|
||||
|
||||
public static void run(ZoneCallback callback) {
|
||||
Timer.create(TimeSpan.Zero, callback);
|
||||
}
|
||||
|
||||
public abstract void cancel();
|
||||
|
||||
public void Dispose() {
|
||||
cancel();
|
||||
}
|
||||
|
||||
public abstract int tick { get; }
|
||||
|
||||
public abstract bool isActive { get; }
|
||||
|
||||
internal static Timer _createTimer(TimeSpan duration, ZoneCallback callback) {
|
||||
return _Timer._createTimer(_ => callback(), (int) duration.TotalMilliseconds, false);
|
||||
}
|
||||
|
||||
internal static Timer _createPeriodicTimer(
|
||||
TimeSpan duration, ZoneUnaryCallback callback) {
|
||||
return _Timer._createTimer(callback, (int) duration.TotalMilliseconds, true);
|
||||
}
|
||||
}
|
||||
|
||||
class _Timer : Timer {
|
||||
int _tick = 0;
|
||||
|
||||
ZoneUnaryCallback _callback;
|
||||
int _wakeupTime;
|
||||
readonly int _milliSeconds;
|
||||
readonly bool _repeating;
|
||||
|
||||
_Timer(ZoneUnaryCallback callback, int wakeupTime, int milliSeconds, bool repeating) {
|
||||
_callback = callback;
|
||||
_wakeupTime = wakeupTime;
|
||||
_milliSeconds = milliSeconds;
|
||||
_repeating = repeating;
|
||||
}
|
||||
|
||||
internal static _Timer _createTimer(ZoneUnaryCallback callback, int milliSeconds, bool repeating) {
|
||||
if (milliSeconds < 0) {
|
||||
milliSeconds = 0;
|
||||
}
|
||||
|
||||
int now = UIMonoState_timerMillisecondClock();
|
||||
int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds);
|
||||
|
||||
_Timer timer = new _Timer(callback, wakeupTime, milliSeconds, repeating);
|
||||
timer._enqueue();
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
public override void cancel() {
|
||||
_callback = null;
|
||||
}
|
||||
|
||||
public override bool isActive => _callback != null;
|
||||
|
||||
public override int tick => _tick;
|
||||
|
||||
void _advanceWakeupTime() {
|
||||
if (_milliSeconds > 0) {
|
||||
_wakeupTime += _milliSeconds;
|
||||
}
|
||||
else {
|
||||
_wakeupTime = UIMonoState_timerMillisecondClock();
|
||||
}
|
||||
}
|
||||
|
||||
void _enqueue() {
|
||||
GCHandle callabackHandle = GCHandle.Alloc(this);
|
||||
UIMonoState_postTaskForTime(_postTaskForTime, (IntPtr) callabackHandle, _wakeupTime * 1000L);
|
||||
}
|
||||
|
||||
[MonoPInvokeCallback(typeof(UIMonoState_postTaskForTimeCallback))]
|
||||
static void _postTaskForTime(IntPtr callbackHandle) {
|
||||
GCHandle timerHandle = (GCHandle) callbackHandle;
|
||||
var timer = (_Timer) timerHandle.Target;
|
||||
timerHandle.Free();
|
||||
|
||||
if (timer._callback != null) {
|
||||
var callback = timer._callback;
|
||||
if (!timer._repeating) {
|
||||
timer._callback = null;
|
||||
}
|
||||
else if (timer._milliSeconds > 0) {
|
||||
var ms = timer._milliSeconds;
|
||||
int overdue = UIMonoState_timerMillisecondClock() - timer._wakeupTime;
|
||||
if (overdue > ms) {
|
||||
int missedTicks = overdue / ms;
|
||||
timer._wakeupTime += missedTicks * ms;
|
||||
timer._tick += missedTicks;
|
||||
}
|
||||
}
|
||||
|
||||
timer._tick += 1;
|
||||
|
||||
callback(timer);
|
||||
|
||||
if (timer._repeating && (timer._callback != null)) {
|
||||
timer._advanceWakeupTime();
|
||||
timer._enqueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern int UIMonoState_timerMillisecondClock();
|
||||
|
||||
delegate void UIMonoState_postTaskForTimeCallback(IntPtr callbackHandle);
|
||||
|
||||
[DllImport(NativeBindings.dllName)]
|
||||
static extern void UIMonoState_postTaskForTime(UIMonoState_postTaskForTimeCallback callback,
|
||||
IntPtr callbackHandle, long targetTimeNanos);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dc44ad1967714937b709d5f22c6b3bec
|
||||
timeCreated: 1599458114
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8dbf866190b44e6a47e0ff7c0e2bdc4
|
||||
timeCreated: 1599458114
|
|
@ -25,7 +25,7 @@ namespace Unity.UIWidgets.debugger {
|
|||
}
|
||||
|
||||
public bool debugEnabled {
|
||||
get { return D.debugEnabled; }
|
||||
get { return foundation_.kDebugMode; }
|
||||
}
|
||||
|
||||
public void close() {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 32b343b53c4d484f8c149742f9d0a919
|
||||
timeCreated: 1599028965
|
|
@ -0,0 +1,8 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace developer {
|
||||
public static partial class developer_ {
|
||||
public static void postEvent(string eventKind, Hashtable eventData) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d2dcff701560471ab813d010a98a7d36
|
||||
timeCreated: 1599030815
|
|
@ -0,0 +1,11 @@
|
|||
using System.Collections;
|
||||
|
||||
namespace developer {
|
||||
public class Timeline {
|
||||
public static void startSync(string name, Hashtable arguments = null) {
|
||||
}
|
||||
|
||||
public static void finishSync() {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 263dee475b944a33a99e0d8925f904c4
|
||||
timeCreated: 1599028971
|
|
@ -51,6 +51,7 @@ namespace Unity.UIWidgets.engine2 {
|
|||
}
|
||||
|
||||
protected virtual void main() {
|
||||
Debug.Log(Debug.isDebugBuild);
|
||||
}
|
||||
|
||||
public void entryPoint() {
|
||||
|
|
|
@ -1,2 +1,55 @@
|
|||
namespace Unity.UIWidgets.foundation {
|
||||
using System;
|
||||
using RSG;
|
||||
using Unity.UIWidgets.async2;
|
||||
using Unity.UIWidgets.ui2;
|
||||
|
||||
namespace Unity.UIWidgets.foundation {
|
||||
public abstract class BindingBase {
|
||||
protected BindingBase() {
|
||||
D.assert(!_debugInitialized);
|
||||
initInstances();
|
||||
D.assert(_debugInitialized);
|
||||
}
|
||||
|
||||
static bool _debugInitialized = false;
|
||||
|
||||
public Window window => Window.instance;
|
||||
|
||||
protected virtual void initInstances() {
|
||||
D.assert(!_debugInitialized);
|
||||
D.assert(() => {
|
||||
_debugInitialized = true;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
protected bool locked => _lockCount > 0;
|
||||
int _lockCount = 0;
|
||||
|
||||
protected Future lockEvents(Func<Future> callback) {
|
||||
developer.Timeline.startSync("Lock events");
|
||||
|
||||
D.assert(callback != null);
|
||||
_lockCount += 1;
|
||||
Future future = callback();
|
||||
D.assert(future != null,
|
||||
() =>
|
||||
"The lockEvents() callback returned null; " +
|
||||
"it should return a Promise that completes when the lock is to expire.");
|
||||
future.whenComplete(() => {
|
||||
_lockCount -= 1;
|
||||
if (!locked) {
|
||||
developer.Timeline.finishSync();
|
||||
unlocked();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
protected virtual void unlocked() {
|
||||
D.assert(!locked);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,23 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Unity.UIWidgets.foundation {
|
||||
public class FoundationConstants {
|
||||
public static bool kReleaseMode = !Debug.isDebugBuild;
|
||||
public static class foundation_ {
|
||||
public static readonly bool kReleaseMode = !Debug.isDebugBuild;
|
||||
|
||||
#if UIWidgets_PROFILE
|
||||
public const bool kProfileMode = true;
|
||||
#else
|
||||
public const bool kProfileMode = false;
|
||||
#endif
|
||||
|
||||
public static readonly bool kDebugMode = !kReleaseMode && !kProfileMode;
|
||||
|
||||
public const float precisionErrorTolerance = 1e-10f;
|
||||
|
||||
#if UNITY_WEBGL
|
||||
public const bool kIsWeb = true;
|
||||
#else
|
||||
public const bool kIsWeb = false;
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -15,24 +15,14 @@ namespace Unity.UIWidgets.foundation {
|
|||
Debug.LogException(new AssertionError(message, ex));
|
||||
}
|
||||
|
||||
public static bool debugEnabled {
|
||||
get {
|
||||
#if UIWidgets_DEBUG
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("UIWidgets_DEBUG")]
|
||||
[Conditional("UNITY_ASSERTIONS")]
|
||||
public static void assert(Func<bool> result, Func<string> message = null) {
|
||||
if (!result()) {
|
||||
throw new AssertionError(message != null ? message() : "");
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("UIWidgets_DEBUG")]
|
||||
[Conditional("UNITY_ASSERTIONS")]
|
||||
public static void assert(bool result, Func<string> message = null) {
|
||||
if (!result) {
|
||||
throw new AssertionError(message != null ? message() : "");
|
||||
|
@ -49,12 +39,6 @@ namespace Unity.UIWidgets.foundation {
|
|||
|
||||
public static bool debugPrintRecognizerCallbacksTrace = false;
|
||||
|
||||
public static bool debugPrintBeginFrameBanner = false;
|
||||
|
||||
public static bool debugPrintEndFrameBanner = false;
|
||||
|
||||
public static bool debugPrintScheduleFrameStacks = false;
|
||||
|
||||
public static bool debugPaintSizeEnabled = false;
|
||||
|
||||
public static bool debugRepaintRainbowEnabled = false;
|
||||
|
|
|
@ -6,7 +6,7 @@ using Unity.UIWidgets.foundation;
|
|||
using Unity.UIWidgets.gestures;
|
||||
using Unity.UIWidgets.painting;
|
||||
using Unity.UIWidgets.rendering;
|
||||
using Unity.UIWidgets.scheduler;
|
||||
using Unity.UIWidgets.scheduler2;
|
||||
using Unity.UIWidgets.ui;
|
||||
using Unity.UIWidgets.widgets;
|
||||
using UnityEngine;
|
||||
|
@ -621,7 +621,7 @@ namespace Unity.UIWidgets.material {
|
|||
this._state.interactionTimer?.cancel();
|
||||
this._state.interactionTimer = Window.instance.run(
|
||||
new TimeSpan(0, 0, 0, 0,
|
||||
(int) (_minimumInteractionTimeMilliSeconds * SchedulerBinding.instance.timeDilation)),
|
||||
(int) (_minimumInteractionTimeMilliSeconds * scheduler_.timeDilation)),
|
||||
() => {
|
||||
this._state.interactionTimer = null;
|
||||
if (!this._active &&
|
||||
|
|
|
@ -4,8 +4,9 @@ using System.Linq;
|
|||
using RSG;
|
||||
using Unity.UIWidgets.async;
|
||||
using Unity.UIWidgets.foundation;
|
||||
using Unity.UIWidgets.scheduler;
|
||||
using Unity.UIWidgets.scheduler2;
|
||||
using Unity.UIWidgets.ui;
|
||||
using SchedulerBinding = Unity.UIWidgets.scheduler.SchedulerBinding;
|
||||
|
||||
namespace Unity.UIWidgets.painting {
|
||||
public class ImageInfo : IEquatable<ImageInfo> {
|
||||
|
@ -350,7 +351,7 @@ namespace Unity.UIWidgets.painting {
|
|||
}
|
||||
|
||||
TimeSpan delay = this._frameDuration.Value - (timestamp - this._shownTimestamp.Value);
|
||||
delay = new TimeSpan((long) (delay.Ticks * SchedulerBinding.instance.timeDilation));
|
||||
delay = new TimeSpan((long) (delay.Ticks * scheduler_.timeDilation));
|
||||
this._timer = Window.instance.run(delay, this._scheduleAppFrame);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,10 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using RSG.Promises;
|
||||
using Unity.UIWidgets.foundation;
|
||||
using Unity.UIWidgets.scheduler2;
|
||||
using Unity.UIWidgets.ui;
|
||||
using Debug = UnityEngine.Debug;
|
||||
using FrameCallback = Unity.UIWidgets.ui.FrameCallback;
|
||||
|
||||
namespace Unity.UIWidgets.scheduler {
|
||||
class _FrameCallbackEntry {
|
||||
|
@ -173,7 +175,7 @@ namespace Unity.UIWidgets.scheduler {
|
|||
}
|
||||
|
||||
D.assert(() => {
|
||||
if (D.debugPrintScheduleFrameStacks) {
|
||||
if (scheduler_.debugPrintScheduleFrameStacks) {
|
||||
Debug.LogFormat("scheduleFrame() called. Current phase is {0}.", this.schedulerPhase);
|
||||
}
|
||||
|
||||
|
@ -190,7 +192,7 @@ namespace Unity.UIWidgets.scheduler {
|
|||
}
|
||||
|
||||
D.assert(() => {
|
||||
if (D.debugPrintScheduleFrameStacks) {
|
||||
if (scheduler_.debugPrintScheduleFrameStacks) {
|
||||
Debug.LogFormat("scheduleForcedFrame() called. Current phase is {0}.", this.schedulerPhase);
|
||||
}
|
||||
|
||||
|
@ -248,7 +250,7 @@ namespace Unity.UIWidgets.scheduler {
|
|||
});
|
||||
|
||||
D.assert(() => {
|
||||
if (D.debugPrintBeginFrameBanner || D.debugPrintEndFrameBanner) {
|
||||
if (scheduler_.debugPrintBeginFrameBanner || scheduler_.debugPrintEndFrameBanner) {
|
||||
var frameTimeStampDescription = new StringBuilder();
|
||||
if (rawTimeStamp != null) {
|
||||
_debugDescribeTimeStamp(
|
||||
|
@ -259,7 +261,7 @@ namespace Unity.UIWidgets.scheduler {
|
|||
|
||||
this._debugBanner =
|
||||
$"▄▄▄▄▄▄▄▄ Frame {this._profileFrameNumber.ToString().PadRight(7)} {frameTimeStampDescription.ToString().PadLeft(18)} ▄▄▄▄▄▄▄▄";
|
||||
if (D.debugPrintBeginFrameBanner) {
|
||||
if (scheduler_.debugPrintBeginFrameBanner) {
|
||||
Debug.Log(this._debugBanner);
|
||||
}
|
||||
}
|
||||
|
@ -306,7 +308,7 @@ namespace Unity.UIWidgets.scheduler {
|
|||
} finally {
|
||||
this._schedulerPhase = SchedulerPhase.idle;
|
||||
D.assert(() => {
|
||||
if (D.debugPrintEndFrameBanner) {
|
||||
if (scheduler_.debugPrintEndFrameBanner) {
|
||||
Debug.Log(new string('▀', this._debugBanner.Length));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1fea1ea4786b4e6bb65b233b11e8b2d5
|
||||
timeCreated: 1599458322
|
|
@ -0,0 +1,674 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using developer;
|
||||
using Unity.UIWidgets.async;
|
||||
using Unity.UIWidgets.async2;
|
||||
using Unity.UIWidgets.foundation;
|
||||
using Unity.UIWidgets.ui2;
|
||||
using UnityEngine;
|
||||
using FrameTiming = Unity.UIWidgets.ui2.FrameTiming;
|
||||
using Timer = Unity.UIWidgets.async2.Timer;
|
||||
|
||||
namespace Unity.UIWidgets.scheduler2 {
|
||||
public static partial class scheduler_ {
|
||||
public static float timeDilation {
|
||||
get { return _timeDilation; }
|
||||
set {
|
||||
D.assert(value > 0.0f);
|
||||
if (_timeDilation == value)
|
||||
return;
|
||||
|
||||
SchedulerBinding.instance?.resetEpoch();
|
||||
_timeDilation = value;
|
||||
}
|
||||
}
|
||||
|
||||
static float _timeDilation = 1.0f;
|
||||
}
|
||||
|
||||
public delegate void FrameCallback(TimeSpan timeStamp);
|
||||
|
||||
public delegate T TaskCallback<out T>();
|
||||
|
||||
public delegate bool SchedulingStrategy(int priority = 0, SchedulerBinding scheduler = null);
|
||||
|
||||
interface _TaskEntry : IComparable<_TaskEntry> {
|
||||
int priority { get; }
|
||||
string debugStack { get; }
|
||||
void run();
|
||||
}
|
||||
|
||||
class _TaskEntry<T> : _TaskEntry {
|
||||
internal _TaskEntry(TaskCallback<T> task, int priority) {
|
||||
this.task = task;
|
||||
this.priority = priority;
|
||||
|
||||
D.assert(() => {
|
||||
debugStack = StackTraceUtility.ExtractStackTrace();
|
||||
return true;
|
||||
});
|
||||
completer = Completer.create();
|
||||
}
|
||||
|
||||
public readonly TaskCallback<T> task;
|
||||
public int priority { get; }
|
||||
public string debugStack { get; private set; }
|
||||
public Completer completer;
|
||||
|
||||
public void run() {
|
||||
if (!foundation_.kReleaseMode) {
|
||||
completer.complete(FutureOr.withValue(task()));
|
||||
}
|
||||
else {
|
||||
completer.complete(FutureOr.withValue(task()));
|
||||
}
|
||||
}
|
||||
|
||||
public int CompareTo(_TaskEntry other) {
|
||||
return -priority.CompareTo(other.priority);
|
||||
}
|
||||
}
|
||||
|
||||
class _FrameCallbackEntry {
|
||||
internal _FrameCallbackEntry(FrameCallback callback, bool rescheduling = false) {
|
||||
this.callback = callback;
|
||||
|
||||
D.assert(() => {
|
||||
if (rescheduling) {
|
||||
D.assert(() => {
|
||||
if (debugCurrentCallbackStack == null) {
|
||||
throw new UIWidgetsError(
|
||||
"scheduleFrameCallback called with rescheduling true, but no callback is in scope.\n" +
|
||||
"The \"rescheduling\" argument should only be set to true if the " +
|
||||
"callback is being reregistered from within the callback itself, " +
|
||||
"and only then if the callback itself is entirely synchronous. \n" +
|
||||
"If this is the initial registration of the callback, or if the " +
|
||||
"callback is asynchronous, then do not use the \"rescheduling\" " +
|
||||
"argument.");
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
debugStack = debugCurrentCallbackStack;
|
||||
}
|
||||
else {
|
||||
debugStack = StackTraceUtility.ExtractStackTrace();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public readonly FrameCallback callback;
|
||||
|
||||
public static string debugCurrentCallbackStack;
|
||||
public string debugStack;
|
||||
}
|
||||
|
||||
public enum SchedulerPhase {
|
||||
idle,
|
||||
transientCallbacks,
|
||||
midFrameMicrotasks,
|
||||
persistentCallbacks,
|
||||
postFrameCallbacks,
|
||||
}
|
||||
|
||||
public class SchedulerBinding : BindingBase {
|
||||
protected override void initInstances() {
|
||||
base.initInstances();
|
||||
instance = this;
|
||||
|
||||
//SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
|
||||
readInitialLifecycleStateFromNativeWindow();
|
||||
|
||||
if (!foundation_.kReleaseMode) {
|
||||
int frameNumber = 0;
|
||||
addTimingsCallback((List<FrameTiming> timings) => {
|
||||
foreach (FrameTiming frameTiming in timings) {
|
||||
frameNumber += 1;
|
||||
_profileFramePostEvent(frameNumber, frameTiming);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
readonly List<TimingsCallback> _timingsCallbacks = new List<TimingsCallback>();
|
||||
|
||||
public void addTimingsCallback(TimingsCallback callback) {
|
||||
_timingsCallbacks.Add(callback);
|
||||
if (_timingsCallbacks.Count == 1) {
|
||||
D.assert(window.onReportTimings == null);
|
||||
window.onReportTimings = _executeTimingsCallbacks;
|
||||
}
|
||||
|
||||
D.assert(window.onReportTimings == _executeTimingsCallbacks);
|
||||
}
|
||||
|
||||
public void removeTimingsCallback(TimingsCallback callback) {
|
||||
D.assert(_timingsCallbacks.Contains(callback));
|
||||
_timingsCallbacks.Remove(callback);
|
||||
if (_timingsCallbacks.isEmpty()) {
|
||||
window.onReportTimings = null;
|
||||
}
|
||||
}
|
||||
|
||||
void _executeTimingsCallbacks(List<FrameTiming> timings) {
|
||||
List<TimingsCallback> clonedCallbacks =
|
||||
new List<TimingsCallback>(_timingsCallbacks);
|
||||
foreach (TimingsCallback callback in clonedCallbacks) {
|
||||
try {
|
||||
if (_timingsCallbacks.Contains(callback)) {
|
||||
callback(timings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
InformationCollector collector = null;
|
||||
D.assert(() => {
|
||||
collector = (StringBuilder sb) => {
|
||||
sb.AppendLine("The TimingsCallback that gets executed was " + callback);
|
||||
};
|
||||
return true;
|
||||
});
|
||||
|
||||
UIWidgetsError.reportError(new UIWidgetsErrorDetails(
|
||||
exception: ex,
|
||||
context: "while executing callbacks for FrameTiming",
|
||||
informationCollector: collector
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static SchedulerBinding instance {
|
||||
get { return (SchedulerBinding) Window.instance._binding; }
|
||||
set { Window.instance._binding = value; }
|
||||
}
|
||||
|
||||
public AppLifecycleState? lifecycleState => _lifecycleState;
|
||||
AppLifecycleState? _lifecycleState;
|
||||
|
||||
protected void readInitialLifecycleStateFromNativeWindow() {
|
||||
if (_lifecycleState == null) {
|
||||
handleAppLifecycleStateChanged(_parseAppLifecycleMessage(window.initialLifecycleState));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void handleAppLifecycleStateChanged(AppLifecycleState state) {
|
||||
_lifecycleState = state;
|
||||
switch (state) {
|
||||
case AppLifecycleState.resumed:
|
||||
case AppLifecycleState.inactive:
|
||||
_setFramesEnabledState(true);
|
||||
break;
|
||||
case AppLifecycleState.paused:
|
||||
case AppLifecycleState.detached:
|
||||
_setFramesEnabledState(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static AppLifecycleState _parseAppLifecycleMessage(string message) {
|
||||
switch (message) {
|
||||
case "AppLifecycleState.paused":
|
||||
return AppLifecycleState.paused;
|
||||
case "AppLifecycleState.resumed":
|
||||
return AppLifecycleState.resumed;
|
||||
case "AppLifecycleState.inactive":
|
||||
return AppLifecycleState.inactive;
|
||||
case "AppLifecycleState.detached":
|
||||
return AppLifecycleState.detached;
|
||||
}
|
||||
|
||||
throw new Exception("unknown AppLifecycleState: " + message);
|
||||
}
|
||||
|
||||
|
||||
public SchedulingStrategy schedulingStrategy = scheduler_.defaultSchedulingStrategy;
|
||||
|
||||
readonly PriorityQueue<_TaskEntry> _taskQueue = new PriorityQueue<_TaskEntry>();
|
||||
|
||||
public Future scheduleTask<T>(
|
||||
TaskCallback<T> task,
|
||||
Priority priority) {
|
||||
bool isFirstTask = _taskQueue.isEmpty;
|
||||
_TaskEntry<T> entry = new _TaskEntry<T>(
|
||||
task,
|
||||
priority.value
|
||||
);
|
||||
_taskQueue.enqueue(entry);
|
||||
if (isFirstTask && !locked)
|
||||
_ensureEventLoopCallback();
|
||||
return entry.completer.future;
|
||||
}
|
||||
|
||||
protected override void unlocked() {
|
||||
base.unlocked();
|
||||
if (_taskQueue.isNotEmpty)
|
||||
_ensureEventLoopCallback();
|
||||
}
|
||||
|
||||
bool _hasRequestedAnEventLoopCallback = false;
|
||||
|
||||
void _ensureEventLoopCallback() {
|
||||
D.assert(!locked);
|
||||
D.assert(_taskQueue.count != 0);
|
||||
if (_hasRequestedAnEventLoopCallback)
|
||||
return;
|
||||
_hasRequestedAnEventLoopCallback = true;
|
||||
Timer.run(_runTasks);
|
||||
}
|
||||
|
||||
object _runTasks() {
|
||||
_hasRequestedAnEventLoopCallback = false;
|
||||
if (handleEventLoopCallback())
|
||||
_ensureEventLoopCallback(); // runs next task when there's time
|
||||
return null;
|
||||
}
|
||||
|
||||
bool handleEventLoopCallback() {
|
||||
if (_taskQueue.isEmpty || locked)
|
||||
return false;
|
||||
|
||||
_TaskEntry entry = _taskQueue.first;
|
||||
if (schedulingStrategy(priority: entry.priority, scheduler: this)) {
|
||||
try {
|
||||
_taskQueue.removeFirst();
|
||||
entry.run();
|
||||
}
|
||||
catch (Exception exception) {
|
||||
string callbackStack = null;
|
||||
D.assert(() => {
|
||||
callbackStack = entry.debugStack;
|
||||
return true;
|
||||
});
|
||||
UIWidgetsError.reportError(new UIWidgetsErrorDetails(
|
||||
exception: exception,
|
||||
library: "scheduler library",
|
||||
context: "during a task callback",
|
||||
informationCollector: callbackStack == null
|
||||
? (InformationCollector) null
|
||||
: sb => {
|
||||
sb.AppendLine("\nThis exception was thrown in the context of a scheduler callback. " +
|
||||
"When the scheduler callback was _registered_ (as opposed to when the " +
|
||||
"exception was thrown), this was the stack: " + callbackStack);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
return _taskQueue.isNotEmpty;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int _nextFrameCallbackId = 0;
|
||||
Dictionary<int, _FrameCallbackEntry> _transientCallbacks = new Dictionary<int, _FrameCallbackEntry>();
|
||||
readonly HashSet<int> _removedIds = new HashSet<int>();
|
||||
|
||||
public int transientCallbackCount => _transientCallbacks.Count;
|
||||
|
||||
public int scheduleFrameCallback(FrameCallback callback, bool rescheduling = false) {
|
||||
scheduleFrame();
|
||||
_nextFrameCallbackId += 1;
|
||||
_transientCallbacks[_nextFrameCallbackId] =
|
||||
new _FrameCallbackEntry(callback, rescheduling: rescheduling);
|
||||
return _nextFrameCallbackId;
|
||||
}
|
||||
|
||||
public void cancelFrameCallbackWithId(int id) {
|
||||
D.assert(id > 0);
|
||||
_transientCallbacks.Remove(id);
|
||||
_removedIds.Add(id);
|
||||
}
|
||||
|
||||
readonly List<FrameCallback> _persistentCallbacks = new List<FrameCallback>();
|
||||
|
||||
public void addPersistentFrameCallback(FrameCallback callback) {
|
||||
_persistentCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
readonly List<FrameCallback> _postFrameCallbacks = new List<FrameCallback>();
|
||||
|
||||
public void addPostFrameCallback(FrameCallback callback) {
|
||||
_postFrameCallbacks.Add(callback);
|
||||
}
|
||||
|
||||
Completer _nextFrameCompleter;
|
||||
|
||||
Future endOfFrame {
|
||||
get {
|
||||
if (_nextFrameCompleter == null) {
|
||||
if (schedulerPhase == SchedulerPhase.idle)
|
||||
scheduleFrame();
|
||||
_nextFrameCompleter = Completer.create();
|
||||
addPostFrameCallback((TimeSpan timeStamp) => {
|
||||
_nextFrameCompleter.complete();
|
||||
_nextFrameCompleter = null;
|
||||
});
|
||||
}
|
||||
|
||||
return _nextFrameCompleter.future;
|
||||
}
|
||||
}
|
||||
|
||||
public bool hasScheduledFrame => _hasScheduledFrame;
|
||||
bool _hasScheduledFrame = false;
|
||||
|
||||
public SchedulerPhase schedulerPhase => _schedulerPhase;
|
||||
SchedulerPhase _schedulerPhase = SchedulerPhase.idle;
|
||||
|
||||
public bool framesEnabled => _framesEnabled;
|
||||
bool _framesEnabled = true;
|
||||
|
||||
void _setFramesEnabledState(bool enabled) {
|
||||
if (_framesEnabled == enabled)
|
||||
return;
|
||||
_framesEnabled = enabled;
|
||||
if (enabled)
|
||||
scheduleFrame();
|
||||
}
|
||||
|
||||
protected void ensureFrameCallbacksRegistered() {
|
||||
window.onBeginFrame = window.onBeginFrame ?? _handleBeginFrame;
|
||||
window.onDrawFrame = window.onDrawFrame ?? _handleDrawFrame;
|
||||
}
|
||||
|
||||
public void ensureVisualUpdate() {
|
||||
switch (schedulerPhase) {
|
||||
case SchedulerPhase.idle:
|
||||
case SchedulerPhase.postFrameCallbacks:
|
||||
scheduleFrame();
|
||||
return;
|
||||
case SchedulerPhase.transientCallbacks:
|
||||
case SchedulerPhase.midFrameMicrotasks:
|
||||
case SchedulerPhase.persistentCallbacks:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void scheduleFrame() {
|
||||
if (_hasScheduledFrame || !_framesEnabled)
|
||||
return;
|
||||
|
||||
D.assert(() => {
|
||||
if (scheduler_.debugPrintScheduleFrameStacks) {
|
||||
Debug.LogFormat("scheduleFrame() called. Current phase is {0}.", schedulerPhase);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ensureFrameCallbacksRegistered();
|
||||
Window.instance.scheduleFrame();
|
||||
_hasScheduledFrame = true;
|
||||
}
|
||||
|
||||
public void scheduleForcedFrame() {
|
||||
if (!framesEnabled)
|
||||
return;
|
||||
|
||||
if (_hasScheduledFrame)
|
||||
return;
|
||||
|
||||
D.assert(() => {
|
||||
if (scheduler_.debugPrintScheduleFrameStacks) {
|
||||
Debug.LogFormat("scheduleForcedFrame() called. Current phase is {0}.", schedulerPhase);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ensureFrameCallbacksRegistered();
|
||||
Window.instance.scheduleFrame();
|
||||
_hasScheduledFrame = true;
|
||||
}
|
||||
|
||||
bool _warmUpFrame = false;
|
||||
|
||||
public void scheduleWarmUpFrame() {
|
||||
if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
|
||||
return;
|
||||
|
||||
_warmUpFrame = true;
|
||||
Timeline.startSync("Warm-up frame");
|
||||
|
||||
bool hadScheduledFrame = _hasScheduledFrame;
|
||||
// We use timers here to ensure that microtasks flush in between.
|
||||
Timer.run(() => {
|
||||
D.assert(_warmUpFrame);
|
||||
handleBeginFrame(null);
|
||||
return null;
|
||||
});
|
||||
Timer.run(() => {
|
||||
D.assert(_warmUpFrame);
|
||||
handleDrawFrame();
|
||||
// We call resetEpoch after this frame so that, in the hot reload case,
|
||||
// the very next frame pretends to have occurred immediately after this
|
||||
// warm-up frame. The warm-up frame's timestamp will typically be far in
|
||||
// the past (the time of the last real frame), so if we didn't reset the
|
||||
// epoch we would see a sudden jump from the old time in the warm-up frame
|
||||
// to the new time in the "real" frame. The biggest problem with this is
|
||||
// that implicit animations end up being triggered at the old time and
|
||||
// then skipping every frame and finishing in the new time.
|
||||
resetEpoch();
|
||||
_warmUpFrame = false;
|
||||
if (hadScheduledFrame)
|
||||
scheduleFrame();
|
||||
return null;
|
||||
});
|
||||
|
||||
// Lock events so touch events etc don't insert themselves until the
|
||||
// scheduled frame has finished.
|
||||
lockEvents(() => endOfFrame.then(v => {
|
||||
Timeline.finishSync();
|
||||
return FutureOr.nullValue;
|
||||
}));
|
||||
}
|
||||
|
||||
TimeSpan? _firstRawTimeStampInEpoch;
|
||||
TimeSpan _epochStart = TimeSpan.Zero;
|
||||
TimeSpan _lastRawTimeStamp = TimeSpan.Zero;
|
||||
|
||||
public void resetEpoch() {
|
||||
_epochStart = _adjustForEpoch(_lastRawTimeStamp);
|
||||
_firstRawTimeStampInEpoch = null;
|
||||
}
|
||||
|
||||
TimeSpan _adjustForEpoch(TimeSpan rawTimeStamp) {
|
||||
var rawDurationSinceEpoch = _firstRawTimeStampInEpoch == null
|
||||
? TimeSpan.Zero
|
||||
: rawTimeStamp - _firstRawTimeStampInEpoch.Value;
|
||||
return new TimeSpan((long) (rawDurationSinceEpoch.Ticks / scheduler_.timeDilation) +
|
||||
_epochStart.Ticks);
|
||||
}
|
||||
|
||||
public TimeSpan currentFrameTimeStamp {
|
||||
get {
|
||||
D.assert(_currentFrameTimeStamp != null);
|
||||
return _currentFrameTimeStamp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
TimeSpan? _currentFrameTimeStamp;
|
||||
|
||||
public TimeSpan currentSystemFrameTimeStamp => _lastRawTimeStamp;
|
||||
|
||||
int _debugFrameNumber = 0;
|
||||
string _debugBanner;
|
||||
bool _ignoreNextEngineDrawFrame = false;
|
||||
|
||||
void _handleBeginFrame(TimeSpan rawTimeStamp) {
|
||||
if (_warmUpFrame) {
|
||||
D.assert(!_ignoreNextEngineDrawFrame);
|
||||
_ignoreNextEngineDrawFrame = true;
|
||||
return;
|
||||
}
|
||||
|
||||
handleBeginFrame(rawTimeStamp);
|
||||
}
|
||||
|
||||
void _handleDrawFrame() {
|
||||
if (_ignoreNextEngineDrawFrame) {
|
||||
_ignoreNextEngineDrawFrame = false;
|
||||
return;
|
||||
}
|
||||
|
||||
handleDrawFrame();
|
||||
}
|
||||
|
||||
public void handleBeginFrame(TimeSpan? rawTimeStamp) {
|
||||
Timeline.startSync("Frame");
|
||||
|
||||
_firstRawTimeStampInEpoch = _firstRawTimeStampInEpoch ?? rawTimeStamp;
|
||||
_currentFrameTimeStamp = _adjustForEpoch(rawTimeStamp ?? _lastRawTimeStamp);
|
||||
|
||||
if (rawTimeStamp != null)
|
||||
_lastRawTimeStamp = rawTimeStamp.Value;
|
||||
|
||||
D.assert(() => {
|
||||
_debugFrameNumber += 1;
|
||||
|
||||
if (scheduler_.debugPrintBeginFrameBanner || scheduler_.debugPrintEndFrameBanner) {
|
||||
StringBuilder frameTimeStampDescription = new StringBuilder();
|
||||
if (rawTimeStamp != null) {
|
||||
_debugDescribeTimeStamp(_currentFrameTimeStamp.Value, frameTimeStampDescription);
|
||||
}
|
||||
else {
|
||||
frameTimeStampDescription.Append("(warm-up frame)");
|
||||
}
|
||||
|
||||
_debugBanner =
|
||||
$"▄▄▄▄▄▄▄▄ Frame {_debugFrameNumber.ToString().PadRight(7)} ${frameTimeStampDescription.ToString().PadLeft(18)} ▄▄▄▄▄▄▄▄";
|
||||
if (scheduler_.debugPrintBeginFrameBanner)
|
||||
Debug.Log(_debugBanner);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
D.assert(_schedulerPhase == SchedulerPhase.idle);
|
||||
_hasScheduledFrame = false;
|
||||
|
||||
try {
|
||||
Timeline.startSync("Animate");
|
||||
_schedulerPhase = SchedulerPhase.transientCallbacks;
|
||||
var callbacks = _transientCallbacks;
|
||||
_transientCallbacks = new Dictionary<int, _FrameCallbackEntry>();
|
||||
foreach (var entry in callbacks) {
|
||||
if (!_removedIds.Contains(entry.Key)) {
|
||||
_invokeFrameCallback(
|
||||
entry.Value.callback, _currentFrameTimeStamp.Value, entry.Value.debugStack);
|
||||
}
|
||||
}
|
||||
|
||||
_removedIds.Clear();
|
||||
}
|
||||
finally {
|
||||
_schedulerPhase = SchedulerPhase.midFrameMicrotasks;
|
||||
}
|
||||
}
|
||||
|
||||
public void handleDrawFrame() {
|
||||
D.assert(_schedulerPhase == SchedulerPhase.midFrameMicrotasks);
|
||||
Timeline.finishSync();
|
||||
|
||||
try {
|
||||
_schedulerPhase = SchedulerPhase.persistentCallbacks;
|
||||
foreach (FrameCallback callback in _persistentCallbacks)
|
||||
_invokeFrameCallback(callback, _currentFrameTimeStamp.Value);
|
||||
|
||||
_schedulerPhase = SchedulerPhase.postFrameCallbacks;
|
||||
var localPostFrameCallbacks = new List<FrameCallback>(_postFrameCallbacks);
|
||||
_postFrameCallbacks.Clear();
|
||||
foreach (FrameCallback callback in localPostFrameCallbacks)
|
||||
_invokeFrameCallback(callback, _currentFrameTimeStamp.Value);
|
||||
}
|
||||
finally {
|
||||
_schedulerPhase = SchedulerPhase.idle;
|
||||
D.assert(() => {
|
||||
if (scheduler_.debugPrintEndFrameBanner)
|
||||
Debug.Log(new string('▀', _debugBanner.Length));
|
||||
_debugBanner = null;
|
||||
return true;
|
||||
});
|
||||
_currentFrameTimeStamp = null;
|
||||
}
|
||||
}
|
||||
|
||||
void _profileFramePostEvent(int frameNumber, FrameTiming frameTiming) {
|
||||
developer_.postEvent("Flutter.Frame", new Hashtable {
|
||||
{"number", frameNumber},
|
||||
{"startTime", frameTiming.timestampInMicroseconds(FramePhase.buildStart)},
|
||||
{"elapsed", (int) (frameTiming.totalSpan.TotalMilliseconds * 1000)},
|
||||
{"build", (int) (frameTiming.buildDuration.TotalMilliseconds * 1000)},
|
||||
{"raster", (int) (frameTiming.rasterDuration.TotalMilliseconds * 1000)},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
static void _debugDescribeTimeStamp(TimeSpan timeStamp, StringBuilder buffer) {
|
||||
if (timeStamp.TotalDays > 0)
|
||||
buffer.AppendFormat("{0}d ", timeStamp.Days);
|
||||
if (timeStamp.TotalHours > 0)
|
||||
buffer.AppendFormat("{0}h ", timeStamp.Hours);
|
||||
if (timeStamp.TotalMinutes > 0)
|
||||
buffer.AppendFormat("{0}m ", timeStamp.Minutes);
|
||||
if (timeStamp.TotalSeconds > 0)
|
||||
buffer.AppendFormat("{0}s ", timeStamp.Seconds);
|
||||
buffer.AppendFormat("{0}", timeStamp.Milliseconds);
|
||||
|
||||
int microseconds = (int) (timeStamp.Ticks % 10000 / 10);
|
||||
if (microseconds > 0)
|
||||
buffer.AppendFormat(".{0}", microseconds.ToString().PadLeft(3, '0'));
|
||||
buffer.Append("ms");
|
||||
}
|
||||
|
||||
void _invokeFrameCallback(FrameCallback callback, TimeSpan timeStamp, string callbackStack = null) {
|
||||
D.assert(callback != null);
|
||||
D.assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
|
||||
D.assert(() => {
|
||||
_FrameCallbackEntry.debugCurrentCallbackStack = callbackStack;
|
||||
return true;
|
||||
});
|
||||
|
||||
try {
|
||||
callback(timeStamp);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
UIWidgetsError.reportError(new UIWidgetsErrorDetails(
|
||||
exception: ex,
|
||||
library: "scheduler library",
|
||||
context: "during a scheduler callback",
|
||||
informationCollector: callbackStack == null
|
||||
? (InformationCollector) null
|
||||
: information => {
|
||||
information.AppendLine(
|
||||
"\nThis exception was thrown in the context of a scheduler callback. " +
|
||||
"When the scheduler callback was _registered_ (as opposed to when the " +
|
||||
"exception was thrown), this was the stack:"
|
||||
);
|
||||
foreach (var line in UIWidgetsError.defaultStackFilter(
|
||||
callbackStack.TrimEnd().Split('\n'))) {
|
||||
information.AppendLine(line);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
D.assert(() => {
|
||||
_FrameCallbackEntry.debugCurrentCallbackStack = null;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static partial class scheduler_ {
|
||||
public static bool defaultSchedulingStrategy(int priority, SchedulerBinding scheduler) {
|
||||
if (scheduler.transientCallbackCount > 0)
|
||||
return priority >= Priority.animation.value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0756240ed259436a82cdc6ee65831a4f
|
||||
timeCreated: 1599458325
|
|
@ -0,0 +1,7 @@
|
|||
namespace Unity.UIWidgets.scheduler2 {
|
||||
public static partial class scheduler_ {
|
||||
public static bool debugPrintBeginFrameBanner = false;
|
||||
public static bool debugPrintEndFrameBanner = false;
|
||||
public static bool debugPrintScheduleFrameStacks = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 486e9bc3d81445fba548c7cddf682b4d
|
||||
timeCreated: 1599458325
|
|
@ -0,0 +1,30 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace Unity.UIWidgets.scheduler2 {
|
||||
public class Priority {
|
||||
Priority(int value) {
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public int value => _value;
|
||||
int _value;
|
||||
|
||||
public static readonly Priority idle = new Priority(0);
|
||||
|
||||
public static readonly Priority animation = new Priority(100000);
|
||||
|
||||
public static readonly Priority touch = new Priority(200000);
|
||||
|
||||
public static readonly int kMaxOffset = 10000;
|
||||
|
||||
public static Priority operator +(Priority it, int offset) {
|
||||
if (Mathf.Abs(offset) > kMaxOffset) {
|
||||
offset = kMaxOffset * (int) Mathf.Sign(offset);
|
||||
}
|
||||
|
||||
return new Priority(it._value + offset);
|
||||
}
|
||||
|
||||
public static Priority operator -(Priority it, int offset) => it + (-offset);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 814da0eaecc042059b36143e73f60f48
|
||||
timeCreated: 1599458325
|
|
@ -0,0 +1,307 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using Unity.UIWidgets.async2;
|
||||
using Unity.UIWidgets.foundation;
|
||||
using Unity.UIWidgets.ui;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.UIWidgets.scheduler2 {
|
||||
public delegate void TickerCallback(TimeSpan elapsed);
|
||||
|
||||
public interface TickerProvider {
|
||||
Ticker createTicker(TickerCallback onTick);
|
||||
}
|
||||
|
||||
public class Ticker : IDisposable {
|
||||
public Ticker(TickerCallback onTick, string debugLabel = null) {
|
||||
D.assert(() => {
|
||||
_debugCreationStack = StackTraceUtility.ExtractStackTrace();
|
||||
return true;
|
||||
});
|
||||
_onTick = onTick;
|
||||
this.debugLabel = debugLabel;
|
||||
}
|
||||
|
||||
TickerFuture _future;
|
||||
|
||||
public bool muted {
|
||||
get => _muted;
|
||||
set {
|
||||
if (value == _muted)
|
||||
return;
|
||||
|
||||
_muted = value;
|
||||
if (value) {
|
||||
unscheduleTick();
|
||||
}
|
||||
else if (shouldScheduleTick) {
|
||||
scheduleTick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _muted = false;
|
||||
|
||||
public bool isTicking {
|
||||
get {
|
||||
if (_future == null)
|
||||
return false;
|
||||
if (muted)
|
||||
return false;
|
||||
if (SchedulerBinding.instance.framesEnabled)
|
||||
return true;
|
||||
if (SchedulerBinding.instance.schedulerPhase != SchedulerPhase.idle)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool isActive {
|
||||
get { return _future != null; }
|
||||
}
|
||||
|
||||
TimeSpan? _startTime;
|
||||
|
||||
public TickerFuture start() {
|
||||
D.assert(() => {
|
||||
if (isActive) {
|
||||
throw new UIWidgetsError(
|
||||
"A ticker was started twice.\nA ticker that is already active cannot be started again without first stopping it.\n" +
|
||||
"The affected ticker was: " + toString(debugIncludeStack: true));
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
D.assert(_startTime == null);
|
||||
_future = new TickerFuture();
|
||||
if (shouldScheduleTick) {
|
||||
scheduleTick();
|
||||
}
|
||||
|
||||
if (SchedulerBinding.instance.schedulerPhase > SchedulerPhase.idle &&
|
||||
SchedulerBinding.instance.schedulerPhase < SchedulerPhase.postFrameCallbacks) {
|
||||
_startTime = SchedulerBinding.instance.currentFrameTimeStamp;
|
||||
}
|
||||
|
||||
return _future;
|
||||
}
|
||||
|
||||
public void stop(bool canceled = false) {
|
||||
if (!isActive)
|
||||
return;
|
||||
|
||||
var localFuture = _future;
|
||||
_future = null;
|
||||
_startTime = null;
|
||||
D.assert(!isActive);
|
||||
|
||||
unscheduleTick();
|
||||
if (canceled) {
|
||||
localFuture._cancel(this);
|
||||
}
|
||||
else {
|
||||
localFuture._complete();
|
||||
}
|
||||
}
|
||||
|
||||
readonly TickerCallback _onTick;
|
||||
|
||||
int? _animationId;
|
||||
|
||||
protected bool scheduled {
|
||||
get => _animationId != null;
|
||||
}
|
||||
|
||||
protected bool shouldScheduleTick => !muted && isActive && !scheduled;
|
||||
|
||||
void _tick(TimeSpan timeStamp) {
|
||||
D.assert(isTicking);
|
||||
D.assert(scheduled);
|
||||
_animationId = null;
|
||||
|
||||
_startTime = _startTime ?? timeStamp;
|
||||
_onTick(timeStamp - _startTime.Value);
|
||||
|
||||
if (shouldScheduleTick)
|
||||
scheduleTick(rescheduling: true);
|
||||
}
|
||||
|
||||
protected void scheduleTick(bool rescheduling = false) {
|
||||
D.assert(!scheduled);
|
||||
D.assert(shouldScheduleTick);
|
||||
_animationId = SchedulerBinding.instance.scheduleFrameCallback(_tick, rescheduling: rescheduling);
|
||||
}
|
||||
|
||||
protected void unscheduleTick() {
|
||||
if (scheduled) {
|
||||
SchedulerBinding.instance.cancelFrameCallbackWithId(_animationId.Value);
|
||||
_animationId = null;
|
||||
}
|
||||
|
||||
D.assert(!shouldScheduleTick);
|
||||
}
|
||||
|
||||
public void absorbTicker(Ticker originalTicker) {
|
||||
D.assert(!isActive);
|
||||
D.assert(_future == null);
|
||||
D.assert(_startTime == null);
|
||||
D.assert(_animationId == null);
|
||||
D.assert((originalTicker._future == null) == (originalTicker._startTime == null),
|
||||
() => "Cannot absorb Ticker after it has been disposed.");
|
||||
if (originalTicker._future != null) {
|
||||
_future = originalTicker._future;
|
||||
_startTime = originalTicker._startTime;
|
||||
if (shouldScheduleTick) {
|
||||
scheduleTick();
|
||||
}
|
||||
|
||||
originalTicker._future = null;
|
||||
originalTicker.unscheduleTick();
|
||||
}
|
||||
|
||||
originalTicker.Dispose();
|
||||
}
|
||||
|
||||
public virtual void Dispose() {
|
||||
if (_future != null) {
|
||||
var localFuture = _future;
|
||||
_future = null;
|
||||
D.assert(!isActive);
|
||||
unscheduleTick();
|
||||
localFuture._cancel(this);
|
||||
}
|
||||
|
||||
D.assert(() => {
|
||||
_startTime = TimeSpan.Zero;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public readonly string debugLabel;
|
||||
internal string _debugCreationStack;
|
||||
|
||||
public override string ToString() {
|
||||
return toString(debugIncludeStack: false);
|
||||
}
|
||||
|
||||
public string toString(bool debugIncludeStack = false) {
|
||||
var buffer = new StringBuilder();
|
||||
buffer.Append(GetType() + "(");
|
||||
D.assert(() => {
|
||||
buffer.Append(debugLabel ?? "");
|
||||
return true;
|
||||
});
|
||||
buffer.Append(')');
|
||||
D.assert(() => {
|
||||
if (debugIncludeStack) {
|
||||
buffer.AppendLine();
|
||||
buffer.AppendLine("The stack trace when the " + GetType() + " was actually created was: ");
|
||||
foreach (var line in UIWidgetsError.defaultStackFilter(
|
||||
_debugCreationStack.TrimEnd().Split('\n'))) {
|
||||
buffer.AppendLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return buffer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class TickerFuture : Future {
|
||||
internal TickerFuture() {
|
||||
}
|
||||
|
||||
public static TickerFuture complete() {
|
||||
var result = new TickerFuture();
|
||||
result._complete();
|
||||
return result;
|
||||
}
|
||||
|
||||
readonly Completer _primaryCompleter = Completer.create();
|
||||
Completer _secondaryCompleter;
|
||||
bool? _completed; // null means unresolved, true means complete, false means canceled
|
||||
|
||||
internal void _complete() {
|
||||
D.assert(_completed == null);
|
||||
_completed = true;
|
||||
_primaryCompleter.complete();
|
||||
_secondaryCompleter?.complete();
|
||||
}
|
||||
|
||||
internal void _cancel(Ticker ticker) {
|
||||
D.assert(_completed == null);
|
||||
_completed = false;
|
||||
_secondaryCompleter?.completeError(new TickerCanceled(ticker));
|
||||
}
|
||||
|
||||
public void whenCompleteOrCancel(VoidCallback callback) {
|
||||
orCancel.then((value) => {
|
||||
callback();
|
||||
return FutureOr.nullValue;
|
||||
}, ex => {
|
||||
callback();
|
||||
return FutureOr.nullValue;
|
||||
});
|
||||
}
|
||||
|
||||
public Future orCancel {
|
||||
get {
|
||||
if (_secondaryCompleter == null) {
|
||||
_secondaryCompleter = Completer.create();
|
||||
if (_completed != null) {
|
||||
if (_completed.Value) {
|
||||
_secondaryCompleter.complete();
|
||||
}
|
||||
else {
|
||||
_secondaryCompleter.completeError(new TickerCanceled());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _secondaryCompleter.future;
|
||||
}
|
||||
}
|
||||
|
||||
// public override Stream asStream() {
|
||||
// return _primaryCompleter.future.asStream();
|
||||
// }
|
||||
|
||||
public override Future catchError(Func<Exception, FutureOr> onError, Func<Exception, bool> test = null) {
|
||||
return _primaryCompleter.future.catchError(onError, test: test);
|
||||
}
|
||||
|
||||
public override Future then(Func<object, FutureOr> onValue, Func<Exception, FutureOr> onError = null) {
|
||||
return _primaryCompleter.future.then(onValue, onError: onError);
|
||||
}
|
||||
|
||||
public override Future timeout(TimeSpan timeLimit, Func<FutureOr> onTimeout = null) {
|
||||
return _primaryCompleter.future.timeout(timeLimit, onTimeout: onTimeout);
|
||||
}
|
||||
|
||||
public override Future whenComplete(Func<object> action) {
|
||||
return _primaryCompleter.future.whenComplete(action);
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"{Diagnostics.describeIdentity(this)}({(_completed == null ? "active" : (_completed.Value ? "complete" : "canceled"))})";
|
||||
}
|
||||
|
||||
|
||||
public class TickerCanceled : Exception {
|
||||
public TickerCanceled(Ticker ticker = null) {
|
||||
this.ticker = ticker;
|
||||
}
|
||||
|
||||
public readonly Ticker ticker;
|
||||
|
||||
public override string ToString() {
|
||||
if (ticker != null) {
|
||||
return "This ticker was canceled: " + ticker;
|
||||
}
|
||||
|
||||
return "The ticker was canceled before the \"orCancel\" property was first used.";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9ee135f21ca64943a5f75a04a0e30cc3
|
||||
timeCreated: 1599458326
|
|
@ -86,6 +86,7 @@ namespace Unity.UIWidgets.ui2 {
|
|||
|
||||
public class Window {
|
||||
internal IntPtr _ptr;
|
||||
internal object _binding;
|
||||
|
||||
internal Window() {
|
||||
_setNeedsReportTimings = Window_setNeedsReportTimings;
|
||||
|
|
|
@ -80,4 +80,22 @@ fml::WeakPtr<ImageDecoder> UIMonoState::GetImageDecoder() const {
|
|||
return image_decoder_;
|
||||
}
|
||||
|
||||
UIWIDGETS_API(void)
|
||||
UIMonoState_scheduleMicrotask(MonoMicrotaskQueue::CallbackFunc callback,
|
||||
Mono_Handle handle) {
|
||||
UIMonoState::Current()->ScheduleMicrotask(callback, handle);
|
||||
}
|
||||
|
||||
UIWIDGETS_API(void)
|
||||
UIMonoState_postTaskForTime(MonoMicrotaskQueue::CallbackFunc callback,
|
||||
Mono_Handle handle, int64_t target_time_nanos) {
|
||||
UIMonoState::Current()->GetTaskRunners().GetUITaskRunner()->PostTaskForTime(
|
||||
[callback, handle]() -> void { callback(handle); },
|
||||
fml::TimePoint::FromEpochDelta(
|
||||
fml::TimeDelta::FromNanoseconds(target_time_nanos)));
|
||||
}
|
||||
|
||||
UIWIDGETS_API(int)
|
||||
UIMonoState_timerMillisecondClock() { return Mono_TimelineGetMicros() / 1000; }
|
||||
|
||||
} // namespace uiwidgets
|
||||
|
|
|
@ -21,6 +21,8 @@ void UIWidgetsSystem::UnregisterPanel(UIWidgetsPanel* panel) {
|
|||
}
|
||||
|
||||
void UIWidgetsSystem::Wait(std::chrono::nanoseconds max_duration) {
|
||||
Update();
|
||||
|
||||
std::chrono::nanoseconds wait_duration =
|
||||
std::max(std::chrono::nanoseconds(0),
|
||||
next_uiwidgets_event_time_ - TimePoint::clock::now());
|
||||
|
@ -48,8 +50,6 @@ void UIWidgetsSystem::VSync() {
|
|||
for (auto* uiwidgets_panel : uiwidgets_panels_) {
|
||||
uiwidgets_panel->ProcessVSync();
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void UIWidgetsSystem::WakeUp() {}
|
||||
|
|
|
@ -67,7 +67,7 @@ class Win32TaskRunner {
|
|||
TaskObservers task_observers_;
|
||||
|
||||
static TaskTimePoint TimePointFromUIWidgetsTime(
|
||||
uint64_t flutter_target_time_nanos);
|
||||
uint64_t uiwidgets_target_time_nanos);
|
||||
};
|
||||
|
||||
} // namespace uiwidgets
|
||||
|
|
Загрузка…
Ссылка в новой задаче