Compare commits

...

24 Commits

Author SHA1 Message Date
github-actions[bot]
7c0f199fe0 feat: Update package.json to 2.5.10 2024-10-03 00:42:37 +00:00
neuecc
9a3ec31533 Merge remote-tracking branch 'origin/master' 2024-10-03 09:41:47 +09:00
neuecc
37d8f4f48e C# 8 #624 2024-10-03 09:41:30 +09:00
github-actions[bot]
27a0c06ede feat: Update package.json to 2.5.9 2024-10-01 06:16:52 +00:00
neuecc
e4082ecd75 Merge remote-tracking branch 'origin/master' 2024-10-01 14:59:02 +09:00
neuecc
3b0fd784ff meta 2024-10-01 14:58:57 +09:00
neuecc
dc9ebfd765 Add AsyncInstantiateOperation.WithCancellation, ToUniTask for Unity 2022.3.20/Unity 2022.3 or newer 2024-10-01 14:50:13 +09:00
Yoshifumi Kawai
005c83fbd7 Merge pull request #623 from Cysharp/feature/ci
ci: remove Unity 2021 LTS from Build matrix
2024-10-01 10:44:30 +09:00
github-actions[bot]
5984b67ecb feat: Update package.json to 2.5.8 2024-09-30 11:45:57 +00:00
neuecc
05fdf48058 Merge remote-tracking branch 'origin/master' 2024-09-30 20:43:06 +09:00
Yoshifumi Kawai
647ed6ff82 Merge pull request #621 from dvsilch/master
add generic type UnityAction support & fix typo
2024-09-30 20:42:57 +09:00
neuecc
0826b7e976 Add UniTask.WhenEach 2024-09-30 20:42:17 +09:00
dvsilch
a51632cd4b fix: add overloads for CancellationToken 2024-09-30 10:13:29 +08:00
dvsilch
bf945a7ef4 fix: rename parameters and type parameters 2024-09-30 10:11:36 +08:00
dvsilch
353f15e94f fix: add .Forget() call and rename paramters to keep coding style consistent 2024-09-27 23:45:05 +08:00
dvsilch
cf19f18662 fix: typo 2024-09-27 22:57:36 +08:00
dvsilch
fdb9d1cf95 feature: add overload in UniTask.UnityAction to support generic type UnityAction 2024-09-27 22:57:05 +08:00
Yoshifumi Kawai
2e0917428b Merge pull request #620 from albermotion/master
Added a note for users using Unity 2023.1 or newer to prevent compilation error
2024-09-27 09:48:04 +09:00
Alberto
0b16005f4b Added a note for users using Unity 2023.1 or newer 2024-09-26 22:33:37 +02:00
github-actions[bot]
74bbe87b58 feat: Update package.json to 2.5.7 2024-09-26 06:24:48 +00:00
Yoshifumi Kawai
6f4131539b Merge pull request #619 from kochounoyume/add-state-argument
Add overload in UniTask.WaitUntil, UniTask.WaitWhile and UniTask.Defer
2024-09-26 12:52:56 +09:00
Kochoyume
06283f0ffb Add to the code that I forgot to write 2024-09-24 23:58:28 +09:00
Kochoyume
dfe5ee43c2 Add overload in UniTask.WaitUntil, UniTask.WaitWhile and UniTask.Defer to avoid closure allocation 2024-09-24 23:37:44 +09:00
Ikiru Yoshizaki
eaa553dc83 ci: remove Unity 2021 LTS from Build matrix 2024-09-24 18:30:54 +09:00
15 changed files with 1203 additions and 27 deletions

View File

@@ -24,7 +24,7 @@ jobs:
fail-fast: false
max-parallel: 2
matrix:
unity: ["2021.3.41f1", "2022.3.39f1", "6000.0.12f1"] # Test with LTS
unity: ["2022.3.39f1", "6000.0.12f1"] # Test with LTS
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
@@ -44,7 +44,7 @@ jobs:
# Execute scripts: Export Package
# /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export
- name: Build Unity (.unitypacakge)
if: ${{ startsWith(matrix.unity, '2021') }} # only execute once
if: ${{ startsWith(matrix.unity, '2022') }} # only execute once
uses: Cysharp/Actions/.github/actions/unity-builder@main
env:
UNITY_EMAIL: ${{ steps.op-load-secret.outputs.UNITY_EMAIL }}

View File

@@ -160,7 +160,7 @@ UniTask provides three pattern of extension methods.
> Note: AssetBundleRequest has `asset` and `allAssets`, default await returns `asset`. If you want to get `allAssets`, you can use `AwaitForAllAssets()` method.
The type of `UniTask` can use utilities like `UniTask.WhenAll`, `UniTask.WhenAny`. They are like `Task.WhenAll`/`Task.WhenAny` but the return type is more useful. They return value tuples so you can deconstruct each result and pass multiple types.
The type of `UniTask` can use utilities like `UniTask.WhenAll`, `UniTask.WhenAny`, `UniTask.WhenEach`. They are like `Task.WhenAll`/`Task.WhenAny` but the return type is more useful. They return value tuples so you can deconstruct each result and pass multiple types.
```csharp
public async UniTaskVoid LoadManyAsync()
@@ -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)
@@ -716,6 +719,19 @@ await UniTaskAsyncEnumerable.EveryUpdate().ForEachAsync(_ =>
}, token);
```
`UniTask.WhenEach` that is similar to .NET 9's `Task.WhenEach` can consume new way for await multiple tasks.
```csharp
await foreach (var result in UniTask.WhenEach(task1, task2, task3))
{
// The result is of type WhenEachResult<T>.
// It contains either `T Result` or `Exception Exception`.
// You can check `IsCompletedSuccessfully` or `IsFaulted` to determine whether to access `.Result` or `.Exception`.
// If you want to throw an exception when `IsFaulted` and retrieve the result when successful, use `GetResult()`.
Debug.Log(result.GetResult());
}
```
UniTaskAsyncEnumerable implements asynchronous LINQ, similar to LINQ in `IEnumerable<T>` or Rx in `IObservable<T>`. All standard LINQ query operators can be applied to asynchronous streams. For example, the following code shows how to apply a Where filter to a button-click asynchronous stream that runs once every two clicks.
```csharp
@@ -1026,6 +1042,7 @@ Use UniTask type.
| `Task.Run` | `UniTask.RunOnThreadPool` |
| `Task.WhenAll` | `UniTask.WhenAll` |
| `Task.WhenAny` | `UniTask.WhenAny` |
| `Task.WhenEach` | `UniTask.WhenEach` |
| `Task.CompletedTask` | `UniTask.CompletedTask` |
| `Task.FromException` | `UniTask.FromException` |
| `Task.FromResult` | `UniTask.FromResult` |

View File

@@ -0,0 +1,69 @@
using Cysharp.Threading.Tasks;
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace NetCoreTests
{
public class WhenEachTest
{
[Fact]
public async Task Each()
{
var a = Delay(1, 3000);
var b = Delay(2, 1000);
var c = Delay(3, 2000);
var l = new List<int>();
await foreach (var item in UniTask.WhenEach(a, b, c))
{
l.Add(item.Result);
}
l.Should().Equal(2, 3, 1);
}
[Fact]
public async Task Error()
{
var a = Delay2(1, 3000);
var b = Delay2(2, 1000);
var c = Delay2(3, 2000);
var l = new List<WhenEachResult<int>>();
await foreach (var item in UniTask.WhenEach(a, b, c))
{
l.Add(item);
}
l[0].IsCompletedSuccessfully.Should().BeTrue();
l[0].IsFaulted.Should().BeFalse();
l[0].Result.Should().Be(2);
l[1].IsCompletedSuccessfully.Should().BeFalse();
l[1].IsFaulted.Should().BeTrue();
l[1].Exception.Message.Should().Be("ERROR");
l[2].IsCompletedSuccessfully.Should().BeTrue();
l[2].IsFaulted.Should().BeFalse();
l[2].Result.Should().Be(1);
}
async UniTask<int> Delay(int id, int sleep)
{
await Task.Delay(sleep);
return id;
}
async UniTask<int> Delay2(int id, int sleep)
{
await Task.Delay(sleep);
if (id == 3) throw new Exception("ERROR");
return id;
}
}
}

View File

@@ -184,6 +184,78 @@ namespace Cysharp.Threading.Tasks
return () => asyncAction(state).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T arg) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T> UnityAction<T>(Func<T, UniTaskVoid> asyncAction)
{
return (arg) => asyncAction(arg).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1> UnityAction<T0, T1>(Func<T0, T1, UniTaskVoid> asyncAction)
{
return (arg0, arg1) => asyncAction(arg0, arg1).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2> UnityAction<T0, T1, T2>(Func<T0, T1, T2, UniTaskVoid> asyncAction)
{
return (arg0, arg1, arg2) => asyncAction(arg0, arg1, arg2).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, T3 arg3) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2, T3> UnityAction<T0, T1, T2, T3>(Func<T0, T1, T2, T3, UniTaskVoid> asyncAction)
{
return (arg0, arg1, arg2, arg3) => asyncAction(arg0, arg1, arg2, arg3).Forget();
}
// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T arg, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T> UnityAction<T>(Func<T, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg) => asyncAction(arg, cancellationToken).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1> UnityAction<T0, T1>(Func<T0, T1, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg0, arg1) => asyncAction(arg0, arg1, cancellationToken).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2> UnityAction<T0, T1, T2>(Func<T0, T1, T2, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg0, arg1, arg2) => asyncAction(arg0, arg1, arg2, cancellationToken).Forget();
}
/// <summary>
/// Create async void(UniTaskVoid) UnityAction.
/// For example: onClick.AddListener(UniTask.UnityAction(async (T0 arg0, T1 arg1, T2 arg2, T3 arg3, CancellationToken cancellationToken) => { /* */ } ))
/// </summary>
public static UnityEngine.Events.UnityAction<T0, T1, T2, T3> UnityAction<T0, T1, T2, T3>(Func<T0, T1, T2, T3, CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{
return (arg0, arg1, arg2, arg3) => asyncAction(arg0, arg1, arg2, arg3, cancellationToken).Forget();
}
#endif
/// <summary>
@@ -202,6 +274,22 @@ namespace Cysharp.Threading.Tasks
return new UniTask<T>(new DeferPromise<T>(factory), 0);
}
/// <summary>
/// Defer the task creation just before call await.
/// </summary>
public static UniTask Defer<TState>(TState state, Func<TState, UniTask> factory)
{
return new UniTask(new DeferPromiseWithState<TState>(state, factory), 0);
}
/// <summary>
/// Defer the task creation just before call await.
/// </summary>
public static UniTask<TResult> Defer<TState, TResult>(TState state, Func<TState, UniTask<TResult>> factory)
{
return new UniTask<TResult>(new DeferPromiseWithState<TState, TResult>(state, factory), 0);
}
/// <summary>
/// Never complete.
/// </summary>
@@ -465,6 +553,93 @@ namespace Cysharp.Threading.Tasks
}
}
sealed class DeferPromiseWithState<TState> : IUniTaskSource
{
Func<TState, UniTask> factory;
TState argument;
UniTask task;
UniTask.Awaiter awaiter;
public DeferPromiseWithState(TState argument, Func<TState, UniTask> 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<object> continuation, object state, short token)
{
awaiter.SourceOnCompleted(continuation, state);
}
public UniTaskStatus UnsafeGetStatus()
{
return task.Status;
}
}
sealed class DeferPromiseWithState<TState, TResult> : IUniTaskSource<TResult>
{
Func<TState, UniTask<TResult>> factory;
TState argument;
UniTask<TResult> task;
UniTask<TResult>.Awaiter awaiter;
public DeferPromiseWithState(TState argument, Func<TState, UniTask<TResult>> 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<object> continuation, object state, short token)
{
awaiter.SourceOnCompleted(continuation, state);
}
public UniTaskStatus UnsafeGetStatus()
{
return task.Status;
}
}
sealed class NeverPromise<T> : IUniTaskSource<T>
{
static readonly Action<object> cancellationCallback = CancellationCallback;

View File

@@ -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>(T state, Func<T, bool> predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
return new UniTask(WaitUntilPromise<T>.Create(state, predicate, timing, cancellationToken, cancelImmediately, out var token), token);
}
public static UniTask WaitWhile(Func<bool> 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>(T state, Func<T, bool> predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
return new UniTask(WaitWhilePromise<T>.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<T> : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitUntilPromise<T>>
{
static TaskPool<WaitUntilPromise<T>> pool;
WaitUntilPromise<T> nextNode;
public ref WaitUntilPromise<T> NextNode => ref nextNode;
static WaitUntilPromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitUntilPromise<T>), () => pool.Size);
}
Func<T, bool> predicate;
T argument;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
UniTaskCompletionSourceCore<object> core;
WaitUntilPromise()
{
}
public static IUniTaskSource Create(T argument, Func<T, bool> 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<T>();
}
result.predicate = predicate;
result.argument = argument;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var promise = (WaitUntilPromise<T>)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<object> 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<WaitWhilePromise>
{
static TaskPool<WaitWhilePromise> 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<T> : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitWhilePromise<T>>
{
static TaskPool<WaitWhilePromise<T>> pool;
WaitWhilePromise<T> nextNode;
public ref WaitWhilePromise<T> NextNode => ref nextNode;
static WaitWhilePromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitWhilePromise<T>), () => pool.Size);
}
Func<T, bool> predicate;
T argument;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
UniTaskCompletionSourceCore<object> core;
WaitWhilePromise()
{
}
public static IUniTaskSource Create(T argument, Func<T, bool> 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<T>();
}
result.predicate = predicate;
result.argument = argument;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var promise = (WaitWhilePromise<T>)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<object> 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<WaitUntilCanceledPromise>
{
static TaskPool<WaitUntilCanceledPromise> pool;
@@ -443,7 +711,7 @@ namespace Cysharp.Threading.Tasks
result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault<U>();
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<U>();
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>

View File

@@ -0,0 +1,183 @@
using Cysharp.Threading.Tasks.Internal;
using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using System.Threading;
namespace Cysharp.Threading.Tasks
{
public partial struct UniTask
{
public static IUniTaskAsyncEnumerable<WhenEachResult<T>> WhenEach<T>(IEnumerable<UniTask<T>> tasks)
{
return new WhenEachEnumerable<T>(tasks);
}
public static IUniTaskAsyncEnumerable<WhenEachResult<T>> WhenEach<T>(params UniTask<T>[] tasks)
{
return new WhenEachEnumerable<T>(tasks);
}
}
public readonly struct WhenEachResult<T>
{
public T Result { get; }
public Exception Exception { get; }
//[MemberNotNullWhen(false, nameof(Exception))]
public bool IsCompletedSuccessfully => Exception == null;
//[MemberNotNullWhen(true, nameof(Exception))]
public bool IsFaulted => Exception != null;
public WhenEachResult(T result)
{
this.Result = result;
this.Exception = null;
}
public WhenEachResult(Exception exception)
{
if (exception == null) throw new ArgumentNullException(nameof(exception));
this.Result = default;
this.Exception = exception;
}
public void TryThrow()
{
if (IsFaulted)
{
ExceptionDispatchInfo.Capture(Exception).Throw();
}
}
public T GetResult()
{
if (IsFaulted)
{
ExceptionDispatchInfo.Capture(Exception).Throw();
}
return Result;
}
public override string ToString()
{
if (IsCompletedSuccessfully)
{
return Result?.ToString() ?? "";
}
else
{
return $"Exception{{{Exception.Message}}}";
}
}
}
internal enum WhenEachState : byte
{
NotRunning,
Running,
Completed
}
internal sealed class WhenEachEnumerable<T> : IUniTaskAsyncEnumerable<WhenEachResult<T>>
{
IEnumerable<UniTask<T>> source;
public WhenEachEnumerable(IEnumerable<UniTask<T>> source)
{
this.source = source;
}
public IUniTaskAsyncEnumerator<WhenEachResult<T>> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new Enumerator(source, cancellationToken);
}
sealed class Enumerator : IUniTaskAsyncEnumerator<WhenEachResult<T>>
{
readonly IEnumerable<UniTask<T>> source;
CancellationToken cancellationToken;
Channel<WhenEachResult<T>> channel;
IUniTaskAsyncEnumerator<WhenEachResult<T>> channelEnumerator;
int completeCount;
WhenEachState state;
public Enumerator(IEnumerable<UniTask<T>> source, CancellationToken cancellationToken)
{
this.source = source;
this.cancellationToken = cancellationToken;
}
public WhenEachResult<T> Current => channelEnumerator.Current;
public UniTask<bool> MoveNextAsync()
{
cancellationToken.ThrowIfCancellationRequested();
if (state == WhenEachState.NotRunning)
{
state = WhenEachState.Running;
channel = Channel.CreateSingleConsumerUnbounded<WhenEachResult<T>>();
channelEnumerator = channel.Reader.ReadAllAsync().GetAsyncEnumerator(cancellationToken);
if (source is UniTask<T>[] array)
{
ConsumeAll(this, array, array.Length);
}
else
{
using (var rentArray = ArrayPoolUtil.Materialize(source))
{
ConsumeAll(this, rentArray.Array, rentArray.Length);
}
}
}
return channelEnumerator.MoveNextAsync();
}
static void ConsumeAll(Enumerator self, UniTask<T>[] array, int length)
{
for (int i = 0; i < length; i++)
{
RunWhenEachTask(self, array[i], length).Forget();
}
}
static async UniTaskVoid RunWhenEachTask(Enumerator self, UniTask<T> task, int length)
{
try
{
var result = await task;
self.channel.Writer.TryWrite(new WhenEachResult<T>(result));
}
catch (Exception ex)
{
self.channel.Writer.TryWrite(new WhenEachResult<T>(ex));
}
if (Interlocked.Increment(ref self.completeCount) == length)
{
self.state = WhenEachState.Completed;
self.channel.Writer.TryComplete();
}
}
public async UniTask DisposeAsync()
{
if (channelEnumerator != null)
{
await channelEnumerator.DisposeAsync();
}
if (state != WhenEachState.Completed)
{
state = WhenEachState.Completed;
channel.Writer.TryComplete(new OperationCanceledException());
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7cac24fdda5112047a1cd3dd66b542c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,386 @@
// AsyncInstantiateOperation was added since Unity 2022.3.20 / 2023.3.0b7
#if UNITY_2022_3 && !(UNITY_2022_3_0 || UNITY_2022_3_1 || UNITY_2022_3_2 || UNITY_2022_3_3 || UNITY_2022_3_4 || UNITY_2022_3_5 || UNITY_2022_3_6 || UNITY_2022_3_7 || UNITY_2022_3_8 || UNITY_2022_3_9 || UNITY_2022_3_10 || UNITY_2022_3_11 || UNITY_2022_3_12 || UNITY_2022_3_13 || UNITY_2022_3_14 || UNITY_2022_3_15 || UNITY_2022_3_16 || UNITY_2022_3_17 || UNITY_2022_3_18 || UNITY_2022_3_19)
#define UNITY_2022_SUPPORT
#endif
#if UNITY_2022_SUPPORT || UNITY_2023_3_OR_NEWER
using Cysharp.Threading.Tasks.Internal;
using System;
using System.Threading;
using UnityEngine;
namespace Cysharp.Threading.Tasks
{
public static class AsyncInstantiateOperationExtensions
{
// AsyncInstantiateOperation<T> has GetAwaiter so no need to impl
// public static UniTask<T[]>.Awaiter GetAwaiter<T>(this AsyncInstantiateOperation<T> operation) where T : Object
public static UniTask<UnityEngine.Object[]> WithCancellation<T>(this AsyncInstantiateOperation asyncOperation, CancellationToken cancellationToken)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
}
public static UniTask<UnityEngine.Object[]> WithCancellation<T>(this AsyncInstantiateOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<UnityEngine.Object[]> ToUniTask(this AsyncInstantiateOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object[]>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.Result);
return new UniTask<UnityEngine.Object[]>(AsyncInstantiateOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
}
public static UniTask<T[]> WithCancellation<T>(this AsyncInstantiateOperation<T> asyncOperation, CancellationToken cancellationToken)
where T : UnityEngine.Object
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
}
public static UniTask<T[]> WithCancellation<T>(this AsyncInstantiateOperation<T> asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
where T : UnityEngine.Object
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<T[]> ToUniTask<T>(this AsyncInstantiateOperation<T> asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
where T : UnityEngine.Object
{
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<T[]>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.Result);
return new UniTask<T[]>(AsyncInstantiateOperationConfiguredSource<T>.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
}
sealed class AsyncInstantiateOperationConfiguredSource : IUniTaskSource<UnityEngine.Object[]>, IPlayerLoopItem, ITaskPoolNode<AsyncInstantiateOperationConfiguredSource>
{
static TaskPool<AsyncInstantiateOperationConfiguredSource> pool;
AsyncInstantiateOperationConfiguredSource nextNode;
public ref AsyncInstantiateOperationConfiguredSource NextNode => ref nextNode;
static AsyncInstantiateOperationConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncInstantiateOperationConfiguredSource), () => pool.Size);
}
AsyncInstantiateOperation asyncOperation;
IProgress<float> progress;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
bool completed;
UniTaskCompletionSourceCore<UnityEngine.Object[]> core;
Action<AsyncOperation> continuationAction;
AsyncInstantiateOperationConfiguredSource()
{
continuationAction = Continuation;
}
public static IUniTaskSource<UnityEngine.Object[]> Create(AsyncInstantiateOperation asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<UnityEngine.Object[]>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new AsyncInstantiateOperationConfiguredSource();
}
result.asyncOperation = asyncOperation;
result.progress = progress;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (AsyncInstantiateOperationConfiguredSource)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version;
return result;
}
public UnityEngine.Object[] GetResult(short token)
{
try
{
return core.GetResult(token);
}
finally
{
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
{
TryReturn();
}
else
{
TaskTracker.RemoveTracking(this);
}
}
}
void IUniTaskSource.GetResult(short token)
{
GetResult(token);
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public bool MoveNext()
{
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
return false;
}
if (progress != null)
{
progress.Report(asyncOperation.progress);
}
if (asyncOperation.isDone)
{
core.TrySetResult(asyncOperation.Result);
return false;
}
return true;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default;
progress = default;
cancellationToken = default;
cancellationTokenRegistration.Dispose();
cancelImmediately = default;
return pool.TryPush(this);
}
void Continuation(AsyncOperation _)
{
if (completed)
{
return;
}
completed = true;
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
}
else
{
core.TrySetResult(asyncOperation.Result);
}
}
}
sealed class AsyncInstantiateOperationConfiguredSource<T> : IUniTaskSource<T[]>, IPlayerLoopItem, ITaskPoolNode<AsyncInstantiateOperationConfiguredSource<T>>
where T : UnityEngine.Object
{
static TaskPool<AsyncInstantiateOperationConfiguredSource<T>> pool;
AsyncInstantiateOperationConfiguredSource<T> nextNode;
public ref AsyncInstantiateOperationConfiguredSource<T> NextNode => ref nextNode;
static AsyncInstantiateOperationConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncInstantiateOperationConfiguredSource<T>), () => pool.Size);
}
AsyncInstantiateOperation<T> asyncOperation;
IProgress<float> progress;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool cancelImmediately;
bool completed;
UniTaskCompletionSourceCore<T[]> core;
Action<AsyncOperation> continuationAction;
AsyncInstantiateOperationConfiguredSource()
{
continuationAction = Continuation;
}
public static IUniTaskSource<T[]> Create(AsyncInstantiateOperation<T> asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<T[]>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new AsyncInstantiateOperationConfiguredSource<T>();
}
result.asyncOperation = asyncOperation;
result.progress = progress;
result.cancellationToken = cancellationToken;
result.cancelImmediately = cancelImmediately;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (AsyncInstantiateOperationConfiguredSource<T>)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version;
return result;
}
public T[] GetResult(short token)
{
try
{
return core.GetResult(token);
}
finally
{
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
{
TryReturn();
}
else
{
TaskTracker.RemoveTracking(this);
}
}
}
void IUniTaskSource.GetResult(short token)
{
GetResult(token);
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public bool MoveNext()
{
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
return false;
}
if (progress != null)
{
progress.Report(asyncOperation.progress);
}
if (asyncOperation.isDone)
{
core.TrySetResult(asyncOperation.Result);
return false;
}
return true;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default;
progress = default;
cancellationToken = default;
cancellationTokenRegistration.Dispose();
cancelImmediately = default;
return pool.TryPush(this);
}
void Continuation(AsyncOperation _)
{
if (completed)
{
return;
}
completed = true;
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
}
else
{
core.TrySetResult(asyncOperation.Result);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8321f4244edfdcd4798b4fcc92a736c9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -2,7 +2,7 @@
"name": "com.cysharp.unitask",
"displayName": "UniTask",
"author": { "name": "Cysharp, Inc.", "url": "https://cysharp.co.jp/en/" },
"version": "2.5.6",
"version": "2.5.10",
"unity": "2018.4",
"description": "Provides an efficient async/await integration to Unity.",
"keywords": [ "async/await", "async", "Task", "UniTask" ],

View File

@@ -18,6 +18,7 @@ using UnityEngine.SceneManagement;
using UnityEngine.Rendering;
using System.IO;
using System.Linq.Expressions;
using UnityEngine.Events;
@@ -119,7 +120,28 @@ public class AsyncMessageBroker<T> : IDisposable
connection.Dispose();
}
}
public class WhenEachTest
{
public async UniTask Each()
{
var a = Delay(1, 3000);
var b = Delay(2, 1000);
var c = Delay(3, 2000);
var l = new List<int>();
await foreach (var item in UniTask.WhenEach(a, b, c))
{
Debug.Log(item.Result);
}
}
async UniTask<int> Delay(int id, int sleep)
{
await UniTask.Delay(sleep);
return id;
}
}
public class SandboxMain : MonoBehaviour
{
@@ -147,6 +169,19 @@ public class SandboxMain : MonoBehaviour
Debug.Log("Again");
// var foo = InstantiateAsync<SandboxMain>(this).ToUniTask();
// var tako = await foo;
//UnityAction action;
return 10;
}
@@ -557,6 +592,7 @@ public class SandboxMain : MonoBehaviour
async UniTaskVoid Start()
{
await new WhenEachTest().Each();
// UniTask.Delay(TimeSpan.FromSeconds(1)).TimeoutWithoutException

View File

@@ -13,7 +13,7 @@ OcclusionCullingSettings:
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
serializedVersion: 10
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
@@ -43,7 +43,6 @@ RenderSettings:
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
@@ -66,9 +65,6 @@ LightmapSettings:
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 0
@@ -719,7 +715,6 @@ GameObject:
- component: {fileID: 1556045507}
- component: {fileID: 1556045506}
- component: {fileID: 1556045505}
- component: {fileID: 1556045509}
m_Layer: 0
m_Name: Canvas
m_TagString: Untagged
@@ -812,18 +807,6 @@ RectTransform:
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0, y: 0}
--- !u!114 &1556045509
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1556045504}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a478e5f6126dc184ca902adfb35401b4, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &1584557231
GameObject:
m_ObjectHideFlags: 0

View File

@@ -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 () =>
{

View File

@@ -1,4 +1,6 @@
using Cysharp.Threading.Tasks;
#pragma warning disable CS0618
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Linq;
using FluentAssertions;
using NUnit.Framework;

View File

@@ -1,4 +1,6 @@
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
#pragma warning disable CS0618
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using UnityEngine;