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.foundation;
|
||||||
using Unity.UIWidgets.material;
|
using Unity.UIWidgets.material;
|
||||||
using Unity.UIWidgets.scheduler;
|
using Unity.UIWidgets.scheduler;
|
||||||
|
using Unity.UIWidgets.scheduler2;
|
||||||
using Unity.UIWidgets.ui;
|
using Unity.UIWidgets.ui;
|
||||||
using Unity.UIWidgets.widgets;
|
using Unity.UIWidgets.widgets;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
@ -63,7 +64,7 @@ namespace UIWidgetsGallery.gallery {
|
||||||
this._options = new GalleryOptions(
|
this._options = new GalleryOptions(
|
||||||
theme: GalleryTheme.kLightGalleryTheme,
|
theme: GalleryTheme.kLightGalleryTheme,
|
||||||
textScaleFactor: GalleryTextScaleValue.kAllGalleryTextScaleValues[0],
|
textScaleFactor: GalleryTextScaleValue.kAllGalleryTextScaleValues[0],
|
||||||
timeDilation: SchedulerBinding.instance.timeDilation,
|
timeDilation: scheduler_.timeDilation,
|
||||||
platform: Application.platform,
|
platform: Application.platform,
|
||||||
showPerformanceOverlay: this.widget.enablePerformanceOverlay
|
showPerformanceOverlay: this.widget.enablePerformanceOverlay
|
||||||
);
|
);
|
||||||
|
@ -82,9 +83,9 @@ namespace UIWidgetsGallery.gallery {
|
||||||
this._timeDilationTimer = null;
|
this._timeDilationTimer = null;
|
||||||
if (newOptions.timeDilation > 1.0f) {
|
if (newOptions.timeDilation > 1.0f) {
|
||||||
this._timeDilationTimer = Window.instance.run(new TimeSpan(0, 0, 0, 0, 150),
|
this._timeDilationTimer = Window.instance.run(new TimeSpan(0, 0, 0, 0, 150),
|
||||||
() => { SchedulerBinding.instance.timeDilation = newOptions.timeDilation; });
|
() => { scheduler_.timeDilation = newOptions.timeDilation; });
|
||||||
} else {
|
} else {
|
||||||
SchedulerBinding.instance.timeDilation = newOptions.timeDilation;
|
scheduler_.timeDilation = newOptions.timeDilation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,24 @@ namespace Unity.UIWidgets.async {
|
||||||
get { return this._data.Count; }
|
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() {
|
public override string ToString() {
|
||||||
string s = "";
|
string s = "";
|
||||||
for (int i = 0; i < this._data.Count; ++i) {
|
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 {
|
public bool debugEnabled {
|
||||||
get { return D.debugEnabled; }
|
get { return foundation_.kDebugMode; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() {
|
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() {
|
protected virtual void main() {
|
||||||
|
Debug.Log(Debug.isDebugBuild);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void entryPoint() {
|
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;
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.UIWidgets.foundation {
|
namespace Unity.UIWidgets.foundation {
|
||||||
public class FoundationConstants {
|
public static class foundation_ {
|
||||||
public static bool kReleaseMode = !Debug.isDebugBuild;
|
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));
|
Debug.LogException(new AssertionError(message, ex));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool debugEnabled {
|
[Conditional("UNITY_ASSERTIONS")]
|
||||||
get {
|
|
||||||
#if UIWidgets_DEBUG
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Conditional("UIWidgets_DEBUG")]
|
|
||||||
public static void assert(Func<bool> result, Func<string> message = null) {
|
public static void assert(Func<bool> result, Func<string> message = null) {
|
||||||
if (!result()) {
|
if (!result()) {
|
||||||
throw new AssertionError(message != null ? message() : "");
|
throw new AssertionError(message != null ? message() : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Conditional("UIWidgets_DEBUG")]
|
[Conditional("UNITY_ASSERTIONS")]
|
||||||
public static void assert(bool result, Func<string> message = null) {
|
public static void assert(bool result, Func<string> message = null) {
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new AssertionError(message != null ? message() : "");
|
throw new AssertionError(message != null ? message() : "");
|
||||||
|
@ -49,12 +39,6 @@ namespace Unity.UIWidgets.foundation {
|
||||||
|
|
||||||
public static bool debugPrintRecognizerCallbacksTrace = false;
|
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 debugPaintSizeEnabled = false;
|
||||||
|
|
||||||
public static bool debugRepaintRainbowEnabled = false;
|
public static bool debugRepaintRainbowEnabled = false;
|
||||||
|
|
|
@ -6,7 +6,7 @@ using Unity.UIWidgets.foundation;
|
||||||
using Unity.UIWidgets.gestures;
|
using Unity.UIWidgets.gestures;
|
||||||
using Unity.UIWidgets.painting;
|
using Unity.UIWidgets.painting;
|
||||||
using Unity.UIWidgets.rendering;
|
using Unity.UIWidgets.rendering;
|
||||||
using Unity.UIWidgets.scheduler;
|
using Unity.UIWidgets.scheduler2;
|
||||||
using Unity.UIWidgets.ui;
|
using Unity.UIWidgets.ui;
|
||||||
using Unity.UIWidgets.widgets;
|
using Unity.UIWidgets.widgets;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
@ -621,7 +621,7 @@ namespace Unity.UIWidgets.material {
|
||||||
this._state.interactionTimer?.cancel();
|
this._state.interactionTimer?.cancel();
|
||||||
this._state.interactionTimer = Window.instance.run(
|
this._state.interactionTimer = Window.instance.run(
|
||||||
new TimeSpan(0, 0, 0, 0,
|
new TimeSpan(0, 0, 0, 0,
|
||||||
(int) (_minimumInteractionTimeMilliSeconds * SchedulerBinding.instance.timeDilation)),
|
(int) (_minimumInteractionTimeMilliSeconds * scheduler_.timeDilation)),
|
||||||
() => {
|
() => {
|
||||||
this._state.interactionTimer = null;
|
this._state.interactionTimer = null;
|
||||||
if (!this._active &&
|
if (!this._active &&
|
||||||
|
|
|
@ -4,8 +4,9 @@ using System.Linq;
|
||||||
using RSG;
|
using RSG;
|
||||||
using Unity.UIWidgets.async;
|
using Unity.UIWidgets.async;
|
||||||
using Unity.UIWidgets.foundation;
|
using Unity.UIWidgets.foundation;
|
||||||
using Unity.UIWidgets.scheduler;
|
using Unity.UIWidgets.scheduler2;
|
||||||
using Unity.UIWidgets.ui;
|
using Unity.UIWidgets.ui;
|
||||||
|
using SchedulerBinding = Unity.UIWidgets.scheduler.SchedulerBinding;
|
||||||
|
|
||||||
namespace Unity.UIWidgets.painting {
|
namespace Unity.UIWidgets.painting {
|
||||||
public class ImageInfo : IEquatable<ImageInfo> {
|
public class ImageInfo : IEquatable<ImageInfo> {
|
||||||
|
@ -350,7 +351,7 @@ namespace Unity.UIWidgets.painting {
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan delay = this._frameDuration.Value - (timestamp - this._shownTimestamp.Value);
|
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);
|
this._timer = Window.instance.run(delay, this._scheduleAppFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,10 @@ using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using RSG.Promises;
|
using RSG.Promises;
|
||||||
using Unity.UIWidgets.foundation;
|
using Unity.UIWidgets.foundation;
|
||||||
|
using Unity.UIWidgets.scheduler2;
|
||||||
using Unity.UIWidgets.ui;
|
using Unity.UIWidgets.ui;
|
||||||
using Debug = UnityEngine.Debug;
|
using Debug = UnityEngine.Debug;
|
||||||
|
using FrameCallback = Unity.UIWidgets.ui.FrameCallback;
|
||||||
|
|
||||||
namespace Unity.UIWidgets.scheduler {
|
namespace Unity.UIWidgets.scheduler {
|
||||||
class _FrameCallbackEntry {
|
class _FrameCallbackEntry {
|
||||||
|
@ -173,7 +175,7 @@ namespace Unity.UIWidgets.scheduler {
|
||||||
}
|
}
|
||||||
|
|
||||||
D.assert(() => {
|
D.assert(() => {
|
||||||
if (D.debugPrintScheduleFrameStacks) {
|
if (scheduler_.debugPrintScheduleFrameStacks) {
|
||||||
Debug.LogFormat("scheduleFrame() called. Current phase is {0}.", this.schedulerPhase);
|
Debug.LogFormat("scheduleFrame() called. Current phase is {0}.", this.schedulerPhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +192,7 @@ namespace Unity.UIWidgets.scheduler {
|
||||||
}
|
}
|
||||||
|
|
||||||
D.assert(() => {
|
D.assert(() => {
|
||||||
if (D.debugPrintScheduleFrameStacks) {
|
if (scheduler_.debugPrintScheduleFrameStacks) {
|
||||||
Debug.LogFormat("scheduleForcedFrame() called. Current phase is {0}.", this.schedulerPhase);
|
Debug.LogFormat("scheduleForcedFrame() called. Current phase is {0}.", this.schedulerPhase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +250,7 @@ namespace Unity.UIWidgets.scheduler {
|
||||||
});
|
});
|
||||||
|
|
||||||
D.assert(() => {
|
D.assert(() => {
|
||||||
if (D.debugPrintBeginFrameBanner || D.debugPrintEndFrameBanner) {
|
if (scheduler_.debugPrintBeginFrameBanner || scheduler_.debugPrintEndFrameBanner) {
|
||||||
var frameTimeStampDescription = new StringBuilder();
|
var frameTimeStampDescription = new StringBuilder();
|
||||||
if (rawTimeStamp != null) {
|
if (rawTimeStamp != null) {
|
||||||
_debugDescribeTimeStamp(
|
_debugDescribeTimeStamp(
|
||||||
|
@ -259,7 +261,7 @@ namespace Unity.UIWidgets.scheduler {
|
||||||
|
|
||||||
this._debugBanner =
|
this._debugBanner =
|
||||||
$"▄▄▄▄▄▄▄▄ Frame {this._profileFrameNumber.ToString().PadRight(7)} {frameTimeStampDescription.ToString().PadLeft(18)} ▄▄▄▄▄▄▄▄";
|
$"▄▄▄▄▄▄▄▄ Frame {this._profileFrameNumber.ToString().PadRight(7)} {frameTimeStampDescription.ToString().PadLeft(18)} ▄▄▄▄▄▄▄▄";
|
||||||
if (D.debugPrintBeginFrameBanner) {
|
if (scheduler_.debugPrintBeginFrameBanner) {
|
||||||
Debug.Log(this._debugBanner);
|
Debug.Log(this._debugBanner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,7 +308,7 @@ namespace Unity.UIWidgets.scheduler {
|
||||||
} finally {
|
} finally {
|
||||||
this._schedulerPhase = SchedulerPhase.idle;
|
this._schedulerPhase = SchedulerPhase.idle;
|
||||||
D.assert(() => {
|
D.assert(() => {
|
||||||
if (D.debugPrintEndFrameBanner) {
|
if (scheduler_.debugPrintEndFrameBanner) {
|
||||||
Debug.Log(new string('▀', this._debugBanner.Length));
|
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 {
|
public class Window {
|
||||||
internal IntPtr _ptr;
|
internal IntPtr _ptr;
|
||||||
|
internal object _binding;
|
||||||
|
|
||||||
internal Window() {
|
internal Window() {
|
||||||
_setNeedsReportTimings = Window_setNeedsReportTimings;
|
_setNeedsReportTimings = Window_setNeedsReportTimings;
|
||||||
|
|
|
@ -80,4 +80,22 @@ fml::WeakPtr<ImageDecoder> UIMonoState::GetImageDecoder() const {
|
||||||
return image_decoder_;
|
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
|
} // namespace uiwidgets
|
||||||
|
|
|
@ -21,6 +21,8 @@ void UIWidgetsSystem::UnregisterPanel(UIWidgetsPanel* panel) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIWidgetsSystem::Wait(std::chrono::nanoseconds max_duration) {
|
void UIWidgetsSystem::Wait(std::chrono::nanoseconds max_duration) {
|
||||||
|
Update();
|
||||||
|
|
||||||
std::chrono::nanoseconds wait_duration =
|
std::chrono::nanoseconds wait_duration =
|
||||||
std::max(std::chrono::nanoseconds(0),
|
std::max(std::chrono::nanoseconds(0),
|
||||||
next_uiwidgets_event_time_ - TimePoint::clock::now());
|
next_uiwidgets_event_time_ - TimePoint::clock::now());
|
||||||
|
@ -48,8 +50,6 @@ void UIWidgetsSystem::VSync() {
|
||||||
for (auto* uiwidgets_panel : uiwidgets_panels_) {
|
for (auto* uiwidgets_panel : uiwidgets_panels_) {
|
||||||
uiwidgets_panel->ProcessVSync();
|
uiwidgets_panel->ProcessVSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
Update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIWidgetsSystem::WakeUp() {}
|
void UIWidgetsSystem::WakeUp() {}
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Win32TaskRunner {
|
||||||
TaskObservers task_observers_;
|
TaskObservers task_observers_;
|
||||||
|
|
||||||
static TaskTimePoint TimePointFromUIWidgetsTime(
|
static TaskTimePoint TimePointFromUIWidgetsTime(
|
||||||
uint64_t flutter_target_time_nanos);
|
uint64_t uiwidgets_target_time_nanos);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace uiwidgets
|
} // namespace uiwidgets
|
||||||
|
|
Загрузка…
Ссылка в новой задаче