diff --git a/Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/gallery/app.cs b/Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/gallery/app.cs index fd5a42f1..26047c55 100644 --- a/Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/gallery/app.cs +++ b/Samples/UIWidgetsSamples_2019_4/Assets/UIWidgetsGallery/gallery/app.cs @@ -6,6 +6,7 @@ using Unity.UIWidgets.async; using Unity.UIWidgets.foundation; using Unity.UIWidgets.material; using Unity.UIWidgets.scheduler; +using Unity.UIWidgets.scheduler2; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; using UnityEngine; @@ -63,7 +64,7 @@ namespace UIWidgetsGallery.gallery { this._options = new GalleryOptions( theme: GalleryTheme.kLightGalleryTheme, textScaleFactor: GalleryTextScaleValue.kAllGalleryTextScaleValues[0], - timeDilation: SchedulerBinding.instance.timeDilation, + timeDilation: scheduler_.timeDilation, platform: Application.platform, showPerformanceOverlay: this.widget.enablePerformanceOverlay ); @@ -82,9 +83,9 @@ namespace UIWidgetsGallery.gallery { this._timeDilationTimer = null; if (newOptions.timeDilation > 1.0f) { this._timeDilationTimer = Window.instance.run(new TimeSpan(0, 0, 0, 0, 150), - () => { SchedulerBinding.instance.timeDilation = newOptions.timeDilation; }); + () => { scheduler_.timeDilation = newOptions.timeDilation; }); } else { - SchedulerBinding.instance.timeDilation = newOptions.timeDilation; + scheduler_.timeDilation = newOptions.timeDilation; } } diff --git a/com.unity.uiwidgets/Runtime/async/priority_queue.cs b/com.unity.uiwidgets/Runtime/async/priority_queue.cs index 7d8fcd6c..062b5f43 100644 --- a/com.unity.uiwidgets/Runtime/async/priority_queue.cs +++ b/com.unity.uiwidgets/Runtime/async/priority_queue.cs @@ -68,6 +68,24 @@ namespace Unity.UIWidgets.async { get { return this._data.Count; } } + public bool isEmpty { + get { return count == 0; } + } + + public bool isNotEmpty { + get { return count != 0; } + } + + public T first { + get { + return peek(); + } + } + + public T removeFirst() { + return dequeue(); + } + public override string ToString() { string s = ""; for (int i = 0; i < this._data.Count; ++i) { diff --git a/com.unity.uiwidgets/Runtime/async2.meta b/com.unity.uiwidgets/Runtime/async2.meta new file mode 100644 index 00000000..ec65d160 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0dd4bb15542f4ad9a7bef43d7defab5f +timeCreated: 1599458064 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/future.cs b/com.unity.uiwidgets/Runtime/async2/future.cs new file mode 100644 index 00000000..b63b9760 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/future.cs @@ -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 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 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 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 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(IEnumerable futures, bool eagerError = false, Action cleanUp = null) { + _Future result = new _Future(); + List 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 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())); + } + + values = new List(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 futures) { + var completer = Completer.sync(); + Func onValue = (object value) => { + if (!completer.isCompleted) completer.complete(FutureOr.withValue(value)); + return FutureOr.nullValue; + }; + + Func 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(IEnumerable elements, Func 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 _kTrue = (_) => FutureOr.trueValue; + + public static Future doWhile(Func 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 onValue, Func onError = null); + + public abstract Future catchError(Func onError, Func test = null); + + public abstract Future whenComplete(Func action); + + // public abstract Stream asStream(); + + public abstract Future timeout(TimeSpan timeLimit, Func 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."); + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/future.cs.meta b/com.unity.uiwidgets/Runtime/async2/future.cs.meta new file mode 100644 index 00000000..ccb4621f --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/future.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bdf10194e91e4558bc97ebad8b81d5a0 +timeCreated: 1599458114 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/future_impl.cs b/com.unity.uiwidgets/Runtime/async2/future_impl.cs new file mode 100644 index 00000000..242d2998 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/future_impl.cs @@ -0,0 +1,812 @@ +using System; +using Unity.UIWidgets.foundation; + +namespace Unity.UIWidgets.async2 { + using _FutureOnValue = Func; + using _FutureErrorTest = Func; + using _FutureAction = Func; + + 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 errorCallback; + + _FutureListener(_Future result, Delegate callback, Func errorCallback, int state) { + this.result = result; + this.state = state; + this.callback = callback; + this.errorCallback = errorCallback; + } + + public static _FutureListener then( + _Future result, _FutureOnValue onValue, Func errorCallback) { + return new _FutureListener( + result, onValue, errorCallback, + (errorCallback == null) ? stateThen : stateThenOnerror + ); + } + + public static _FutureListener thenAwait( + _Future result, _FutureOnValue onValue, Func errorCallback) { + return new _FutureListener( + result, onValue, errorCallback, + ((errorCallback == null) ? stateThen : stateThenOnerror) | stateIsAwait + ); + } + + public static _FutureListener catchError(_Future result, Func 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 _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 f, Func 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 onError, Func 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 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 asStream() => new Stream.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 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 _registerHandler(Func handler, Zone zone) { + var callback = zone.registerCallback(() => handler()); + return () => callback(); + } + + internal static Func _registerHandler(Func handler, Zone zone) { + var callback = zone.registerCallback(() => handler()); + return () => (FutureOr) callback(); + } + + internal static Func _registerUnaryHandler(Func handler, Zone zone) { + var callback = zone.registerUnaryCallback(arg => handler(arg)); + return arg => (FutureOr) callback(arg); + } + + internal static Func _registerUnaryHandler(Func handler, Zone zone) { + var callback = zone.registerUnaryCallback(arg => handler((Exception) arg)); + return arg => (bool) callback(arg); + } + + internal static Func _registerErrorHandler(Func errorHandler, + Zone zone) { + var callback = zone.registerUnaryCallback(arg => errorHandler((Exception) arg)); + return arg => (FutureOr) callback(arg); + } + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/future_impl.cs.meta b/com.unity.uiwidgets/Runtime/async2/future_impl.cs.meta new file mode 100644 index 00000000..3b3b0323 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/future_impl.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4072406cc46448f99159c137b458841e +timeCreated: 1599458114 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/schedule_microtask.cs b/com.unity.uiwidgets/Runtime/async2/schedule_microtask.cs new file mode 100644 index 00000000..27c92a1e --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/schedule_microtask.cs @@ -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 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); + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/schedule_microtask.cs.meta b/com.unity.uiwidgets/Runtime/async2/schedule_microtask.cs.meta new file mode 100644 index 00000000..552fd0c4 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/schedule_microtask.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e9d7bdadaa9c46e0b96ba19f4261185a +timeCreated: 1599458114 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/timer.cs b/com.unity.uiwidgets/Runtime/async2/timer.cs new file mode 100644 index 00000000..5afd040a --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/timer.cs @@ -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); + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/timer.cs.meta b/com.unity.uiwidgets/Runtime/async2/timer.cs.meta new file mode 100644 index 00000000..2a1fd3fa --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/timer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dc44ad1967714937b709d5f22c6b3bec +timeCreated: 1599458114 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/zone.cs b/com.unity.uiwidgets/Runtime/async2/zone.cs new file mode 100644 index 00000000..f4fdd148 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/zone.cs @@ -0,0 +1,1050 @@ +using System; +using System.Collections; +using System.Runtime.ExceptionServices; +using System.Runtime.Serialization; +using Unity.UIWidgets.foundation; +using UnityEngine; + +namespace Unity.UIWidgets.async2 { + public delegate object ZoneCallback(); + + public delegate object ZoneUnaryCallback(object arg); + + public delegate object ZoneBinaryCallback(object arg1, object arg2); + + + public delegate void HandleUncaughtErrorHandler(Zone self, ZoneDelegate parent, Zone zone, Exception error); + + public delegate object RunHandler( + Zone self, ZoneDelegate parent, Zone zone, ZoneCallback f); + + public delegate object RunUnaryHandler( + Zone self, ZoneDelegate parent, Zone zone, ZoneUnaryCallback f, object arg); + + public delegate object RunBinaryHandler(Zone self, ZoneDelegate parent, + Zone zone, ZoneBinaryCallback f, object arg1, object arg2); + + + public delegate ZoneCallback RegisterCallbackHandler( + Zone self, ZoneDelegate parent, Zone zone, ZoneCallback f); + + public delegate ZoneUnaryCallback RegisterUnaryCallbackHandler( + Zone self, ZoneDelegate parent, Zone zone, ZoneUnaryCallback f); + + public delegate ZoneBinaryCallback RegisterBinaryCallbackHandler(Zone self, + ZoneDelegate parent, Zone zone, ZoneBinaryCallback f); + + + public delegate AsyncError ErrorCallbackHandler(Zone self, ZoneDelegate parent, + Zone zone, Exception error); + + public delegate void ScheduleMicrotaskHandler( + Zone self, ZoneDelegate parent, Zone zone, ZoneCallback f); + + public delegate Timer CreateTimerHandler( + Zone self, ZoneDelegate parent, Zone zone, TimeSpan duration, ZoneCallback f); + + public delegate Timer CreatePeriodicTimerHandler(Zone self, ZoneDelegate parent, + Zone zone, TimeSpan period, ZoneUnaryCallback f); + + public delegate void PrintHandler( + Zone self, ZoneDelegate parent, Zone zone, string line); + + public delegate Zone ForkHandler(Zone self, ZoneDelegate parent, Zone zone, + ZoneSpecification specification, Hashtable zoneValues); + + public class AsyncError : Exception { + public AsyncError() { + } + + protected AsyncError(SerializationInfo info, StreamingContext context) : base(info, context) { + } + + public AsyncError(string message) : base(message) { + } + + public AsyncError(string message, Exception innerException) : base(message, innerException) { + } + + public AsyncError(Exception innerException) : base(null, innerException) { + } + } + + struct _ZoneFunction where T : Delegate { + public readonly _Zone zone; + public readonly T function; + + internal _ZoneFunction(_Zone zone, T function) { + this.zone = zone; + this.function = function; + } + } + + struct _RunNullaryZoneFunction { + public readonly _Zone zone; + public readonly RunHandler function; + + internal _RunNullaryZoneFunction(_Zone zone, RunHandler function) { + this.zone = zone; + this.function = function; + } + } + + struct _RunUnaryZoneFunction { + public readonly _Zone zone; + public readonly RunUnaryHandler function; + + internal _RunUnaryZoneFunction(_Zone zone, RunUnaryHandler function) { + this.zone = zone; + this.function = function; + } + } + + struct _RunBinaryZoneFunction { + public readonly _Zone zone; + public readonly RunBinaryHandler function; + + internal _RunBinaryZoneFunction(_Zone zone, RunBinaryHandler function) { + this.zone = zone; + this.function = function; + } + } + + struct _RegisterNullaryZoneFunction { + public readonly _Zone zone; + public readonly RegisterCallbackHandler function; + + internal _RegisterNullaryZoneFunction(_Zone zone, RegisterCallbackHandler function) { + this.zone = zone; + this.function = function; + } + } + + struct _RegisterUnaryZoneFunction { + public readonly _Zone zone; + public readonly RegisterUnaryCallbackHandler function; + + internal _RegisterUnaryZoneFunction(_Zone zone, RegisterUnaryCallbackHandler function) { + this.zone = zone; + this.function = function; + } + } + + struct _RegisterBinaryZoneFunction { + public readonly _Zone zone; + public readonly RegisterBinaryCallbackHandler function; + + internal _RegisterBinaryZoneFunction(_Zone zone, RegisterBinaryCallbackHandler function) { + this.zone = zone; + this.function = function; + } + } + + public class ZoneSpecification { + public ZoneSpecification( + HandleUncaughtErrorHandler handleUncaughtError = null, + RunHandler run = null, + RunUnaryHandler runUnary = null, + RunBinaryHandler runBinary = null, + RegisterCallbackHandler registerCallback = null, + RegisterUnaryCallbackHandler registerUnaryCallback = null, + RegisterBinaryCallbackHandler registerBinaryCallback = null, + ErrorCallbackHandler errorCallback = null, + ScheduleMicrotaskHandler scheduleMicrotask = null, + CreateTimerHandler createTimer = null, + CreatePeriodicTimerHandler createPeriodicTimer = null, + PrintHandler print = null, + ForkHandler fork = null) { + this.handleUncaughtError = handleUncaughtError; + this.run = run; + this.runUnary = runUnary; + this.runBinary = runBinary; + this.registerCallback = registerCallback; + this.registerUnaryCallback = registerUnaryCallback; + this.registerBinaryCallback = registerBinaryCallback; + this.errorCallback = errorCallback; + this.scheduleMicrotask = scheduleMicrotask; + this.createTimer = createTimer; + this.createPeriodicTimer = createPeriodicTimer; + this.print = print; + this.fork = fork; + } + + public static ZoneSpecification from( + ZoneSpecification other, + HandleUncaughtErrorHandler handleUncaughtError = null, + RunHandler run = null, + RunUnaryHandler runUnary = null, + RunBinaryHandler runBinary = null, + RegisterCallbackHandler registerCallback = null, + RegisterUnaryCallbackHandler registerUnaryCallback = null, + RegisterBinaryCallbackHandler registerBinaryCallback = null, + ErrorCallbackHandler errorCallback = null, + ScheduleMicrotaskHandler scheduleMicrotask = null, + CreateTimerHandler createTimer = null, + CreatePeriodicTimerHandler createPeriodicTimer = null, + PrintHandler print = null, + ForkHandler fork = null) { + return new ZoneSpecification( + handleUncaughtError: handleUncaughtError ?? other.handleUncaughtError, + run: run ?? other.run, + runUnary: runUnary ?? other.runUnary, + runBinary: runBinary ?? other.runBinary, + registerCallback: registerCallback ?? other.registerCallback, + registerUnaryCallback: + registerUnaryCallback ?? other.registerUnaryCallback, + registerBinaryCallback: + registerBinaryCallback ?? other.registerBinaryCallback, + errorCallback: errorCallback ?? other.errorCallback, + scheduleMicrotask: scheduleMicrotask ?? other.scheduleMicrotask, + createTimer: createTimer ?? other.createTimer, + createPeriodicTimer: createPeriodicTimer ?? other.createPeriodicTimer, + print: print ?? other.print, + fork: fork ?? other.fork + ); + } + + public HandleUncaughtErrorHandler handleUncaughtError { get; } + public RunHandler run { get; } + public RunUnaryHandler runUnary { get; } + public RunBinaryHandler runBinary { get; } + public RegisterCallbackHandler registerCallback { get; } + public RegisterUnaryCallbackHandler registerUnaryCallback { get; } + public RegisterBinaryCallbackHandler registerBinaryCallback { get; } + public ErrorCallbackHandler errorCallback { get; } + public ScheduleMicrotaskHandler scheduleMicrotask { get; } + public CreateTimerHandler createTimer { get; } + public CreatePeriodicTimerHandler createPeriodicTimer { get; } + public PrintHandler print { get; } + public ForkHandler fork { get; } + } + + + public interface ZoneDelegate { + void handleUncaughtError(Zone zone, Exception error); + + object run(Zone zone, ZoneCallback f); + object runUnary(Zone zone, ZoneUnaryCallback f, object arg); + object runBinary(Zone zone, ZoneBinaryCallback f, object arg1, object arg2); + + ZoneCallback registerCallback(Zone zone, ZoneCallback f); + ZoneUnaryCallback registerUnaryCallback(Zone zone, ZoneUnaryCallback f); + ZoneBinaryCallback registerBinaryCallback(Zone zone, ZoneBinaryCallback f); + + AsyncError errorCallback(Zone zone, Exception error); + void scheduleMicrotask(Zone zone, ZoneCallback f); + Timer createTimer(Zone zone, TimeSpan duration, ZoneCallback f); + Timer createPeriodicTimer(Zone zone, TimeSpan period, ZoneUnaryCallback f); + void print(Zone zone, string line); + Zone fork(Zone zone, ZoneSpecification specification, Hashtable zoneValues); + } + + public abstract class Zone { + protected Zone() { + } + + public static readonly Zone root = async_._rootZone; + + internal static Zone _current = async_._rootZone; + public static Zone current => _current; + + public abstract void handleUncaughtError(Exception error); + public abstract Zone parent { get; } + public abstract Zone errorZone { get; } + public abstract bool inSameErrorZone(Zone otherZone); + public abstract Zone fork(ZoneSpecification specification = null, Hashtable zoneValues = null); + + public abstract object run(ZoneCallback action); + public abstract object runUnary(ZoneUnaryCallback action, object argument); + public abstract object runBinary(ZoneBinaryCallback action, object argument1, object argument2); + public abstract object runGuarded(ZoneCallback action); + public abstract object runUnaryGuarded(ZoneUnaryCallback action, object argument); + public abstract object runBinaryGuarded(ZoneBinaryCallback action, object argument1, object argument2); + + public abstract ZoneCallback registerCallback(ZoneCallback callback); + public abstract ZoneUnaryCallback registerUnaryCallback(ZoneUnaryCallback callback); + public abstract ZoneBinaryCallback registerBinaryCallback(ZoneBinaryCallback fcallback); + + public abstract ZoneCallback bindCallback(ZoneCallback callback); + public abstract ZoneUnaryCallback bindUnaryCallback(ZoneUnaryCallback callback); + public abstract ZoneBinaryCallback bindBinaryCallback(ZoneBinaryCallback callback); + public abstract ZoneCallback bindCallbackGuarded(ZoneCallback callback); + public abstract ZoneUnaryCallback bindUnaryCallbackGuarded(ZoneUnaryCallback callback); + public abstract ZoneBinaryCallback bindBinaryCallbackGuarded(ZoneBinaryCallback callback); + + public abstract AsyncError errorCallback(Exception error); + public abstract void scheduleMicrotask(ZoneCallback callback); + public abstract Timer createTimer(TimeSpan duration, ZoneCallback callback); + public abstract Timer createPeriodicTimer(TimeSpan period, ZoneUnaryCallback callback); + public abstract void print(string line); + + internal static Zone _enter(Zone zone) { + D.assert(zone != null); + D.assert(!ReferenceEquals(zone, _current)); + Zone previous = _current; + _current = zone; + return previous; + } + + internal static void _leave(Zone previous) { + D.assert(previous != null); + _current = previous; + } + + public abstract object this[object key] { get; } + } + + public static partial class async_ { + internal static ZoneDelegate _parentDelegate(_Zone zone) { + if (zone.parent == null) return null; + return zone._parent._delegate; + } + } + + class _ZoneDelegate : ZoneDelegate { + readonly _Zone _delegationTarget; + + internal _ZoneDelegate(_Zone delegationTarget) { + _delegationTarget = delegationTarget; + } + + public void handleUncaughtError(Zone zone, Exception error) { + var implementation = _delegationTarget._handleUncaughtError; + _Zone implZone = implementation.zone; + HandleUncaughtErrorHandler handler = implementation.function; + handler(implZone, async_._parentDelegate(implZone), zone, error); + } + + public object run(Zone zone, ZoneCallback f) { + var implementation = _delegationTarget._run; + _Zone implZone = implementation.zone; + RunHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, f); + } + + public object runUnary(Zone zone, ZoneUnaryCallback f, object arg) { + var implementation = _delegationTarget._runUnary; + _Zone implZone = implementation.zone; + RunUnaryHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, f, arg); + } + + public object runBinary(Zone zone, ZoneBinaryCallback f, object arg1, object arg2) { + var implementation = _delegationTarget._runBinary; + _Zone implZone = implementation.zone; + RunBinaryHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, f, arg1, arg2); + } + + public ZoneCallback registerCallback(Zone zone, ZoneCallback f) { + var implementation = _delegationTarget._registerCallback; + _Zone implZone = implementation.zone; + RegisterCallbackHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, f); + } + + public ZoneUnaryCallback registerUnaryCallback(Zone zone, ZoneUnaryCallback f) { + var implementation = _delegationTarget._registerUnaryCallback; + _Zone implZone = implementation.zone; + RegisterUnaryCallbackHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, f); + } + + public ZoneBinaryCallback registerBinaryCallback(Zone zone, ZoneBinaryCallback f) { + var implementation = _delegationTarget._registerBinaryCallback; + _Zone implZone = implementation.zone; + RegisterBinaryCallbackHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, f); + } + + public AsyncError errorCallback(Zone zone, Exception error) { + if (error == null) + throw new ArgumentNullException(nameof(error)); + + var implementation = _delegationTarget._errorCallback; + _Zone implZone = implementation.zone; + if (ReferenceEquals(implZone, async_._rootZone)) return null; + ErrorCallbackHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, error); + } + + public void scheduleMicrotask(Zone zone, ZoneCallback f) { + var implementation = _delegationTarget._scheduleMicrotask; + _Zone implZone = implementation.zone; + ScheduleMicrotaskHandler handler = implementation.function; + handler(implZone, async_._parentDelegate(implZone), zone, f); + } + + public Timer createTimer(Zone zone, TimeSpan duration, ZoneCallback f) { + var implementation = _delegationTarget._createTimer; + _Zone implZone = implementation.zone; + CreateTimerHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, duration, f); + } + + public Timer createPeriodicTimer(Zone zone, TimeSpan period, ZoneUnaryCallback f) { + var implementation = _delegationTarget._createPeriodicTimer; + _Zone implZone = implementation.zone; + CreatePeriodicTimerHandler handler = implementation.function; + return handler(implZone, async_._parentDelegate(implZone), zone, period, f); + } + + public void print(Zone zone, string line) { + var implementation = _delegationTarget._print; + _Zone implZone = implementation.zone; + PrintHandler handler = implementation.function; + handler(implZone, async_._parentDelegate(implZone), zone, line); + } + + public Zone fork(Zone zone, ZoneSpecification specification, Hashtable zoneValues) { + var implementation = _delegationTarget._fork; + _Zone implZone = implementation.zone; + ForkHandler handler = implementation.function; + return handler( + implZone, async_._parentDelegate(implZone), zone, specification, zoneValues); + } + } + + abstract class _Zone : Zone { + protected _Zone() { + } + + internal abstract _RunNullaryZoneFunction _run { get; } + internal abstract _RunUnaryZoneFunction _runUnary { get; } + internal abstract _RunBinaryZoneFunction _runBinary { get; } + internal abstract _RegisterNullaryZoneFunction _registerCallback { get; } + internal abstract _RegisterUnaryZoneFunction _registerUnaryCallback { get; } + internal abstract _RegisterBinaryZoneFunction _registerBinaryCallback { get; } + internal abstract _ZoneFunction _errorCallback { get; } + internal abstract _ZoneFunction _scheduleMicrotask { get; } + internal abstract _ZoneFunction _createTimer { get; } + internal abstract _ZoneFunction _createPeriodicTimer { get; } + internal abstract _ZoneFunction _print { get; } + internal abstract _ZoneFunction _fork { get; } + internal abstract _ZoneFunction _handleUncaughtError { get; } + internal abstract ZoneDelegate _delegate { get; } + internal abstract Hashtable _map { get; } + internal abstract _Zone _parent { get; } + public override Zone parent => _parent; + + public override bool inSameErrorZone(Zone otherZone) { + return ReferenceEquals(this, otherZone) || + ReferenceEquals(errorZone, otherZone.errorZone); + } + } + + class _CustomZone : _Zone { + internal override _RunNullaryZoneFunction _run { get; } + internal override _RunUnaryZoneFunction _runUnary { get; } + internal override _RunBinaryZoneFunction _runBinary { get; } + internal override _RegisterNullaryZoneFunction _registerCallback { get; } + internal override _RegisterUnaryZoneFunction _registerUnaryCallback { get; } + internal override _RegisterBinaryZoneFunction _registerBinaryCallback { get; } + internal override _ZoneFunction _errorCallback { get; } + internal override _ZoneFunction _scheduleMicrotask { get; } + internal override _ZoneFunction _createTimer { get; } + internal override _ZoneFunction _createPeriodicTimer { get; } + internal override _ZoneFunction _print { get; } + internal override _ZoneFunction _fork { get; } + internal override _ZoneFunction _handleUncaughtError { get; } + + ZoneDelegate _delegateCache; + internal override _Zone _parent { get; } + + internal override Hashtable _map { get; } + + internal override ZoneDelegate _delegate { + get { + if (_delegateCache != null) return _delegateCache; + _delegateCache = new _ZoneDelegate(this); + return _delegateCache; + } + } + + internal _CustomZone(_Zone parent, ZoneSpecification specification, Hashtable map) { + _parent = parent; + _map = map; + + _run = (specification.run != null) + ? new _RunNullaryZoneFunction(this, specification.run) + : parent._run; + _runUnary = (specification.runUnary != null) + ? new _RunUnaryZoneFunction(this, specification.runUnary) + : parent._runUnary; + _runBinary = (specification.runBinary != null) + ? new _RunBinaryZoneFunction(this, specification.runBinary) + : parent._runBinary; + _registerCallback = (specification.registerCallback != null) + ? new _RegisterNullaryZoneFunction(this, specification.registerCallback) + : parent._registerCallback; + _registerUnaryCallback = (specification.registerUnaryCallback != null) + ? new _RegisterUnaryZoneFunction( + this, specification.registerUnaryCallback) + : parent._registerUnaryCallback; + _registerBinaryCallback = (specification.registerBinaryCallback != null) + ? new _RegisterBinaryZoneFunction( + this, specification.registerBinaryCallback) + : parent._registerBinaryCallback; + _errorCallback = (specification.errorCallback != null) + ? new _ZoneFunction( + this, specification.errorCallback) + : parent._errorCallback; + _scheduleMicrotask = (specification.scheduleMicrotask != null) + ? new _ZoneFunction( + this, specification.scheduleMicrotask) + : parent._scheduleMicrotask; + _createTimer = (specification.createTimer != null) + ? new _ZoneFunction(this, specification.createTimer) + : parent._createTimer; + _createPeriodicTimer = (specification.createPeriodicTimer != null) + ? new _ZoneFunction( + this, specification.createPeriodicTimer) + : parent._createPeriodicTimer; + _print = (specification.print != null) + ? new _ZoneFunction(this, specification.print) + : parent._print; + _fork = (specification.fork != null) + ? new _ZoneFunction(this, specification.fork) + : parent._fork; + _handleUncaughtError = (specification.handleUncaughtError != null) + ? new _ZoneFunction( + this, specification.handleUncaughtError) + : parent._handleUncaughtError; + } + + public override Zone errorZone => _handleUncaughtError.zone; + + public override object runGuarded(ZoneCallback f) { + try { + return run(f); + } + catch (Exception e) { + handleUncaughtError(e); + return null; + } + } + + public override object runUnaryGuarded(ZoneUnaryCallback f, object arg) { + try { + return runUnary(f, arg); + } + catch (Exception e) { + handleUncaughtError(e); + return null; + } + } + + public override object runBinaryGuarded(ZoneBinaryCallback f, object arg1, object arg2) { + try { + return runBinary(f, arg1, arg2); + } + catch (Exception e) { + handleUncaughtError(e); + return null; + } + } + + public override ZoneCallback bindCallback(ZoneCallback f) { + var registered = registerCallback(f); + return () => run(registered); + } + + public override ZoneUnaryCallback bindUnaryCallback(ZoneUnaryCallback f) { + var registered = registerUnaryCallback(f); + return (arg) => runUnary(registered, arg); + } + + public override ZoneBinaryCallback bindBinaryCallback(ZoneBinaryCallback f) { + var registered = registerBinaryCallback(f); + return (arg1, arg2) => runBinary(registered, arg1, arg2); + } + + public override ZoneCallback bindCallbackGuarded(ZoneCallback f) { + var registered = registerCallback(f); + return () => runGuarded(registered); + } + + public override ZoneUnaryCallback bindUnaryCallbackGuarded(ZoneUnaryCallback f) { + var registered = registerUnaryCallback(f); + return (arg) => runUnaryGuarded(registered, arg); + } + + public override ZoneBinaryCallback bindBinaryCallbackGuarded(ZoneBinaryCallback f) { + var registered = registerBinaryCallback(f); + return (arg1, arg2) => runBinaryGuarded(registered, arg1, arg2); + } + + public override object this[object key] { + get { + var result = _map[key]; + if (result != null || _map.ContainsKey(key)) return result; + // If we are not the root zone, look up in the parent zone. + if (parent != null) { + // We do not optimize for repeatedly looking up a key which isn't + // there. That would require storing the key and keeping it alive. + // Copying the key/value from the parent does not keep any new values + // alive. + var value = parent[key]; + if (value != null) { + _map[key] = value; + } + + return value; + } + + D.assert(this == async_._rootZone); + return null; + } + } + + public override void handleUncaughtError(Exception error) { + var implementation = _handleUncaughtError; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + HandleUncaughtErrorHandler handler = implementation.function; + handler(implementation.zone, parentDelegate, this, error); + } + + public override Zone fork(ZoneSpecification specification = null, Hashtable zoneValues = null) { + var implementation = _fork; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + ForkHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, specification, zoneValues); + } + + public override object run(ZoneCallback f) { + var implementation = _run; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + RunHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, f); + } + + public override object runUnary(ZoneUnaryCallback f, object arg) { + var implementation = _runUnary; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + RunUnaryHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, f, arg); + } + + public override object runBinary(ZoneBinaryCallback f, object arg1, object arg2) { + var implementation = _runBinary; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + RunBinaryHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, f, arg1, arg2); + } + + public override ZoneCallback registerCallback(ZoneCallback callback) { + var implementation = _registerCallback; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + RegisterCallbackHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, callback); + } + + public override ZoneUnaryCallback registerUnaryCallback(ZoneUnaryCallback callback) { + var implementation = _registerUnaryCallback; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + RegisterUnaryCallbackHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, callback); + } + + public override ZoneBinaryCallback registerBinaryCallback(ZoneBinaryCallback callback) { + var implementation = _registerBinaryCallback; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + RegisterBinaryCallbackHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, callback); + } + + public override AsyncError errorCallback(Exception error) { + if (error == null) + throw new ArgumentNullException(nameof(error)); + var implementation = _errorCallback; + _Zone implementationZone = implementation.zone; + if (ReferenceEquals(implementationZone, async_._rootZone)) return null; + ZoneDelegate parentDelegate = async_._parentDelegate(implementationZone); + ErrorCallbackHandler handler = implementation.function; + return handler(implementationZone, parentDelegate, this, error); + } + + public override void scheduleMicrotask(ZoneCallback f) { + var implementation = _scheduleMicrotask; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + ScheduleMicrotaskHandler handler = implementation.function; + handler(implementation.zone, parentDelegate, this, f); + } + + public override Timer createTimer(TimeSpan duration, ZoneCallback f) { + var implementation = _createTimer; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + CreateTimerHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, duration, f); + } + + public override Timer createPeriodicTimer(TimeSpan duration, ZoneUnaryCallback f) { + var implementation = _createPeriodicTimer; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + CreatePeriodicTimerHandler handler = implementation.function; + return handler(implementation.zone, parentDelegate, this, duration, f); + } + + public override void print(String line) { + var implementation = _print; + ZoneDelegate parentDelegate = async_._parentDelegate(implementation.zone); + PrintHandler handler = implementation.function; + handler(implementation.zone, parentDelegate, this, line); + } + } + + public static partial class async_ { + public static void _rootHandleUncaughtError( + Zone self, ZoneDelegate parent, Zone zone, Exception error) { + if (error == null) + error = new ArgumentNullException(nameof(error)); + + async_._schedulePriorityAsyncCallback(() => { + _rethrow(error); + return null; + }); + } + + internal static void _rethrow(Exception ex) { + ExceptionDispatchInfo.Capture(ex).Throw(); + } + + internal static object _rootRun(Zone self, ZoneDelegate parent, Zone zone, ZoneCallback f) { + if (Zone._current == zone) return f(); + + Zone old = Zone._enter(zone); + try { + return f(); + } + finally { + Zone._leave(old); + } + } + + internal static object _rootRunUnary( + Zone self, ZoneDelegate parent, Zone zone, ZoneUnaryCallback f, object arg) { + if (Zone._current == zone) return f(arg); + + Zone old = Zone._enter(zone); + try { + return f(arg); + } + finally { + Zone._leave(old); + } + } + + internal static object _rootRunBinary(Zone self, ZoneDelegate parent, Zone zone, + ZoneBinaryCallback f, object arg1, object arg2) { + if (Zone._current == zone) return f(arg1, arg2); + + Zone old = Zone._enter(zone); + try { + return f(arg1, arg2); + } + finally { + Zone._leave(old); + } + } + + internal static ZoneCallback _rootRegisterCallback( + Zone self, ZoneDelegate parent, Zone zone, ZoneCallback f) { + return f; + } + + internal static ZoneUnaryCallback _rootRegisterUnaryCallback( + Zone self, ZoneDelegate parent, Zone zone, ZoneUnaryCallback f) { + return f; + } + + internal static ZoneBinaryCallback _rootRegisterBinaryCallback( + Zone self, ZoneDelegate parent, Zone zone, ZoneBinaryCallback f) { + return f; + } + + internal static AsyncError _rootErrorCallback(Zone self, ZoneDelegate parent, Zone zone, Exception error) => + null; + + internal static void _rootScheduleMicrotask( + Zone self, ZoneDelegate parent, Zone zone, ZoneCallback f) { + if (!ReferenceEquals(_rootZone, zone)) { + bool hasErrorHandler = !_rootZone.inSameErrorZone(zone); + if (hasErrorHandler) { + f = zone.bindCallbackGuarded(f); + } + else { + f = zone.bindCallback(f); + } + } + + _scheduleAsyncCallback(f); + } + + internal static Timer _rootCreateTimer(Zone self, ZoneDelegate parent, Zone zone, + TimeSpan duration, ZoneCallback callback) { + if (!ReferenceEquals(_rootZone, zone)) { + callback = zone.bindCallback(callback); + } + + return Timer._createTimer(duration, callback); + } + + internal static Timer _rootCreatePeriodicTimer(Zone self, ZoneDelegate parent, Zone zone, + TimeSpan duration, ZoneUnaryCallback callback) { + if (!ReferenceEquals(_rootZone, zone)) { + callback = zone.bindUnaryCallback(callback); + } + + return Timer._createPeriodicTimer(duration, callback); + } + + internal static void _rootPrint(Zone self, ZoneDelegate parent, Zone zone, string line) { + Debug.Log(line); + } + + internal static Zone _rootFork(Zone self, ZoneDelegate parent, Zone zone, + ZoneSpecification specification, Hashtable zoneValues) { + if (specification == null) { + specification = new ZoneSpecification(); + } + + Hashtable valueMap; + if (zoneValues == null) { + valueMap = ((_Zone) zone)._map; + } + else { + valueMap = new Hashtable(zoneValues); + } + + return new _CustomZone((_Zone) zone, specification, valueMap); + } + } + + class _RootZone : _Zone { + internal override _RunNullaryZoneFunction _run => + new _RunNullaryZoneFunction(async_._rootZone, async_._rootRun); + + internal override _RunUnaryZoneFunction _runUnary => + new _RunUnaryZoneFunction(async_._rootZone, async_._rootRunUnary); + + internal override _RunBinaryZoneFunction _runBinary => + new _RunBinaryZoneFunction(async_._rootZone, async_._rootRunBinary); + + internal override _RegisterNullaryZoneFunction _registerCallback => + new _RegisterNullaryZoneFunction(async_._rootZone, async_._rootRegisterCallback); + + internal override _RegisterUnaryZoneFunction _registerUnaryCallback => + new _RegisterUnaryZoneFunction(async_._rootZone, async_._rootRegisterUnaryCallback); + + internal override _RegisterBinaryZoneFunction _registerBinaryCallback => + new _RegisterBinaryZoneFunction(async_._rootZone, async_._rootRegisterBinaryCallback); + + internal override _ZoneFunction _errorCallback => + new _ZoneFunction(async_._rootZone, async_._rootErrorCallback); + + internal override _ZoneFunction _scheduleMicrotask => + new _ZoneFunction(async_._rootZone, async_._rootScheduleMicrotask); + + internal override _ZoneFunction _createTimer => + new _ZoneFunction(async_._rootZone, async_._rootCreateTimer); + + internal override _ZoneFunction _createPeriodicTimer => + new _ZoneFunction(async_._rootZone, async_._rootCreatePeriodicTimer); + + internal override _ZoneFunction _print => + new _ZoneFunction(async_._rootZone, async_._rootPrint); + + internal override _ZoneFunction _fork => + new _ZoneFunction(async_._rootZone, async_._rootFork); + + internal override _ZoneFunction _handleUncaughtError => + new _ZoneFunction( + async_._rootZone, async_._rootHandleUncaughtError); + + internal override _Zone _parent => null; + + internal override Hashtable _map => _rootMap; + static readonly Hashtable _rootMap = new Hashtable(); + + static ZoneDelegate _rootDelegate; + + internal override ZoneDelegate _delegate { + get { + if (_rootDelegate != null) return _rootDelegate; + return _rootDelegate = new _ZoneDelegate(this); + } + } + + public override Zone errorZone => this; + + public override object runGuarded(ZoneCallback f) { + try { + if (ReferenceEquals(async_._rootZone, Zone._current)) { + return f(); + } + + return async_._rootRun(null, null, this, f); + } + catch (Exception e) { + handleUncaughtError(e); + return null; + } + } + + public override object runUnaryGuarded(ZoneUnaryCallback f, object arg) { + try { + if (ReferenceEquals(async_._rootZone, Zone._current)) { + return f(arg); + } + + return async_._rootRunUnary(null, null, this, f, arg); + } + catch (Exception e) { + handleUncaughtError(e); + return null; + } + } + + public override object runBinaryGuarded(ZoneBinaryCallback f, object arg1, object arg2) { + try { + if (ReferenceEquals(async_._rootZone, Zone._current)) { + return f(arg1, arg2); + } + + return async_._rootRunBinary(null, null, this, f, arg1, arg2); + } + catch (Exception e) { + handleUncaughtError(e); + return null; + } + } + + public override ZoneCallback bindCallback(ZoneCallback f) { + return () => run(f); + } + + public override ZoneUnaryCallback bindUnaryCallback(ZoneUnaryCallback f) { + return (arg) => runUnary(f, arg); + } + + public override ZoneBinaryCallback bindBinaryCallback(ZoneBinaryCallback f) { + return (arg1, arg2) => runBinary(f, arg1, arg2); + } + + public override ZoneCallback bindCallbackGuarded(ZoneCallback f) { + return () => runGuarded(f); + } + + public override ZoneUnaryCallback bindUnaryCallbackGuarded(ZoneUnaryCallback f) { + return (arg) => runUnaryGuarded(f, arg); + } + + public override ZoneBinaryCallback bindBinaryCallbackGuarded(ZoneBinaryCallback f) { + return (arg1, arg2) => runBinaryGuarded(f, arg1, arg2); + } + + public override object this[object key] => null; + + public override void handleUncaughtError(Exception error) { + async_._rootHandleUncaughtError(null, null, this, error); + } + + public override Zone fork(ZoneSpecification specification = null, Hashtable zoneValues = null) { + return async_._rootFork(null, null, this, specification, zoneValues); + } + + public override object run(ZoneCallback f) { + if (ReferenceEquals(Zone._current, async_._rootZone)) return f(); + return async_._rootRun(null, null, this, f); + } + + public override object runUnary(ZoneUnaryCallback f, object arg) { + if (ReferenceEquals(Zone._current, async_._rootZone)) return f(arg); + return async_._rootRunUnary(null, null, this, f, arg); + } + + public override object runBinary(ZoneBinaryCallback f, object arg1, object arg2) { + if (ReferenceEquals(Zone._current, async_._rootZone)) return f(arg1, arg2); + return async_._rootRunBinary(null, null, this, f, arg1, arg2); + } + + public override ZoneCallback registerCallback(ZoneCallback f) => f; + + public override ZoneUnaryCallback registerUnaryCallback(ZoneUnaryCallback f) => f; + + public override ZoneBinaryCallback registerBinaryCallback(ZoneBinaryCallback f) => f; + + public override AsyncError errorCallback(Exception error) => null; + + public override void scheduleMicrotask(ZoneCallback f) { + async_._rootScheduleMicrotask(null, null, this, f); + } + + public override Timer createTimer(TimeSpan duration, ZoneCallback f) { + return Timer._createTimer(duration, f); + } + + public override Timer createPeriodicTimer(TimeSpan duration, ZoneUnaryCallback f) { + return Timer._createPeriodicTimer(duration, f); + } + + public override void print(string line) { + Debug.Log(line); + } + } + + public static partial class async_ { + internal static readonly _Zone _rootZone = new _RootZone(); + + public static R runZoned(Func body, + Hashtable zoneValues = null, ZoneSpecification zoneSpecification = null) { + if (body == null) + throw new ArgumentNullException(nameof(body)); + + return _runZoned(body, zoneValues, zoneSpecification); + } + + public static R runZonedGuarded(Func body, Action onError, + Hashtable zoneValues = null, ZoneSpecification zoneSpecification = null) { + if (body == null) + throw new ArgumentNullException(nameof(body)); + if (onError == null) + throw new ArgumentNullException(nameof(onError)); + + HandleUncaughtErrorHandler errorHandler = + (Zone self, ZoneDelegate parent, Zone zone, Exception error) => { + try { + self.parent.runUnary((e) => { + onError((Exception) e); + return null; + }, error); + } + catch (Exception e) { + parent.handleUncaughtError(zone, e); + } + }; + if (zoneSpecification == null) { + zoneSpecification = + new ZoneSpecification(handleUncaughtError: errorHandler); + } + else { + zoneSpecification = ZoneSpecification.from(zoneSpecification, + handleUncaughtError: errorHandler); + } + + try { + return _runZoned(body, zoneValues, zoneSpecification); + } + catch (Exception error) { + onError(error); + } + + return default; + } + + internal static R _runZoned(Func body, Hashtable zoneValues, ZoneSpecification specification) => + (R) Zone.current + .fork(specification: specification, zoneValues: zoneValues) + .run(() => body()); + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/async2/zone.cs.meta b/com.unity.uiwidgets/Runtime/async2/zone.cs.meta new file mode 100644 index 00000000..00a58696 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/async2/zone.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d8dbf866190b44e6a47e0ff7c0e2bdc4 +timeCreated: 1599458114 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/debugger/inspector_service.cs b/com.unity.uiwidgets/Runtime/debugger/inspector_service.cs index ed02ae2a..14b4456f 100644 --- a/com.unity.uiwidgets/Runtime/debugger/inspector_service.cs +++ b/com.unity.uiwidgets/Runtime/debugger/inspector_service.cs @@ -25,7 +25,7 @@ namespace Unity.UIWidgets.debugger { } public bool debugEnabled { - get { return D.debugEnabled; } + get { return foundation_.kDebugMode; } } public void close() { diff --git a/com.unity.uiwidgets/Runtime/developer.meta b/com.unity.uiwidgets/Runtime/developer.meta new file mode 100644 index 00000000..889ea57b --- /dev/null +++ b/com.unity.uiwidgets/Runtime/developer.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 32b343b53c4d484f8c149742f9d0a919 +timeCreated: 1599028965 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/developer/extension.cs b/com.unity.uiwidgets/Runtime/developer/extension.cs new file mode 100644 index 00000000..12386b18 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/developer/extension.cs @@ -0,0 +1,8 @@ +using System.Collections; + +namespace developer { + public static partial class developer_ { + public static void postEvent(string eventKind, Hashtable eventData) { + } + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/developer/extension.cs.meta b/com.unity.uiwidgets/Runtime/developer/extension.cs.meta new file mode 100644 index 00000000..0728d3cc --- /dev/null +++ b/com.unity.uiwidgets/Runtime/developer/extension.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d2dcff701560471ab813d010a98a7d36 +timeCreated: 1599030815 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/developer/timeline.cs b/com.unity.uiwidgets/Runtime/developer/timeline.cs new file mode 100644 index 00000000..5b4c09c1 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/developer/timeline.cs @@ -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() { + } + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/developer/timeline.cs.meta b/com.unity.uiwidgets/Runtime/developer/timeline.cs.meta new file mode 100644 index 00000000..a18ccf92 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/developer/timeline.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 263dee475b944a33a99e0d8925f904c4 +timeCreated: 1599028971 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/engine2/UIWidgetsPanel.cs b/com.unity.uiwidgets/Runtime/engine2/UIWidgetsPanel.cs index 29da5896..184b5c73 100644 --- a/com.unity.uiwidgets/Runtime/engine2/UIWidgetsPanel.cs +++ b/com.unity.uiwidgets/Runtime/engine2/UIWidgetsPanel.cs @@ -51,6 +51,7 @@ namespace Unity.UIWidgets.engine2 { } protected virtual void main() { + Debug.Log(Debug.isDebugBuild); } public void entryPoint() { diff --git a/com.unity.uiwidgets/Runtime/foundation/binding.cs b/com.unity.uiwidgets/Runtime/foundation/binding.cs index 4bdaf773..8abd2d54 100644 --- a/com.unity.uiwidgets/Runtime/foundation/binding.cs +++ b/com.unity.uiwidgets/Runtime/foundation/binding.cs @@ -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 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); + } + } } \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/foundation/constants.cs b/com.unity.uiwidgets/Runtime/foundation/constants.cs index 397b9709..e3f611fa 100644 --- a/com.unity.uiwidgets/Runtime/foundation/constants.cs +++ b/com.unity.uiwidgets/Runtime/foundation/constants.cs @@ -1,7 +1,23 @@ using UnityEngine; namespace Unity.UIWidgets.foundation { - public class FoundationConstants { - public static bool kReleaseMode = !Debug.isDebugBuild; + public static class foundation_ { + public static readonly bool kReleaseMode = !Debug.isDebugBuild; + +#if UIWidgets_PROFILE + public const bool kProfileMode = true; +#else + public const bool kProfileMode = false; +#endif + + public static readonly bool kDebugMode = !kReleaseMode && !kProfileMode; + + public const float precisionErrorTolerance = 1e-10f; + +#if UNITY_WEBGL + public const bool kIsWeb = true; +#else + public const bool kIsWeb = false; +#endif } } \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/foundation/debug.cs b/com.unity.uiwidgets/Runtime/foundation/debug.cs index a5bbadf9..60c2ace3 100644 --- a/com.unity.uiwidgets/Runtime/foundation/debug.cs +++ b/com.unity.uiwidgets/Runtime/foundation/debug.cs @@ -15,24 +15,14 @@ namespace Unity.UIWidgets.foundation { Debug.LogException(new AssertionError(message, ex)); } - public static bool debugEnabled { - get { -#if UIWidgets_DEBUG - return true; -#else - return false; -#endif - } - } - - [Conditional("UIWidgets_DEBUG")] + [Conditional("UNITY_ASSERTIONS")] public static void assert(Func result, Func message = null) { if (!result()) { throw new AssertionError(message != null ? message() : ""); } } - [Conditional("UIWidgets_DEBUG")] + [Conditional("UNITY_ASSERTIONS")] public static void assert(bool result, Func message = null) { if (!result) { throw new AssertionError(message != null ? message() : ""); @@ -49,12 +39,6 @@ namespace Unity.UIWidgets.foundation { public static bool debugPrintRecognizerCallbacksTrace = false; - public static bool debugPrintBeginFrameBanner = false; - - public static bool debugPrintEndFrameBanner = false; - - public static bool debugPrintScheduleFrameStacks = false; - public static bool debugPaintSizeEnabled = false; public static bool debugRepaintRainbowEnabled = false; diff --git a/com.unity.uiwidgets/Runtime/material/slider.cs b/com.unity.uiwidgets/Runtime/material/slider.cs index 048e7242..ec49a310 100644 --- a/com.unity.uiwidgets/Runtime/material/slider.cs +++ b/com.unity.uiwidgets/Runtime/material/slider.cs @@ -6,7 +6,7 @@ using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; -using Unity.UIWidgets.scheduler; +using Unity.UIWidgets.scheduler2; using Unity.UIWidgets.ui; using Unity.UIWidgets.widgets; using UnityEngine; @@ -621,7 +621,7 @@ namespace Unity.UIWidgets.material { this._state.interactionTimer?.cancel(); this._state.interactionTimer = Window.instance.run( new TimeSpan(0, 0, 0, 0, - (int) (_minimumInteractionTimeMilliSeconds * SchedulerBinding.instance.timeDilation)), + (int) (_minimumInteractionTimeMilliSeconds * scheduler_.timeDilation)), () => { this._state.interactionTimer = null; if (!this._active && diff --git a/com.unity.uiwidgets/Runtime/painting/image_stream.cs b/com.unity.uiwidgets/Runtime/painting/image_stream.cs index 168aa0c9..1adf7703 100644 --- a/com.unity.uiwidgets/Runtime/painting/image_stream.cs +++ b/com.unity.uiwidgets/Runtime/painting/image_stream.cs @@ -4,8 +4,9 @@ using System.Linq; using RSG; using Unity.UIWidgets.async; using Unity.UIWidgets.foundation; -using Unity.UIWidgets.scheduler; +using Unity.UIWidgets.scheduler2; using Unity.UIWidgets.ui; +using SchedulerBinding = Unity.UIWidgets.scheduler.SchedulerBinding; namespace Unity.UIWidgets.painting { public class ImageInfo : IEquatable { @@ -350,7 +351,7 @@ namespace Unity.UIWidgets.painting { } TimeSpan delay = this._frameDuration.Value - (timestamp - this._shownTimestamp.Value); - delay = new TimeSpan((long) (delay.Ticks * SchedulerBinding.instance.timeDilation)); + delay = new TimeSpan((long) (delay.Ticks * scheduler_.timeDilation)); this._timer = Window.instance.run(delay, this._scheduleAppFrame); } diff --git a/com.unity.uiwidgets/Runtime/scheduler/binding.cs b/com.unity.uiwidgets/Runtime/scheduler/binding.cs index 84576da9..683c65d2 100644 --- a/com.unity.uiwidgets/Runtime/scheduler/binding.cs +++ b/com.unity.uiwidgets/Runtime/scheduler/binding.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Text; using RSG.Promises; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.scheduler2; using Unity.UIWidgets.ui; using Debug = UnityEngine.Debug; +using FrameCallback = Unity.UIWidgets.ui.FrameCallback; namespace Unity.UIWidgets.scheduler { class _FrameCallbackEntry { @@ -173,7 +175,7 @@ namespace Unity.UIWidgets.scheduler { } D.assert(() => { - if (D.debugPrintScheduleFrameStacks) { + if (scheduler_.debugPrintScheduleFrameStacks) { Debug.LogFormat("scheduleFrame() called. Current phase is {0}.", this.schedulerPhase); } @@ -190,7 +192,7 @@ namespace Unity.UIWidgets.scheduler { } D.assert(() => { - if (D.debugPrintScheduleFrameStacks) { + if (scheduler_.debugPrintScheduleFrameStacks) { Debug.LogFormat("scheduleForcedFrame() called. Current phase is {0}.", this.schedulerPhase); } @@ -248,7 +250,7 @@ namespace Unity.UIWidgets.scheduler { }); D.assert(() => { - if (D.debugPrintBeginFrameBanner || D.debugPrintEndFrameBanner) { + if (scheduler_.debugPrintBeginFrameBanner || scheduler_.debugPrintEndFrameBanner) { var frameTimeStampDescription = new StringBuilder(); if (rawTimeStamp != null) { _debugDescribeTimeStamp( @@ -259,7 +261,7 @@ namespace Unity.UIWidgets.scheduler { this._debugBanner = $"▄▄▄▄▄▄▄▄ Frame {this._profileFrameNumber.ToString().PadRight(7)} {frameTimeStampDescription.ToString().PadLeft(18)} ▄▄▄▄▄▄▄▄"; - if (D.debugPrintBeginFrameBanner) { + if (scheduler_.debugPrintBeginFrameBanner) { Debug.Log(this._debugBanner); } } @@ -306,7 +308,7 @@ namespace Unity.UIWidgets.scheduler { } finally { this._schedulerPhase = SchedulerPhase.idle; D.assert(() => { - if (D.debugPrintEndFrameBanner) { + if (scheduler_.debugPrintEndFrameBanner) { Debug.Log(new string('▀', this._debugBanner.Length)); } diff --git a/com.unity.uiwidgets/Runtime/scheduler2.meta b/com.unity.uiwidgets/Runtime/scheduler2.meta new file mode 100644 index 00000000..81cefc1f --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1fea1ea4786b4e6bb65b233b11e8b2d5 +timeCreated: 1599458322 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/binding.cs b/com.unity.uiwidgets/Runtime/scheduler2/binding.cs new file mode 100644 index 00000000..e62368e9 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/binding.cs @@ -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(); + + public delegate bool SchedulingStrategy(int priority = 0, SchedulerBinding scheduler = null); + + interface _TaskEntry : IComparable<_TaskEntry> { + int priority { get; } + string debugStack { get; } + void run(); + } + + class _TaskEntry : _TaskEntry { + internal _TaskEntry(TaskCallback task, int priority) { + this.task = task; + this.priority = priority; + + D.assert(() => { + debugStack = StackTraceUtility.ExtractStackTrace(); + return true; + }); + completer = Completer.create(); + } + + public readonly TaskCallback 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 timings) => { + foreach (FrameTiming frameTiming in timings) { + frameNumber += 1; + _profileFramePostEvent(frameNumber, frameTiming); + } + }); + } + } + + readonly List _timingsCallbacks = new List(); + + 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 timings) { + List clonedCallbacks = + new List(_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( + TaskCallback task, + Priority priority) { + bool isFirstTask = _taskQueue.isEmpty; + _TaskEntry entry = new _TaskEntry( + 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 _transientCallbacks = new Dictionary(); + readonly HashSet _removedIds = new HashSet(); + + 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 _persistentCallbacks = new List(); + + public void addPersistentFrameCallback(FrameCallback callback) { + _persistentCallbacks.Add(callback); + } + + readonly List _postFrameCallbacks = new List(); + + 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(); + 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(_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; + } + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/binding.cs.meta b/com.unity.uiwidgets/Runtime/scheduler2/binding.cs.meta new file mode 100644 index 00000000..88c034f3 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/binding.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0756240ed259436a82cdc6ee65831a4f +timeCreated: 1599458325 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/debug.cs b/com.unity.uiwidgets/Runtime/scheduler2/debug.cs new file mode 100644 index 00000000..87949210 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/debug.cs @@ -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; + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/debug.cs.meta b/com.unity.uiwidgets/Runtime/scheduler2/debug.cs.meta new file mode 100644 index 00000000..d3c88717 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/debug.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 486e9bc3d81445fba548c7cddf682b4d +timeCreated: 1599458325 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/priority.cs b/com.unity.uiwidgets/Runtime/scheduler2/priority.cs new file mode 100644 index 00000000..9d2e9ab8 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/priority.cs @@ -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); + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/priority.cs.meta b/com.unity.uiwidgets/Runtime/scheduler2/priority.cs.meta new file mode 100644 index 00000000..00b6146a --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/priority.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 814da0eaecc042059b36143e73f60f48 +timeCreated: 1599458325 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/ticker.cs b/com.unity.uiwidgets/Runtime/scheduler2/ticker.cs new file mode 100644 index 00000000..4586b830 --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/ticker.cs @@ -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 onError, Func test = null) { + return _primaryCompleter.future.catchError(onError, test: test); + } + + public override Future then(Func onValue, Func onError = null) { + return _primaryCompleter.future.then(onValue, onError: onError); + } + + public override Future timeout(TimeSpan timeLimit, Func onTimeout = null) { + return _primaryCompleter.future.timeout(timeLimit, onTimeout: onTimeout); + } + + public override Future whenComplete(Func 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."; + } + } +} \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/scheduler2/ticker.cs.meta b/com.unity.uiwidgets/Runtime/scheduler2/ticker.cs.meta new file mode 100644 index 00000000..d669c5dd --- /dev/null +++ b/com.unity.uiwidgets/Runtime/scheduler2/ticker.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9ee135f21ca64943a5f75a04a0e30cc3 +timeCreated: 1599458326 \ No newline at end of file diff --git a/com.unity.uiwidgets/Runtime/ui2/window.cs b/com.unity.uiwidgets/Runtime/ui2/window.cs index a67fdb7b..f7788988 100644 --- a/com.unity.uiwidgets/Runtime/ui2/window.cs +++ b/com.unity.uiwidgets/Runtime/ui2/window.cs @@ -86,6 +86,7 @@ namespace Unity.UIWidgets.ui2 { public class Window { internal IntPtr _ptr; + internal object _binding; internal Window() { _setNeedsReportTimings = Window_setNeedsReportTimings; diff --git a/engine/src/lib/ui/ui_mono_state.cc b/engine/src/lib/ui/ui_mono_state.cc index 3f5ec6b5..6aabc0b0 100644 --- a/engine/src/lib/ui/ui_mono_state.cc +++ b/engine/src/lib/ui/ui_mono_state.cc @@ -80,4 +80,22 @@ fml::WeakPtr UIMonoState::GetImageDecoder() const { return image_decoder_; } +UIWIDGETS_API(void) +UIMonoState_scheduleMicrotask(MonoMicrotaskQueue::CallbackFunc callback, + Mono_Handle handle) { + UIMonoState::Current()->ScheduleMicrotask(callback, handle); +} + +UIWIDGETS_API(void) +UIMonoState_postTaskForTime(MonoMicrotaskQueue::CallbackFunc callback, + Mono_Handle handle, int64_t target_time_nanos) { + UIMonoState::Current()->GetTaskRunners().GetUITaskRunner()->PostTaskForTime( + [callback, handle]() -> void { callback(handle); }, + fml::TimePoint::FromEpochDelta( + fml::TimeDelta::FromNanoseconds(target_time_nanos))); +} + +UIWIDGETS_API(int) +UIMonoState_timerMillisecondClock() { return Mono_TimelineGetMicros() / 1000; } + } // namespace uiwidgets diff --git a/engine/src/shell/platform/unity/uiwidgets_system.cc b/engine/src/shell/platform/unity/uiwidgets_system.cc index 477bf6fe..02692495 100644 --- a/engine/src/shell/platform/unity/uiwidgets_system.cc +++ b/engine/src/shell/platform/unity/uiwidgets_system.cc @@ -21,6 +21,8 @@ void UIWidgetsSystem::UnregisterPanel(UIWidgetsPanel* panel) { } void UIWidgetsSystem::Wait(std::chrono::nanoseconds max_duration) { + Update(); + std::chrono::nanoseconds wait_duration = std::max(std::chrono::nanoseconds(0), next_uiwidgets_event_time_ - TimePoint::clock::now()); @@ -48,8 +50,6 @@ void UIWidgetsSystem::VSync() { for (auto* uiwidgets_panel : uiwidgets_panels_) { uiwidgets_panel->ProcessVSync(); } - - Update(); } void UIWidgetsSystem::WakeUp() {} diff --git a/engine/src/shell/platform/unity/win32_task_runner.h b/engine/src/shell/platform/unity/win32_task_runner.h index ada91a69..32608068 100644 --- a/engine/src/shell/platform/unity/win32_task_runner.h +++ b/engine/src/shell/platform/unity/win32_task_runner.h @@ -67,7 +67,7 @@ class Win32TaskRunner { TaskObservers task_observers_; static TaskTimePoint TimePointFromUIWidgetsTime( - uint64_t flutter_target_time_nanos); + uint64_t uiwidgets_target_time_nanos); }; } // namespace uiwidgets