diff --git a/.github/workflows/build-debug.yml b/.github/workflows/build-debug.yml index 5c069f6..e3bd6a6 100644 --- a/.github/workflows/build-debug.yml +++ b/.github/workflows/build-debug.yml @@ -81,6 +81,7 @@ jobs: # Store artifacts. - uses: Cysharp/Actions/.github/actions/upload-artifact@main + if: ${{ startsWith(matrix.unity, '2021') }} # only execute 2021 with: name: UniTask.unitypackage-${{ matrix.unity }}.zip path: ./src/UniTask/*.unitypackage diff --git a/README.md b/README.md index 41ed888..2fe49de 100644 --- a/README.md +++ b/README.md @@ -526,6 +526,9 @@ It indicates when to run, you can check [PlayerLoopList.md](https://gist.github. In UniTask, await directly uses native timing, while `WithCancellation` and `ToUniTask` use specified timing. This is usually not a particular problem, but with `LoadSceneAsync`, it causes a different order of Start and continuation after await. So it is recommended not to use `LoadSceneAsync.ToUniTask`. +> Note: When using Unity 2023.1 or newer, ensure you have `using UnityEngine;` in the using statements of your file when working with new `UnityEngine.Awaitable` methods like `SceneManager.LoadSceneAsync`. +> This prevents compilation errors by avoiding the use of the `UnityEngine.AsyncOperation` version. + In the stacktrace, you can check where it is running in playerloop. ![image](https://user-images.githubusercontent.com/46207/83735571-83caea80-a68b-11ea-8d22-5e22864f0d24.png) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Factory.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Factory.cs index bd66aba..8bdec75 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Factory.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Factory.cs @@ -184,6 +184,78 @@ namespace Cysharp.Threading.Tasks return () => asyncAction(state).Forget(); } + /// + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T arg) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction) + { + return (arg) => asyncAction(arg).Forget(); + } + + /// + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction) + { + return (arg0, arg1) => asyncAction(arg0, arg1).Forget(); + } + + /// + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction) + { + return (arg0, arg1, arg2) => asyncAction(arg0, arg1, arg2).Forget(); + } + + /// + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, T3 arg3) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction) + { + return (arg0, arg1, arg2, arg3) => asyncAction(arg0, arg1, arg2, arg3).Forget(); + } + + // + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T arg, CancellationToken cancellationToken) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction, CancellationToken cancellationToken) + { + return (arg) => asyncAction(arg, cancellationToken).Forget(); + } + + /// + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, CancellationToken cancellationToken) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction, CancellationToken cancellationToken) + { + return (arg0, arg1) => asyncAction(arg0, arg1, cancellationToken).Forget(); + } + + /// + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, CancellationToken cancellationToken) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction, CancellationToken cancellationToken) + { + return (arg0, arg1, arg2) => asyncAction(arg0, arg1, arg2, cancellationToken).Forget(); + } + + /// + /// Create async void(UniTaskVoid) UnityAction. + /// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, T3 arg3, CancellationToken cancellationToken) => { /* */ } )) + /// + public static UnityEngine.Events.UnityAction UnityAction(Func asyncAction, CancellationToken cancellationToken) + { + return (arg0, arg1, arg2, arg3) => asyncAction(arg0, arg1, arg2, arg3, cancellationToken).Forget(); + } + #endif /// @@ -202,6 +274,22 @@ namespace Cysharp.Threading.Tasks return new UniTask(new DeferPromise(factory), 0); } + /// + /// Defer the task creation just before call await. + /// + public static UniTask Defer(TState state, Func factory) + { + return new UniTask(new DeferPromiseWithState(state, factory), 0); + } + + /// + /// Defer the task creation just before call await. + /// + public static UniTask Defer(TState state, Func> factory) + { + return new UniTask(new DeferPromiseWithState(state, factory), 0); + } + /// /// Never complete. /// @@ -465,6 +553,93 @@ namespace Cysharp.Threading.Tasks } } + sealed class DeferPromiseWithState : IUniTaskSource + { + Func factory; + TState argument; + UniTask task; + UniTask.Awaiter awaiter; + + public DeferPromiseWithState(TState argument, Func factory) + { + this.argument = argument; + this.factory = factory; + } + + public void GetResult(short token) + { + awaiter.GetResult(); + } + + public UniTaskStatus GetStatus(short token) + { + var f = Interlocked.Exchange(ref factory, null); + if (f != null) + { + task = f(argument); + awaiter = task.GetAwaiter(); + } + + return task.Status; + } + + public void OnCompleted(Action continuation, object state, short token) + { + awaiter.SourceOnCompleted(continuation, state); + } + + public UniTaskStatus UnsafeGetStatus() + { + return task.Status; + } + } + + sealed class DeferPromiseWithState : IUniTaskSource + { + Func> factory; + TState argument; + UniTask task; + UniTask.Awaiter awaiter; + + public DeferPromiseWithState(TState argument, Func> factory) + { + this.argument = argument; + this.factory = factory; + } + + public TResult GetResult(short token) + { + return awaiter.GetResult(); + } + + void IUniTaskSource.GetResult(short token) + { + awaiter.GetResult(); + } + + public UniTaskStatus GetStatus(short token) + { + var f = Interlocked.Exchange(ref factory, null); + if (f != null) + { + task = f(argument); + awaiter = task.GetAwaiter(); + } + + return task.Status; + } + + public void OnCompleted(Action continuation, object state, short token) + { + awaiter.SourceOnCompleted(continuation, state); + } + + public UniTaskStatus UnsafeGetStatus() + { + return task.Status; + } + } + sealed class NeverPromise : IUniTaskSource { static readonly Action cancellationCallback = CancellationCallback; diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs index c5e5915..b113353 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs @@ -15,11 +15,21 @@ namespace Cysharp.Threading.Tasks return new UniTask(WaitUntilPromise.Create(predicate, timing, cancellationToken, cancelImmediately, out var token), token); } + public static UniTask WaitUntil(T state, Func predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + { + return new UniTask(WaitUntilPromise.Create(state, predicate, timing, cancellationToken, cancelImmediately, out var token), token); + } + public static UniTask WaitWhile(Func predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { return new UniTask(WaitWhilePromise.Create(predicate, timing, cancellationToken, cancelImmediately, out var token), token); } + public static UniTask WaitWhile(T state, Func predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + { + return new UniTask(WaitWhilePromise.Create(state, predicate, timing, cancellationToken, cancelImmediately, out var token), token); + } + public static UniTask WaitUntilCanceled(CancellationToken cancellationToken, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool completeImmediately = false) { return new UniTask(WaitUntilCanceledPromise.Create(cancellationToken, timing, completeImmediately, out var token), token); @@ -162,6 +172,135 @@ namespace Cysharp.Threading.Tasks } } + sealed class WaitUntilPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode> + { + static TaskPool> pool; + WaitUntilPromise nextNode; + public ref WaitUntilPromise NextNode => ref nextNode; + + static WaitUntilPromise() + { + TaskPool.RegisterSizeGetter(typeof(WaitUntilPromise), () => pool.Size); + } + + Func predicate; + T argument; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool cancelImmediately; + + UniTaskCompletionSourceCore core; + + WaitUntilPromise() + { + } + + public static IUniTaskSource Create(T argument, Func predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new WaitUntilPromise(); + } + + result.predicate = predicate; + result.argument = argument; + result.cancellationToken = cancellationToken; + result.cancelImmediately = cancelImmediately; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitUntilPromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(timing, result); + + token = result.core.Version; + return result; + } + + public void GetResult(short token) + { + try + { + core.GetResult(token); + } + finally + { + if (!(cancelImmediately && cancellationToken.IsCancellationRequested)) + { + TryReturn(); + } + else + { + TaskTracker.RemoveTracking(this); + } + } + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + return false; + } + + try + { + if (!predicate(argument)) + { + return true; + } + } + catch (Exception ex) + { + core.TrySetException(ex); + return false; + } + + core.TrySetResult(null); + return false; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + predicate = default; + argument = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + cancelImmediately = default; + return pool.TryPush(this); + } + } + sealed class WaitWhilePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -199,7 +338,7 @@ namespace Cysharp.Threading.Tasks result.predicate = predicate; result.cancellationToken = cancellationToken; result.cancelImmediately = cancelImmediately; - + if (cancelImmediately && cancellationToken.CanBeCanceled) { result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => @@ -288,6 +427,135 @@ namespace Cysharp.Threading.Tasks } } + sealed class WaitWhilePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode> + { + static TaskPool> pool; + WaitWhilePromise nextNode; + public ref WaitWhilePromise NextNode => ref nextNode; + + static WaitWhilePromise() + { + TaskPool.RegisterSizeGetter(typeof(WaitWhilePromise), () => pool.Size); + } + + Func predicate; + T argument; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool cancelImmediately; + + UniTaskCompletionSourceCore core; + + WaitWhilePromise() + { + } + + public static IUniTaskSource Create(T argument, Func predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new WaitWhilePromise(); + } + + result.predicate = predicate; + result.argument = argument; + result.cancellationToken = cancellationToken; + result.cancelImmediately = cancelImmediately; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitWhilePromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + PlayerLoopHelper.AddAction(timing, result); + + token = result.core.Version; + return result; + } + + public void GetResult(short token) + { + try + { + core.GetResult(token); + } + finally + { + if (!(cancelImmediately && cancellationToken.IsCancellationRequested)) + { + TryReturn(); + } + else + { + TaskTracker.RemoveTracking(this); + } + } + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + public bool MoveNext() + { + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + return false; + } + + try + { + if (predicate(argument)) + { + return true; + } + } + catch (Exception ex) + { + core.TrySetException(ex); + return false; + } + + core.TrySetResult(null); + return false; + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + predicate = default; + argument = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + cancelImmediately = default; + return pool.TryPush(this); + } + } + sealed class WaitUntilCanceledPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -443,7 +711,7 @@ namespace Cysharp.Threading.Tasks result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault(); result.cancellationToken = cancellationToken; result.cancelImmediately = cancelImmediately; - + if (cancelImmediately && cancellationToken.CanBeCanceled) { result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => @@ -586,7 +854,7 @@ namespace Cysharp.Threading.Tasks result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault(); result.cancellationToken = cancellationToken; result.cancelImmediately = cancelImmediately; - + if (cancelImmediately && cancellationToken.CanBeCanceled) { result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => diff --git a/src/UniTask/Assets/Plugins/UniTask/package.json b/src/UniTask/Assets/Plugins/UniTask/package.json index 65f7a13..0408504 100644 --- a/src/UniTask/Assets/Plugins/UniTask/package.json +++ b/src/UniTask/Assets/Plugins/UniTask/package.json @@ -2,7 +2,7 @@ "name": "com.cysharp.unitask", "displayName": "UniTask", "author": { "name": "Cysharp, Inc.", "url": "https://cysharp.co.jp/en/" }, - "version": "2.5.5", + "version": "2.5.7", "unity": "2018.4", "description": "Provides an efficient async/await integration to Unity.", "keywords": [ "async/await", "async", "Task", "UniTask" ], diff --git a/src/UniTask/Assets/Tests/AsyncTest.cs b/src/UniTask/Assets/Tests/AsyncTest.cs index 3cd98a3..2f07d1e 100644 --- a/src/UniTask/Assets/Tests/AsyncTest.cs +++ b/src/UniTask/Assets/Tests/AsyncTest.cs @@ -145,6 +145,11 @@ namespace Cysharp.Threading.TasksTests public int MyProperty { get; set; } } + class MyBooleanClass + { + public bool MyProperty { get; set; } + } + [UnityTest] public IEnumerator WaitUntil() => UniTask.ToCoroutine(async () => { @@ -159,6 +164,20 @@ namespace Cysharp.Threading.TasksTests diff.Should().Be(11); }); + [UnityTest] + public IEnumerator WaitUntilWithState() => UniTask.ToCoroutine(async () => + { + var v = new MyBooleanClass { MyProperty = false }; + + UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => v.MyProperty = true).Forget(); + + var startFrame = Time.frameCount; + await UniTask.WaitUntil(v, static v => v.MyProperty, PlayerLoopTiming.EarlyUpdate); + + var diff = Time.frameCount - startFrame; + diff.Should().Be(11); + }); + [UnityTest] public IEnumerator WaitWhile() => UniTask.ToCoroutine(async () => { @@ -173,6 +192,20 @@ namespace Cysharp.Threading.TasksTests diff.Should().Be(11); }); + [UnityTest] + public IEnumerator WaitWhileWithState() => UniTask.ToCoroutine(async () => + { + var v = new MyBooleanClass { MyProperty = true }; + + UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => v.MyProperty = false).Forget(); + + var startFrame = Time.frameCount; + await UniTask.WaitWhile(v, static v => v.MyProperty, PlayerLoopTiming.EarlyUpdate); + + var diff = Time.frameCount - startFrame; + diff.Should().Be(11); + }); + [UnityTest] public IEnumerator WaitUntilValueChanged() => UniTask.ToCoroutine(async () => {