Compare commits

..

30 Commits

Author SHA1 Message Date
neuecc
bbfb8354bb 2.0.17 2020-06-14 15:32:37 +09:00
Yoshifumi Kawai
2f68e47443 Merge pull request #92 from yashihei/fix-cancellation-token
Fixed CancelationToken not being passed correctly
2020-06-14 15:08:37 +09:00
yashihei
89339ffb29 Fixed CancelationToken not being passed correctly 2020-06-14 08:24:25 +09:00
neuecc
0535862fe6 Merge remote-tracking branch 'origin/master' 2020-06-13 18:58:56 +09:00
neuecc
a9e5fd4589 Add UniTaskAsyncEnumerable.Subscribe 2020-06-13 18:58:43 +09:00
Yoshifumi Kawai
a3f3a28ea1 Update README.md 2020-06-12 17:18:31 +09:00
Yoshifumi Kawai
59020df965 Update README.md 2020-06-12 17:18:22 +09:00
Yoshifumi Kawai
ac01be79bf Update README.md 2020-06-12 16:59:14 +09:00
neuecc
de5951f208 lock q 2020-06-12 10:46:29 +09:00
neuecc
ded9a561db 2.0.16 2020-06-12 03:16:06 +09:00
neuecc
a2c18eb343 AsyncReactiveProperty document 2020-06-11 23:50:13 +09:00
neuecc
0b27c3a342 Merge remote-tracking branch 'origin/master' 2020-06-11 23:32:00 +09:00
neuecc
9c86cfb508 Add IDisposable.AddTo(cancellationToken) 2020-06-11 23:31:55 +09:00
Yoshifumi Kawai
7e5e6ed6c2 Update README.md 2020-06-11 20:21:51 +09:00
Yoshifumi Kawai
d081e5f40b Update README.md 2020-06-11 17:48:41 +09:00
neuecc
344ae0738c docs: update TOC 2020-06-11 08:44:42 +00:00
neuecc
1b553f67b0 compare table 2020-06-11 17:44:22 +09:00
neuecc
bf0adad427 test for IL2CPP bug 2020-06-11 17:10:29 +09:00
neuecc
11ca42a527 Merge remote-tracking branch 'origin/master' 2020-06-11 16:43:11 +09:00
neuecc
4898e4c7bf Add (I/ReadOnly)AsyncReactiveProperty.WaitAsync 2020-06-11 16:42:59 +09:00
Yoshifumi Kawai
d494e0b9e3 Merge pull request #88 from guitarrapc/master
chore: stop run build-unity via fork PR
2020-06-11 14:35:12 +09:00
Ikiru Yoshizaki
be26ab249b chore: strict filter 2020-06-10 18:17:53 +09:00
Ikiru Yoshizaki
611d8d5513 chore: filter build-unity to be run on Cysharp only 2020-06-10 18:06:50 +09:00
Yoshifumi Kawai
95c93b7c3d Merge pull request #87 from guitarrapc/master
feat: Android il2cpp support
2020-06-10 17:07:56 +09:00
Ikiru Yoshizaki
1dd0c49eec faet: android il2cpp 2020-06-10 13:06:45 +09:00
neuecc
5d8e0e61ad workaround for IL2CPP bug, back to zero allocation 2020-06-10 11:36:58 +09:00
neuecc
478126e256 v 2020-06-09 15:06:34 +09:00
neuecc
80704e489d Merge branch 'fix-il2cpp' 2020-06-09 14:52:23 +09:00
neuecc
3c0aa03643 if def 2020-06-09 14:52:04 +09:00
neuecc
37cd00d347 2.0.15? 2020-06-09 12:16:28 +09:00
20 changed files with 974 additions and 1855 deletions

View File

@@ -26,9 +26,10 @@ jobs:
- run: dotnet test -c Debug ./src/UniTask.NetCoreTests/UniTask.NetCoreTests.csproj
build-unity:
if: "(github.event == 'push' && github.repository_owner == 'Cysharp') || startsWith(github.event.pull_request.head.label, 'Cysharp:')"
strategy:
matrix:
unity: ['2019.3.9f1', '2020.1.0b5']
unity: ["2019.3.9f1", "2020.1.0b5"]
include:
- unity: 2019.3.9f1
license: UNITY_2019_3

View File

@@ -2,7 +2,7 @@ UniTask
===
[![GitHub Actions](https://github.com/Cysharp/UniTask/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/UniTask/actions) [![Releases](https://img.shields.io/github/release/Cysharp/UniTask.svg)](https://github.com/Cysharp/UniTask/releases)
Provides an efficient async/await integration to Unity.
Provides an efficient allocation free async/await integration to Unity.
* Struct based `UniTask<T>` and custom AsyncMethodBuilder to achive zero allocation
* All Unity AsyncOperations and Coroutine to awaitable
@@ -13,6 +13,9 @@ Provides an efficient async/await integration to Unity.
* TaskTracker window to prevent memory leak
* Highly compatible behaviour with Task/ValueTask/IValueTaskSource
Techinical details, see blog post: [UniTask v2 — Zero Allocation async/await for Unity, with Asynchronous LINQ
](https://medium.com/@neuecc/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd)
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents
@@ -29,6 +32,7 @@ Provides an efficient async/await integration to Unity.
- [Awaitable Events](#awaitable-events)
- [Channel](#channel)
- [For Unit Testing](#for-unit-testing)
- [Compare with Standard Task API](#compare-with-standard-task-api)
- [Pooling Configuration](#pooling-configuration)
- [API References](#api-references)
- [UPM Package](#upm-package)
@@ -346,7 +350,7 @@ It indicates when to run, you can check [PlayerLoopList.md](https://gist.github.
> UniTask.Yield(without CancellationToken) is a special type, returns `YieldAwaitable` and run on YieldRunner. It is most lightweight and faster.
AsyncOperation is returned from native timing. For example, await `SceneManager.LoadSceneAsync` is returned from `EarlyUpdate.UpdatePreloading` and after called, loaded scene called from `EarlyUpdate.ScriptRunDelayedStartupFrame`. Also `await UnityWebRequest` is returned from `EarlyUpdate.ExecuteMainThreadJobs`.
AsyncOperation is returned from native timing. For example, await `SceneManager.LoadSceneAsync` is returned from `EarlyUpdate.UpdatePreloading` and after called, loaded scene's `Start` called from `EarlyUpdate.ScriptRunDelayedStartupFrame`. Also `await UnityWebRequest` is returned from `EarlyUpdate.ExecuteMainThreadJobs`.
In UniTask, await directly and `WithCancellation` use native timing, `ToUniTask` use specified timing. This is usually not a particular problem, but with `LoadSceneAsync`, causes different order of Start and continuation after await. so recommend not to use `LoadSceneAsync.ToUniTask`.
@@ -583,9 +587,30 @@ await this.GetAsyncMoveTrigger().ForEachAsync(axisEventData =>
});
```
`AsyncReactiveProperty`, `AsyncReadOnlyReactiveProperty` is UniTask version of UniTask's ReactiveProperty.
`AsyncReactiveProperty`, `AsyncReadOnlyReactiveProperty` is UniTask version of UniTask's ReactiveProperty. `BindTo` extension method of `IUniTaskAsyncEnumerable<T>` for binding asynchronous stream values to Unity components(Text/Selectable/TMP/Text).
`BindTo` extension method of `IUniTaskAsyncEnumerable<T>` for binding asynchronous stream values to Unity components(Text/Selectable/TMP/Text).
```csharp
var rp = new AsyncReactiveProperty<int>(99);
// AsyncReactiveProperty itself is IUniTaskAsyncEnumerable, you can query by LINQ
rp.ForEachAsync(x =>
{
Debug.Log(x);
}, this.GetCancellationTokenOnDestroy()).Forget();
rp.Value = 10; // push 10 to all subscriber
rp.Value = 11; // push 11 to all subscriber
// WithoutCurrent ignore initial value
// BindTo bind stream value to unity components.
rp.WithoutCurrent().BindTo(this.textComponent);
await rp.WaitAsync(); // wait until next value set
// also exists ToReadOnlyReactiveProperty
var rp2 = new AsyncReactiveProperty<int>(99);
var rorp = rp.CombineLatest(rp2, (x, y) => (x, y)).ToReadOnlyReactiveProperty();
```
A pull-type asynchronous stream does not get the next values until the asynchronous processing in the sequence is complete. This could spill data from push-type events such as buttons.
@@ -679,6 +704,52 @@ public IEnumerator DelayIgnore() => UniTask.ToCoroutine(async () =>
UniTask itself's unit test is written by Unity Test Runner and [Cysharp/RuntimeUnitTestToolkit](https://github.com/Cysharp/RuntimeUnitTestToolkit) to check on CI and IL2CPP working.
Compare with Standard Task API
---
UniTask has many standard Task-like APIs. This table shows what is the alternative apis.
Use standard type.
| .NET Type | UniTask Type |
| --- | --- |
| `IProgress<T>` | --- |
| `CancellationToken` | --- |
| `CancellationTokenSource` | --- |
Use UniTask type.
| .NET Type | UniTask Type |
| --- | --- |
| `Task`/`ValueTask` | `UniTask` |
| `Task<T>`/`ValueTask<T>` | `UniTask<T>` |
| `async void` | `async UniTaskVoid` |
| `+= async () => { }` | `UniTask.Void`, `UniTask.Action`, `UniTask.UnityAction` |
| --- | `UniTaskCompletionSource` |
| `TaskCompletionSource<T>` | `UniTaskCompletionSource<T>`/`AutoResetUniTaskCompletionSource<T>` |
| `ManualResetValueTaskSourceCore<T>` | `UniTaskCompletionSourceCore<T>` |
| `IValueTaskSource` | `IUniTaskSource` |
| `IValueTaskSource<T>` | `IUniTaskSource<T>` |
| `ValueTask.IsCompleted` | `UniTask.Status.IsCompleted()` |
| `ValueTask<T>.IsCompleted` | `UniTask<T>.Status.IsCompleted()` |
| `new Progress<T>` | `Progress.Create<T>` |
| `CancellationToken.Register(UnsafeRegister)` | `CancellationToken.RegisterWithoutCaptureExecutionContext` |
| `CancellationTokenSource.CancelAfter` | `CancellationTokenSource.CancelAfterSlim` |
| `Channel.CreateUnbounded<T>(false){ SingleReader = true }` | `Channel.CreateSingleConsumerUnbounded<T>` |
| `IAsyncEnumerable<T>` | `IUniTaskAsyncEnumerable<T>` |
| `IAsyncEnumerator<T>` | `IUniTaskAsyncEnumerator<T>` |
| `IAsyncDisposable` | `IUniTaskAsyncDisposable` |
| `Task.Delay` | `UniTask.Delay` |
| `Task.Yield` | `UniTask.Yield` |
| `Task.Run` | `UniTask.Run` |
| `Task.WhenAll` | `UniTask.WhenAll` |
| `Task.WhenAny` | `UniTask.WhenAny` |
| `Task.CompletedTask` | `UniTask.CompletedTask` |
| `Task.FromException` | `UniTask.FromException` |
| `Task.FromResult` | `UniTask.FromResult` |
| `Task.FromCanceled` | `UniTask.FromCanceled` |
| `Task.ContinueWith` | `UniTask.ContinueWith` |
| `TaskScheduler.UnobservedTaskException` | `UniTaskScheduler.UnobservedTaskException` |
Pooling Configuration
---
UniTask is aggressively caching async promise object to achive zero allocation. In default, cache all promises but you can configure `TaskPool.SetMaxPoolSize` to your value, the value indicates cache size per type. `TaskPool.GetCacheSizeInfo` returns current cached object in pool.
@@ -690,6 +761,8 @@ foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
}
```
> In UnityEditor profiler shows allocation of compiler generated AsyncStateMachine but it only occurs in debug(development) build. C# Compiler generate AsyncStateMachine as class on Debug build and as struct on Release build.
API References
---
UniTask's API References is hosted at [cysharp.github.io/UniTask](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.html) by [DocFX](https://dotnet.github.io/docfx/) and [Cysharp/DocfXTemplate](https://github.com/Cysharp/DocfxTemplate).
@@ -708,7 +781,7 @@ After Unity 2019.3.4f1, Unity 2020.1a21, that support path query parameter of gi
or add `"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask"` to `Packages/manifest.json`.
If you want to set a target version, UniTask is using `*.*.*` release tag so you can specify a version like `#2.0.13`. For example `https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.0.13`.
If you want to set a target version, UniTask is using `*.*.*` release tag so you can specify a version like `#2.0.15`. For example `https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.0.15`.
### Install via OpenUPM

View File

@@ -112,6 +112,85 @@ namespace NetCoreTests
state.Value.Should().Be(20);
}
[Fact]
public async Task WaitAsyncTest()
{
var rp = new AsyncReactiveProperty<int>(128);
var f = await rp.FirstAsync();
f.Should().Be(128);
{
var t = rp.WaitAsync();
rp.Value = 99;
rp.Value = 100;
var v = await t;
v.Should().Be(99);
}
{
var t = rp.WaitAsync();
rp.Value = 99;
rp.Value = 100;
var v = await t;
v.Should().Be(99);
}
}
[Fact]
public async Task WaitAsyncCancellationTest()
{
var cts = new CancellationTokenSource();
var rp = new AsyncReactiveProperty<int>(128);
var t = rp.WaitAsync(cts.Token);
cts.Cancel();
rp.Value = 99;
rp.Value = 100;
await Assert.ThrowsAsync<OperationCanceledException>(async () => { await t; });
}
[Fact]
public async Task ReadOnlyWaitAsyncTest()
{
var rp = new AsyncReactiveProperty<int>(128);
var rrp = rp.ToReadOnlyAsyncReactiveProperty(CancellationToken.None);
var t = rrp.WaitAsync();
rp.Value = 99;
rp.Value = 100;
var v = await t;
v.Should().Be(99);
}
[Fact]
public async Task ReadOnlyWaitAsyncCancellationTest()
{
var cts = new CancellationTokenSource();
var rp = new AsyncReactiveProperty<int>(128);
var rrp = rp.ToReadOnlyAsyncReactiveProperty(CancellationToken.None);
var t = rrp.WaitAsync(cts.Token);
cts.Cancel();
rp.Value = 99;
rp.Value = 100;
await Assert.ThrowsAsync<OperationCanceledException>(async () => { await t; });
}
}

View File

@@ -7,6 +7,7 @@ namespace Cysharp.Threading.Tasks
{
T Value { get; }
IUniTaskAsyncEnumerable<T> WithoutCurrent();
UniTask<T> WaitAsync(CancellationToken cancellationToken = default);
}
public interface IAsyncReactiveProperty<T> : IReadOnlyAsyncReactiveProperty<T>
@@ -69,6 +70,11 @@ namespace Cysharp.Threading.Tasks
return latestValue?.ToString();
}
public UniTask<T> WaitAsync(CancellationToken cancellationToken = default)
{
return new UniTask<T>(WaitAsyncSource.Create(this, cancellationToken, out var token), token);
}
static bool isValueType;
static AsyncReactiveProperty()
@@ -76,7 +82,143 @@ namespace Cysharp.Threading.Tasks
isValueType = typeof(T).IsValueType;
}
class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T>
sealed class WaitAsyncSource : IUniTaskSource<T>, ITriggerHandler<T>, ITaskPoolNode<WaitAsyncSource>
{
static Action<object> cancellationCallback = CancellationCallback;
static TaskPool<WaitAsyncSource> pool;
WaitAsyncSource ITaskPoolNode<WaitAsyncSource>.NextNode { get; set; }
static WaitAsyncSource()
{
TaskPool.RegisterSizeGetter(typeof(WaitAsyncSource), () => pool.Size);
}
AsyncReactiveProperty<T> parent;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<T> core;
WaitAsyncSource()
{
}
public static IUniTaskSource<T> Create(AsyncReactiveProperty<T> parent, CancellationToken cancellationToken, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new WaitAsyncSource();
}
result.parent = parent;
result.cancellationToken = cancellationToken;
if (cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, result);
}
result.parent.triggerEvent.Add(result);
TaskTracker.TrackActiveTask(result, 3);
token = result.core.Version;
return result;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
cancellationTokenRegistration.Dispose();
cancellationTokenRegistration = default;
parent.triggerEvent.Remove(this);
parent = null;
cancellationToken = default;
return pool.TryPush(this);
}
~WaitAsyncSource()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
static void CancellationCallback(object state)
{
var self = (WaitAsyncSource)state;
self.OnCanceled(self.cancellationToken);
}
// IUniTaskSource
public T GetResult(short token)
{
try
{
return core.GetResult(token);
}
finally
{
TryReturn();
}
}
void IUniTaskSource.GetResult(short token)
{
GetResult(token);
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
// ITriggerHandler
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
public void OnCanceled(CancellationToken cancellationToken)
{
core.TrySetCanceled(cancellationToken);
}
public void OnCompleted()
{
// Complete as Cancel.
core.TrySetCanceled(CancellationToken.None);
}
public void OnError(Exception ex)
{
core.TrySetException(ex);
}
public void OnNext(T value)
{
core.TrySetResult(value);
}
}
sealed class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T>
{
readonly AsyncReactiveProperty<T> parent;
@@ -253,6 +395,11 @@ namespace Cysharp.Threading.Tasks
return latestValue?.ToString();
}
public UniTask<T> WaitAsync(CancellationToken cancellationToken = default)
{
return new UniTask<T>(WaitAsyncSource.Create(this, cancellationToken, out var token), token);
}
static bool isValueType;
static ReadOnlyAsyncReactiveProperty()
@@ -260,7 +407,143 @@ namespace Cysharp.Threading.Tasks
isValueType = typeof(T).IsValueType;
}
class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T>
sealed class WaitAsyncSource : IUniTaskSource<T>, ITriggerHandler<T>, ITaskPoolNode<WaitAsyncSource>
{
static Action<object> cancellationCallback = CancellationCallback;
static TaskPool<WaitAsyncSource> pool;
WaitAsyncSource ITaskPoolNode<WaitAsyncSource>.NextNode { get; set; }
static WaitAsyncSource()
{
TaskPool.RegisterSizeGetter(typeof(WaitAsyncSource), () => pool.Size);
}
ReadOnlyAsyncReactiveProperty<T> parent;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<T> core;
WaitAsyncSource()
{
}
public static IUniTaskSource<T> Create(ReadOnlyAsyncReactiveProperty<T> parent, CancellationToken cancellationToken, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new WaitAsyncSource();
}
result.parent = parent;
result.cancellationToken = cancellationToken;
if (cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, result);
}
result.parent.triggerEvent.Add(result);
TaskTracker.TrackActiveTask(result, 3);
token = result.core.Version;
return result;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
cancellationTokenRegistration.Dispose();
cancellationTokenRegistration = default;
parent.triggerEvent.Remove(this);
parent = null;
cancellationToken = default;
return pool.TryPush(this);
}
~WaitAsyncSource()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
static void CancellationCallback(object state)
{
var self = (WaitAsyncSource)state;
self.OnCanceled(self.cancellationToken);
}
// IUniTaskSource
public T GetResult(short token)
{
try
{
return core.GetResult(token);
}
finally
{
TryReturn();
}
}
void IUniTaskSource.GetResult(short token)
{
GetResult(token);
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
// ITriggerHandler
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
public void OnCanceled(CancellationToken cancellationToken)
{
core.TrySetCanceled(cancellationToken);
}
public void OnCompleted()
{
// Complete as Cancel.
core.TrySetCanceled(CancellationToken.None);
}
public void OnError(Exception ex)
{
core.TrySetException(ex);
}
public void OnNext(T value)
{
core.TrySetResult(value);
}
}
sealed class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T>
{
readonly ReadOnlyAsyncReactiveProperty<T> parent;

View File

@@ -9,6 +9,7 @@ namespace Cysharp.Threading.Tasks
public static class CancellationTokenExtensions
{
static readonly Action<object> cancellationTokenCallback = Callback;
static readonly Action<object> disposeCallback = DisposeCallback;
public static (UniTask, CancellationTokenRegistration) ToUniTask(this CancellationToken cancellationToken)
{
@@ -75,6 +76,17 @@ namespace Cysharp.Threading.Tasks
}
}
}
public static CancellationTokenRegistration AddTo(this IDisposable disposable, CancellationToken cancellationToken)
{
return cancellationToken.RegisterWithoutCaptureExecutionContext(disposeCallback, disposable);
}
static void DisposeCallback(object state)
{
var d = (IDisposable)state;
d.Dispose();
}
}
public struct CancellationTokenAwaitable

View File

@@ -41,7 +41,12 @@ namespace Cysharp.Threading.Tasks.CompilerServices
// runner is finished, return first.
if (runner != null)
{
#if ENABLE_IL2CPP
// workaround for IL2CPP bug.
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, runner.ReturnAction);
#else
runner.Return();
#endif
runner = null;
}
@@ -56,7 +61,12 @@ namespace Cysharp.Threading.Tasks.CompilerServices
// runner is finished, return.
if (runner != null)
{
#if ENABLE_IL2CPP
// workaround for IL2CPP bug.
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, runner.ReturnAction);
#else
runner.Return();
#endif
runner = null;
}
}

View File

@@ -12,6 +12,10 @@ namespace Cysharp.Threading.Tasks.CompilerServices
{
Action MoveNext { get; }
void Return();
#if ENABLE_IL2CPP
Action ReturnAction { get; }
#endif
}
internal interface IStateMachineRunnerPromise : IUniTaskSource
@@ -32,6 +36,7 @@ namespace Cysharp.Threading.Tasks.CompilerServices
internal static class StateMachineUtility
{
// Get AsyncStateMachine internal state to check IL2CPP bug
public static int GetState(IAsyncStateMachine stateMachine)
{
var info = stateMachine.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
@@ -45,6 +50,10 @@ namespace Cysharp.Threading.Tasks.CompilerServices
{
static TaskPool<AsyncUniTaskVoid<TStateMachine>> pool;
#if ENABLE_IL2CPP
public Action ReturnAction { get; }
#endif
TStateMachine stateMachine;
public Action MoveNext { get; }
@@ -52,6 +61,9 @@ namespace Cysharp.Threading.Tasks.CompilerServices
public AsyncUniTaskVoid()
{
MoveNext = Run;
#if ENABLE_IL2CPP
ReturnAction = Return;
#endif
}
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunner runnerFieldRef)
@@ -113,15 +125,20 @@ namespace Cysharp.Threading.Tasks.CompilerServices
{
static TaskPool<AsyncUniTask<TStateMachine>> pool;
TStateMachine stateMachine;
#if ENABLE_IL2CPP
readonly Action returnDelegate;
#endif
public Action MoveNext { get; }
TStateMachine stateMachine;
UniTaskCompletionSourceCore<AsyncUnit> core;
AsyncUniTask()
{
MoveNext = Run;
#if ENABLE_IL2CPP
returnDelegate = Return;
#endif
}
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunnerPromise runnerPromiseFieldRef)
@@ -143,6 +160,14 @@ namespace Cysharp.Threading.Tasks.CompilerServices
TaskPool.RegisterSizeGetter(typeof(AsyncUniTask<TStateMachine>), () => pool.Size);
}
void Return()
{
TaskTracker.RemoveTracking(this);
core.Reset();
stateMachine = default;
pool.TryPush(this);
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
@@ -188,7 +213,12 @@ namespace Cysharp.Threading.Tasks.CompilerServices
}
finally
{
#if ENABLE_IL2CPP
// workaround for IL2CPP bug.
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, returnDelegate);
#else
TryReturn();
#endif
}
}
@@ -224,15 +254,21 @@ namespace Cysharp.Threading.Tasks.CompilerServices
{
static TaskPool<AsyncUniTask<TStateMachine, T>> pool;
TStateMachine stateMachine;
#if ENABLE_IL2CPP
readonly Action returnDelegate;
#endif
public Action MoveNext { get; }
TStateMachine stateMachine;
UniTaskCompletionSourceCore<T> core;
AsyncUniTask()
{
MoveNext = Run;
#if ENABLE_IL2CPP
returnDelegate = Return;
#endif
}
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunnerPromise<T> runnerPromiseFieldRef)
@@ -245,10 +281,8 @@ namespace Cysharp.Threading.Tasks.CompilerServices
runnerPromiseFieldRef = result; // set runner before copied.
result.stateMachine = stateMachine; // copy struct StateMachine(in release build).
UnityEngine.Debug.Log($"SetStateMachine State:" + StateMachineUtility.GetState(stateMachine));
}
public AsyncUniTask<TStateMachine, T> NextNode { get; set; }
static AsyncUniTask()
@@ -256,6 +290,14 @@ namespace Cysharp.Threading.Tasks.CompilerServices
TaskPool.RegisterSizeGetter(typeof(AsyncUniTask<TStateMachine, T>), () => pool.Size);
}
void Return()
{
TaskTracker.RemoveTracking(this);
core.Reset();
stateMachine = default;
pool.TryPush(this);
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
@@ -268,7 +310,7 @@ namespace Cysharp.Threading.Tasks.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Run()
{
UnityEngine.Debug.Log($"MoveNext State:" + StateMachineUtility.GetState(stateMachine));
// UnityEngine.Debug.Log($"MoveNext State:" + StateMachineUtility.GetState(stateMachine));
stateMachine.MoveNext();
}
@@ -302,7 +344,12 @@ namespace Cysharp.Threading.Tasks.CompilerServices
}
finally
{
#if ENABLE_IL2CPP
// workaround for IL2CPP bug.
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, returnDelegate);
#else
TryReturn();
#endif
}
}

View File

@@ -0,0 +1,263 @@
using Cysharp.Threading.Tasks.Internal;
using System;
using System.Threading;
using Subscribes = Cysharp.Threading.Tasks.Linq.Subscribe;
namespace Cysharp.Threading.Tasks.Linq
{
public static partial class UniTaskAsyncEnumerable
{
// OnNext
public static IDisposable Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Action<TSource> action)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(action, nameof(action));
var cts = new CancellationTokenDisposable();
Subscribes.SubscribeCore(source, action, Subscribes.NopError, Subscribes.NopCompleted, cts.Token).Forget();
return cts;
}
public static IDisposable Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTaskVoid> action)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(action, nameof(action));
var cts = new CancellationTokenDisposable();
Subscribes.SubscribeCore(source, action, Subscribes.NopError, Subscribes.NopCompleted, cts.Token).Forget();
return cts;
}
public static void Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Action<TSource> action, CancellationToken cancellationToken)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(action, nameof(action));
Subscribes.SubscribeCore(source, action, Subscribes.NopError, Subscribes.NopCompleted, cancellationToken).Forget();
}
public static void Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTaskVoid> action, CancellationToken cancellationToken)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(action, nameof(action));
Subscribes.SubscribeCore(source, action, Subscribes.NopError, Subscribes.NopCompleted, cancellationToken).Forget();
}
// OnNext, OnError
public static IDisposable Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Action<TSource> onNext, Action<Exception> onError)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onError, nameof(onError));
var cts = new CancellationTokenDisposable();
Subscribes.SubscribeCore(source, onNext, onError, Subscribes.NopCompleted, cts.Token).Forget();
return cts;
}
public static IDisposable Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTaskVoid> onNext, Action<Exception> onError)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onError, nameof(onError));
var cts = new CancellationTokenDisposable();
Subscribes.SubscribeCore(source, onNext, onError, Subscribes.NopCompleted, cts.Token).Forget();
return cts;
}
public static void Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Action<TSource> onNext, Action<Exception> onError, CancellationToken cancellationToken)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onError, nameof(onError));
Subscribes.SubscribeCore(source, onNext, onError, Subscribes.NopCompleted, cancellationToken).Forget();
}
public static void Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTaskVoid> onNext, Action<Exception> onError, CancellationToken cancellationToken)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onError, nameof(onError));
Subscribes.SubscribeCore(source, onNext, onError, Subscribes.NopCompleted, cancellationToken).Forget();
}
// OnNext, OnCompleted
public static IDisposable Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Action<TSource> onNext, Action onCompleted)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onCompleted, nameof(onCompleted));
var cts = new CancellationTokenDisposable();
Subscribes.SubscribeCore(source, onNext, Subscribes.NopError, onCompleted, cts.Token).Forget();
return cts;
}
public static IDisposable Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTaskVoid> onNext, Action onCompleted)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onCompleted, nameof(onCompleted));
var cts = new CancellationTokenDisposable();
Subscribes.SubscribeCore(source, onNext, Subscribes.NopError, onCompleted, cts.Token).Forget();
return cts;
}
public static void Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Action<TSource> onNext, Action onCompleted, CancellationToken cancellationToken)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onCompleted, nameof(onCompleted));
Subscribes.SubscribeCore(source, onNext, Subscribes.NopError, onCompleted, cancellationToken).Forget();
}
public static void Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTaskVoid> onNext, Action onCompleted, CancellationToken cancellationToken)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(onNext, nameof(onNext));
Error.ThrowArgumentNullException(onCompleted, nameof(onCompleted));
Subscribes.SubscribeCore(source, onNext, Subscribes.NopError, onCompleted, cancellationToken).Forget();
}
// IObserver
public static IDisposable Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, IObserver<TSource> observer)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(observer, nameof(observer));
var cts = new CancellationTokenDisposable();
Subscribes.SubscribeCore(source, observer, cts.Token).Forget();
return cts;
}
public static void Subscribe<TSource>(this IUniTaskAsyncEnumerable<TSource> source, IObserver<TSource> observer, CancellationToken cancellationToken)
{
Error.ThrowArgumentNullException(source, nameof(source));
Error.ThrowArgumentNullException(observer, nameof(observer));
Subscribes.SubscribeCore(source, observer, cancellationToken).Forget();
}
}
internal sealed class CancellationTokenDisposable : IDisposable
{
readonly CancellationTokenSource cts = new CancellationTokenSource();
public CancellationToken Token => cts.Token;
public void Dispose()
{
if (!cts.IsCancellationRequested)
{
cts.Cancel();
}
}
}
internal static class Subscribe
{
public static readonly Action<Exception> NopError = _ => { };
public static readonly Action NopCompleted = () => { };
public static async UniTaskVoid SubscribeCore<TSource>(IUniTaskAsyncEnumerable<TSource> source, Action<TSource> onNext, Action<Exception> onError, Action onCompleted, CancellationToken cancellationToken)
{
var e = source.GetAsyncEnumerator(cancellationToken);
try
{
while (await e.MoveNextAsync())
{
onNext(e.Current);
}
onCompleted();
}
catch (Exception ex)
{
if (onError == NopError)
{
UniTaskScheduler.PublishUnobservedTaskException(ex);
return;
}
if (ex is OperationCanceledException) return;
onError(ex);
}
finally
{
if (e != null)
{
await e.DisposeAsync();
}
}
}
public static async UniTaskVoid SubscribeCore<TSource>(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTaskVoid> onNext, Action<Exception> onError, Action onCompleted, CancellationToken cancellationToken)
{
var e = source.GetAsyncEnumerator(cancellationToken);
try
{
while (await e.MoveNextAsync())
{
onNext(e.Current).Forget();
}
onCompleted();
}
catch (Exception ex)
{
if (onError == NopError)
{
UniTaskScheduler.PublishUnobservedTaskException(ex);
return;
}
if (ex is OperationCanceledException) return;
onError(ex);
}
finally
{
if (e != null)
{
await e.DisposeAsync();
}
}
}
public static async UniTaskVoid SubscribeCore<TSource>(IUniTaskAsyncEnumerable<TSource> source, IObserver<TSource> observer, CancellationToken cancellationToken)
{
var e = source.GetAsyncEnumerator(cancellationToken);
try
{
while (await e.MoveNextAsync())
{
observer.OnNext(e.Current);
}
observer.OnCompleted();
}
catch (Exception ex)
{
if (ex is OperationCanceledException) return;
observer.OnError(ex);
}
finally
{
if (e != null)
{
await e.DisposeAsync();
}
}
}
}
}

View File

@@ -19,7 +19,7 @@ namespace Cysharp.Threading.Tasks.Linq
{
internal static async UniTask<TSource[]> ToArrayAsync<TSource>(IUniTaskAsyncEnumerable<TSource> source, CancellationToken cancellationToken)
{
UnityEngine.Debug.Log("Called ToArray");
// UnityEngine.Debug.Log("Called ToArray");
var pool = ArrayPool<TSource>.Shared;
var array = pool.Rent(16);

View File

@@ -248,15 +248,18 @@ namespace Cysharp.Threading.Tasks.Linq
return current;
}
if (queuedResult.Count != 0)
lock (queuedResult)
{
current = queuedResult.Dequeue();
useCachedCurrent = true;
return current;
}
else
{
return default; // undefined.
if (queuedResult.Count != 0)
{
current = queuedResult.Dequeue();
useCachedCurrent = true;
return current;
}
else
{
return default; // undefined.
}
}
}
}

View File

@@ -34,6 +34,7 @@ namespace Cysharp.Threading.Tasks.Linq
public _EveryUpdate(PlayerLoopTiming updateTiming, CancellationToken cancellationToken)
{
this.updateTiming = updateTiming;
this.cancellationToken = cancellationToken;
TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(updateTiming, this);

View File

@@ -104,6 +104,7 @@ namespace Cysharp.Threading.Tasks.Linq
this.dueTimePhase = true;
this.updateTiming = updateTiming;
this.ignoreTimeScale = ignoreTimeScale;
this.cancellationToken = cancellationToken;
TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(updateTiming, this);
}
@@ -223,6 +224,7 @@ namespace Cysharp.Threading.Tasks.Linq
this.dueTimePhase = true;
this.dueTimeFrameCount = dueTimeFrameCount;
this.periodFrameCount = periodFrameCount;
this.cancellationToken = cancellationToken;
TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(updateTiming, this);

View File

@@ -1,7 +1,7 @@
{
"name": "com.cysharp.unitask",
"displayName": "UniTask",
"version": "2.0.14",
"version": "2.0.17",
"unity": "2018.4",
"description": "Provides an efficient async/await integration to Unity.",
"keywords": [ "async/await", "async", "Task", "UniTask" ],

View File

@@ -413,18 +413,38 @@ public class SandboxMain : MonoBehaviour
Debug.Log("after");
}
private async UniTaskVoid ExecuteAsync()
{
Debug.Log("1");
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
Debug.Log("------------------");
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
Debug.Log("2");
}
}
void Start()
{
PlayerLoopInfo.Inject();
UnityEngine.Debug.Log("Start:" + PlayerLoopInfo.CurrentLoopType);
//PlayerLoopInfo.Inject();
//_ = AsyncFixedUpdate();
//StartCoroutine(CoroutineFixedUpdate());
//StartCoroutine(TestCoroutine().ToCoroutine());
Application.logMessageReceived += Application_logMessageReceived;
// Application.logMessageReceived += Application_logMessageReceived;
// var rp = new AsyncReactiveProperty<int>();
// rp.AddTo(this.GetCancellationTokenOnDestroy());
var cts = new CancellationTokenSource();
@@ -432,13 +452,9 @@ public class SandboxMain : MonoBehaviour
okButton.onClick.AddListener(UniTask.UnityAction(async () =>
{
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
Debug.Log("------------------");
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
_ = ExecuteAsync();
await UniTask.Yield();
//await DelayCheck();
/*
@@ -960,6 +976,7 @@ public class PlayerLoopInfo
public static Type CurrentLoopType { get; private set; }
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Inject()
{
var system = PlayerLoop.GetCurrentPlayerLoop();

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 370f60b2e4cd92d4faafda153c9c5f8a
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -443,6 +443,7 @@ namespace Cysharp.Threading.TasksTests
public void OnError(Exception error) => OnErrorCalled = true;
}
#endif
#endif
}

View File

@@ -84,7 +84,6 @@ namespace Cysharp.Threading.TasksTests
}
}

View File

@@ -16,379 +16,170 @@ namespace Cysharp.Threading.TasksTests
{
public class DelayTest
{
//[UnityTest]
//public IEnumerator DelayFrame() => UniTask.ToCoroutine(async () =>
//{
// for (int i = 1; i < 5; i++)
// {
// await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var frameCount = Time.frameCount;
// await UniTask.DelayFrame(i);
// Time.frameCount.Should().Be(frameCount + i);
// }
[UnityTest]
public IEnumerator DelayFrame() => UniTask.ToCoroutine(async () =>
{
for (int i = 1; i < 5; i++)
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var frameCount = Time.frameCount;
await UniTask.DelayFrame(i);
Time.frameCount.Should().Be(frameCount + i);
}
// for (int i = 1; i < 5; i++)
// {
// await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
// var frameCount = Time.frameCount;
// await UniTask.DelayFrame(i);
// Time.frameCount.Should().Be(frameCount + i);
// }
//});
//[UnityTest]
//public IEnumerator DelayFrameZero() => UniTask.ToCoroutine(async () =>
//{
// {
// await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var frameCount = Time.frameCount;
// await UniTask.DelayFrame(0);
// Time.frameCount.Should().Be(frameCount); // same frame
// }
// {
// await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
// var frameCount = Time.frameCount;
// await UniTask.DelayFrame(0);
// Time.frameCount.Should().Be(frameCount + 1); // next frame
// }
//});
//[UnityTest]
//public IEnumerator TimerFramePre() => UniTask.ToCoroutine(async () =>
//{
// await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var initialFrame = Time.frameCount;
// var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync();
// xs[0].Should().Be(initialFrame + 2);
// xs[1].Should().Be(initialFrame + 2 + (3 * 1));
// xs[2].Should().Be(initialFrame + 2 + (3 * 2));
// xs[3].Should().Be(initialFrame + 2 + (3 * 3));
// xs[4].Should().Be(initialFrame + 2 + (3 * 4));
//});
//[UnityTest]
//public IEnumerator TimerFramePost() => UniTask.ToCoroutine(async () =>
//{
// await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
// var initialFrame = Time.frameCount;
// var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync();
// xs[0].Should().Be(initialFrame + 2);
// xs[1].Should().Be(initialFrame + 2 + (3 * 1));
// xs[2].Should().Be(initialFrame + 2 + (3 * 2));
// xs[3].Should().Be(initialFrame + 2 + (3 * 3));
// xs[4].Should().Be(initialFrame + 2 + (3 * 4));
//});
//[UnityTest]
//public IEnumerator TimerFrameTest() => UniTask.ToCoroutine(async () =>
//{
// await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var initialFrame = Time.frameCount;
// var xs = await UniTaskAsyncEnumerable.TimerFrame(0, 0).Take(5).Select(_ => Time.frameCount).ToArrayAsync();
// xs[0].Should().Be(initialFrame);
// xs[1].Should().Be(initialFrame + 1);
// xs[2].Should().Be(initialFrame + 2);
// xs[3].Should().Be(initialFrame + 3);
// xs[4].Should().Be(initialFrame + 4);
//});
for (int i = 1; i < 5; i++)
{
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
var frameCount = Time.frameCount;
await UniTask.DelayFrame(i);
Time.frameCount.Should().Be(frameCount + i);
}
});
[UnityTest]
public IEnumerator TimerFrameSinglePre2() => UniTask.ToCoroutine(async () =>
public IEnumerator DelayFrameZero() => UniTask.ToCoroutine(async () =>
{
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var frameCount = Time.frameCount;
await UniTask.DelayFrame(0);
Time.frameCount.Should().Be(frameCount); // same frame
}
{
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
var frameCount = Time.frameCount;
await UniTask.DelayFrame(0);
Time.frameCount.Should().Be(frameCount + 1); // next frame
}
//Debug.Log("------------------");
//{
// var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
//}
});
//[UnityTest]
//public IEnumerator TimerFrameSinglePre2() => UniTask.ToCoroutine(async () =>
//{
// {
// var initialFrame = Time.frameCount;
// var xs = await new MyTimerFrame(0, null)/*.Select(_ => Time.frameCount)*/.ToArrayAsync();
// Debug.Log("OK 0 ------------------");
// }
// {
// var xs = await new MyTimerFrame(1, null)/*.Select(_ =>
// {
// var t = Time.frameCount;
// UnityEngine.Debug.Log("store frameCount:" + t);
// return t;
// })*/.ToArrayAsync();
// }
//});
//[UnityTest]
//public IEnumerator TimerFrameSinglePre() => UniTask.ToCoroutine(async () =>
//{
// {
// await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var initialFrame = Time.frameCount;
// var xs = await UniTaskAsyncEnumerable.Return(UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None))/*.Select(_ => Time.frameCount)*/.ToArrayAsync();
// xs[0].Should().Be(initialFrame);
// Debug.Log("OK 0 ------------------");
// }
// {
// await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var initialFrame = Time.frameCount;
// Debug.Log("initialFrame:" + initialFrame);
// var xs = await UniTaskAsyncEnumerable.Return(UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None))/*.Select(_ =>
// {
// var t = Time.frameCount;
// UnityEngine.Debug.Log("store frameCount:" + t);
// return t;
// })*/.ToArrayAsync();
// Debug.Log("xs len:" + xs.Length);
// Debug.Log("xs[0]:" + xs[0]);
// xs[0].Should().Be(initialFrame + 1);
// Debug.Log("OK 1");
// }
// {
// //await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// var initialFrame = Time.frameCount;
// var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync();
// xs[0].Should().Be(initialFrame + 2);
// Debug.Log("OK 2");
// }
//});
//[UnityTest]
//public IEnumerator TimerFrameSinglePost() => UniTask.ToCoroutine(async () =>
//{
// {
// //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
// //var initialFrame = Time.frameCount;
// //var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync();
// //xs[0].Should().Be(initialFrame);
// }
// {
// //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
// var initialFrame = Time.frameCount;
// var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => Time.frameCount).ToArrayAsync();
// xs[0].Should().Be(initialFrame + 1);
// }
// {
// //await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
// var initialFrame = Time.frameCount;
// var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync();
// xs[0].Should().Be(initialFrame + 2);
// }
//});
//[UnityTest]
//public IEnumerator Timer() => UniTask.ToCoroutine(async () =>
//{
// await UniTask.Yield(PlayerLoopTiming.PreUpdate);
// {
// var initialSeconds = Time.realtimeSinceStartup;
// var xs = await UniTaskAsyncEnumerable.Timer(TimeSpan.FromSeconds(2)).Select(_ => Time.realtimeSinceStartup).ToArrayAsync();
// Mathf.Approximately(initialSeconds, xs[0]).Should().BeFalse();
// Debug.Log("Init:" + initialSeconds);
// Debug.Log("After:" + xs[0]);
// }
//});
}
public class DelayTest2
{
[UnityTest]
public IEnumerator TimerFrameSinglePre2() => UniTask.ToCoroutine(async () =>
public IEnumerator TimerFramePre() => UniTask.ToCoroutine(async () =>
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame + 2);
xs[1].Should().Be(initialFrame + 2 + (3 * 1));
xs[2].Should().Be(initialFrame + 2 + (3 * 2));
xs[3].Should().Be(initialFrame + 2 + (3 * 3));
xs[4].Should().Be(initialFrame + 2 + (3 * 4));
});
[UnityTest]
public IEnumerator TimerFramePost() => UniTask.ToCoroutine(async () =>
{
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(2, 3).Take(5).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame + 2);
xs[1].Should().Be(initialFrame + 2 + (3 * 1));
xs[2].Should().Be(initialFrame + 2 + (3 * 2));
xs[3].Should().Be(initialFrame + 2 + (3 * 3));
xs[4].Should().Be(initialFrame + 2 + (3 * 4));
});
[UnityTest]
public IEnumerator TimerFrameTest() => UniTask.ToCoroutine(async () =>
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(0, 0).Take(5).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame);
xs[1].Should().Be(initialFrame + 1);
xs[2].Should().Be(initialFrame + 2);
xs[3].Should().Be(initialFrame + 3);
xs[4].Should().Be(initialFrame + 4);
});
[UnityTest]
public IEnumerator TimerFrameSinglePre() => UniTask.ToCoroutine(async () =>
{
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame);
}
Debug.Log("------------------");
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ =>
{
var t = Time.frameCount;
return t;
}).ToArrayAsync();
xs[0].Should().Be(initialFrame + 1);
}
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame + 2);
}
});
[UnityTest]
public IEnumerator TimerFrameSinglePost() => UniTask.ToCoroutine(async () =>
{
{
//await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
//var initialFrame = Time.frameCount;
//var xs = await UniTaskAsyncEnumerable.TimerFrame(0).Select(_ => Time.frameCount).ToArrayAsync();
//xs[0].Should().Be(initialFrame);
}
{
//await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame + 1);
}
{
//await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
var initialFrame = Time.frameCount;
var xs = await UniTaskAsyncEnumerable.TimerFrame(2).Select(_ => Time.frameCount).ToArrayAsync();
xs[0].Should().Be(initialFrame + 2);
}
});
[UnityTest]
public IEnumerator Timer() => UniTask.ToCoroutine(async () =>
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
{
var initialSeconds = Time.realtimeSinceStartup;
var xs = await UniTaskAsyncEnumerable.Timer(TimeSpan.FromSeconds(2)).Select(_ => Time.realtimeSinceStartup).ToArrayAsync();
Mathf.Approximately(initialSeconds, xs[0]).Should().BeFalse();
Debug.Log("Init:" + initialSeconds);
Debug.Log("After:" + xs[0]);
}
});
}
public class ThreadRunner
{
Thread thread;
public void Start(IPlayerLoopItem runner)
{
thread = new Thread(() =>
{
Thread.Sleep(30);
while (runner.MoveNext())
{
Thread.Sleep(30);
}
});
thread.Start();
}
}
internal class MyTimerFrame : IUniTaskAsyncEnumerable<AsyncUnit>
{
//readonly PlayerLoopTiming updateTiming;
readonly int dueTimeFrameCount;
readonly int? periodFrameCount;
public MyTimerFrame(int dueTimeFrameCount, int? periodFrameCount)
{
//this.updateTiming = updateTiming;
this.dueTimeFrameCount = dueTimeFrameCount;
this.periodFrameCount = periodFrameCount;
}
public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{
return new _TimerFrame(dueTimeFrameCount, periodFrameCount, cancellationToken);
}
class _TimerFrame : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem
{
readonly int dueTimeFrameCount;
readonly int? periodFrameCount;
CancellationToken cancellationToken;
int initialFrame;
int currentFrame;
bool dueTimePhase;
bool completed;
bool disposed;
ThreadRunner runner;
public _TimerFrame(int dueTimeFrameCount, int? periodFrameCount, CancellationToken cancellationToken)
{
if (dueTimeFrameCount <= 0) dueTimeFrameCount = 0;
if (periodFrameCount != null)
{
if (periodFrameCount <= 0) periodFrameCount = 1;
}
//this.initialFrame = Time.frameCount;
this.dueTimePhase = true;
this.dueTimeFrameCount = dueTimeFrameCount;
this.periodFrameCount = periodFrameCount;
//TaskTracker.TrackActiveTask(this, 2);
//PlayerLoopHelper.AddAction(updateTiming, this);
runner = new ThreadRunner();
runner.Start(this);
}
public AsyncUnit Current => default;
public UniTask<bool> MoveNextAsync()
{
// return false instead of throw
if (disposed || cancellationToken.IsCancellationRequested || completed) return default;
// reset value here.
this.currentFrame = 0;
completionSource.Reset();
return new UniTask<bool>(this, completionSource.Version);
}
public UniTask DisposeAsync()
{
if (!disposed)
{
disposed = true;
TaskTracker.RemoveTracking(this);
}
return default;
}
public bool MoveNext()
{
UnityEngine.Debug.Log("Called MoveNext");
if (disposed || cancellationToken.IsCancellationRequested)
{
UnityEngine.Debug.Log("Disposing");
completionSource.TrySetResult(false);
return false;
}
if (dueTimePhase)
{
UnityEngine.Debug.Log("In DueTime Phase");
if (currentFrame == 0)
{
if (dueTimeFrameCount == 0)
{
dueTimePhase = false;
completionSource.TrySetResult(true);
return true;
}
// skip in initial frame.
/*
UnityEngine.Debug.Log("(Init, frameConut)" + (initialFrame, Time.frameCount));
if (initialFrame == Time.frameCount)
{
UnityEngine.Debug.Log("Skip Here");
return true;
}
*/
}
UnityEngine.Debug.Log("Which Go?");
if (++currentFrame >= dueTimeFrameCount)
{
UnityEngine.Debug.Log("END Go?");
dueTimePhase = false;
completionSource.TrySetResult(true);
}
else
{
UnityEngine.Debug.Log("NG Go?");
}
}
else
{
if (periodFrameCount == null)
{
UnityEngine.Debug.Log("PERIOD");
completed = true;
completionSource.TrySetResult(false);
return false;
}
if (++currentFrame >= periodFrameCount)
{
completionSource.TrySetResult(true);
}
}
return true;
}
}
}
}

View File

@@ -556,6 +556,7 @@ PlayerSettings:
scriptingDefineSymbols: {}
platformArchitecture: {}
scriptingBackend:
Android: 1
Standalone: 1
il2cppCompilerConfiguration: {}
managedStrippingLevel: {}