diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.UIToolkit.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.UIToolkit.cs new file mode 100644 index 0000000..12812af --- /dev/null +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.UIToolkit.cs @@ -0,0 +1,483 @@ +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +#if UNITY_2021_2_OR_NEWER +using System; +using System.Threading; +using UnityEngine.UIElements; + +namespace Cysharp.Threading.Tasks +{ + public static partial class UnityAsyncExtensions + { + public static IAsyncUIToolkitEventHandler GetAsyncEventHandler(this CallbackEventHandler eventHandler, CancellationToken cancellationToken) + where TEventType : EventBase, new() + { + return new AsyncUIToolkitDefaultEventHandler(eventHandler, cancellationToken, false); + } + + public static UniTask OnInvokeAsync(this CallbackEventHandler eventHandler, CancellationToken cancellationToken) + where TEventType : EventBase, new() + { + return new AsyncUIToolkitDefaultEventHandler(eventHandler, cancellationToken, true).OnInvokeAsync(); + } + + public static IUniTaskAsyncEnumerable OnInvokeAsAsyncEnumerable(this CallbackEventHandler eventHandler, CancellationToken cancellationToken) + where TEventType : EventBase, new() + { + return new UIToolkitEventHandlerAsyncEnumerable(eventHandler, cancellationToken); + } + + public static IAsyncClickEventHandler GetAsyncClickEventHandler(this Button button, CancellationToken cancellationToken) + { + return new AsyncUIToolkitDefaultEventHandler(button, cancellationToken, false); + } + + public static UniTask OnClickAsync(this Button button, CancellationToken cancellationToken) + { + return new AsyncUIToolkitDefaultEventHandler(button, cancellationToken, true).OnInvokeAsync(); + } + + public static IUniTaskAsyncEnumerable OnClickAsAsyncEnumerable(this Button button, CancellationToken cancellationToken) + { + return new UIToolkitEventHandlerAsyncEnumerable(button, cancellationToken); + } + + public static IAsyncValueChangedEventHandler GetAsyncValueChangedEventHandler(this INotifyValueChanged eventHandler, CancellationToken cancellationToken) + { + return new AsyncUIToolkitChangeEventHandler(eventHandler, cancellationToken, false); + } + + public static UniTask OnValueChangedAsync(this INotifyValueChanged eventHandler, CancellationToken cancellationToken) + { + return new AsyncUIToolkitChangeEventHandler(eventHandler, cancellationToken, true).OnInvokeAsync(); + } + + public static IUniTaskAsyncEnumerable OnValueChangedAsAsyncEnumerable(this INotifyValueChanged eventHandler, CancellationToken cancellationToken) + { + return new UIToolkitChangeEventHandlerAsyncEnumerable(eventHandler, cancellationToken); + } + + public static void BindTo(this IUniTaskAsyncEnumerable source, TextField text, CancellationToken cancellationToken, bool rebindOnError = true) + { + BindToCore(source, text, cancellationToken, rebindOnError, SetTextFromStringEnumerator).Forget(); + } + + public static void BindTo(this IUniTaskAsyncEnumerable source, TextElement text, CancellationToken cancellationToken, bool rebindOnError = true) + { + BindToCore(source, text, cancellationToken, rebindOnError, SetTextFromStringEnumerator).Forget(); + } + + public static void BindTo(this IUniTaskAsyncEnumerable source, TextField text, CancellationToken cancellationToken, bool rebindOnError = true) + { + BindToCore(source, text, cancellationToken, rebindOnError, SetTextFromEnumerator).Forget(); + } + + public static void BindTo(this IUniTaskAsyncEnumerable source, TextElement text, CancellationToken cancellationToken, bool rebindOnError = true) + { + BindToCore(source, text, cancellationToken, rebindOnError, SetTextFromEnumerator).Forget(); + } + + static void SetTextFromStringEnumerator(INotifyValueChanged control, IUniTaskAsyncEnumerator enumerator) + { + control.value = enumerator.Current; + } + + static void SetTextFromEnumerator(INotifyValueChanged control, IUniTaskAsyncEnumerator enumerator) + { + control.value = enumerator.Current.ToString(); + } + + static async UniTaskVoid BindToCore(IUniTaskAsyncEnumerable source, + INotifyValueChanged text, + CancellationToken cancellationToken, + bool rebindOnError, + Action, IUniTaskAsyncEnumerator> setter) + { + var repeat = false; + BIND_AGAIN: + var e = source.GetAsyncEnumerator(cancellationToken); + try + { + while (true) + { + bool moveNext; + try + { + moveNext = await e.MoveNextAsync(); + repeat = false; + } + catch (Exception ex) + { + if (ex is OperationCanceledException) return; + + if (rebindOnError && !repeat) + { + repeat = true; + goto BIND_AGAIN; + } + else + { + throw; + } + } + + if (!moveNext) return; + + setter(text, e); + } + } + finally + { + if (e != null) + { + await e.DisposeAsync(); + } + } + } + } + + public interface IAsyncUIToolkitEventHandler : IDisposable + { + UniTask OnInvokeAsync(); + } + + public class AsyncUIToolkitChangeEventHandler : AsyncUIToolkitEventHandler, ChangeEvent, TReturnType> + , IAsyncValueChangedEventHandler + { + public AsyncUIToolkitChangeEventHandler(INotifyValueChanged eventHandler, CancellationToken cancellationToken, bool callOnce) + : base(eventHandler, cancellationToken, callOnce) + { + eventHandler.RegisterValueChangedCallback(action); + } + + UniTask IAsyncValueChangedEventHandler.OnValueChangedAsync() + { + return OnInvokeAsync(); + } + + protected override TReturnType GetValueFor(ChangeEvent changeEvent) + { + return changeEvent.newValue; + } + + protected override void UnregisterCallback() + { + eventHandler.UnregisterValueChangedCallback(action); + } + } + + public class UIToolkitChangeEventHandlerAsyncEnumerable : IUniTaskAsyncEnumerable + { + readonly INotifyValueChanged eventHandler; + readonly CancellationToken cancellationToken1; + + public UIToolkitChangeEventHandlerAsyncEnumerable(INotifyValueChanged eventHandler, CancellationToken cancellationToken) + { + this.eventHandler = eventHandler; + this.cancellationToken1 = cancellationToken; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + if (this.cancellationToken1 == cancellationToken) + { + return new UIToolkitChangeEventHandlerAsyncEnumerator(eventHandler, this.cancellationToken1, CancellationToken.None); + } + else + { + return new UIToolkitChangeEventHandlerAsyncEnumerator(eventHandler, this.cancellationToken1, cancellationToken); + } + } + + class UIToolkitChangeEventHandlerAsyncEnumerator : UIToolkitEventHandlerAsyncEnumerator, ChangeEvent, T> + { + public UIToolkitChangeEventHandlerAsyncEnumerator(INotifyValueChanged eventHandler, CancellationToken cancellationToken1, CancellationToken cancellationToken2) + : base(eventHandler, cancellationToken1, cancellationToken2) + { + } + + protected override T GetValueFrom(ChangeEvent changeEvent) + { + return changeEvent.newValue; + } + + protected override void RegisterCallback(EventCallback> callback) + { + eventHandler.RegisterValueChangedCallback(callback); + } + + protected override void UnregisterCallback(EventCallback> callback) + { + eventHandler.UnregisterValueChangedCallback(callback); + } + } + } + + public class AsyncUIToolkitDefaultEventHandler : AsyncUIToolkitEventHandler, IAsyncClickEventHandler where TEventType : EventBase, new() + { + public AsyncUIToolkitDefaultEventHandler(CallbackEventHandler eventHandler, CancellationToken cancellationToken, bool callOnce) + : base(eventHandler, cancellationToken, callOnce) + { + eventHandler.RegisterCallback(action); + } + + protected override TEventType GetValueFor(TEventType result) + { + return result; + } + + protected override void UnregisterCallback() + { + eventHandler.UnregisterCallback(action); + } + + UniTask IAsyncClickEventHandler.OnClickAsync() + { + return OnInvokeAsync(); + } + } + + public class UIToolkitEventHandlerAsyncEnumerable : IUniTaskAsyncEnumerable where TEventType : EventBase, new() + { + readonly CallbackEventHandler eventHandler; + readonly CancellationToken cancellationToken1; + + public UIToolkitEventHandlerAsyncEnumerable(CallbackEventHandler eventHandler, CancellationToken cancellationToken) + { + this.eventHandler = eventHandler; + this.cancellationToken1 = cancellationToken; + } + + public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + if (this.cancellationToken1 == cancellationToken) + { + return new UIToolkitEventHandlerAsyncEnumerator(eventHandler, this.cancellationToken1, CancellationToken.None); + } + else + { + return new UIToolkitEventHandlerAsyncEnumerator(eventHandler, this.cancellationToken1, cancellationToken); + } + } + + class UIToolkitEventHandlerAsyncEnumerator : UIToolkitEventHandlerAsyncEnumerator + { + public UIToolkitEventHandlerAsyncEnumerator(CallbackEventHandler eventHandler, CancellationToken cancellationToken1, CancellationToken cancellationToken2) + : base(eventHandler, cancellationToken1, cancellationToken2) + { + } + + protected override TEventType GetValueFrom(TEventType eventValue) + { + return eventValue; + } + + protected override void RegisterCallback(EventCallback callback) + { + eventHandler.RegisterCallback(callback); + } + + protected override void UnregisterCallback(EventCallback callback) + { + eventHandler.UnregisterCallback(callback); + } + } + } + + + public abstract class AsyncUIToolkitEventHandler : IUniTaskSource, IAsyncUIToolkitEventHandler where TEventType : EventBase, new() + { + static Action cancellationCallback = CancellationCallback; + protected readonly EventCallback action; + protected readonly TEventHandler eventHandler; + CancellationToken cancellationToken; + CancellationTokenRegistration registration; + bool isDisposed; + bool callOnce; + + UniTaskCompletionSourceCore core; + + public AsyncUIToolkitEventHandler(TEventHandler eventHandler, CancellationToken cancellationToken, bool callOnce) + { + this.cancellationToken = cancellationToken; + if (cancellationToken.IsCancellationRequested) + { + isDisposed = true; + return; + } + + this.action = Invoke; + this.eventHandler = eventHandler; + this.callOnce = callOnce; + + if (cancellationToken.CanBeCanceled) + { + registration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this); + } + + TaskTracker.TrackActiveTask(this, 3); + } + + public UniTask OnInvokeAsync() + { + core.Reset(); + if (isDisposed) + { + core.TrySetCanceled(this.cancellationToken); + } + return new UniTask(this, core.Version); + } + + protected abstract TReturnType GetValueFor(TEventType eventValue); + + void Invoke(TEventType eventValue) + { + core.TrySetResult(GetValueFor(eventValue)); + } + + static void CancellationCallback(object state) + { + var self = (AsyncUIToolkitEventHandler)state; + self.Dispose(); + } + + public void Dispose() + { + if (!isDisposed) + { + isDisposed = true; + TaskTracker.RemoveTracking(this); + registration.Dispose(); + if (eventHandler != null) + { + UnregisterCallback(); + } + + core.TrySetCanceled(); + } + } + + protected abstract void UnregisterCallback(); + + TReturnType IUniTaskSource.GetResult(short token) + { + try + { + return core.GetResult(token); + } + finally + { + if (callOnce) + { + Dispose(); + } + } + } + + void IUniTaskSource.GetResult(short token) + { + ((IUniTaskSource)this).GetResult(token); + } + + UniTaskStatus IUniTaskSource.GetStatus(short token) + { + return core.GetStatus(token); + } + + UniTaskStatus IUniTaskSource.UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + void IUniTaskSource.OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + } + + internal abstract class UIToolkitEventHandlerAsyncEnumerator : MoveNextSource, IUniTaskAsyncEnumerator + { + static readonly Action cancel1 = OnCanceled1; + static readonly Action cancel2 = OnCanceled2; + + protected readonly TEventHandler eventHandler; + CancellationToken cancellationToken1; + CancellationToken cancellationToken2; + + EventCallback unityAction; + CancellationTokenRegistration registration1; + CancellationTokenRegistration registration2; + bool isDisposed; + + public UIToolkitEventHandlerAsyncEnumerator(TEventHandler eventHandler, CancellationToken cancellationToken1, CancellationToken cancellationToken2) + { + this.eventHandler = eventHandler; + this.cancellationToken1 = cancellationToken1; + this.cancellationToken2 = cancellationToken2; + } + + public TReturnType Current { get; private set; } + + protected abstract TReturnType GetValueFrom(TEventType eventValue); + protected abstract void RegisterCallback(EventCallback callback); + protected abstract void UnregisterCallback(EventCallback calback); + + public UniTask MoveNextAsync() + { + cancellationToken1.ThrowIfCancellationRequested(); + cancellationToken2.ThrowIfCancellationRequested(); + completionSource.Reset(); + + if (unityAction == null) + { + unityAction = Invoke; + + TaskTracker.TrackActiveTask(this, 3); + RegisterCallback(unityAction); + if (cancellationToken1.CanBeCanceled) + { + registration1 = cancellationToken1.RegisterWithoutCaptureExecutionContext(cancel1, this); + } + if (cancellationToken2.CanBeCanceled) + { + registration2 = cancellationToken1.RegisterWithoutCaptureExecutionContext(cancel2, this); + } + } + + return new UniTask(this, completionSource.Version); + } + + void Invoke(TEventType changeEvent) + { + Current = GetValueFrom(changeEvent); + completionSource.TrySetResult(true); + } + + static void OnCanceled1(object state) + { + var self = (UIToolkitEventHandlerAsyncEnumerator )state; + self.DisposeAsync().Forget(); + } + + static void OnCanceled2(object state) + { + var self = (UIToolkitEventHandlerAsyncEnumerator )state; + self.DisposeAsync().Forget(); + } + + public UniTask DisposeAsync() + { + if (!isDisposed) + { + isDisposed = true; + TaskTracker.RemoveTracking(this); + registration1.Dispose(); + registration2.Dispose(); + UnregisterCallback(unityAction); + } + + return default; + } + } +} + +#endif \ No newline at end of file diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.UIToolkit.cs.meta b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.UIToolkit.cs.meta new file mode 100644 index 0000000..76af5a2 --- /dev/null +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.UIToolkit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a8c5ee1b94ba11240bf8faae1e60d986 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: