Compare commits

...

75 Commits

Author SHA1 Message Date
neuecc
859c4d706f logging 2020-06-09 12:00:12 +09:00
neuecc
7289fe6e25 ToCoroutine throws exception when error detected 2020-06-09 11:26:03 +09:00
neuecc
0c33977f5a timer and delay skip current frame 2020-06-09 11:25:45 +09:00
neuecc
4d4466e801 Add UniTask.WaitForFixedUpdate 2020-06-08 02:22:10 +09:00
neuecc
79330d7cdb ReadMe 2020-06-08 01:50:20 +09:00
neuecc
680ce1098b ReadMe 2020-06-08 01:45:57 +09:00
neuecc
2337d705ec WithCancellation uses native timing of asyncOperation 2020-06-08 01:45:41 +09:00
neuecc
d2880a818f Add UniTask.NextFrame, UniTask.WaitForEndOfFrame 2020-06-08 01:44:31 +09:00
neuecc
86ea128bf4 t4gen 2020-06-08 00:30:27 +09:00
neuecc
a66f378622 // for CI 2020-06-07 00:27:33 +09:00
neuecc
265f88584b store application.datapath on initialize #86 2020-06-06 14:21:31 +09:00
neuecc
686394c861 2.0.14 pkg version 2020-06-05 17:16:34 +09:00
neuecc
8bad158ab4 fix ci 2020-06-05 17:05:36 +09:00
neuecc
5e59e7ec86 netstandard2.0 support #84 2020-06-05 16:45:40 +09:00
neuecc
8bb0a48720 workaround for struct enumerator await 2020-06-05 15:08:13 +09:00
neuecc
b4468b4eba improve DOTween Extensions 2020-06-05 12:32:16 +09:00
neuecc
0725bd1b30 Merge remote-tracking branch 'origin/master' 2020-06-05 00:44:16 +09:00
neuecc
ebd80243e0 Fix UniTask.ReturnToCurrentSynchronizationContext 2020-06-05 00:43:54 +09:00
neuecc
f1ac469058 Fix UniTask.Defer 2020-06-05 00:43:03 +09:00
neuecc
2e0b603d25 docs: update TOC 2020-06-04 08:54:24 +00:00
neuecc
1d90a40f66 more ReadMe 2020-06-04 17:53:57 +09:00
Mayuki Sawatari
1bec3f507e Merge pull request #75 from Cysharp/feature/docs
Generate and publish documentation to GitHub Pages
2020-06-04 16:16:03 +09:00
Mayuki Sawatari
1d5ecbb3ab Include sub-directory source codes 2020-06-04 15:46:21 +09:00
Mayuki Sawatari
e1a4aeb9da Update toc.yml 2020-06-04 15:46:21 +09:00
Mayuki Sawatari
43f1bb4d85 Generate and publish documentation to GitHub Pages 2020-06-04 15:46:21 +09:00
Yoshifumi Kawai
8b7a0e9b15 Update README.md 2020-06-04 15:31:42 +09:00
neuecc
da329e19d1 CI more 2020-06-04 15:24:09 +09:00
Yoshifumi Kawai
b8260d4e91 Merge pull request #61 from Cysharp/unitask2
UniTask2
2020-06-04 15:17:19 +09:00
neuecc
345e32aaf0 CI again 2020-06-04 15:14:40 +09:00
neuecc
d7bef8c5b5 setup release 2020-06-04 15:11:51 +09:00
neuecc
b2f82df4d3 fix action 2020-06-04 14:58:11 +09:00
neuecc
83596b3d1f docs: update TOC 2020-06-04 05:53:07 +00:00
neuecc
0022598a1c readme 2020-06-04 14:52:42 +09:00
neuecc
f4294d3752 Task Pool moved from Internal 2020-06-04 14:52:36 +09:00
neuecc
a1444c0b39 refactor DistinctUntilChanged 2020-06-03 02:03:31 +09:00
neuecc
e1d5359d73 refactor Where 2020-06-03 01:18:39 +09:00
neuecc
239bf749b6 refactor UniTaskAsyncEnumerable.Select 2020-06-03 00:50:39 +09:00
neuecc
2bf3b1e172 pj 2020-06-02 23:27:42 +09:00
neuecc
d225de201f gitignore 2020-06-02 23:00:47 +09:00
neuecc
c3b8a3852d UniTask.Linq.asmdef 2020-06-02 23:00:20 +09:00
neuecc
c31dab888e fix UnITaskTracker causes Stackoverflow 2020-06-02 22:49:40 +09:00
neuecc
d4cf59bd2f CI 2020-06-01 14:50:25 +09:00
neuecc
d5edc3acd3 guard for trigger invalid ops 2020-06-01 13:51:37 +09:00
neuecc
130286e8c2 TriggerEvent becomes linkedlist 2020-06-01 13:40:35 +09:00
neuecc
a9baa52309 Add ReturnToMainThread, ReturnToSynchronizationContext, ReturnToCurrentSynchronizationContext 2020-05-31 04:30:03 +09:00
neuecc
3001996298 Q check 2020-05-31 04:28:05 +09:00
neuecc
bfcd18aabb MoveNextRunner -> StateMachineRunner 2020-05-29 14:35:50 +09:00
neuecc
96aa299e7e support version -> unity 2018.4 2020-05-29 14:25:26 +09:00
neuecc
24faa34418 TaskPoolMonitor -> TaskPool 2020-05-29 03:58:22 +09:00
neuecc
21bf08a6b3 replace all promisepool to new taskpool system 2020-05-29 03:43:18 +09:00
neuecc
d5db96b913 rename StackNode to TaskPool 2020-05-29 02:08:11 +09:00
neuecc
a8455af16d Improve pooling mechanism 2020-05-29 01:22:46 +09:00
neuecc
2290b14532 reduce AsyncBuilder heap allocation 2020-05-28 22:20:06 +09:00
neuecc
90c5a6311b check il2cpp generics limitation 2020-05-28 21:18:35 +09:00
neuecc
6e0ad3623b non public 2020-05-27 09:16:46 +09:00
neuecc
005e02a1fa sealed 2020-05-27 07:39:19 +09:00
neuecc
10fb8060fa use PooledDelegate to avoid convert Action to Action<AsyncOperation> allocation 2020-05-27 07:37:16 +09:00
neuecc
35b933730b 2.0.11-rc8 2020-05-25 19:44:57 +09:00
neuecc
7ab9467069 IObservable<T>.ToUniTask parameter order changed from CancellationToken cancellationToken, bool useFirst to bool useFirst, CancellationToken cancellationToken 2020-05-25 19:42:51 +09:00
neuecc
598312ba61 DOTween's WithCancellation remove = default 2020-05-25 19:38:08 +09:00
neuecc
985aa5c43a add allocationchecker in netcore sandbox project 2020-05-25 19:37:11 +09:00
neuecc
10eff95a42 fix: does not work zero-allocation mechanism in release build 2020-05-25 19:36:39 +09:00
neuecc
d27d6d5d9d UniTask.Yield in .NET Core becomes zero allocation 2020-05-25 19:33:54 +09:00
neuecc
b8c109848e UniTaskVoid can not await. 2020-05-25 19:33:11 +09:00
neuecc
8b7f832c0f JobHandle.WaitAsync accepts CancellationToken 2020-05-25 09:58:06 +09:00
neuecc
7cce0f48e5 call webRequest.Abort() on canceled 2020-05-25 09:54:26 +09:00
neuecc
8a56838111 modify cannot await twice message 2020-05-25 01:55:35 +09:00
neuecc
ff15e00003 f 2020-05-24 03:33:25 +09:00
neuecc
f60d2c51fb fix in UnityEditor performance issue 2020-05-24 03:27:05 +09:00
neuecc
6dfb969015 Add ValueTask.AsUniTask only for .NET Core 2020-05-24 01:30:52 +09:00
neuecc
da7e9fc4b3 UniTask marked StructLayout(LayoutKind.Auto) 2020-05-24 01:30:33 +09:00
neuecc
70385c4115 Fix channel's cancellationTokenRegistration does not handle correctly 2020-05-24 00:19:13 +09:00
neuecc
51ba740413 In .NET Core, IUniTaskSource implements IValueTaskSource and implicit, zero overhead conversion 2020-05-24 00:18:39 +09:00
neuecc
f3e3ba8864 fix DOTweenExt's TweenCancelBehaviour.CancelAwait 2020-05-23 02:53:48 +09:00
neuecc
07cf65c1ec lower support is 2019.1 2020-05-23 02:12:19 +09:00
90 changed files with 10719 additions and 1809 deletions

31
.github/workflows/build-docs.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
name: build-docs
on:
push:
branches:
- master
- feature/docs
jobs:
run-docfx:
if: "!(contains(github.event.head_commit.message, '[skip ci]') || contains(github.event.head_commit.message, '[ci skip]'))"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v2
with:
repository: Cysharp/DocfxTemplate
path: docs/_DocfxTemplate
- uses: Kirbyrawr/docfx-action@master
name: Docfx metadata
with:
args: metadata docs/docfx.json
- uses: Kirbyrawr/docfx-action@master
name: Docfx build
with:
args: build docs/docfx.json
- name: Publish to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: docs/_site

View File

@@ -20,17 +20,16 @@ jobs:
# set release tag(*.*.*) to env.GIT_TAG # set release tag(*.*.*) to env.GIT_TAG
- run: echo ::set-env name=GIT_TAG::${GITHUB_REF#refs/tags/} - run: echo ::set-env name=GIT_TAG::${GITHUB_REF#refs/tags/}
# build CommandTools first (use dotnet run command in ZLogger.csproj) # build and pack
- run: dotnet build -c Release ./tools/CommandTools/CommandTools.csproj
- run: dotnet build -c Release -p:Version=${{ env.GIT_TAG }} - run: dotnet build -c Release -p:Version=${{ env.GIT_TAG }}
- run: dotnet test -c Release --no-build - run: dotnet test -c Release --no-build
- run: dotnet pack ./src/ZLogger/ZLogger.csproj -c Release --no-build -p:Version=${{ env.GIT_TAG }} - run: dotnet pack ./src/UniTask.NetCore/UniTask.NetCore.csproj -c Release --no-build -p:Version=${{ env.GIT_TAG }}
# Store artifacts. # Store artifacts.
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v1
with: with:
name: nuget name: nuget
path: ./src/ZLogger/bin/Release/ZLogger.${{ env.GIT_TAG }}.nupkg path: ./src/UniTask.NetCore/bin/Release/UniTask.${{ env.GIT_TAG }}.nupkg
build-unity: build-unity:
strategy: strategy:
@@ -57,15 +56,13 @@ jobs:
# Execute scripts: Export Package # Execute scripts: Export Package
- name: Export unitypackage - name: Export unitypackage
run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export run: /opt/Unity/Editor/Unity -quit -batchmode -nographics -silent-crashes -logFile -projectPath . -executeMethod PackageExporter.Export
working-directory: src/ZLogger.Unity working-directory: src/UniTask
env:
UNITY_PACKAGE_VERSION: ${{ env.GIT_TAG }}
# Store artifacts. # Store artifacts.
- uses: actions/upload-artifact@v1 - uses: actions/upload-artifact@v2
with: with:
name: ZLogger.Unity.${{ env.GIT_TAG }}.unitypackage name: UniTask.${{ env.GIT_TAG }}.unitypackage
path: ./src/ZLogger.Unity/ZLogger.Unity.${{ env.GIT_TAG }}.unitypackage path: ./src/UniTask/UniTask.${{ env.GIT_TAG }}.unitypackage
create-release: create-release:
needs: [build-dotnet, build-unity] needs: [build-dotnet, build-unity]
@@ -103,6 +100,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./ZLogger.Unity.${{ env.GIT_TAG }}.unitypackage/ZLogger.Unity.${{ env.GIT_TAG }}.unitypackage asset_path: ./UniTask.${{ env.GIT_TAG }}.unitypackage/UniTask.${{ env.GIT_TAG }}.unitypackage
asset_name: ZLogger.Unity.${{ env.GIT_TAG }}.unitypackage asset_name: UniTask.${{ env.GIT_TAG }}.unitypackage
asset_content_type: application/octet-stream asset_content_type: application/octet-stream

48
.gitignore vendored
View File

@@ -157,3 +157,51 @@ src/UniTask/UniTask.Tests.csproj
src/UniTask/UniTask.Tests.Editor.csproj src/UniTask/UniTask.Tests.Editor.csproj
src/UniTask/UniTask.*.unitypackage src/UniTask/UniTask.*.unitypackage
src/UniTask/UniTask.Linq.csproj
src/UniTask/DOTween.Modules.csproj
src/UniTask/Unity.Addressables.csproj
src/UniTask/Unity.Addressables.Editor.csproj
src/UniTask/Unity.Analytics.DataPrivacy.csproj
src/UniTask/Unity.Recorder.csproj
src/UniTask/Unity.Recorder.Editor.csproj
src/UniTask/Unity.ResourceManager.csproj
src/UniTask/Unity.Rider.Editor.csproj
src/UniTask/Unity.ScriptableBuildPipeline.csproj
src/UniTask/Unity.ScriptableBuildPipeline.Editor.csproj
src/UniTask/Unity.TextMeshPro.csproj
src/UniTask/Unity.TextMeshPro.Editor.csproj
src/UniTask/Unity.Timeline.csproj
src/UniTask/Unity.Timeline.Editor.csproj
src/UniTask/Unity.VisualStudio.Editor.csproj
src/UniTask/Unity.VSCode.Editor.csproj
src/UniTask/UnityEditor.CacheServer.csproj
src/UniTask/UnityEditor.TestRunner.csproj
src/UniTask/UnityEditor.UI.csproj
src/UniTask/UnityEngine.Advertisements.csproj
src/UniTask/UnityEngine.Monetization.csproj
src/UniTask/UnityEngine.TestRunner.csproj
src/UniTask/UnityEngine.UI.csproj

571
README.md
View File

@@ -4,66 +4,103 @@ UniTask
Provides an efficient async/await integration to Unity. Provides an efficient async/await integration to Unity.
* Struct based `UniTask<T>` and custom AsyncMethodBuilder to achive zero allocation
* All Unity AsyncOperations and Coroutine to awaitable
* PlayerLoop based task(`UniTask.Yield`, `UniTask.Delay`, `UniTask.DelayFrame`, etc..) that enable to replace all coroutine operation
* MonoBehaviour Message Events and uGUI Events as awaitable/async-enumerable
* Completely run on Unity's PlayerLoop so don't use thread and run on WebGL, wasm, etc.
* Asynchronous LINQ, with Channel and AsyncReactiveProperty
* TaskTracker window to prevent memory leak
* Highly compatible behaviour with Task/ValueTask/IValueTaskSource
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents ## Table of Contents
- [Getting started](#getting-started) - [Getting started](#getting-started)
- [`UniTask<T>`](#unitaskt) - [Basics of UniTask and AsyncOperation](#basics-of-unitask-and-asyncoperation)
- [Cancellation and Exception handling](#cancellation-and-exception-handling) - [Cancellation and Exception handling](#cancellation-and-exception-handling)
- [Progress](#progress) - [Progress](#progress)
- [PlayerLoop](#playerloop)
- [async void vs async UniTaskVoid](#async-void-vs-async-unitaskvoid)
- [UniTaskTracker](#unitasktracker) - [UniTaskTracker](#unitasktracker)
- [Reusable Promises](#reusable-promises) - [External Assets](#external-assets)
- [awaitable Events](#awaitable-events) - [AsyncEnumerable and Async LINQ](#asyncenumerable-and-async-linq)
- [async void vs async UniTask/UniTaskVoid](#async-void-vs-async-unitaskunitaskvoid) - [Awaitable Events](#awaitable-events)
- [Channel](#channel)
- [For Unit Testing](#for-unit-testing) - [For Unit Testing](#for-unit-testing)
- [Method List](#method-list) - [Pooling Configuration](#pooling-configuration)
- [API References](#api-references)
- [UPM Package](#upm-package) - [UPM Package](#upm-package)
- [ECS, PlayerLoop](#ecs-playerloop) - [Install via git URL](#install-via-git-url)
- [Install via OpenUPM](#install-via-openupm)
- [.NET Core](#net-core)
- [License](#license) - [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
Getting started Getting started
--- ---
Install package(`UniRx.Async.unitypackage`) is available in [UniTask/releases](https://github.com/Cysharp/UniTask/releases) page. Install via [UPM package](#upm-package) or asset package(`UniTask.*.*.*.unitypackage`) available in [UniTask/releases](https://github.com/Cysharp/UniTask/releases) page.
```csharp ```csharp
// extension awaiter/methods can be used by this namespace // extension awaiter/methods can be used by this namespace
using UniRx.Async; using Cysharp.Threading.Tasks;
// You can return type as struct UniTask<T>(or UniTask), it is unity specialized lightweight alternative of Task<T> // You can return type as struct UniTask<T>(or UniTask), it is unity specialized lightweight alternative of Task<T>
// no(or less) allocation and fast excution for zero overhead async/await integrate with Unity // zero allocation and fast excution for zero overhead async/await integrate with Unity
async UniTask<string> DemoAsync() async UniTask<string> DemoAsync()
{ {
// You can await Unity's AsyncObject // You can await Unity's AsyncObject
var asset = await Resources.LoadAsync<TextAsset>("foo"); var asset = await Resources.LoadAsync<TextAsset>("foo");
var txt = (await UnityWebRequest.Get("https://...").SendWebRequest()).downloadHandler.text;
await SceneManager.LoadSceneAsync("scene2");
// .ConfigureAwait accepts progress callback // .WithCancellation enables Cancel, GetCancellationTokenOnDestroy synchornizes with lifetime of GameObject
await SceneManager.LoadSceneAsync("scene2").ConfigureAwait(Progress.Create<float>(x => Debug.Log(x))); var asset2 = await Resources.LoadAsync<TextAsset>("bar").WithCancellation(this.GetCancellationTokenOnDestroy());
// .ToUniTask accepts progress callback(and all options), Progress.Create is a lightweight alternative of IProgress<T>
var asset3 = await Resources.LoadAsync<TextAsset>("baz").ToUniTask(Progress.Create<float>(x => Debug.Log(x)));
// await frame-based operation like coroutine // await frame-based operation like coroutine
await UniTask.DelayFrame(100); await UniTask.DelayFrame(100);
// replacement of WaitForSeconds/WaitForSecondsRealtime // replacement of yield return new WaitForSeconds/WaitForSecondsRealtime
await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false); await UniTask.Delay(TimeSpan.FromSeconds(10), ignoreTimeScale: false);
// replacement of WaitForEndOfFrame(or other timing like yield return null, yield return WaitForFixedUpdate) // yield any playerloop timing(PreUpdate, Update, LateUpdate, etc...)
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); await UniTask.Yield(PlayerLoopTiming.PreLateUpdate);
// replacement of yield return null
await UniTask.Yield();
await UniTask.NextFrame();
// replacement of WaitForEndOfFrame(same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate))
await UniTask.WaitForEndOfFrame();
// replacement of yield return new WaitForFixedUpdate(same as UniTask.Yield(PlayerLoopTiming.FixedUpdate))
await UniTask.WaitForFixedUpdate();
// replacement of yield return WaitUntil // replacement of yield return WaitUntil
await UniTask.WaitUntil(() => isActive == false); await UniTask.WaitUntil(() => isActive == false);
// special helper of WaitUntil
await UniTask.WaitUntilValueChanged(this, x => x.isActive);
// You can await IEnumerator coroutine // You can await IEnumerator coroutine
await FooCoroutineEnumerator(); await FooCoroutineEnumerator();
// You can await standard task // You can await standard task
await Task.Run(() => 100); await Task.Run(() => 100);
// Multithreading, run on ThreadPool under this code(use SwitchToMainThread, same as `ObserveOnMainThreadDispatcher`) // Multithreading, run on ThreadPool under this code
await UniTask.SwitchToThreadPool(); await UniTask.SwitchToThreadPool();
/* work on ThreadPool */
// return to MainThread(same as `ObserveOnMainThread` in UniRx)
await UniTask.SwitchToMainThread();
// get async webrequest // get async webrequest
async UniTask<string> GetTextAsync(UnityWebRequest req) async UniTask<string> GetTextAsync(UnityWebRequest req)
{ {
@@ -78,6 +115,9 @@ async UniTask<string> DemoAsync()
// concurrent async-wait and get result easily by tuple syntax // concurrent async-wait and get result easily by tuple syntax
var (google, bing, yahoo) = await UniTask.WhenAll(task1, task2, task3); var (google, bing, yahoo) = await UniTask.WhenAll(task1, task2, task3);
// shorthand of WhenAll, tuple can await directly
var (google2, bing2, yahoo2) = await (task1, task2, task3);
// You can handle timeout easily // You can handle timeout easily
await GetTextAsync(UnityWebRequest.Get("http://unity.com")).Timeout(TimeSpan.FromMilliseconds(300)); await GetTextAsync(UnityWebRequest.Get("http://unity.com")).Timeout(TimeSpan.FromMilliseconds(300));
@@ -86,47 +126,38 @@ async UniTask<string> DemoAsync()
} }
``` ```
`UniTask<T>` Basics of UniTask and AsyncOperation
--- ---
UniTask feature rely on C# 7.0([task-like custom async method builder feature](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)) so required Unity version is after `Unity 2018.3`. UniTask feature rely on C# 7.0([task-like custom async method builder feature](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md)) so required Unity version is after `Unity 2018.3`, officialy lower support version is `Unity 2018.4.13f1`.
Why UniTask(custom task-like object) is required? Because Task is too heavy, not matched to Unity threading(single-thread). UniTask does not use thread and SynchronizationContext because almost Unity's asynchronous object is automaticaly dispatched by Unity's engine layer. It acquires more fast and more less allocation, completely integrated with Unity. Why UniTask(custom task-like object) is required? Because Task is too heavy, not matched to Unity threading(single-thread). UniTask does not use thread and SynchronizationContext/ExecutionContext because almost Unity's asynchronous object is automaticaly dispatched by Unity's engine layer. It acquires more fast and more less allocation, completely integrated with Unity.
> More details, please see this slide: [Deep Dive async/await in Unity with UniTask(EN) You can await `AsyncOperation`, `ResourceRequest`, `AssetBundleRequest`, `AssetBundleCreateRequest`, `UnityWebRequestAsyncOperation`, `IEnumerator` and others when `using Cysharp.Threading.Tasks;`.
](https://www.slideshare.net/neuecc/deep-dive-asyncawait-in-unity-with-unitasken)
You can await `AsyncOperation`, `ResourceRequest`, `UnityWebRequestAsyncOperation`, `IEnumerator` and others when using `UniRx.Async`. UniTask provides three pattern of extension methods.
`UniTask.Delay`, `UniTask.Yield`, `UniTask.Timeout` that is frame-based timer operators(no uses thread so works on WebGL publish) driven by custom PlayerLoop(Unity 2018 experimental feature). In default, UniTask initialize automatically when application begin, but it is override all. If you want to append PlayerLoop, please call `PlayerLoopHelper.Initialize(ref yourCustomizedPlayerLoop)` manually.
> Before Unity 2019.3, Unity does not have `PlayerLooop.GetCurrentPlayerLoop` so you can't use with Unity ECS package in default. If you want to use with ECS and before Unity 2019.3, you can use this hack below.
```csharp ```csharp
// Get ECS Loop. * await asyncOperation;
var playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop; * .WithCancellation(CancellationToken);
* .ToUniTask(IProgress, PlayerLoopTiming, CancellationToken);
// Setup UniTask's PlayerLoop.
PlayerLoopHelper.Initialize(ref playerLoop);
``` ```
`UniTask.WhenAll`, `UniTask.WhenAny` is like Task.WhenAll/WhenAny but return type is more useful. `WithCancellation` is a simple version of `ToUniTask`, both returns `UniTask`. Details of cancellation, see: [Cancellation and Exception handling](#cancellation-and-exception-handling) section.
`UniTask.ctor(Func<UniTask>)` is like the embeded [`AsyncLazy<T>`](https://blogs.msdn.microsoft.com/pfxteam/2011/01/15/asynclazyt/) > Note: WithCancellation is returned from native timing of PlayerLoop but ToUniTask is returned from specified PlayerLoopTiming. Details of timing, see: [PlayerLoop](#playerloop) section.
The type of `UniTask` can use utility like `UniTask.WhenAll`, `UniTask.WhenAny`. It is like Task.WhenAll/WhenAny but return type is more useful, returns value tuple so can deconsrtuct each result and pass multiple type.
```csharp ```csharp
public class SceneAssets public class SceneAssets
{ {
public readonly UniTask<Sprite> Front;
public readonly UniTask<Sprite> Background;
public readonly UniTask<Sprite> Effect;
public SceneAssets() public SceneAssets()
{ {
// ctor(Func) overload is AsyncLazy, initialized once when await. // parallel load.
// and after it, await returns zero-allocation value immediately. var (a, b, c) = await UniTask.WhenAll(
Front = new UniTask<Sprite>(() => LoadAsSprite("foo")); LoadAsSprite("foo"),
Background = new UniTask<Sprite>(() => LoadAsSprite("bar")); LoadAsSprite("bar"),
Effect = new UniTask<Sprite>(() => LoadAsSprite("baz")); LoadAsSprite("baz"));
} }
async UniTask<Sprite> LoadAsSprite(string path) async UniTask<Sprite> LoadAsSprite(string path)
@@ -152,13 +183,32 @@ public UniTask<int> WrapByUniTaskCompletionSource()
} }
``` ```
You can convert Task -> UniTask: `AsUniTask`, `UniTask` -> `UniTask<AsyncUnit>`: `AsAsyncUnitUniTask`(this is useful to use WhenAll/WhenAny), `UniTask<T>` -> `UniTask`: `AsUniTask`. You can convert Task -> UniTask: `AsUniTask`, `UniTask` -> `UniTask<AsyncUnit>`: `AsAsyncUnitUniTask`, `UniTask<T>` -> `UniTask`: `AsUniTask`. `UniTask<T>` -> `UniTask`'s conversion cost is free.
If you want to convert async to coroutine, you can use `UniTask.ToCoroutine`, this is useful to use only allow coroutine system. If you want to convert async to coroutine, you can use `.ToCoroutine()`, this is useful to use only allow coroutine system.
UniTask can not await twice. This is a similar constraint to the [ValueTask/IValueTaskSource](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1?view=netcore-3.1) introduced in .NET Standard 2.1.
> The following operations should never be performed on a ValueTask<TResult> instance:
>
> * Awaiting the instance multiple times.
> * Calling AsTask multiple times.
> * Using .Result or .GetAwaiter().GetResult() when the operation hasn't yet completed, or using them multiple times.
> * Using more than one of these techniques to consume the instance.
>
> If you do any of the above, the results are undefined.
```csharp
var task = UniTask.DelayFrame(10);
await task;
await task; // NG, throws Exception
```
Store to the class field, you can use `UniTask.Lazy` that gurantee call multipletimes. `.Prevent()` allows for multiple calls (internally cached results). This is useful when multiple calls in a function scope.
Cancellation and Exception handling Cancellation and Exception handling
--- ---
Some UniTask factory methods have `CancellationToken cancellation = default(CancellationToken)` parameter. Andalso some async operation for unity have `ConfigureAwait(..., CancellationToken cancellation = default(CancellationToken))` extension methods. Some UniTask factory methods have `CancellationToken cancellationToken = default` parameter. Andalso some async operation for unity have `WithCancellation(CancellationToken)` and `ToUniTask(..., CancellationToken cancellation = default)` extension methods.
You can pass `CancellationToken` to parameter by standard [`CancellationTokenSource`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource). You can pass `CancellationToken` to parameter by standard [`CancellationTokenSource`](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource).
@@ -170,7 +220,7 @@ cancelButton.onClick.AddListener(() =>
cts.Cancel(); cts.Cancel();
}); });
await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().ConfigureAwait(cancellation: cts.Token); await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellation(cts.Token);
await UniTask.DelayFrame(1000, cancellationToken: cts.Token); await UniTask.DelayFrame(1000, cancellationToken: cts.Token);
``` ```
@@ -184,7 +234,7 @@ await UniTask.DelayFrame(1000, cancellationToken: this.GetCancellationTokenOnDes
When detect cancellation, all methods throws `OperationCanceledException` and propagate to upstream. `OperationCanceledException` is special exception, if not handled this exception, finally it is propagated to `UniTaskScheduler.UnobservedTaskException`. When detect cancellation, all methods throws `OperationCanceledException` and propagate to upstream. `OperationCanceledException` is special exception, if not handled this exception, finally it is propagated to `UniTaskScheduler.UnobservedTaskException`.
Default behaviour of received unhandled exception is write log as warning. Log level can change by `UniTaskScheduler.UnobservedExceptionWriteLogType`. If you want to change custom beavhiour, set action to `UniTaskScheduler.UnobservedTaskException.` Default behaviour of received unhandled exception is write log as exception. Log level can change by `UniTaskScheduler.UnobservedExceptionWriteLogType`. If you want to change custom beavhiour, set action to `UniTaskScheduler.UnobservedTaskException.`
If you want to cancel behaviour in async UniTask method, throws `OperationCanceledException` manually. If you want to cancel behaviour in async UniTask method, throws `OperationCanceledException` manually.
@@ -223,23 +273,23 @@ if (isCanceled)
} }
``` ```
Note: Only suppress throws if you call it directly into the most source method. Note: Only suppress throws if you call it directly into the most source method. Otherwise, the return value will be converted, but the entire pipeline will not be suppressed throws.
Progress Progress
--- ---
Some async operation for unity have `ConfigureAwait(IProgress<float> progress = null, ...)` extension methods. Some async operation for unity have `ToUniTask(IProgress<float> progress = null, ...)` extension methods.
```csharp ```csharp
var progress = Progress.Create<float>(x => Debug.Log(x)); var progress = Progress.Create<float>(x => Debug.Log(x));
var request = await UnityWebRequest.Get("http://google.co.jp") var request = await UnityWebRequest.Get("http://google.co.jp")
.SendWebRequest() .SendWebRequest()
.ConfigureAwait(progress: progress); .ToUniTask(progress: progress);
``` ```
Should not use `new System.Progress<T>`, because it allocate every times. Use `UniRx.Async.Progress` instead. Progress factory has two methods, `Create` and `CreateOnlyValueChanged`. `CreateOnlyValueChanged` calls only when progress value changed. Should not use `new System.Progress<T>`, it allocate every times. You should not use standard `new System.Progress<T>`, because it causes allocation every times. Use `Cysharp.Threading.Tasks.Progress` instead. This progress factory has two methods, `Create` and `CreateOnlyValueChanged`. `CreateOnlyValueChanged` calls only when progress value changed.
Implements interface is more better. Implements IProgress interface to caller is more better, there is no allocation of lambda.
```csharp ```csharp
public class Foo : MonoBehaviour, IProgress<float> public class Foo : MonoBehaviour, IProgress<float>
@@ -253,16 +303,135 @@ public class Foo : MonoBehaviour, IProgress<float>
{ {
var request = await UnityWebRequest.Get("http://google.co.jp") var request = await UnityWebRequest.Get("http://google.co.jp")
.SendWebRequest() .SendWebRequest()
.ConfigureAwait(progress: this); // pass this .ToUniTask(progress: this); // pass this
} }
} }
``` ```
PlayerLoop
---
UniTask is run on custom [PlayerLoop](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html). UniTask's playerloop based method(such as `Delay`, `DelayFrame`, `asyncOperation.ToUniTask`, etc...) accepts this `PlayerLoopTiming`.
```csharp
public enum PlayerLoopTiming
{
Initialization = 0,
LastInitialization = 1,
EarlyUpdate = 2,
LastEarlyUpdate = 3,
FixedUpdate = 4,
LastFixedUpdate = 5,
PreUpdate = 6,
LastPreUpdate = 7,
Update = 8,
LastUpdate = 9,
PreLateUpdate = 10,
LastPreLateUpdate = 11,
PostLateUpdate = 12,
LastPostLateUpdate = 13
}
```
It indicates when to run, you can check [PlayerLoopList.md](https://gist.github.com/neuecc/bc3a1cfd4d74501ad057e49efcd7bdae) to Unity's default playerloop and injected UniTask's custom loop.
`PlayerLoopTiming.Update` is similar as `yield return null` in coroutine, but it is called before Update(Update and uGUI events(button.onClick, etc...) are called on `ScriptRunBehaviourUpdate`, yield return null is called on `ScriptRunDelayedDynamicFrameRate`). `PlayerLoopTiming.FixedUpdate` is similar as `WaitForFixedUpdate`, `PlayerLoopTiming.LastPostLateUpdate` is similar as `WaitForEndOfFrame` in coroutine.
`yield return null` and `UniTask.Yield` is similar but different. `yield return null` always return next frame but `UniTask.Yield` return next called, that is, call `UniTask.Yield(PlayerLoopTiming.Update)` on `PreUpdate`, it returns same frame. `UniTask.NextFrame()` gurantees return next frame, this would be expected to behave exactly the same as `yield return null`.
> 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`.
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`.
In stacktrace, you can check where is running in playerloop.
![image](https://user-images.githubusercontent.com/46207/83735571-83caea80-a68b-11ea-8d22-5e22864f0d24.png)
In default, UniTask's PlayerLoop is initialized at `[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]`.
The order in which methods are called in BeforeSceneLoad is indeterminate, so if you want to use UniTask in other BeforeSceneLoad methods, you should try to initialize it before this.
```csharp
// AfterAssembliesLoaded is called before BeforeSceneLoad
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
public static void InitUniTaskLoop()
{
var loop = PlayerLoop.GetCurrentPlayerLoop();
Cysharp.Threading.Tasks.PlayerLoopHelper.Initialize(ref loop);
}
```
If you import Unity's `Entities` package, that reset custom player loop to default at `BeforeSceneLoad` and inject ECS's loop. When Unity call ECS's inject method after UniTask's initialize method, UniTask will no longer work.
To solve this issue, you can re-initialize UniTask PlayerLoop after ECS initialized.
```csharp
// Get ECS Loop.
var playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
// Setup UniTask's PlayerLoop.
PlayerLoopHelper.Initialize(ref playerLoop);
```
async void vs async UniTaskVoid
---
`async void` is a standard C# taks system so does not run on UniTask systems. It is better not to use. `async UniTaskVoid` is a lightweight version of `async UniTask` because it does not have awaitable completion and report error immediately to `UniTaskScheduler.UnobservedTaskException`. If you don't require to await it(fire and forget), use `UniTaskVoid` is better. Unfortunately to dismiss warning, require to using with `Forget()`.
```csharp
public async UniTaskVoid FireAndForgetMethod()
{
// do anything...
await UniTask.Yield();
}
public void Caller()
{
FireAndForgetMethod().Forget();
}
```
Also UniTask have `Forget` method, it is similar with UniTaskVoid and same effects with it. However still UniTaskVoid is more efficient if completely do not use await。
```csharp
public async UniTask DoAsync()
{
// do anything...
await UniTask.Yield();
}
public void Caller()
{
DoAsync().Forget();
}
```
Using async lambda in register event, it is used `async void`. To avoid it, you can use `UniTask.Action` or `UniTask.UnityAction` that creates delegate via `async UniTaskVoid` lambda.
```csharp
Action actEvent;
UnityAction unityEvent; // especially used in uGUI
// Bad: async void
actEvent += async () => { };
unityEvent += async () => { };
// Ok: create Action delegate by lambda
actEvent += UniTask.Action(async () => { await UniTask.Yield(); });
unityEvent += UniTask.UnityAction(async () => { await UniTask.Yield(); });
```
UniTaskTracker UniTaskTracker
--- ---
useful for check(leak) UniTasks. You can open tracker window in `Window -> UniTask Tracker`. useful for check(leak) UniTasks. You can open tracker window in `Window -> UniTask Tracker`.
![](https://user-images.githubusercontent.com/46207/50421527-abf1cf80-0883-11e9-928a-ffcd47b8c454.png) ![image](https://user-images.githubusercontent.com/46207/83527073-4434bf00-a522-11ea-86e9-3b3975b26266.png)
* Enable AutoReload(Toggle) - Reload automatically. * Enable AutoReload(Toggle) - Reload automatically.
* Reload - Reload view. * Reload - Reload view.
@@ -272,38 +441,89 @@ useful for check(leak) UniTasks. You can open tracker window in `Window -> UniTa
For debug use, enable tracking and capture stacktrace is useful but it it decline performance. Recommended usage is enable both to find task leak, and when done, finally disable both. For debug use, enable tracking and capture stacktrace is useful but it it decline performance. Recommended usage is enable both to find task leak, and when done, finally disable both.
Reusable Promises External Assets
--- ---
Some UniTask factory can reuse to reduce allocation. The list is `Yield`, `Delay`, `DelayFrame`, `WaitUntil`, `WaitWhile`, `WaitUntilValueChanged`. In default, UniTask supports DOTween and Addressables(`AsyncOperationHandle` and `AsyncOpereationHandle<T>` as awaitable).
For DOTween support, require to `com.demigiant.dotween` import from [OpenUPM](https://openupm.com/packages/com.demigiant.dotween/) or define `UNITASK_DOTWEEN_SUPPORT` to enable it.
```csharp ```csharp
var reusePromise = UniTask.DelayFrame(10); // sequential
await transform.DOMoveX(2, 10);
await transform.DOMoveZ(5, 20);
for (int i = 0; i < 10; i++) // parallel with cancellation
var ct = this.GetCancellationTokenOnDestroy();
await UniTask.WhenAll(
transform.DOMoveX(10, 3).WithCancellation(ct),
transform.DOScale(10, 3).WithCancellation(ct));
```
AsyncEnumerable and Async LINQ
---
Unity 2020.2.0a12 supports C# 8.0 so you can use `await foreach`. This is the new Update notation in async era.
```csharp
// Unity 2020.2.0a12, C# 8.0
await foreach (var _ in UniTaskAsyncEnumerable.EveryUpdate(token))
{ {
await reusePromise; Debug.Log("Update() " + Time.frameCount);
} }
``` ```
awaitable Events In a C# 7.3 environment, you can use the `ForEachAsync` method to work in almost the same way.
---
Unity events can await like `OnClickAsync`, `OnCollisionEnterAsync`. It can use by `UniRx.Async.Triggers`.
```csharp ```csharp
using UniRx.Async.Triggers; // C# 7.3(Unity 2018.3~)
await UniTaskAsyncEnumerable.EveryUpdate(token).ForEachAsync(_ =>
async UniTask TripleClick(CancellationToken token)
{ {
await button.OnClickAsync(token); Debug.Log("Update() " + Time.frameCount);
await button.OnClickAsync(token); });
await button.OnClickAsync(token); ```
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
await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsync(_ =>
{
});
```
Async LINQ is enabled when `using Cysharp.Threading.Tasks.Linq;`, and `UniTaskAsyncEnumerable` is defined in `UniTask.Linq` asmdef.
It's closer to UniRx (Reactive Extensions), but UniTaskAsyncEnumerable is a pull-based asynchronous stream, whereas Rx was a push-based asynchronous stream. Note that although similar, the characteristics are different and the details behave differently along with them.
`UniTaskAsyncEnumerable` is the entry point like `Enumerbale`. In addition to the standard query operators, there are other generators for Unity such as `EveryUpdate`, `Timer`, `TimerFrame`, `Interval`, `IntervalFrame`, and `EveryValueChanged`. And also added additional UniTask original query operators like `Append`, `Prepend`, `DistinctUntilChanged`, `ToHashSet`, `Buffer`, `CombineLatest`, `Do`, `Never`, `ForEachAsync`, `Pairwise`, `Publish`, `Queue`, `Return`, `SkipUntilCanceled`, `TakeUntilCanceled`, `TakeLast`.
The method with Func as an argument has three additional overloads, `***Await`, `***AwaitWithCancellation`.
```csharp
Select(Func<T, TR> selector)
SelectAwait(Func<T, UniTask<TR>> selector)
SelectAwaitWithCancellation(Func<T, CancellationToken, UniTask<TR>> selector)
```
If you want to use the `async` method inside the func, use the `***Await` or `***AwaitWithCancellation`.
Awaitable Events
---
All uGUI component implements `***AsAsyncEnumerable` to convert asynchronous streams of events.
```csharp
async UniTask TripleClick()
{
// In default, used button.GetCancellationTokenOnDestroy to manage lieftime of async
await button.OnClickAsync();
await button.OnClickAsync();
await button.OnClickAsync();
Debug.Log("Three times clicked"); Debug.Log("Three times clicked");
} }
// more efficient way // more efficient way
async UniTask TripleClick(CancellationToken token) async UniTask TripleClick()
{ {
using (var handler = button.GetAsyncClickEventHandler(token)) using (var handler = button.GetAsyncClickEventHandler())
{ {
await handler.OnClickAsync(); await handler.OnClickAsync();
await handler.OnClickAsync(); await handler.OnClickAsync();
@@ -311,11 +531,126 @@ async UniTask TripleClick(CancellationToken token)
Debug.Log("Three times clicked"); Debug.Log("Three times clicked");
} }
} }
// use async LINQ
async UniTask TripleClick(CancellationToken token)
{
await button.OnClickAsAsyncEnumerable().Take(3).Last();
Debug.Log("Three times clicked");
}
// use async LINQ2
async UniTask TripleClick(CancellationToken token)
{
await button.OnClickAsAsyncEnumerable().Take(3).ForEachAsync(_ =>
{
Debug.Log("Every clicked");
});
Debug.Log("Three times clicked, complete.");
}
``` ```
async void vs async UniTask/UniTaskVoid All MonoBehaviour message events can convert async-streams by `AsyncTriggers` that can enable by `using Cysharp.Threading.Tasks.Triggers;`.
```csharp
using Cysharp.Threading.Tasks.Triggers;
async UniTaskVoid MonitorCollision()
{
await gameObject.OnCollisionEnterAsync();
Debug.Log("Collision Enter");
/* do anything */
await gameObject.OnCollisionExitAsync();
Debug.Log("Collision Exit");
}
```
Similar as uGUI event, AsyncTrigger can get by `GetAsync***Trigger` and trigger it self is UniTaskAsyncEnumerable.
```csharp
// use await multiple times, get AsyncTriggerHandler is more efficient.
using(var trigger = this.GetOnCollisionEnterAsyncHandler())
{
await OnCollisionEnterAsync();
await OnCollisionEnterAsync();
await OnCollisionEnterAsync();
}
// every moves.
await this.GetAsyncMoveTrigger().ForEachAsync(axisEventData =>
{
});
```
`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).
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.
```csharp
// can not get click event during 3 seconds complete.
await button.OnClickAsAsyncEnumerable().ForEachAwaitAsync()
{
await UniTask.Delay(TimeSpan.FromSeconds(3));
}
```
It is useful(prevent double-click) but not useful in sometimes.
Using `Queue()` method, which will also queue events during asynchronous processing.
```csharp
// queued message in asynchronous processing
await button.OnClickAsAsyncEnumerable().Queue().ForEachAwaitAsync()
{
await UniTask.Delay(TimeSpan.FromSeconds(3));
}
```
Channel
--- ---
`async void` is standard C# system so does not run on UniTask systems. It is better not to use. `async UniTaskVoid` is lightweight version of `async UniTask` because it does not have awaitable completion. If you don't require to await it(fire and forget), use `UniTaskVoid` is better. Unfortunately to dismiss warning, require to using with `Forget()`. `Channel` is same as [System.Threading.Tasks.Channels](https://docs.microsoft.com/ja-jp/dotnet/api/system.threading.channels?view=netcore-3.1) that is similar as GoLang Channel.
Currently only supports multiple-producer, single-consumer unbounded channel. It can create by `Channel.CreateSingleConsumerUnbounded<T>()`.
For producer(`.Writer`), `TryWrite` to push value and `TryComplete` to complete channel. For consumer(`.Reader`), `TryRead`, `WaitToReadAsync`, `ReadAsync`, `Completion` and `ReadAllAsync` to read queued messages.
`ReadAllAsync` returns `IUniTaskAsyncEnumerable<T>` so query LINQ operators. Reader only allows single-consumer but use `.Publish()` query operator to enable multicast message. For example, make pub/sub utility.
```csharp
public class AsyncMessageBroker<T> : IDisposable
{
Channel<T> channel;
IConnectableUniTaskAsyncEnumerable<T> multicastSource;
IDisposable connection;
public AsyncMessageBroker()
{
channel = Channel.CreateSingleConsumerUnbounded<T>();
multicastSource = channel.Reader.ReadAllAsync().Publish();
connection = multicastSource.Connect(); // Publish returns IConnectableUniTaskAsyncEnumerable.
}
public void Publish(T value)
{
channel.Writer.TryWrite(value);
}
public IUniTaskAsyncEnumerable<T> Subscribe()
{
return multicastSource;
}
public void Dispose()
{
channel.Writer.TryComplete();
connection.Dispose();
}
}
```
For Unit Testing For Unit Testing
--- ---
@@ -342,46 +677,92 @@ public IEnumerator DelayIgnore() => UniTask.ToCoroutine(async () =>
}); });
``` ```
Method List 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.
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.
```csharp ```csharp
UniTask.WaitUntil foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
UniTask.WaitWhile {
UniTask.WaitUntilValueChanged Debug.Log(type + ":" + size);
UniTask.SwitchToThreadPool }
UniTask.SwitchToTaskPool
UniTask.SwitchToMainThread
UniTask.SwitchToSynchronizationContext
UniTask.Yield
UniTask.Run
UniTask.Lazy
UniTask.Void
UniTask.ConfigureAwait
UniTask.DelayFrame
UniTask.Delay(..., bool ignoreTimeScale = false, ...) parameter
``` ```
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).
For example, UniTask's factory methods can see at [UniTask#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.UniTask.html#methods-1). UniTaskAsyncEnumerable's factory/extension methods can see at [UniTaskAsyncEnumerable#methods](https://cysharp.github.io/UniTask/api/Cysharp.Threading.Tasks.Linq.UniTaskAsyncEnumerable.html#methods-1).
UPM Package UPM Package
--- ---
After Unity 2019.3.4f1, Unity 2020.1a21, that support path query parameter of git package. You can add `https://github.com/Cysharp/UniTask.git?path=Assets/UniRx.Async` to Package Manager ### Install via git URL
After Unity 2019.3.4f1, Unity 2020.1a21, that support path query parameter of git package. You can add `https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask` to Package Manager
![image](https://user-images.githubusercontent.com/46207/79450714-3aadd100-8020-11ea-8aae-b8d87fc4d7be.png) ![image](https://user-images.githubusercontent.com/46207/79450714-3aadd100-8020-11ea-8aae-b8d87fc4d7be.png)
![image](https://user-images.githubusercontent.com/46207/79450774-56b17280-8020-11ea-91eb-21e1f51eb60c.png) ![image](https://user-images.githubusercontent.com/46207/83702872-e0f17c80-a648-11ea-8183-7469dcd4f810.png)
or add `"com.cysharp.unitask": "https://github.com/Cysharp/UniTask.git?path=Assets/UniRx.Async"` to `Packages/manifest.json`. 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 `#1.3.0`. For example `https://github.com/Cysharp/UniTask.git?path=Assets/UniRx.Async#1.3.1`. 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`.
ECS, PlayerLoop ### Install via OpenUPM
The package is available on the [openupm registry](https://openupm.com). It's recommended to install it via [openupm-cli](https://github.com/openupm/openupm-cli).
```
openupm add com.cysharp.unitask
```
.NET Core
--- ---
TODO: For .NET Core, use NuGet.
> PM> Install-Package [UniTask](https://www.nuget.org/packages/UniTask)
UniTask of .NET Core version is a subset of Unity UniTask, removed PlayerLoop dependent methods.
It runs at higher performance than the standard Task/ValueTask, but you should be careful to ignore the ExecutionContext/SynchronizationContext when using it. `AysncLocal` also does not work because it ignores ExecutionContext.
If you use UniTask internally, but provide ValueTask as an external API, you can write like the following(Inspired by [PooledAwait](https://github.com/mgravell/PooledAwait)).
```csharp ```csharp
var loop = PlayerLoop.GetCurrentPlayerLoop(); public class ZeroAllocAsyncAwaitInDotNetCore
PlayerLoopHelper.Initialize(ref loop); {
public ValueTask<int> DoAsync(int x, int y)
{
return Core(this, x, y);
static async UniTask<int> Core(ZeroAllocAsyncAwaitInDotNetCore self, int x, int y)
{
// do anything...
await Task.Delay(TimeSpan.FromSeconds(x + y));
await UniTask.Yield();
return 10;
}
}
}
// UniTask does not return to original SynchronizationContext but you can use helper `ReturnToCurrentSynchronizationContext`.
public ValueTask TestAsync()
{
await using (UniTask.ReturnToCurrentSynchronizationContext())
{
await UniTask.SwitchToThreadPool();
// do anything..
}
}
``` ```
.NET Core version is intended to allow users to use UniTask as an interface when sharing code with Unity (such as [Cysharp/MagicOnion](https://github.com/Cysharp/MagicOnion/)). .NET Core version of UniTask enables smooth code sharing.
Utility methods such as WhenAll which is equivalent to UniTask are provided as [Cysharp/ValueTaskSupplement](https://github.com/Cysharp/ValueTaskSupplement).
License License
--- ---
This library is under the MIT License. This library is under the MIT License.

10
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
###############
# folder #
###############
/**/DROP/
/**/TEMP/
/**/packages/
/**/bin/
/**/obj/
_site
_DocfxTemplate

5
docs/api/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
###############
# temp file #
###############
*.yml
.manifest

70
docs/docfx.json Normal file
View File

@@ -0,0 +1,70 @@
{
"metadata": [
{
"src": [
{
"files": [
"UniTask/Assets/Plugins/UniTask/Runtime/**/*.cs"
],
"src": "../src"
}
],
"dest": "api",
"disableGitFeatures": false,
"disableDefaultFilter": false
}
],
"build": {
"globalMetadata": {
"_disableContribution": true,
"_appTitle": "UniTask"
},
"content": [
{
"files": [
"api/**.yml",
"api/index.md"
]
},
{
"files": [
"articles/**.md",
"articles/**/toc.yml",
"toc.yml",
"*.md"
]
}
],
"resource": [
{
"files": [
"images/**"
]
}
],
"overwrite": [
{
"files": [
"apidoc/**.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"dest": "_site",
"globalMetadataFiles": [],
"fileMetadataFiles": [],
"template": [
"_DocfxTemplate/templates/default-v2.5.2",
"_DocfxTemplate/templates/cysharp"
],
"postProcessors": [],
"markdownEngineName": "markdig",
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false
}
}

8
docs/index.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: Home
---
# UniTask
Provides an efficient async/await integration to Unity.
https://github.com/Cysharp/UniTask

11
docs/toc.yml Normal file
View File

@@ -0,0 +1,11 @@
- name: API Documentation
href: api/
homepage: api/Cysharp.Threading.Tasks.html
- name: Repository
href: https://github.com/Cysharp/UniTask
homepage: https://github.com/Cysharp/UniTask
- name: Releases
href: https://github.com/Cysharp/UniTask/releases
homepage: https://github.com/Cysharp/UniTask/releases

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,7 +1,6 @@
#pragma warning disable 0649 #pragma warning disable 0649
using System; using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Threading.Tasks.Sources; using System.Threading.Tasks.Sources;
@@ -9,115 +8,91 @@ namespace Cysharp.Threading.Tasks
{ {
public static class UniTaskValueTaskExtensions public static class UniTaskValueTaskExtensions
{ {
public static ValueTask AsValueTask(this UniTask task) public static ValueTask AsValueTask(this in UniTask task)
{ {
ref var core = ref Unsafe.As<UniTask, UniTaskToValueTask>(ref task); #if NETSTANDARD2_0
if (core.source == null) return new ValueTask(new UniTaskValueTaskSource(task), 0);
{ #else
return default; return task;
#endif
} }
return new ValueTask(new UniTaskValueTaskSource(core.source), core.token); public static ValueTask<T> AsValueTask<T>(this in UniTask<T> task)
{
#if NETSTANDARD2_0
return new ValueTask<T>(new UniTaskValueTaskSource<T>(task), 0);
#else
return task;
#endif
} }
public static ValueTask<T> AsValueTask<T>(this UniTask<T> task) public static UniTask<T> AsUniTask<T>(this ValueTask<T> task, bool useCurrentSynchronizationContext = true)
{ {
ref var core = ref Unsafe.As<UniTask<T>, UniTaskToValueTask<T>>(ref task); // NOTE: get _obj and _token directly for low overhead conversion but not yet implemented.
if (core.source == null) return task.AsTask().AsUniTask(useCurrentSynchronizationContext);
{
return new ValueTask<T>(core.result);
} }
return new ValueTask<T>(new UniTaskValueTaskSource<T>(core.source), core.token); public static UniTask AsUniTask(this ValueTask task, bool useCurrentSynchronizationContext = true)
{
return task.AsTask().AsUniTask(useCurrentSynchronizationContext);
} }
struct UniTaskToValueTask #if NETSTANDARD2_0
{
public IUniTaskSource source;
public short token;
}
class UniTaskValueTaskSource : IValueTaskSource class UniTaskValueTaskSource : IValueTaskSource
{ {
readonly IUniTaskSource source; readonly UniTask task;
readonly UniTask.Awaiter awaiter;
public UniTaskValueTaskSource(IUniTaskSource source) public UniTaskValueTaskSource(UniTask task)
{ {
this.source = source; this.task = task;
this.awaiter = task.GetAwaiter();
} }
public void GetResult(short token) public void GetResult(short token)
{ {
source.GetResult(token); awaiter.GetResult();
} }
public ValueTaskSourceStatus GetStatus(short token) public ValueTaskSourceStatus GetStatus(short token)
{ {
var status = source.GetStatus(token); return (ValueTaskSourceStatus)task.Status;
switch (status)
{
case UniTaskStatus.Pending:
return ValueTaskSourceStatus.Pending;
case UniTaskStatus.Succeeded:
return ValueTaskSourceStatus.Succeeded;
case UniTaskStatus.Faulted:
return ValueTaskSourceStatus.Faulted;
case UniTaskStatus.Canceled:
return ValueTaskSourceStatus.Canceled;
default:
return (ValueTaskSourceStatus)status;
}
} }
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{ {
source.OnCompleted(continuation, state, token); awaiter.SourceOnCompleted(continuation, state);
} }
} }
struct UniTaskToValueTask<T>
{
public IUniTaskSource<T> source;
public T result;
public short token;
}
class UniTaskValueTaskSource<T> : IValueTaskSource<T> class UniTaskValueTaskSource<T> : IValueTaskSource<T>
{ {
readonly IUniTaskSource<T> source; readonly UniTask<T> task;
readonly UniTask<T>.Awaiter awaiter;
public UniTaskValueTaskSource(IUniTaskSource<T> source) public UniTaskValueTaskSource(UniTask<T> task)
{ {
this.source = source; this.task = task;
this.awaiter = task.GetAwaiter();
} }
public T GetResult(short token) public T GetResult(short token)
{ {
return source.GetResult(token); return awaiter.GetResult();
} }
public ValueTaskSourceStatus GetStatus(short token) public ValueTaskSourceStatus GetStatus(short token)
{ {
var status = source.GetStatus(token); return (ValueTaskSourceStatus)task.Status;
switch (status)
{
case UniTaskStatus.Pending:
return ValueTaskSourceStatus.Pending;
case UniTaskStatus.Succeeded:
return ValueTaskSourceStatus.Succeeded;
case UniTaskStatus.Faulted:
return ValueTaskSourceStatus.Faulted;
case UniTaskStatus.Canceled:
return ValueTaskSourceStatus.Canceled;
default:
return (ValueTaskSourceStatus)status;
}
} }
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{ {
source.OnCompleted(continuation, state, token); awaiter.SourceOnCompleted(continuation, state);
} }
} }
#endif
} }
} }

View File

@@ -0,0 +1,39 @@
//using Cysharp.Threading.Tasks.Internal;
//using System;
//using System.Collections.Concurrent;
//using System.Runtime.CompilerServices;
//using System.Threading;
//namespace Cysharp.Threading.Tasks
//{
// public partial struct UniTask
// {
// public static UniTask Delay()
// {
// return default;
// }
// sealed class DelayPromise : IUniTaskSource
// {
// public void GetResult(short token)
// {
// throw new NotImplementedException();
// }
// public UniTaskStatus GetStatus(short token)
// {
// throw new NotImplementedException();
// }
// public void OnCompleted(Action<object> continuation, object state, short token)
// {
// throw new NotImplementedException();
// }
// public UniTaskStatus UnsafeGetStatus()
// {
// throw new NotImplementedException();
// }
// }
// }
//}

View File

@@ -1,4 +1,6 @@
using System; using Cysharp.Threading.Tasks.Internal;
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
@@ -41,7 +43,11 @@ namespace Cysharp.Threading.Tasks
} }
else else
{ {
#if NETCOREAPP3_1
ThreadPool.UnsafeQueueUserWorkItem(ThreadPoolWorkItem.Create(continuation), false);
#else
ThreadPool.UnsafeQueueUserWorkItem(WaitCallbackDelegate, continuation); ThreadPool.UnsafeQueueUserWorkItem(WaitCallbackDelegate, continuation);
#endif
} }
} }
@@ -50,6 +56,47 @@ namespace Cysharp.Threading.Tasks
((Action)state).Invoke(); ((Action)state).Invoke();
} }
} }
#if NETCOREAPP3_1
sealed class ThreadPoolWorkItem : IThreadPoolWorkItem, ITaskPoolNode<ThreadPoolWorkItem>
{
static TaskPool<ThreadPoolWorkItem> pool;
public ThreadPoolWorkItem NextNode { get; set; }
static ThreadPoolWorkItem()
{
TaskPool.RegisterSizeGetter(typeof(ThreadPoolWorkItem), () => pool.Size);
}
Action continuation;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ThreadPoolWorkItem Create(Action continuation)
{
if (!pool.TryPop(out var item))
{
item = new ThreadPoolWorkItem();
}
item.continuation = continuation;
return item;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute()
{
var call = continuation;
continuation = null;
if (call != null)
{
pool.TryPush(this);
call.Invoke();
}
}
}
#endif
} }
} }
} }

View File

@@ -1,11 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFrameworks>netcoreapp3.1;netstandard2.1;netstandard2.0</TargetFrameworks>
<AssemblyName>UniTask</AssemblyName> <AssemblyName>UniTask</AssemblyName>
<LangVersion>8.0</LangVersion>
<RootNamespace>Cysharp.Threading.Tasks</RootNamespace> <RootNamespace>Cysharp.Threading.Tasks</RootNamespace>
<!-- NuGet Packaging -->
<Id>UniTask</Id>
<PackageVersion>$(Version)</PackageVersion>
<Company>Cysharp</Company>
<Authors>Cysharp</Authors>
<Copyright>© Cysharp, Inc.</Copyright>
<PackageTags>task;async</PackageTags>
<Description>Provides an efficient async/await integration to Unity and .NET Core.</Description>
<PackageProjectUrl>https://github.com/Cysharp/UniTask</PackageProjectUrl>
<RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Include="Icon.png" Pack="true" PackagePath="/" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\UniTask\Assets\Plugins\UniTask\Runtime\**\*.cs" <Compile Include="..\UniTask\Assets\Plugins\UniTask\Runtime\**\*.cs"
Exclude=" Exclude="

View File

@@ -0,0 +1,258 @@
using BenchmarkDotNet.Attributes;
using System.Linq;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Cysharp.Threading.Tasks;
using PooledAwait;
using System;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks.CompilerServices;
using System.Collections.Concurrent;
[Config(typeof(BenchmarkConfig))]
public class AllocationCheck
{
// note: all the benchmarks use Task/Task<T> for the public API, because BenchmarkDotNet
// doesn't work reliably with more exotic task-types (even just ValueTask fails); instead,
// we'll obscure the cost of the outer awaitable by doing a relatively large number of
// iterations, so that we're only really measuring the inner loop
private const int InnerOps = 1000;
[Benchmark(OperationsPerInvoke = InnerOps)]
public async Task ViaUniTask()
{
for (int i = 0; i < InnerOps; i++)
{
var a = Core();
var b = Core();
var c = Core();
await a;
await b;
await c;
}
static async UniTask Core()
{
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Succeeded);
}
}
[Benchmark(OperationsPerInvoke = InnerOps)]
public async Task<int> ViaUniTaskT()
{
var sum = 0;
for (int i = 0; i < InnerOps; i++)
{
var a = Core();
var b = Core();
var c = Core();
sum += await a;
sum += await b;
sum += await c;
}
return sum;
static async UniTask<int> Core()
{
var a = await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 10);
var b = await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 10);
var c = await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 10);
return 10;
}
}
//[Benchmark(OperationsPerInvoke = InnerOps)]
//[Benchmark]
public void ViaUniTaskVoid()
{
for (int i = 0; i < InnerOps; i++)
{
Core().Forget();
Core().Forget();
Core().Forget();
}
static async UniTaskVoid Core()
{
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Succeeded);
}
}
struct Foo : IAsyncStateMachine
{
public AsyncUniTaskVoidMethodBuilder builder;
public TestAwaiter awaiter;
public TestAwaiter awaiterawaiter;
public int state;
public void MoveNext()
{
switch (state)
{
case -1:
awaiterawaiter = awaiter.GetAwaiter();
if (awaiterawaiter.IsCompleted)
{
goto case 0;
}
else
{
state = 0;
builder.AwaitUnsafeOnCompleted(ref awaiterawaiter, ref this);
return;
}
case 0:
default:
goto END;
}
END:
builder.SetResult();
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
}
}
}
public class TaskTestException : Exception
{
}
public struct TestAwaiter : ICriticalNotifyCompletion
{
readonly UniTaskStatus status;
readonly bool isCompleted;
public TestAwaiter(bool isCompleted, UniTaskStatus status)
{
this.isCompleted = isCompleted;
this.status = status;
}
public TestAwaiter GetAwaiter() => this;
public bool IsCompleted => isCompleted;
public void GetResult()
{
switch (status)
{
case UniTaskStatus.Faulted:
throw new TaskTestException();
case UniTaskStatus.Canceled:
throw new OperationCanceledException();
case UniTaskStatus.Pending:
case UniTaskStatus.Succeeded:
default:
break;
}
}
public void OnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(ThreadPoolWorkItem.Create(continuation), false);
}
public void UnsafeOnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(ThreadPoolWorkItem.Create(continuation), false);
}
}
public struct TestAwaiter<T> : ICriticalNotifyCompletion
{
readonly UniTaskStatus status;
readonly bool isCompleted;
readonly T value;
public TestAwaiter(bool isCompleted, UniTaskStatus status, T value)
{
this.isCompleted = isCompleted;
this.status = status;
this.value = value;
}
public TestAwaiter<T> GetAwaiter() => this;
public bool IsCompleted => isCompleted;
public T GetResult()
{
switch (status)
{
case UniTaskStatus.Faulted:
throw new TaskTestException();
case UniTaskStatus.Canceled:
throw new OperationCanceledException();
case UniTaskStatus.Pending:
case UniTaskStatus.Succeeded:
default:
return value;
}
}
public void OnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(ThreadPoolWorkItem.Create(continuation), false);
}
public void UnsafeOnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(ThreadPoolWorkItem.Create(continuation), false);
}
}
public sealed class ThreadPoolWorkItem : IThreadPoolWorkItem
{
public static readonly ConcurrentQueue<ThreadPoolWorkItem> pool = new ConcurrentQueue<ThreadPoolWorkItem>();
public static void CreatePoolItems(int count)
{
for (int i = 0; i < count; i++)
{
pool.Enqueue(new ThreadPoolWorkItem());
}
}
Action continuation;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ThreadPoolWorkItem Create(Action continuation)
{
if (!pool.TryDequeue(out var item))
{
item = new ThreadPoolWorkItem();
}
item.continuation = continuation;
return item;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute()
{
var call = continuation;
continuation = null;
pool.Enqueue(this);
call.Invoke();
}
}

View File

@@ -0,0 +1,283 @@
using BenchmarkDotNet.Attributes;
using System.Linq;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using Cysharp.Threading.Tasks;
using PooledAwait;
using System;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks.CompilerServices;
//class Program
//{
// static void Main(string[] args)
// {
// var switcher = new BenchmarkSwitcher(new[]
// {
// typeof(StandardBenchmark)
// });
//#if DEBUG
// var b = new StandardBenchmark();
//#else
// switcher.Run(args);
//#endif
// }
//}
public class BenchmarkConfig : ManualConfig
{
public BenchmarkConfig()
{
AddDiagnoser(MemoryDiagnoser.Default);
AddJob(Job.ShortRun.WithLaunchCount(1).WithIterationCount(1).WithWarmupCount(1)/*.RunOncePerIteration()*/);
}
}
// borrowed from PooledAwait
[Config(typeof(BenchmarkConfig))]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class ComparisonBenchmarks
{
// note: all the benchmarks use Task/Task<T> for the public API, because BenchmarkDotNet
// doesn't work reliably with more exotic task-types (even just ValueTask fails); instead,
// we'll obscure the cost of the outer awaitable by doing a relatively large number of
// iterations, so that we're only really measuring the inner loop
private const int InnerOps = 1000;
public bool ConfigureAwait { get; set; } = false;
[Benchmark(OperationsPerInvoke = InnerOps, Description = ".NET")]
[BenchmarkCategory("Task<T>")]
public async Task<int> ViaTaskT()
{
int sum = 0;
for (int i = 0; i < InnerOps; i++)
sum += await Inner(1, 2).ConfigureAwait(ConfigureAwait);
return sum;
static async Task<int> Inner(int x, int y)
{
int i = x;
await Task.Yield();
i *= y;
await Task.Yield();
return 5 * i;
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = ".NET")]
[BenchmarkCategory("Task")]
public async Task ViaTask()
{
for (int i = 0; i < InnerOps; i++)
await Inner().ConfigureAwait(ConfigureAwait);
static async Task Inner()
{
await Task.Yield();
await Task.Yield();
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = ".NET")]
[BenchmarkCategory("ValueTask<T>")]
public async Task<int> ViaValueTaskT()
{
int sum = 0;
for (int i = 0; i < InnerOps; i++)
sum += await Inner(1, 2).ConfigureAwait(ConfigureAwait);
return sum;
static async ValueTask<int> Inner(int x, int y)
{
int i = x;
await Task.Yield();
i *= y;
await Task.Yield();
return 5 * i;
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = ".NET")]
[BenchmarkCategory("ValueTask")]
public async Task ViaValueTask()
{
for (int i = 0; i < InnerOps; i++)
await Inner().ConfigureAwait(ConfigureAwait);
static async ValueTask Inner()
{
await Task.Yield();
await Task.Yield();
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = "Pooled")]
[BenchmarkCategory("ValueTask<T>")]
public async Task<int> ViaPooledValueTaskT()
{
int sum = 0;
for (int i = 0; i < InnerOps; i++)
sum += await Inner(1, 2).ConfigureAwait(ConfigureAwait);
return sum;
static async PooledValueTask<int> Inner(int x, int y)
{
int i = x;
await Task.Yield();
i *= y;
await Task.Yield();
return 5 * i;
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = "Pooled")]
[BenchmarkCategory("ValueTask")]
public async Task ViaPooledValueTask()
{
for (int i = 0; i < InnerOps; i++)
await Inner().ConfigureAwait(ConfigureAwait);
static async PooledValueTask Inner()
{
await Task.Yield();
await Task.Yield();
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = "Pooled")]
[BenchmarkCategory("Task<T>")]
public async Task<int> ViaPooledTaskT()
{
int sum = 0;
for (int i = 0; i < InnerOps; i++)
sum += await Inner(1, 2).ConfigureAwait(ConfigureAwait);
return sum;
static async PooledTask<int> Inner(int x, int y)
{
int i = x;
await Task.Yield();
i *= y;
await Task.Yield();
return 5 * i;
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = "Pooled")]
[BenchmarkCategory("Task")]
public async Task ViaPooledTask()
{
for (int i = 0; i < InnerOps; i++)
await Inner().ConfigureAwait(ConfigureAwait);
static async PooledTask Inner()
{
await Task.Yield();
await Task.Yield();
}
}
// ---
//[Benchmark(OperationsPerInvoke = InnerOps, Description = "UniTaskVoid")]
//[BenchmarkCategory("UniTask")]
//public async Task ViaUniTaskVoid()
//{
// for (int i = 0; i < InnerOps; i++)
// {
// await Inner();
// }
// static async UniTaskVoid Inner()
// {
// await UniTask.Yield();
// await UniTask.Yield();
// }
//}
[Benchmark(OperationsPerInvoke = InnerOps, Description = "UniTask")]
[BenchmarkCategory("UniTask")]
public async Task ViaUniTask()
{
for (int i = 0; i < InnerOps; i++)
{
await Inner();
}
static async UniTask Inner()
{
await UniTask.Yield();
await UniTask.Yield();
}
}
[Benchmark(OperationsPerInvoke = InnerOps, Description = "UniTaskT")]
[BenchmarkCategory("UniTask")]
public async Task<int> ViaUniTaskT()
{
var sum = 0;
for (int i = 0; i < InnerOps; i++)
{
sum += await Inner(1, 2);
}
return sum;
static async UniTask<int> Inner(int x, int y)
{
int i = x;
await UniTask.Yield();
i *= y;
await UniTask.Yield();
return 5 * i;
}
}
}
public struct MyAwaiter : ICriticalNotifyCompletion
{
public MyAwaiter GetAwaiter() => this;
public bool IsCompleted => false;
public void GetResult()
{
}
public void OnCompleted(Action continuation)
{
continuation();
}
public void UnsafeOnCompleted(Action continuation)
{
continuation();
}
}
public struct MyTestStateMachine : IAsyncStateMachine
{
public void MoveNext()
{
//throw new NotImplementedException();
}
public void SetStateMachine(IAsyncStateMachine stateMachine)
{
//throw new NotImplementedException();
}
}

View File

@@ -15,12 +15,132 @@ using System.Reactive.Concurrency;
namespace NetCoreSandbox namespace NetCoreSandbox
{ {
public class MySyncContext : SynchronizationContext
{
public MySyncContext()
{
}
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine("Called SyncContext Post!");
base.Post(d, state);
}
}
public class Text public class Text
{ {
public string text { get; set; } public string text { get; set; }
} }
public class ZeroAllocAsyncAwaitInDotNetCore
{
public ValueTask<int> NanikaAsync(int x, int y)
{
return Core(this, x, y);
static async UniTask<int> Core(ZeroAllocAsyncAwaitInDotNetCore self, int x, int y)
{
// nanika suru...
await Task.Delay(TimeSpan.FromSeconds(x + y));
return 10;
}
}
}
public class TaskTestException : Exception
{
}
public struct TestAwaiter : ICriticalNotifyCompletion
{
readonly UniTaskStatus status;
readonly bool isCompleted;
public TestAwaiter(bool isCompleted, UniTaskStatus status)
{
this.isCompleted = isCompleted;
this.status = status;
}
public TestAwaiter GetAwaiter() => this;
public bool IsCompleted => isCompleted;
public void GetResult()
{
switch (status)
{
case UniTaskStatus.Faulted:
throw new TaskTestException();
case UniTaskStatus.Canceled:
throw new OperationCanceledException();
case UniTaskStatus.Pending:
case UniTaskStatus.Succeeded:
default:
break;
}
}
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem(_ => continuation(), null);
}
public void UnsafeOnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), null);
}
}
public struct TestAwaiter<T> : ICriticalNotifyCompletion
{
readonly UniTaskStatus status;
readonly bool isCompleted;
readonly T value;
public TestAwaiter(bool isCompleted, UniTaskStatus status, T value)
{
this.isCompleted = isCompleted;
this.status = status;
this.value = value;
}
public TestAwaiter<T> GetAwaiter() => this;
public bool IsCompleted => isCompleted;
public T GetResult()
{
switch (status)
{
case UniTaskStatus.Faulted:
throw new TaskTestException();
case UniTaskStatus.Canceled:
throw new OperationCanceledException();
case UniTaskStatus.Pending:
case UniTaskStatus.Succeeded:
default:
return value;
}
}
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem(_ => continuation(), null);
}
public void UnsafeOnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), null);
}
}
public static partial class UnityUIComponentExtensions public static partial class UnityUIComponentExtensions
{ {
public static void BindTo(this IUniTaskAsyncEnumerable<string> source, Text text) public static void BindTo(this IUniTaskAsyncEnumerable<string> source, Text text)
@@ -86,27 +206,144 @@ namespace NetCoreSandbox
await Task.Delay(10, cancellationToken); await Task.Delay(10, cancellationToken);
} }
public class MyDisposable : IDisposable
{
static void Main(string[] args) public void Dispose()
{ {
var channel = Channel.CreateSingleConsumerUnbounded<int>(); }
}
// Observable.Range(1,10).CombineLatest( static void Test()
{
var disp = new MyDisposable();
var cts = new CancellationTokenSource(); using var _ = new MyDisposable();
var token = cts.Token;
FooAsync(token).ForEachAsync(x => { }, token);
// Observable.Range(1,10).CombineLatest(
Console.WriteLine("tako");
} }
static async UniTask FooBarAsync()
{
await using (UniTask.ReturnToCurrentSynchronizationContext())
{
await UniTask.SwitchToThreadPool();
}
}
static async UniTask Aaa()
{
await FooBarAsync();
Console.WriteLine("FooBarAsync End");
}
static async UniTask WhereSelect()
{
await foreach (var item in UniTaskAsyncEnumerable.Range(1, 10)
.SelectAwait(async x =>
{
await UniTask.Yield();
return x;
})
.Where(x => x % 2 == 0))
{
Console.WriteLine(item);
}
}
static async Task Main(string[] args)
{
#if !DEBUG
//await new AllocationCheck().ViaUniTaskVoid();
//Console.ReadLine();
BenchmarkDotNet.Running.BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
//await new ComparisonBenchmarks().ViaUniTaskT();
return;
#endif
// await new AllocationCheck().ViaUniTaskVoid();
// AsyncTest().Forge
Console.WriteLine("A?");
var a = await new ZeroAllocAsyncAwaitInDotNetCore().NanikaAsync(1, 2);
Console.WriteLine("RET:" + a);
await WhereSelect();
SynchronizationContext.SetSynchronizationContext(new MySyncContext());
await Aaa();
//AsyncTest().Forget();
// AsyncTest().Forget();
ThreadPool.SetMinThreads(100, 100);
//List<UniTask<int>> list = new List<UniTask<int>>();
for (int i = 0; i < short.MaxValue; i++)
{
//// list.Add(AsyncTest());
await YieldCore();
}
//await UniTask.WhenAll(list);
//Console.WriteLine("TOGO");
//var a = await AsyncTest();
//var b = AsyncTest();
//var c = AsyncTest();
await YieldCore();
//await b;
//await c;
//foreach (var item in Cysharp.Threading.Tasks.Internal.TaskPool.GetCacheSizeInfo())
//{
// Console.WriteLine(item);
//}
Console.ReadLine();
}
static async UniTask YieldCore()
{
await UniTask.Yield();
}
#pragma warning disable CS1998
static async UniTask<int> AsyncTest()
{
// empty
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(true, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Succeeded);
return 10;
}
#pragma warning restore CS1998
void Foo() void Foo()
{ {

View File

@@ -0,0 +1,396 @@
using BenchmarkDotNet.Attributes;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
[Config(typeof(BenchmarkConfig))]
public class QueueCheck
{
Node node1 = new Node();
Node node2 = new Node();
Queue<Node> q1 = new Queue<Node>();
Stack<Node> s1 = new Stack<Node>();
ConcurrentQueue<Node> cq = new ConcurrentQueue<Node>();
ConcurrentStack<Node> cs = new ConcurrentStack<Node>();
static TaskPool<Node> pool;
static TaskPoolEqualNull<Node> poolEqualNull;
static TaskPoolClass<Node> poolClass = new TaskPoolClass<Node>();
static TaskPoolWithoutSize<Node> poolWithoutSize;
static TaskPoolWithoutLock<Node> poolWithoutLock;
[Benchmark]
public void Queue()
{
q1.Enqueue(node1);
q1.Enqueue(node1);
q1.TryDequeue(out _);
q1.TryDequeue(out _);
}
[Benchmark]
public void QueueLock()
{
lock (q1) { q1.Enqueue(node1); }
lock (q1) { q1.Enqueue(node1); }
lock (q1) { q1.TryDequeue(out _); }
lock (q1) { q1.TryDequeue(out _); }
}
[Benchmark]
public void Stack()
{
s1.Push(node1);
s1.Push(node2);
s1.TryPop(out _);
s1.TryPop(out _);
}
[Benchmark]
public void StackLock()
{
lock (s1) { s1.Push(node1); }
lock (s1) { s1.Push(node2); }
lock (s1) { s1.TryPop(out _); }
lock (s1) { s1.TryPop(out _); }
}
[Benchmark]
public void ConcurrentQueue()
{
cq.Enqueue(node1);
cq.Enqueue(node1);
cq.TryDequeue(out _);
cq.TryDequeue(out _);
}
[Benchmark]
public void ConcurrentStack()
{
cs.Push(node1);
cs.Push(node2);
cs.TryPop(out _);
cs.TryPop(out _);
}
[Benchmark]
public void TaskPool()
{
pool.TryPush(node1);
pool.TryPush(node2);
pool.TryPop(out _);
pool.TryPop(out _);
}
[Benchmark]
public void TaskPoolEqualNull()
{
poolEqualNull.TryPush(node1);
poolEqualNull.TryPush(node2);
poolEqualNull.TryPop(out _);
poolEqualNull.TryPop(out _);
}
[Benchmark]
public void TaskPoolClass()
{
poolClass.TryPush(node1);
poolClass.TryPush(node2);
poolClass.TryPop(out _);
poolClass.TryPop(out _);
}
[Benchmark]
public void TaskPoolWithoutSize()
{
poolWithoutSize.TryPush(node1);
poolWithoutSize.TryPush(node2);
poolWithoutSize.TryPop(out _);
poolWithoutSize.TryPop(out _);
}
[Benchmark]
public void TaskPoolWithoutLock()
{
poolWithoutLock.TryPush(node1);
poolWithoutLock.TryPush(node2);
poolWithoutLock.TryPop(out _);
poolWithoutLock.TryPop(out _);
}
}
public sealed class Node : ITaskPoolNode<Node>
{
public Node NextNode { get; set; }
}
public interface ITaskPoolNode<T>
{
T NextNode { get; set; }
}
// mutable struct, don't mark readonly.
[StructLayout(LayoutKind.Auto)]
public struct TaskPoolWithoutLock<T>
where T : class, ITaskPoolNode<T>
{
int size;
T root;
public int Size => size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPop(out T result)
{
//if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
var v = root;
if (!(v is null))
{
root = v.NextNode;
v.NextNode = null;
size--;
result = v;
// Volatile.Write(ref gate, 0);
return true;
}
//Volatile.Write(ref gate, 0);
}
result = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T item)
{
//if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
//if (size < TaskPool.MaxPoolSize)
{
item.NextNode = root;
root = item;
size++;
// Volatile.Write(ref gate, 0);
return true;
}
//else
{
// Volatile.Write(ref gate, 0);
}
}
//return false;
}
}
[StructLayout(LayoutKind.Auto)]
public struct TaskPool<T>
where T : class, ITaskPoolNode<T>
{
int gate;
int size;
T root;
public int Size => size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPop(out T result)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
var v = root;
if (!(v is null))
{
root = v.NextNode;
v.NextNode = null;
size--;
result = v;
Volatile.Write(ref gate, 0);
return true;
}
Volatile.Write(ref gate, 0);
}
result = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T item)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
//if (size < TaskPool.MaxPoolSize)
{
item.NextNode = root;
root = item;
size++;
Volatile.Write(ref gate, 0);
return true;
}
//else
{
// Volatile.Write(ref gate, 0);
}
}
return false;
}
}
[StructLayout(LayoutKind.Auto)]
public struct TaskPoolEqualNull<T>
where T : class, ITaskPoolNode<T>
{
int gate;
int size;
T root;
public int Size => size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPop(out T result)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
var v = root;
if (v != null)
{
root = v.NextNode;
v.NextNode = null;
size--;
result = v;
Volatile.Write(ref gate, 0);
return true;
}
Volatile.Write(ref gate, 0);
}
result = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T item)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
//if (size < TaskPool.MaxPoolSize)
{
item.NextNode = root;
root = item;
size++;
Volatile.Write(ref gate, 0);
return true;
}
//else
{
// Volatile.Write(ref gate, 0);
}
}
return false;
}
}
public class TaskPoolClass<T>
where T : class, ITaskPoolNode<T>
{
int gate;
int size;
T root;
public int Size => size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPop(out T result)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
var v = root;
if (!(v is null))
{
root = v.NextNode;
v.NextNode = null;
size--;
result = v;
Volatile.Write(ref gate, 0);
return true;
}
Volatile.Write(ref gate, 0);
}
result = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T item)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
//if (size < TaskPool.MaxPoolSize)
{
item.NextNode = root;
root = item;
size++;
Volatile.Write(ref gate, 0);
return true;
}
//else
{
// Volatile.Write(ref gate, 0);
}
}
return false;
}
}
[StructLayout(LayoutKind.Auto)]
public struct TaskPoolWithoutSize<T>
where T : class, ITaskPoolNode<T>
{
int gate;
T root;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPop(out T result)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
var v = root;
if (!(v is null))
{
root = v.NextNode;
v.NextNode = null;
result = v;
Volatile.Write(ref gate, 0);
return true;
}
Volatile.Write(ref gate, 0);
}
result = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T item)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
//if (size < TaskPool.MaxPoolSize)
{
item.NextNode = root;
root = item;
Volatile.Write(ref gate, 0);
return true;
}
//else
{
// Volatile.Write(ref gate, 0);
}
}
return false;
}
}

View File

@@ -7,6 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
<PackageReference Include="PooledAwait" Version="1.0.49" />
<PackageReference Include="System.Interactive.Async" Version="4.1.1" /> <PackageReference Include="System.Interactive.Async" Version="4.1.1" />
<PackageReference Include="System.Reactive" Version="4.4.1" /> <PackageReference Include="System.Reactive" Version="4.4.1" />
</ItemGroup> </ItemGroup>

View File

@@ -0,0 +1,301 @@
#pragma warning disable CS1998
using Cysharp.Threading.Tasks;
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using Cysharp.Threading.Tasks.Linq;
using System.Threading.Tasks;
using Xunit;
using System.Runtime.CompilerServices;
namespace NetCoreTests
{
public class UniTaskBuilderTest
{
[Fact]
public async Task Empty()
{
await Core();
static async UniTask Core()
{
}
}
[Fact]
public async Task EmptyThrow()
{
await Assert.ThrowsAsync<TaskTestException>(async () => await Core());
static async UniTask Core()
{
throw new TaskTestException();
}
}
[Fact]
public async Task Task_Done()
{
await Core();
static async UniTask Core()
{
await new TestAwaiter(true, UniTaskStatus.Succeeded);
}
}
[Fact]
public async Task Task_Fail()
{
await Assert.ThrowsAsync<TaskTestException>(async () => await Core());
static async UniTask Core()
{
await new TestAwaiter(true, UniTaskStatus.Faulted);
}
}
[Fact]
public async Task Task_Cancel()
{
await Assert.ThrowsAsync<OperationCanceledException>(async () => await Core());
static async UniTask Core()
{
await new TestAwaiter(true, UniTaskStatus.Canceled);
}
}
[Fact]
public async Task AwaitUnsafeOnCompletedCall_Task_SetResult()
{
await Core();
static async UniTask Core()
{
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Succeeded);
}
}
[Fact]
public async Task AwaitUnsafeOnCompletedCall_Task_SetException()
{
await Assert.ThrowsAsync<TaskTestException>(async () => await Core());
static async UniTask Core()
{
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Faulted);
throw new InvalidOperationException();
}
}
[Fact]
public async Task AwaitUnsafeOnCompletedCall_Task_SetCancelException()
{
await Assert.ThrowsAsync<OperationCanceledException>(async () => await Core());
static async UniTask Core()
{
await new TestAwaiter(false, UniTaskStatus.Succeeded);
await new TestAwaiter(false, UniTaskStatus.Canceled);
throw new InvalidOperationException();
}
}
}
public class UniTask_T_BuilderTest
{
[Fact]
public async Task Empty()
{
(await Core()).Should().Be(10);
static async UniTask<int> Core()
{
return 10;
}
}
[Fact]
public async Task EmptyThrow()
{
await Assert.ThrowsAsync<TaskTestException>(async () => await Core());
static async UniTask<int> Core()
{
throw new TaskTestException();
}
}
[Fact]
public async Task Task_Done()
{
(await Core()).Should().Be(10);
static async UniTask<int> Core()
{
return await new TestAwaiter<int>(true, UniTaskStatus.Succeeded, 10);
}
}
[Fact]
public async Task Task_Fail()
{
await Assert.ThrowsAsync<TaskTestException>(async () => await Core());
static async UniTask<int> Core()
{
return await new TestAwaiter<int>(true, UniTaskStatus.Faulted, 10);
}
}
[Fact]
public async Task Task_Cancel()
{
await Assert.ThrowsAsync<OperationCanceledException>(async () => await Core());
static async UniTask<int> Core()
{
return await new TestAwaiter<int>(true, UniTaskStatus.Canceled, 10);
}
}
[Fact]
public async Task AwaitUnsafeOnCompletedCall_Task_SetResult()
{
(await Core()).Should().Be(6);
static async UniTask<int> Core()
{
var sum = 0;
sum += await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 1);
sum += await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 2);
sum += await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 3);
return sum;
}
}
[Fact]
public async Task AwaitUnsafeOnCompletedCall_Task_SetException()
{
await Assert.ThrowsAsync<TaskTestException>(async () => await Core());
static async UniTask<int> Core()
{
await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 10);
await new TestAwaiter<int>(false, UniTaskStatus.Faulted, 10);
throw new InvalidOperationException();
}
}
[Fact]
public async Task AwaitUnsafeOnCompletedCall_Task_SetCancelException()
{
await Assert.ThrowsAsync<OperationCanceledException>(async () => await Core());
static async UniTask<int> Core()
{
await new TestAwaiter<int>(false, UniTaskStatus.Succeeded, 10);
await new TestAwaiter<int>(false, UniTaskStatus.Canceled, 10);
throw new InvalidOperationException();
}
}
}
public class TaskTestException : Exception
{
}
public struct TestAwaiter : ICriticalNotifyCompletion
{
readonly UniTaskStatus status;
readonly bool isCompleted;
public TestAwaiter(bool isCompleted, UniTaskStatus status)
{
this.isCompleted = isCompleted;
this.status = status;
}
public TestAwaiter GetAwaiter() => this;
public bool IsCompleted => isCompleted;
public void GetResult()
{
switch (status)
{
case UniTaskStatus.Faulted:
throw new TaskTestException();
case UniTaskStatus.Canceled:
throw new OperationCanceledException();
case UniTaskStatus.Pending:
case UniTaskStatus.Succeeded:
default:
break;
}
}
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem(_ => continuation(), null);
}
public void UnsafeOnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), null);
}
}
public struct TestAwaiter<T> : ICriticalNotifyCompletion
{
readonly UniTaskStatus status;
readonly bool isCompleted;
readonly T value;
public TestAwaiter(bool isCompleted, UniTaskStatus status, T value)
{
this.isCompleted = isCompleted;
this.status = status;
this.value = value;
}
public TestAwaiter<T> GetAwaiter() => this;
public bool IsCompleted => isCompleted;
public T GetResult()
{
switch (status)
{
case UniTaskStatus.Faulted:
throw new TaskTestException();
case UniTaskStatus.Canceled:
throw new OperationCanceledException();
case UniTaskStatus.Pending:
case UniTaskStatus.Succeeded:
default:
return value;
}
}
public void OnCompleted(Action continuation)
{
ThreadPool.QueueUserWorkItem(_ => continuation(), null);
}
public void UnsafeOnCompleted(Action continuation)
{
ThreadPool.UnsafeQueueUserWorkItem(_ => continuation(), null);
}
}
}

View File

@@ -0,0 +1,637 @@
using Cysharp.Threading.Tasks;
using FluentAssertions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Channels;
using Cysharp.Threading.Tasks.Linq;
using System.Threading.Tasks;
using Xunit;
namespace NetCoreTests
{
public class TriggerEventTest
{
[Fact]
public void SimpleAdd()
{
var ev = new TriggerEvent<int>();
// do nothing
ev.SetResult(0);
ev.SetError(null);
ev.SetCompleted();
ev.SetCanceled(default);
{
var one = new TestEvent(1);
ev.Add(one);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
ev.SetCompleted();
one.CompletedCalled.Count.Should().Be(1);
// do nothing
ev.SetResult(0);
ev.SetError(null);
ev.SetCompleted();
ev.SetCanceled(default);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
one.CompletedCalled.Count.Should().Be(1);
}
// after removed, onemore
{
var one = new TestEvent(1);
ev.Add(one);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
ev.SetCompleted();
one.CompletedCalled.Count.Should().Be(1);
// do nothing
ev.SetResult(0);
ev.SetError(null);
ev.SetCompleted();
ev.SetCanceled(default);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
one.CompletedCalled.Count.Should().Be(1);
}
}
[Fact]
public void AddFour()
{
var ev = new TriggerEvent<int>();
// do nothing
ev.SetResult(0);
ev.SetError(null);
ev.SetCompleted();
ev.SetCanceled(default);
{
var one = new TestEvent(1);
var two = new TestEvent(2);
var three = new TestEvent(3);
var four = new TestEvent(4);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.Add(four);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
four.NextCalled.Should().BeEquivalentTo(10, 20, 30);
ev.SetCompleted();
one.CompletedCalled.Count.Should().Be(1);
two.CompletedCalled.Count.Should().Be(1);
three.CompletedCalled.Count.Should().Be(1);
// do nothing
ev.SetResult(0);
ev.SetError(null);
ev.SetCompleted();
ev.SetCanceled(default);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
one.CompletedCalled.Count.Should().Be(1);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.CompletedCalled.Count.Should().Be(1);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.CompletedCalled.Count.Should().Be(1);
}
// after removed, onemore.
{
var one = new TestEvent(1);
var two = new TestEvent(2);
var three = new TestEvent(3);
var four = new TestEvent(4);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.Add(four);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
ev.Add(four);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
four.NextCalled.Should().BeEquivalentTo(10, 20, 30);
ev.SetCompleted();
one.CompletedCalled.Count.Should().Be(1);
two.CompletedCalled.Count.Should().Be(1);
three.CompletedCalled.Count.Should().Be(1);
// do nothing
ev.SetResult(0);
ev.SetError(null);
ev.SetCompleted();
ev.SetCanceled(default);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
one.CompletedCalled.Count.Should().Be(1);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.CompletedCalled.Count.Should().Be(1);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.CompletedCalled.Count.Should().Be(1);
}
}
[Fact]
public void OneRemove()
{
var ev = new TriggerEvent<int>();
{
var one = new TestEvent(1);
var two = new TestEvent(2);
var three = new TestEvent(3);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
ev.Remove(one);
ev.SetResult(40);
ev.SetResult(50);
ev.SetResult(60);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60);
}
}
[Fact]
public void TwoRemove()
{
var ev = new TriggerEvent<int>();
{
var one = new TestEvent(1);
var two = new TestEvent(2);
var three = new TestEvent(3);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
ev.Remove(two);
ev.SetResult(40);
ev.SetResult(50);
ev.SetResult(60);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60);
}
}
[Fact]
public void ThreeRemove()
{
var ev = new TriggerEvent<int>();
{
var one = new TestEvent(1);
var two = new TestEvent(2);
var three = new TestEvent(3);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
ev.Remove(three);
ev.SetResult(40);
ev.SetResult(50);
ev.SetResult(60);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40, 50, 60);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
}
[Fact]
public void RemoveSelf()
{
new RemoveMe().Run1();
new RemoveMe().Run2();
new RemoveMe().Run3();
}
[Fact]
public void RemoveNextInIterating()
{
new RemoveNext().Run1();
new RemoveNext().Run2();
new RemoveNext().Run3();
}
[Fact]
public void RemoveNextNextTest()
{
new RemoveNextNext().Run1();
new RemoveNextNext().Run2();
}
[Fact]
public void AddTest()
{
new AddMe().Run1();
new AddMe().Run2();
}
public class RemoveMe
{
TriggerEvent<int> ev;
public void Run1()
{
TestEvent one = default;
one = new TestEvent(1, () => ev.Remove(one));
var two = new TestEvent(2);
var three = new TestEvent(3);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
public void Run2()
{
TestEvent one = default;
one = new TestEvent(1, () => ev.Remove(one));
var two = new TestEvent(2);
var three = new TestEvent(3);
ev.Add(two);
ev.Add(one); // add second.
ev.Add(three);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
public void Run3()
{
TestEvent one = default;
one = new TestEvent(1, () => ev.Remove(one));
var two = new TestEvent(2);
var three = new TestEvent(3);
ev.Add(two);
ev.Add(three);
ev.Add(one); // add thired.
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10);
two.NextCalled.Should().BeEquivalentTo(10, 20, 30);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
}
public class RemoveNext
{
TriggerEvent<int> ev;
public void Run1()
{
TestEvent one = default;
TestEvent two = default;
TestEvent three = default;
one = new TestEvent(1, () => ev.Remove(two));
two = new TestEvent(2);
three = new TestEvent(3);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Count.Should().Be(0);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
public void Run2()
{
TestEvent one = default;
TestEvent two = default;
TestEvent three = default;
one = new TestEvent(1, () => ev.Remove(two));
two = new TestEvent(2);
three = new TestEvent(3);
ev.Add(two);
ev.Add(one); // add second
ev.Add(three);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
public void Run3()
{
TestEvent one = default;
TestEvent two = default;
TestEvent three = default;
one = new TestEvent(1, () => ev.Remove(two));
two = new TestEvent(2);
three = new TestEvent(3);
ev.Add(two);
ev.Add(three);
ev.Add(one); // add thired.
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Should().BeEquivalentTo(10);
three.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
}
public class RemoveNextNext
{
TriggerEvent<int> ev;
public void Run1()
{
TestEvent one = default;
TestEvent two = default;
TestEvent three = default;
TestEvent four = default;
one = new TestEvent(1, () => { ev.Remove(two); ev.Remove(three); });
two = new TestEvent(2);
three = new TestEvent(3);
four = new TestEvent(4);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.Add(four);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30);
two.NextCalled.Count.Should().Be(0);
three.NextCalled.Count.Should().Be(0);
four.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
public void Run2()
{
TestEvent one = default;
TestEvent two = default;
TestEvent three = default;
TestEvent four = default;
one = new TestEvent(1, () => { ev.Remove(one); ev.Remove(two); ev.Remove(three); });
two = new TestEvent(2);
three = new TestEvent(3);
four = new TestEvent(4);
ev.Add(one);
ev.Add(two);
ev.Add(three);
ev.Add(four);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
one.NextCalled.Should().BeEquivalentTo(10);
two.NextCalled.Count.Should().Be(0);
three.NextCalled.Count.Should().Be(0);
four.NextCalled.Should().BeEquivalentTo(10, 20, 30);
}
}
public class AddMe
{
TriggerEvent<int> ev;
public void Run1()
{
TestEvent one = default;
TestEvent two = default;
TestEvent three = default;
TestEvent four = default;
one = new TestEvent(1, () =>
{
if (two == null)
{
ev.Add(two = new TestEvent(2));
}
else if (three == null)
{
ev.Add(three = new TestEvent(3));
}
else if (four == null)
{
ev.Add(four = new TestEvent(4));
}
});
ev.Add(one);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
ev.SetResult(40);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40);
two.NextCalled.Should().BeEquivalentTo(20, 30, 40);
three.NextCalled.Should().BeEquivalentTo(30, 40);
four.NextCalled.Should().BeEquivalentTo(40);
}
public void Run2()
{
TestEvent one = default;
TestEvent two = default;
TestEvent three = default;
TestEvent four = default;
one = new TestEvent(1, () =>
{
if (two == null)
{
ev.Add(two = new TestEvent(2, () =>
{
if (three == null)
{
ev.Add(three = new TestEvent(3, () =>
{
if (four == null)
{
ev.Add(four = new TestEvent(4));
}
}));
}
}));
}
});
ev.Add(one);
ev.SetResult(10);
ev.SetResult(20);
ev.SetResult(30);
ev.SetResult(40);
one.NextCalled.Should().BeEquivalentTo(10, 20, 30, 40);
two.NextCalled.Should().BeEquivalentTo(20, 30, 40);
three.NextCalled.Should().BeEquivalentTo(30, 40);
four.NextCalled.Should().BeEquivalentTo(40);
}
}
}
public class TestEvent : ITriggerHandler<int>
{
public readonly int Id;
readonly Action iteratingEvent;
public TestEvent(int id)
{
this.Id = id;
}
public TestEvent(int id, Action iteratingEvent)
{
this.Id = id;
this.iteratingEvent = iteratingEvent;
}
public List<int> NextCalled = new List<int>();
public List<Exception> ErrorCalled = new List<Exception>();
public List<object> CompletedCalled = new List<object>();
public List<CancellationToken> CancelCalled = new List<CancellationToken>();
public ITriggerHandler<int> Prev { get; set; }
public ITriggerHandler<int> Next { get; set; }
public void OnCanceled(CancellationToken cancellationToken)
{
CancelCalled.Add(cancellationToken);
}
public void OnCompleted()
{
CompletedCalled.Add(new object());
}
public void OnError(Exception ex)
{
ErrorCalled.Add(ex);
}
public void OnNext(int value)
{
NextCalled.Add(value);
iteratingEvent?.Invoke();
}
public override string ToString()
{
return Id.ToString();
}
}
}

View File

@@ -0,0 +1,30 @@
#if UNITY_EDITOR
using Cysharp.Threading.Tasks;
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using UnityEngine.Networking;
public static class EditorRunnerChecker
{
[MenuItem("Tools/UniTaskEditorRunnerChecker")]
public static void RunUniTaskAsync()
{
RunCore().Forget();
}
static async UniTaskVoid RunCore()
{
Debug.Log("Start");
var r = await UnityWebRequest.Get("https://bing.com/").SendWebRequest().ToUniTask();
Debug.Log(r.downloadHandler.text.Substring(0, 100));
Debug.Log("End");
}
}
#endif

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: fcb1f7467a3e2b64c8a016c8aee2f9b4 guid: e51b78c06cb410f42b36e0af9de3b065
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -1,5 +1,4 @@
using Cysharp.Threading.Tasks.Linq; using System;
using System;
using System.Threading; using System.Threading;
namespace Cysharp.Threading.Tasks namespace Cysharp.Threading.Tasks
@@ -120,6 +119,9 @@ namespace Cysharp.Threading.Tasks
public T Current => value; public T Current => value;
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {
// raise latest value on first call. // raise latest value on first call.
@@ -300,6 +302,8 @@ namespace Cysharp.Threading.Tasks
} }
public T Current => value; public T Current => value;
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {

View File

@@ -4,7 +4,7 @@ using System;
namespace Cysharp.Threading.Tasks namespace Cysharp.Threading.Tasks
{ {
public struct AsyncUnit : IEquatable<AsyncUnit> public readonly struct AsyncUnit : IEquatable<AsyncUnit>
{ {
public static readonly AsyncUnit Default = new AsyncUnit(); public static readonly AsyncUnit Default = new AsyncUnit();

View File

@@ -368,8 +368,8 @@ namespace Cysharp.Threading.Tasks
readonly SingleConsumerUnboundedChannelReader parent; readonly SingleConsumerUnboundedChannelReader parent;
CancellationToken cancellationToken1; CancellationToken cancellationToken1;
CancellationToken cancellationToken2; CancellationToken cancellationToken2;
CancellationTokenRegistration CancellationTokenRegistration1; CancellationTokenRegistration cancellationTokenRegistration1;
CancellationTokenRegistration CancellationTokenRegistration2; CancellationTokenRegistration cancellationTokenRegistration2;
T current; T current;
bool cacheValue; bool cacheValue;
@@ -395,12 +395,12 @@ namespace Cysharp.Threading.Tasks
if (this.cancellationToken1.CanBeCanceled) if (this.cancellationToken1.CanBeCanceled)
{ {
this.cancellationToken1.RegisterWithoutCaptureExecutionContext(CancellationCallback1Delegate, this); this.cancellationTokenRegistration1 = this.cancellationToken1.RegisterWithoutCaptureExecutionContext(CancellationCallback1Delegate, this);
} }
if (this.cancellationToken2.CanBeCanceled) if (this.cancellationToken2.CanBeCanceled)
{ {
this.cancellationToken2.RegisterWithoutCaptureExecutionContext(CancellationCallback2Delegate, this); this.cancellationTokenRegistration2 = this.cancellationToken2.RegisterWithoutCaptureExecutionContext(CancellationCallback2Delegate, this);
} }
running = true; running = true;
@@ -428,8 +428,8 @@ namespace Cysharp.Threading.Tasks
public UniTask DisposeAsync() public UniTask DisposeAsync()
{ {
CancellationTokenRegistration1.Dispose(); cancellationTokenRegistration1.Dispose();
CancellationTokenRegistration2.Dispose(); cancellationTokenRegistration2.Dispose();
return default; return default;
} }

View File

@@ -12,9 +12,8 @@ namespace Cysharp.Threading.Tasks.CompilerServices
[StructLayout(LayoutKind.Auto)] [StructLayout(LayoutKind.Auto)]
public struct AsyncUniTaskMethodBuilder public struct AsyncUniTaskMethodBuilder
{ {
// cache items. IStateMachineRunnerPromise runnerPromise;
AutoResetUniTaskCompletionSource promise; Exception ex;
IMoveNextRunner runner;
// 1. Static Create method. // 1. Static Create method.
[DebuggerHidden] [DebuggerHidden]
@@ -31,98 +30,81 @@ namespace Cysharp.Threading.Tasks.CompilerServices
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if (promise != null) if (runnerPromise != null)
{ {
return promise.Task; return runnerPromise.Task;
} }
else if (ex != null)
if (runner == null) {
return UniTask.FromException(ex);
}
else
{ {
return UniTask.CompletedTask; return UniTask.CompletedTask;
} }
promise = AutoResetUniTaskCompletionSource.Create();
return promise.Task;
} }
} }
// 3. SetException // 3. SetException
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetException(Exception exception) public void SetException(Exception exception)
{ {
// runner is finished, return first. if (runnerPromise == null)
if (runner != null)
{ {
runner.Return(); ex = exception;
runner = null;
}
if (promise != null)
{
promise.TrySetException(exception);
} }
else else
{ {
promise = AutoResetUniTaskCompletionSource.CreateFromException(exception, out _); runnerPromise.SetException(exception);
} }
} }
// 4. SetResult // 4. SetResult
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetResult() public void SetResult()
{ {
// runner is finished, return first. if (runnerPromise != null)
if (runner != null)
{ {
runner.Return(); runnerPromise.SetResult();
runner = null;
}
if (promise != null)
{
promise.TrySetResult();
} }
} }
// 5. AwaitOnCompleted // 5. AwaitOnCompleted
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {
if (promise == null) if (runnerPromise == null)
{ {
promise = AutoResetUniTaskCompletionSource.Create(); AsyncUniTask<TStateMachine>.SetStateMachine(ref stateMachine, ref runnerPromise);
}
if (runner == null)
{
runner = MoveNextRunner<TStateMachine>.Create(ref stateMachine);
} }
awaiter.OnCompleted(runner.CallMoveNext); awaiter.OnCompleted(runnerPromise.MoveNext);
} }
// 6. AwaitUnsafeOnCompleted // 6. AwaitUnsafeOnCompleted
[DebuggerHidden] [DebuggerHidden]
[SecuritySafeCritical] [SecuritySafeCritical]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {
if (promise == null) if (runnerPromise == null)
{ {
promise = AutoResetUniTaskCompletionSource.Create(); AsyncUniTask<TStateMachine>.SetStateMachine(ref stateMachine, ref runnerPromise);
}
if (runner == null)
{
runner = MoveNextRunner<TStateMachine>.Create(ref stateMachine);
} }
awaiter.OnCompleted(runner.CallMoveNext); awaiter.UnsafeOnCompleted(runnerPromise.MoveNext);
} }
// 7. Start // 7. Start
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<TStateMachine>(ref TStateMachine stateMachine) public void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {
@@ -156,9 +138,8 @@ namespace Cysharp.Threading.Tasks.CompilerServices
[StructLayout(LayoutKind.Auto)] [StructLayout(LayoutKind.Auto)]
public struct AsyncUniTaskMethodBuilder<T> public struct AsyncUniTaskMethodBuilder<T>
{ {
// cache items. IStateMachineRunnerPromise<T> runnerPromise;
AutoResetUniTaskCompletionSource<T> promise; Exception ex;
IMoveNextRunner runner;
T result; T result;
// 1. Static Create method. // 1. Static Create method.
@@ -170,106 +151,91 @@ namespace Cysharp.Threading.Tasks.CompilerServices
} }
// 2. TaskLike Task property. // 2. TaskLike Task property.
[DebuggerHidden]
public UniTask<T> Task public UniTask<T> Task
{ {
[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
if (promise != null) if (runnerPromise != null)
{ {
return promise.Task; return runnerPromise.Task;
} }
else if (ex != null)
if (runner == null) {
return UniTask.FromException<T>(ex);
}
else
{ {
return UniTask.FromResult(result); return UniTask.FromResult(result);
} }
promise = AutoResetUniTaskCompletionSource<T>.Create();
return promise.Task;
} }
} }
// 3. SetException // 3. SetException
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetException(Exception exception) public void SetException(Exception exception)
{ {
// runner is finished, return first. if (runnerPromise == null)
if (runner != null)
{ {
runner.Return(); ex = exception;
runner = null;
}
if (promise == null)
{
promise = AutoResetUniTaskCompletionSource<T>.CreateFromException(exception, out _);
} }
else else
{ {
promise.TrySetException(exception); runnerPromise.SetException(exception);
} }
} }
// 4. SetResult // 4. SetResult
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetResult(T result) public void SetResult(T result)
{ {
// runner is finished, return first. if (runnerPromise == null)
if (runner != null)
{
runner.Return();
runner = null;
}
if (promise == null)
{ {
this.result = result; this.result = result;
return;
} }
else
promise.TrySetResult(result); {
runnerPromise.SetResult(result);
}
} }
// 5. AwaitOnCompleted // 5. AwaitOnCompleted
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {
if (promise == null) if (runnerPromise == null)
{ {
promise = AutoResetUniTaskCompletionSource<T>.Create(); AsyncUniTask<TStateMachine, T>.SetStateMachine(ref stateMachine, ref runnerPromise);
}
if (runner == null)
{
runner = MoveNextRunner<TStateMachine>.Create(ref stateMachine);
} }
awaiter.OnCompleted(runner.CallMoveNext); awaiter.OnCompleted(runnerPromise.MoveNext);
} }
// 6. AwaitUnsafeOnCompleted // 6. AwaitUnsafeOnCompleted
[DebuggerHidden] [DebuggerHidden]
[SecuritySafeCritical] [SecuritySafeCritical]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {
if (promise == null) if (runnerPromise == null)
{ {
promise = AutoResetUniTaskCompletionSource<T>.Create(); AsyncUniTask<TStateMachine, T>.SetStateMachine(ref stateMachine, ref runnerPromise);
}
if (runner == null)
{
runner = MoveNextRunner<TStateMachine>.Create(ref stateMachine);
} }
awaiter.OnCompleted(runner.CallMoveNext); awaiter.UnsafeOnCompleted(runnerPromise.MoveNext);
} }
// 7. Start // 7. Start
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Start<TStateMachine>(ref TStateMachine stateMachine) public void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {

View File

@@ -4,13 +4,15 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security; using System.Security;
namespace Cysharp.Threading.Tasks.CompilerServices namespace Cysharp.Threading.Tasks.CompilerServices
{ {
[StructLayout(LayoutKind.Auto)]
public struct AsyncUniTaskVoidMethodBuilder public struct AsyncUniTaskVoidMethodBuilder
{ {
IMoveNextRunner runner; IStateMachineRunner runner;
// 1. Static Create method. // 1. Static Create method.
[DebuggerHidden] [DebuggerHidden]
@@ -33,6 +35,7 @@ namespace Cysharp.Threading.Tasks.CompilerServices
// 3. SetException // 3. SetException
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetException(Exception exception) public void SetException(Exception exception)
{ {
// runner is finished, return first. // runner is finished, return first.
@@ -47,6 +50,7 @@ namespace Cysharp.Threading.Tasks.CompilerServices
// 4. SetResult // 4. SetResult
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetResult() public void SetResult()
{ {
// runner is finished, return. // runner is finished, return.
@@ -59,31 +63,33 @@ namespace Cysharp.Threading.Tasks.CompilerServices
// 5. AwaitOnCompleted // 5. AwaitOnCompleted
[DebuggerHidden] [DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {
if (runner == null) if (runner == null)
{ {
runner = MoveNextRunner<TStateMachine>.Create(ref stateMachine); AsyncUniTaskVoid<TStateMachine>.SetStateMachine(ref stateMachine, ref runner);
} }
awaiter.OnCompleted(runner.CallMoveNext); awaiter.OnCompleted(runner.MoveNext);
} }
// 6. AwaitUnsafeOnCompleted // 6. AwaitUnsafeOnCompleted
[DebuggerHidden] [DebuggerHidden]
[SecuritySafeCritical] [SecuritySafeCritical]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine where TStateMachine : IAsyncStateMachine
{ {
if (runner == null) if (runner == null)
{ {
runner = MoveNextRunner<TStateMachine>.Create(ref stateMachine); AsyncUniTaskVoid<TStateMachine>.SetStateMachine(ref stateMachine, ref runner);
} }
awaiter.OnCompleted(runner.CallMoveNext); awaiter.UnsafeOnCompleted(runner.MoveNext);
} }
// 7. Start // 7. Start

View File

@@ -1,57 +0,0 @@

#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks.Internal;
namespace Cysharp.Threading.Tasks.CompilerServices
{
internal interface IMoveNextRunner
{
Action CallMoveNext { get; }
void Return();
}
internal sealed class MoveNextRunner<TStateMachine> : IMoveNextRunner, IPromisePoolItem
where TStateMachine : IAsyncStateMachine
{
static PromisePool<MoveNextRunner<TStateMachine>> pool = new PromisePool<MoveNextRunner<TStateMachine>>();
TStateMachine stateMachine;
internal readonly Action callMoveNext;
public Action CallMoveNext => callMoveNext;
MoveNextRunner()
{
callMoveNext = MoveNext;
}
public static MoveNextRunner<TStateMachine> Create(ref TStateMachine stateMachine)
{
var result = pool.TryRent() ?? new MoveNextRunner<TStateMachine>();
result.stateMachine = stateMachine;
return result;
}
[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void MoveNext()
{
stateMachine.MoveNext();
}
public void Return()
{
pool.TryReturn(this);
}
void IPromisePoolItem.Reset()
{
stateMachine = default;
}
}
}

View File

@@ -0,0 +1,342 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using Cysharp.Threading.Tasks.Internal;
using System;
using System.Linq;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Cysharp.Threading.Tasks.CompilerServices
{
internal interface IStateMachineRunner
{
Action MoveNext { get; }
void Return();
}
internal interface IStateMachineRunnerPromise : IUniTaskSource
{
Action MoveNext { get; }
UniTask Task { get; }
void SetResult();
void SetException(Exception exception);
}
internal interface IStateMachineRunnerPromise<T> : IUniTaskSource<T>
{
Action MoveNext { get; }
UniTask<T> Task { get; }
void SetResult(T result);
void SetException(Exception exception);
}
internal static class StateMachineUtility
{
public static int GetState(IAsyncStateMachine stateMachine)
{
var info = stateMachine.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
.First(x => x.Name.EndsWith("__state"));
return (int)info.GetValue(stateMachine);
}
}
internal sealed class AsyncUniTaskVoid<TStateMachine> : IStateMachineRunner, ITaskPoolNode<AsyncUniTaskVoid<TStateMachine>>, IUniTaskSource
where TStateMachine : IAsyncStateMachine
{
static TaskPool<AsyncUniTaskVoid<TStateMachine>> pool;
TStateMachine stateMachine;
public Action MoveNext { get; }
public AsyncUniTaskVoid()
{
MoveNext = Run;
}
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunner runnerFieldRef)
{
if (!pool.TryPop(out var result))
{
result = new AsyncUniTaskVoid<TStateMachine>();
}
TaskTracker.TrackActiveTask(result, 3);
runnerFieldRef = result; // set runner before copied.
result.stateMachine = stateMachine; // copy struct StateMachine(in release build).
}
static AsyncUniTaskVoid()
{
TaskPool.RegisterSizeGetter(typeof(AsyncUniTaskVoid<TStateMachine>), () => pool.Size);
}
public AsyncUniTaskVoid<TStateMachine> NextNode { get; set; }
public void Return()
{
TaskTracker.RemoveTracking(this);
stateMachine = default;
pool.TryPush(this);
}
[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Run()
{
stateMachine.MoveNext();
}
// dummy interface implementation for TaskTracker.
UniTaskStatus IUniTaskSource.GetStatus(short token)
{
return UniTaskStatus.Pending;
}
UniTaskStatus IUniTaskSource.UnsafeGetStatus()
{
return UniTaskStatus.Pending;
}
void IUniTaskSource.OnCompleted(Action<object> continuation, object state, short token)
{
}
void IUniTaskSource.GetResult(short token)
{
}
}
internal sealed class AsyncUniTask<TStateMachine> : IStateMachineRunnerPromise, IUniTaskSource, ITaskPoolNode<AsyncUniTask<TStateMachine>>
where TStateMachine : IAsyncStateMachine
{
static TaskPool<AsyncUniTask<TStateMachine>> pool;
TStateMachine stateMachine;
public Action MoveNext { get; }
UniTaskCompletionSourceCore<AsyncUnit> core;
AsyncUniTask()
{
MoveNext = Run;
}
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunnerPromise runnerPromiseFieldRef)
{
if (!pool.TryPop(out var result))
{
result = new AsyncUniTask<TStateMachine>();
}
TaskTracker.TrackActiveTask(result, 3);
runnerPromiseFieldRef = result; // set runner before copied.
result.stateMachine = stateMachine; // copy struct StateMachine(in release build).
}
public AsyncUniTask<TStateMachine> NextNode { get; set; }
static AsyncUniTask()
{
TaskPool.RegisterSizeGetter(typeof(AsyncUniTask<TStateMachine>), () => pool.Size);
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
stateMachine = default;
return pool.TryPush(this);
}
[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Run()
{
stateMachine.MoveNext();
}
public UniTask Task
{
[DebuggerHidden]
get
{
return new UniTask(this, core.Version);
}
}
[DebuggerHidden]
public void SetResult()
{
core.TrySetResult(AsyncUnit.Default);
}
[DebuggerHidden]
public void SetException(Exception exception)
{
core.TrySetException(exception);
}
[DebuggerHidden]
public void GetResult(short token)
{
try
{
core.GetResult(token);
}
finally
{
TryReturn();
}
}
[DebuggerHidden]
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
[DebuggerHidden]
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
[DebuggerHidden]
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
~AsyncUniTask()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
}
internal sealed class AsyncUniTask<TStateMachine, T> : IStateMachineRunnerPromise<T>, IUniTaskSource<T>, ITaskPoolNode<AsyncUniTask<TStateMachine, T>>
where TStateMachine : IAsyncStateMachine
{
static TaskPool<AsyncUniTask<TStateMachine, T>> pool;
TStateMachine stateMachine;
public Action MoveNext { get; }
UniTaskCompletionSourceCore<T> core;
AsyncUniTask()
{
MoveNext = Run;
}
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunnerPromise<T> runnerPromiseFieldRef)
{
if (!pool.TryPop(out var result))
{
result = new AsyncUniTask<TStateMachine, T>();
}
TaskTracker.TrackActiveTask(result, 3);
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()
{
TaskPool.RegisterSizeGetter(typeof(AsyncUniTask<TStateMachine, T>), () => pool.Size);
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
stateMachine = default;
return pool.TryPush(this);
}
[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Run()
{
UnityEngine.Debug.Log($"MoveNext State:" + StateMachineUtility.GetState(stateMachine));
stateMachine.MoveNext();
}
public UniTask<T> Task
{
[DebuggerHidden]
get
{
return new UniTask<T>(this, core.Version);
}
}
[DebuggerHidden]
public void SetResult(T result)
{
core.TrySetResult(result);
}
[DebuggerHidden]
public void SetException(Exception exception)
{
core.TrySetException(exception);
}
[DebuggerHidden]
public T GetResult(short token)
{
try
{
return core.GetResult(token);
}
finally
{
TryReturn();
}
}
[DebuggerHidden]
void IUniTaskSource.GetResult(short token)
{
GetResult(token);
}
[DebuggerHidden]
public UniTaskStatus GetStatus(short token)
{
return core.GetStatus(token);
}
[DebuggerHidden]
public UniTaskStatus UnsafeGetStatus()
{
return core.UnsafeGetStatus();
}
[DebuggerHidden]
public void OnCompleted(Action<object> continuation, object state, short token)
{
core.OnCompleted(continuation, state, token);
}
~AsyncUniTask()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
}
}

View File

@@ -12,10 +12,12 @@ namespace Cysharp.Threading.Tasks
{ {
public static class EnumeratorAsyncExtensions public static class EnumeratorAsyncExtensions
{ {
public static UniTask.Awaiter GetAwaiter(this IEnumerator enumerator) public static UniTask.Awaiter GetAwaiter<T>(this T enumerator)
where T : IEnumerator
{ {
Error.ThrowArgumentNullException(enumerator, nameof(enumerator)); var e = (IEnumerator)enumerator;
return new UniTask(EnumeratorPromise.Create(enumerator, PlayerLoopTiming.Update, CancellationToken.None, out var token), token).GetAwaiter(); Error.ThrowArgumentNullException(e, nameof(enumerator));
return new UniTask(EnumeratorPromise.Create(e, PlayerLoopTiming.Update, CancellationToken.None, out var token), token).GetAwaiter();
} }
public static UniTask WithCancellation(this IEnumerator enumerator, CancellationToken cancellationToken) public static UniTask WithCancellation(this IEnumerator enumerator, CancellationToken cancellationToken)
@@ -30,9 +32,15 @@ namespace Cysharp.Threading.Tasks
return new UniTask(EnumeratorPromise.Create(enumerator, timing, cancellationToken, out var token), token); return new UniTask(EnumeratorPromise.Create(enumerator, timing, cancellationToken, out var token), token);
} }
class EnumeratorPromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class EnumeratorPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<EnumeratorPromise>
{ {
static readonly PromisePool<EnumeratorPromise> pool = new PromisePool<EnumeratorPromise>(); static TaskPool<EnumeratorPromise> pool;
public EnumeratorPromise NextNode { get; set; }
static EnumeratorPromise()
{
TaskPool.RegisterSizeGetter(typeof(EnumeratorPromise), () => pool.Size);
}
IEnumerator innerEnumerator; IEnumerator innerEnumerator;
CancellationToken cancellationToken; CancellationToken cancellationToken;
@@ -50,13 +58,15 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new EnumeratorPromise(); if (!pool.TryPop(out var result))
{
result = new EnumeratorPromise();
}
TaskTracker.TrackActiveTask(result, 3);
result.innerEnumerator = ConsumeEnumerator(innerEnumerator); result.innerEnumerator = ConsumeEnumerator(innerEnumerator);
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version; token = result.core.Version;
@@ -67,12 +77,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -116,16 +125,18 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
innerEnumerator = default; innerEnumerator = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~EnumeratorPromise() ~EnumeratorPromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }

View File

@@ -23,7 +23,7 @@ namespace Cysharp.Threading.Tasks
public static UniTask WithCancellation(this AsyncOperationHandle handle, CancellationToken cancellationToken) public static UniTask WithCancellation(this AsyncOperationHandle handle, CancellationToken cancellationToken)
{ {
if (handle.IsDone) return UniTask.CompletedTask; if (handle.IsDone) return UniTask.CompletedTask;
return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); return new UniTask(AsyncOperationHandleWithCancellationSource.Create(handle, cancellationToken, out var token), token);
} }
public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
@@ -72,14 +72,146 @@ namespace Cysharp.Threading.Tasks
public void UnsafeOnCompleted(Action continuation) public void UnsafeOnCompleted(Action continuation)
{ {
Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction); Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction);
continuationAction = continuation.AsFuncOfT<AsyncOperationHandle>(); // allocate delegate. continuationAction = PooledDelegate<AsyncOperationHandle>.Create(continuation);
handle.Completed += continuationAction; handle.Completed += continuationAction;
} }
} }
sealed class AsyncOperationHandleConfiguredSource : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class AsyncOperationHandleWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleWithCancellationSource>
{ {
static readonly PromisePool<AsyncOperationHandleConfiguredSource> pool = new PromisePool<AsyncOperationHandleConfiguredSource>(); static TaskPool<AsyncOperationHandleWithCancellationSource> pool;
public AsyncOperationHandleWithCancellationSource NextNode { get; set; }
static AsyncOperationHandleWithCancellationSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleWithCancellationSource), () => pool.Size);
}
readonly Action<AsyncOperationHandle> continuationAction;
AsyncOperationHandle handle;
CancellationToken cancellationToken;
bool completed;
UniTaskCompletionSourceCore<AsyncUnit> core;
AsyncOperationHandleWithCancellationSource()
{
continuationAction = Continuation;
}
public static IUniTaskSource Create(AsyncOperationHandle handle, CancellationToken cancellationToken, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new AsyncOperationHandleWithCancellationSource();
}
result.handle = handle;
result.cancellationToken = cancellationToken;
result.completed = false;
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result);
handle.Completed += result.continuationAction;
token = result.core.Version;
return result;
}
void Continuation(AsyncOperationHandle _)
{
handle.Completed -= continuationAction;
if (completed)
{
TryReturn();
}
else
{
completed = true;
if (handle.Status == AsyncOperationStatus.Failed)
{
core.TrySetException(handle.OperationException);
}
else
{
core.TrySetResult(AsyncUnit.Default);
}
}
}
public void GetResult(short token)
{
core.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()
{
if (completed)
{
TryReturn();
return false;
}
if (cancellationToken.IsCancellationRequested)
{
completed = true;
core.TrySetCanceled(cancellationToken);
return false;
}
return true;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
handle = default;
cancellationToken = default;
return pool.TryPush(this);
}
~AsyncOperationHandleWithCancellationSource()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
}
sealed class AsyncOperationHandleConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleConfiguredSource>
{
static TaskPool<AsyncOperationHandleConfiguredSource> pool;
public AsyncOperationHandleConfiguredSource NextNode { get; set; }
static AsyncOperationHandleConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleConfiguredSource), () => pool.Size);
}
AsyncOperationHandle handle; AsyncOperationHandle handle;
IProgress<float> progress; IProgress<float> progress;
@@ -99,7 +231,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new AsyncOperationHandleConfiguredSource(); if (!pool.TryPop(out var result))
{
result = new AsyncOperationHandleConfiguredSource();
}
result.handle = handle; result.handle = handle;
result.progress = progress; result.progress = progress;
@@ -117,12 +252,12 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -170,17 +305,19 @@ namespace Cysharp.Threading.Tasks
return true; return true;
} }
public void Reset() bool TryReturn()
{ {
core.Reset(); core.Reset();
TaskTracker.RemoveTracking(this);
handle = default; handle = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~AsyncOperationHandleConfiguredSource() ~AsyncOperationHandleConfiguredSource()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
@@ -199,13 +336,13 @@ namespace Cysharp.Threading.Tasks
public static UniTask<T> WithCancellation<T>(this AsyncOperationHandle<T> handle, CancellationToken cancellationToken) public static UniTask<T> WithCancellation<T>(this AsyncOperationHandle<T> handle, CancellationToken cancellationToken)
{ {
if (handle.IsDone) return UniTask.FromResult(handle.Result); if (handle.IsDone) return UniTask.FromResult(handle.Result);
return new UniTask<T>(AsyncOperationHandleConfiguredSource<T>.Create(handle, PlayerLoopTiming.Update, null, cancellationToken, out var token), token); return new UniTask<T>(AsyncOperationHandleWithCancellationSource<T>.Create(handle, cancellationToken, out var token), token);
} }
public static UniTask<T> ToUniTask<T>(this AsyncOperationHandle<T> handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellation = default(CancellationToken)) public static UniTask<T> ToUniTask<T>(this AsyncOperationHandle<T> handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
{ {
if (handle.IsDone) return UniTask.FromResult(handle.Result); if (handle.IsDone) return UniTask.FromResult(handle.Result);
return new UniTask<T>(AsyncOperationHandleConfiguredSource<T>.Create(handle, timing, progress, cancellation, out var token), token); return new UniTask<T>(AsyncOperationHandleConfiguredSource<T>.Create(handle, timing, progress, cancellationToken, out var token), token);
} }
public struct AsyncOperationHandleAwaiter<T> : ICriticalNotifyCompletion public struct AsyncOperationHandleAwaiter<T> : ICriticalNotifyCompletion
@@ -249,14 +386,151 @@ namespace Cysharp.Threading.Tasks
public void UnsafeOnCompleted(Action continuation) public void UnsafeOnCompleted(Action continuation)
{ {
Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction); Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction);
continuationAction = continuation.AsFuncOfT<AsyncOperationHandle>(); // allocate delegate. continuationAction = PooledDelegate<AsyncOperationHandle>.Create(continuation);
handle.CompletedTypeless += continuationAction; handle.CompletedTypeless += continuationAction;
} }
} }
sealed class AsyncOperationHandleConfiguredSource<T> : IUniTaskSource<T>, IPlayerLoopItem, IPromisePoolItem sealed class AsyncOperationHandleWithCancellationSource<T> : IUniTaskSource<T>, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleWithCancellationSource<T>>
{ {
static readonly PromisePool<AsyncOperationHandleConfiguredSource<T>> pool = new PromisePool<AsyncOperationHandleConfiguredSource<T>>(); static TaskPool<AsyncOperationHandleWithCancellationSource<T>> pool;
public AsyncOperationHandleWithCancellationSource<T> NextNode { get; set; }
static AsyncOperationHandleWithCancellationSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleWithCancellationSource<T>), () => pool.Size);
}
readonly Action<AsyncOperationHandle<T>> continuationAction;
AsyncOperationHandle<T> handle;
CancellationToken cancellationToken;
bool completed;
UniTaskCompletionSourceCore<T> core;
AsyncOperationHandleWithCancellationSource()
{
continuationAction = Continuation;
}
public static IUniTaskSource<T> Create(AsyncOperationHandle<T> handle, CancellationToken cancellationToken, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new AsyncOperationHandleWithCancellationSource<T>();
}
result.handle = handle;
result.cancellationToken = cancellationToken;
result.completed = false;
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result);
handle.Completed += result.continuationAction;
token = result.core.Version;
return result;
}
void Continuation(AsyncOperationHandle<T> _)
{
handle.Completed -= continuationAction;
if (completed)
{
TryReturn();
}
else
{
completed = true;
if (handle.Status == AsyncOperationStatus.Failed)
{
core.TrySetException(handle.OperationException);
}
else
{
core.TrySetResult(handle.Result);
}
}
}
public T GetResult(short token)
{
return core.GetResult(token);
}
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()
{
if (completed)
{
TryReturn();
return false;
}
if (cancellationToken.IsCancellationRequested)
{
completed = true;
core.TrySetCanceled(cancellationToken);
return false;
}
return true;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
handle = default;
cancellationToken = default;
return pool.TryPush(this);
}
~AsyncOperationHandleWithCancellationSource()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
}
sealed class AsyncOperationHandleConfiguredSource<T> : IUniTaskSource<T>, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleConfiguredSource<T>>
{
static TaskPool<AsyncOperationHandleConfiguredSource<T>> pool;
public AsyncOperationHandleConfiguredSource<T> NextNode { get; set; }
static AsyncOperationHandleConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleConfiguredSource<T>), () => pool.Size);
}
AsyncOperationHandle<T> handle; AsyncOperationHandle<T> handle;
IProgress<float> progress; IProgress<float> progress;
@@ -276,7 +550,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new AsyncOperationHandleConfiguredSource<T>(); if (!pool.TryPop(out var result))
{
result = new AsyncOperationHandleConfiguredSource<T>();
}
result.handle = handle; result.handle = handle;
result.progress = progress; result.progress = progress;
@@ -294,13 +571,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
return core.GetResult(token); return core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -353,17 +628,19 @@ namespace Cysharp.Threading.Tasks
return true; return true;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
handle = default; handle = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~AsyncOperationHandleConfiguredSource() ~AsyncOperationHandleConfiguredSource()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }

View File

@@ -5,6 +5,7 @@
using Cysharp.Threading.Tasks.Internal; using Cysharp.Threading.Tasks.Internal;
using DG.Tweening; using DG.Tweening;
using System; using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
@@ -33,7 +34,7 @@ namespace Cysharp.Threading.Tasks
return new TweenAwaiter(tween); return new TweenAwaiter(tween);
} }
public static UniTask WithCancellation(this Tween tween, CancellationToken cancellationToken = default) public static UniTask WithCancellation(this Tween tween, CancellationToken cancellationToken)
{ {
Error.ThrowArgumentNullException(tween, nameof(tween)); Error.ThrowArgumentNullException(tween, nameof(tween));
@@ -77,120 +78,164 @@ namespace Cysharp.Threading.Tasks
public void UnsafeOnCompleted(System.Action continuation) public void UnsafeOnCompleted(System.Action continuation)
{ {
// convert Action -> TweenCallback allocation.
// onKill is called after OnCompleted, both Complete(false/true) and Kill(false/true). // onKill is called after OnCompleted, both Complete(false/true) and Kill(false/true).
tween.onKill = new TweenCallback(continuation); tween.onKill = PooledTweenCallback.Create(continuation);
} }
} }
sealed class TweenConfiguredSource : IUniTaskSource, IPromisePoolItem sealed class TweenConfiguredSource : IUniTaskSource, ITaskPoolNode<TweenConfiguredSource>
{ {
static readonly PromisePool<TweenConfiguredSource> pool = new PromisePool<TweenConfiguredSource>(); static TaskPool<TweenConfiguredSource> pool;
static Action<object> CancellationCallbackDelegate = CancellationCallback; public TweenConfiguredSource NextNode { get; set; }
static TweenConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(TweenConfiguredSource), () => pool.Size);
}
static readonly TweenCallback EmptyTweenCallback = () => { };
readonly TweenCallback onKillDelegate;
readonly TweenCallback onUpdateDelegate;
Tween tween; Tween tween;
TweenCancelBehaviour cancelBehaviour; TweenCancelBehaviour cancelBehaviour;
CancellationToken cancellationToken; CancellationToken cancellationToken;
bool canceled; bool canceled;
CancellationTokenRegistration cancellationTokenRegistration; TweenCallback originalUpdateAction;
UniTaskCompletionSourceCore<AsyncUnit> core; UniTaskCompletionSourceCore<AsyncUnit> core;
TweenConfiguredSource() TweenConfiguredSource()
{ {
onKillDelegate = OnKill;
onUpdateDelegate = OnUpdate;
} }
public static IUniTaskSource Create(Tween tween, TweenCancelBehaviour cancelBehaviour, CancellationToken cancellationToken, out short token) public static IUniTaskSource Create(Tween tween, TweenCancelBehaviour cancelBehaviour, CancellationToken cancellationToken, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
DoCancelBeforeCreate(tween, cancelBehaviour);
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new TweenConfiguredSource(); if (!pool.TryPop(out var result))
{
result = new TweenConfiguredSource();
}
result.tween = tween; result.tween = tween;
result.cancelBehaviour = cancelBehaviour; result.cancelBehaviour = cancelBehaviour;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
TaskTracker.TrackActiveTask(result, 3); result.originalUpdateAction = tween.onUpdate;
result.canceled = false;
result.RegisterEvent(); if (result.originalUpdateAction == result.onUpdateDelegate)
{
result.originalUpdateAction = null;
}
tween.onUpdate = result.onUpdateDelegate;
tween.onKill = result.onKillDelegate;
TaskTracker.TrackActiveTask(result, 3);
token = result.core.Version; token = result.core.Version;
return result; return result;
} }
void RegisterEvent()
{
if (cancellationToken.CanBeCanceled)
{
cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(CancellationCallbackDelegate, this);
}
// allocate delegate.
tween.OnKill(new TweenCallback(OnKill));
}
void OnKill() void OnKill()
{ {
cancellationTokenRegistration.Dispose();
if (canceled) if (canceled)
{
if (cancelBehaviour == TweenCancelBehaviour.CancelAwait)
{
// already called TrySetCanceled, do nothing.
}
else
{ {
core.TrySetCanceled(cancellationToken); core.TrySetCanceled(cancellationToken);
} }
}
else else
{ {
core.TrySetResult(AsyncUnit.Default); core.TrySetResult(AsyncUnit.Default);
} }
} }
static void CancellationCallback(object state) void OnUpdate()
{ {
var self = (TweenConfiguredSource)state; originalUpdateAction?.Invoke();
switch (self.cancelBehaviour) if (!cancellationToken.IsCancellationRequested)
{
return;
}
switch (this.cancelBehaviour)
{ {
case TweenCancelBehaviour.Kill: case TweenCancelBehaviour.Kill:
default: default:
self.tween.Kill(false); this.tween.Kill(false);
break; break;
case TweenCancelBehaviour.KillAndCancelAwait: case TweenCancelBehaviour.KillAndCancelAwait:
self.canceled = true; this.canceled = true;
self.tween.Kill(false); this.tween.Kill(false);
break; break;
case TweenCancelBehaviour.KillWithCompleteCallback: case TweenCancelBehaviour.KillWithCompleteCallback:
self.tween.Kill(true); this.tween.Kill(true);
break; break;
case TweenCancelBehaviour.KillWithCompleteCallbackAndCancelAwait: case TweenCancelBehaviour.KillWithCompleteCallbackAndCancelAwait:
self.canceled = true; this.canceled = true;
self.tween.Kill(true); this.tween.Kill(true);
break; break;
case TweenCancelBehaviour.Complete: case TweenCancelBehaviour.Complete:
self.tween.Complete(false); this.tween.Complete(false);
break; break;
case TweenCancelBehaviour.CompleteAndCancelAwait: case TweenCancelBehaviour.CompleteAndCancelAwait:
self.canceled = true; this.canceled = true;
self.tween.Complete(false); this.tween.Complete(false);
break; break;
case TweenCancelBehaviour.CompleteWithSeqeunceCallback: case TweenCancelBehaviour.CompleteWithSeqeunceCallback:
self.tween.Complete(true); this.tween.Complete(true);
break; break;
case TweenCancelBehaviour.CompleteWithSeqeunceCallbackAndCancelAwait: case TweenCancelBehaviour.CompleteWithSeqeunceCallbackAndCancelAwait:
self.canceled = true; this.canceled = true;
self.tween.Complete(true); this.tween.Complete(true);
break;
case TweenCancelBehaviour.CancelAwait:
this.tween.onKill = EmptyTweenCallback; // replace to empty(avoid callback after Canceled(instance is returned to pool.)
this.core.TrySetCanceled(this.cancellationToken);
break;
}
}
static void DoCancelBeforeCreate(Tween tween, TweenCancelBehaviour tweenCancelBehaviour)
{
switch (tweenCancelBehaviour)
{
case TweenCancelBehaviour.Kill:
default:
tween.Kill(false);
break;
case TweenCancelBehaviour.KillAndCancelAwait:
tween.Kill(false);
break;
case TweenCancelBehaviour.KillWithCompleteCallback:
tween.Kill(true);
break;
case TweenCancelBehaviour.KillWithCompleteCallbackAndCancelAwait:
tween.Kill(true);
break;
case TweenCancelBehaviour.Complete:
tween.Complete(false);
break;
case TweenCancelBehaviour.CompleteAndCancelAwait:
tween.Complete(false);
break;
case TweenCancelBehaviour.CompleteWithSeqeunceCallback:
tween.Complete(true);
break;
case TweenCancelBehaviour.CompleteWithSeqeunceCallbackAndCancelAwait:
tween.Complete(true);
break; break;
case TweenCancelBehaviour.CancelAwait: case TweenCancelBehaviour.CancelAwait:
self.canceled = true;
self.core.TrySetCanceled(self.cancellationToken);
break; break;
} }
} }
@@ -199,16 +244,14 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
public UniTaskStatus GetStatus(short token) public UniTaskStatus GetStatus(short token)
{ {
return core.GetStatus(token); return core.GetStatus(token);
@@ -224,22 +267,66 @@ namespace Cysharp.Threading.Tasks
core.OnCompleted(continuation, state, token); core.OnCompleted(continuation, state, token);
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
tween.onUpdate = originalUpdateAction;
tween.onKill = null;
tween = default; tween = default;
cancellationToken = default; cancellationToken = default;
originalUpdateAction = default;
return pool.TryPush(this);
} }
~TweenConfiguredSource() ~TweenConfiguredSource()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
} }
} }
} }
sealed class PooledTweenCallback
{
static readonly ConcurrentQueue<PooledTweenCallback> pool = new ConcurrentQueue<PooledTweenCallback>();
readonly TweenCallback runDelegate;
Action continuation;
PooledTweenCallback()
{
runDelegate = Run;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TweenCallback Create(Action continuation)
{
if (!pool.TryDequeue(out var item))
{
item = new PooledTweenCallback();
}
item.continuation = continuation;
return item.runDelegate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Run()
{
var call = continuation;
continuation = null;
if (call != null)
{
pool.Enqueue(this);
call.Invoke();
}
}
}
} }
#endif #endif

View File

@@ -19,17 +19,75 @@ namespace Cysharp.Threading.Tasks
// similar as IValueTaskSource // similar as IValueTaskSource
public interface IUniTaskSource public interface IUniTaskSource
#if !UNITY_2018_3_OR_NEWER && !NETSTANDARD2_0
: System.Threading.Tasks.Sources.IValueTaskSource
#pragma warning disable CS0108
#endif
{ {
UniTaskStatus GetStatus(short token); UniTaskStatus GetStatus(short token);
void OnCompleted(Action<object> continuation, object state, short token); void OnCompleted(Action<object> continuation, object state, short token);
void GetResult(short token); void GetResult(short token);
UniTaskStatus UnsafeGetStatus(); // only for debug use. UniTaskStatus UnsafeGetStatus(); // only for debug use.
#if !UNITY_2018_3_OR_NEWER && !NETSTANDARD2_0
#pragma warning restore CS0108
System.Threading.Tasks.Sources.ValueTaskSourceStatus System.Threading.Tasks.Sources.IValueTaskSource.GetStatus(short token)
{
return (System.Threading.Tasks.Sources.ValueTaskSourceStatus)(int)((IUniTaskSource)this).GetStatus(token);
}
void System.Threading.Tasks.Sources.IValueTaskSource.GetResult(short token)
{
((IUniTaskSource)this).GetResult(token);
}
void System.Threading.Tasks.Sources.IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags)
{
// ignore flags, always none.
((IUniTaskSource)this).OnCompleted(continuation, state, token);
}
#endif
} }
public interface IUniTaskSource<out T> : IUniTaskSource public interface IUniTaskSource<out T> : IUniTaskSource
#if !UNITY_2018_3_OR_NEWER && !NETSTANDARD2_0
, System.Threading.Tasks.Sources.IValueTaskSource<T>
#endif
{ {
new T GetResult(short token); new T GetResult(short token);
#if !UNITY_2018_3_OR_NEWER && !NETSTANDARD2_0
new public UniTaskStatus GetStatus(short token)
{
return ((IUniTaskSource)this).GetStatus(token);
}
new public void OnCompleted(Action<object> continuation, object state, short token)
{
((IUniTaskSource)this).OnCompleted(continuation, state, token);
}
System.Threading.Tasks.Sources.ValueTaskSourceStatus System.Threading.Tasks.Sources.IValueTaskSource<T>.GetStatus(short token)
{
return (System.Threading.Tasks.Sources.ValueTaskSourceStatus)(int)((IUniTaskSource)this).GetStatus(token);
}
T System.Threading.Tasks.Sources.IValueTaskSource<T>.GetResult(short token)
{
return ((IUniTaskSource<T>)this).GetResult(token);
}
void System.Threading.Tasks.Sources.IValueTaskSource<T>.OnCompleted(Action<object> continuation, object state, short token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags)
{
// ignore flags, always none.
((IUniTaskSource)this).OnCompleted(continuation, state, token);
}
#endif
} }
public static class UniTaskStatusExtensions public static class UniTaskStatusExtensions

View File

@@ -239,7 +239,7 @@ namespace Cysharp.Threading.Tasks.Internal
} }
else else
{ {
var fname = fi.FullName.Replace(Path.DirectorySeparatorChar, '/').Replace(Application.dataPath, ""); var fname = fi.FullName.Replace(Path.DirectorySeparatorChar, '/').Replace(PlayerLoopHelper.ApplicationDataPath, "");
var withAssetsPath = "Assets/" + fname; var withAssetsPath = "Assets/" + fname;
return "<a href=\"" + withAssetsPath + "\" line=\"" + line + "\">" + withAssetsPath + ":" + line + "</a>"; return "<a href=\"" + withAssetsPath + "\" line=\"" + line + "\">" + withAssetsPath + ":" + line + "</a>";
} }

View File

@@ -1,23 +0,0 @@
#if NET_4_6 || NET_STANDARD_2_0 || CSHARP_7_OR_LATER
using System;
namespace Cysharp.Threading.Tasks.Internal
{
internal static class FuncExtensions
{
// avoid lambda capture
internal static Action<T> AsFuncOfT<T>(this Action action)
{
return new Action<T>(action.Invoke);
}
static void Invoke<T>(this Action action, T unused)
{
action();
}
}
}
#endif

View File

@@ -0,0 +1,49 @@
using System;
using System.Runtime.CompilerServices;
namespace Cysharp.Threading.Tasks.Internal
{
internal sealed class PooledDelegate<T> : ITaskPoolNode<PooledDelegate<T>>
{
static TaskPool<PooledDelegate<T>> pool;
public PooledDelegate<T> NextNode { get; set; }
static PooledDelegate()
{
TaskPool.RegisterSizeGetter(typeof(PooledDelegate<T>), () => pool.Size);
}
readonly Action<T> runDelegate;
Action continuation;
PooledDelegate()
{
runDelegate = Run;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Action<T> Create(Action continuation)
{
if (!pool.TryPop(out var item))
{
item = new PooledDelegate<T>();
}
item.continuation = continuation;
return item.runDelegate;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Run(T _)
{
var call = continuation;
continuation = null;
if (call != null)
{
pool.TryPush(this);
call.Invoke();
}
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 4d5a9a3e1f0f069478969f752fde29a9 guid: 8932579438742fa40b010edd412dbfba
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -1,55 +0,0 @@
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Cysharp.Threading.Tasks.Internal
{
// public, allow to user create custom operator with pool.
public interface IPromisePoolItem
{
void Reset();
}
public class PromisePool<T>
where T : class, IPromisePoolItem
{
int count = 0;
readonly ConcurrentQueue<T> queue = new ConcurrentQueue<T>();
readonly int maxSize;
public PromisePool(int maxSize = 256)
{
this.maxSize = maxSize;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T TryRent()
{
if (queue.TryDequeue(out var value))
{
Interlocked.Decrement(ref count);
return value;
}
return null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryReturn(T value)
{
value.Reset(); // reset when return.
if (count < maxSize)
{
queue.Enqueue(value);
Interlocked.Increment(ref count);
return true;
}
else
{
return false;
}
}
}
}

View File

@@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks.Internal; using Cysharp.Threading.Tasks.Internal;
@@ -59,9 +60,9 @@ namespace Cysharp.Threading.Tasks
#endif #endif
static List<KeyValuePair<IUniTaskSource, (int trackingId, DateTime addTime, string stackTrace)>> listPool = new List<KeyValuePair<IUniTaskSource, (int trackingId, DateTime addTime, string stackTrace)>>(); static List<KeyValuePair<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)>> listPool = new List<KeyValuePair<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)>>();
static readonly WeakDictionary<IUniTaskSource, (int trackingId, DateTime addTime, string stackTrace)> tracking = new WeakDictionary<IUniTaskSource, (int trackingId, DateTime addTime, string stackTrace)>(); static readonly WeakDictionary<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)> tracking = new WeakDictionary<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)>();
[Conditional("UNITY_EDITOR")] [Conditional("UNITY_EDITOR")]
public static void TrackActiveTask(IUniTaskSource task, int skipFrame) public static void TrackActiveTask(IUniTaskSource task, int skipFrame)
@@ -70,7 +71,19 @@ namespace Cysharp.Threading.Tasks
dirty = true; dirty = true;
if (!EditorEnableState.EnableTracking) return; if (!EditorEnableState.EnableTracking) return;
var stackTrace = EditorEnableState.EnableStackTrace ? new StackTrace(skipFrame, true).CleanupAsyncStackTrace() : ""; var stackTrace = EditorEnableState.EnableStackTrace ? new StackTrace(skipFrame, true).CleanupAsyncStackTrace() : "";
tracking.TryAdd(task, (Interlocked.Increment(ref trackingId), DateTime.UtcNow, stackTrace));
string typeName;
if (EditorEnableState.EnableStackTrace)
{
var sb = new StringBuilder();
TypeBeautify(task.GetType(), sb);
typeName = sb.ToString();
}
else
{
typeName = task.GetType().Name;
}
tracking.TryAdd(task, (typeName, Interlocked.Increment(ref trackingId), DateTime.UtcNow, stackTrace));
#endif #endif
} }
@@ -103,19 +116,8 @@ namespace Cysharp.Threading.Tasks
{ {
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
string typeName = null; action(listPool[i].Value.trackingId, listPool[i].Value.formattedType, listPool[i].Key.UnsafeGetStatus(), listPool[i].Value.addTime, listPool[i].Value.stackTrace);
var keyType = listPool[i].Key.GetType(); listPool[i] = default;
if (keyType.IsNested)
{
typeName = keyType.DeclaringType.Name + "." + keyType.Name;
}
else
{
typeName = keyType.Name;
}
action(listPool[i].Value.trackingId, typeName, listPool[i].Key.UnsafeGetStatus(), listPool[i].Value.addTime, listPool[i].Value.stackTrace);
listPool[i] = new KeyValuePair<IUniTaskSource, (int trackingId, DateTime addTime, string stackTrace)>(null, (0, default(DateTime), null)); // clear
} }
} }
catch catch
@@ -125,6 +127,52 @@ namespace Cysharp.Threading.Tasks
} }
} }
} }
static void TypeBeautify(Type type, StringBuilder sb)
{
if (type.IsNested)
{
// TypeBeautify(type.DeclaringType, sb);
sb.Append(type.DeclaringType.Name.ToString());
sb.Append(".");
}
if (type.IsGenericType)
{
var genericsStart = type.Name.IndexOf("`");
if (genericsStart != -1)
{
sb.Append(type.Name.Substring(0, genericsStart));
}
else
{
sb.Append(type.Name);
}
sb.Append("<");
var first = true;
foreach (var item in type.GetGenericArguments())
{
if (!first)
{
sb.Append(", ");
}
first = false;
TypeBeautify(item, sb);
}
sb.Append(">");
}
else
{
sb.Append(type.Name);
}
}
//static string RemoveUniTaskNamespace(string str)
//{
// return str.Replace("Cysharp.Threading.Tasks.CompilerServices", "")
// .Replace("Cysharp.Threading.Tasks.Linq", "")
// .Replace("Cysharp.Threading.Tasks", "");
//}
} }
} }

View File

@@ -3,66 +3,9 @@ using System.Threading;
namespace Cysharp.Threading.Tasks.Linq namespace Cysharp.Threading.Tasks.Linq
{ {
public abstract class MoveNextSource : IUniTaskSource<bool> // note: refactor all inherit class and should remove this.
{ // see Select and Where.
protected UniTaskCompletionSourceCore<bool> completionSource; internal abstract class AsyncEnumeratorBase<TSource, TResult> : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
public bool GetResult(short token)
{
return completionSource.GetResult(token);
}
public UniTaskStatus GetStatus(short token)
{
return completionSource.GetStatus(token);
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
completionSource.OnCompleted(continuation, state, token);
}
public UniTaskStatus UnsafeGetStatus()
{
return completionSource.UnsafeGetStatus();
}
void IUniTaskSource.GetResult(short token)
{
completionSource.GetResult(token);
}
protected bool TryGetResult<T>(UniTask<T>.Awaiter awaiter, out T result)
{
try
{
result = awaiter.GetResult();
return true;
}
catch (Exception ex)
{
completionSource.TrySetException(ex);
result = default;
return false;
}
}
protected bool TryGetResult(UniTask.Awaiter awaiter)
{
try
{
awaiter.GetResult();
return true;
}
catch (Exception ex)
{
completionSource.TrySetException(ex);
return false;
}
}
}
public abstract class AsyncEnumeratorBase<TSource, TResult> : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
static readonly Action<object> moveNextCallbackDelegate = MoveNextCallBack; static readonly Action<object> moveNextCallbackDelegate = MoveNextCallBack;
@@ -188,7 +131,7 @@ namespace Cysharp.Threading.Tasks.Linq
} }
} }
public abstract class AsyncEnumeratorAwaitSelectorBase<TSource, TResult, TAwait> : MoveNextSource, IUniTaskAsyncEnumerator<TResult> internal abstract class AsyncEnumeratorAwaitSelectorBase<TSource, TResult, TAwait> : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
static readonly Action<object> moveNextCallbackDelegate = MoveNextCallBack; static readonly Action<object> moveNextCallbackDelegate = MoveNextCallBack;
static readonly Action<object> setCurrentCallbackDelegate = SetCurrentCallBack; static readonly Action<object> setCurrentCallbackDelegate = SetCurrentCallBack;
@@ -410,5 +353,4 @@ namespace Cysharp.Threading.Tasks.Linq
return default; return default;
} }
} }
} }

View File

@@ -79,41 +79,123 @@ namespace Cysharp.Threading.Tasks.Linq
return new _DistinctUntilChanged(source, comparer, cancellationToken); return new _DistinctUntilChanged(source, comparer, cancellationToken);
} }
class _DistinctUntilChanged : AsyncEnumeratorBase<TSource, TSource> sealed class _DistinctUntilChanged : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly IEqualityComparer<TSource> comparer; readonly IEqualityComparer<TSource> comparer;
TSource prev; readonly CancellationToken cancellationToken;
bool first;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
Action moveNextAction;
public _DistinctUntilChanged(IUniTaskAsyncEnumerable<TSource> source, IEqualityComparer<TSource> comparer, CancellationToken cancellationToken) public _DistinctUntilChanged(IUniTaskAsyncEnumerable<TSource> source, IEqualityComparer<TSource> comparer, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.comparer = comparer; this.comparer = comparer;
this.first = true; this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
if (sourceHasCurrent) if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
}
void MoveNext()
{ {
var v = SourceCurrent; REPEAT:
if (first || !comparer.Equals(prev, v)) try
{ {
first = false; switch (state)
Current = prev = v; {
result = true; case -1: // init
return true; enumerator = source.GetAsyncEnumerator(cancellationToken);
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case -3;
} }
else else
{ {
result = default; state = -3;
return false; awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case -3: // first
if (awaiter.GetResult())
{
Current = enumerator.Current;
goto CONTINUE;
}
else
{
goto DONE;
}
case 0: // normal
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
var v = enumerator.Current;
if (!comparer.Equals(Current, v))
{
Current = v;
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
}
else
{
goto DONE;
}
case -2:
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
} }
result = false; DONE:
return true; state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -136,45 +218,128 @@ namespace Cysharp.Threading.Tasks.Linq
return new _DistinctUntilChanged(source, keySelector, comparer, cancellationToken); return new _DistinctUntilChanged(source, keySelector, comparer, cancellationToken);
} }
class _DistinctUntilChanged : AsyncEnumeratorBase<TSource, TSource> sealed class _DistinctUntilChanged : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IEqualityComparer<TKey> comparer; readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, TKey> keySelector; readonly Func<TSource, TKey> keySelector;
readonly IEqualityComparer<TKey> comparer;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
Action moveNextAction;
TKey prev; TKey prev;
bool first;
public _DistinctUntilChanged(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) public _DistinctUntilChanged(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.comparer = comparer; this.source = source;
this.keySelector = keySelector; this.keySelector = keySelector;
this.first = true; this.comparer = comparer;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
if (sourceHasCurrent) if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
}
void MoveNext()
{ {
var v = SourceCurrent; REPEAT:
var key = keySelector(v); try
if (first || !comparer.Equals(prev, key))
{ {
first = false; switch (state)
prev = key; {
Current = v; case -1: // init
result = true; enumerator = source.GetAsyncEnumerator(cancellationToken);
return true; awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case -3;
} }
else else
{ {
result = default; state = -3;
return false; awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case -3: // first
if (awaiter.GetResult())
{
Current = enumerator.Current;
goto CONTINUE;
}
else
{
goto DONE;
}
case 0: // normal
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
var v = enumerator.Current;
var key = keySelector(v);
if (!comparer.Equals(prev, key))
{
prev = key;
Current = v;
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
}
else
{
goto DONE;
}
case -2:
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
} }
result = false; DONE:
return true; state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -197,42 +362,142 @@ namespace Cysharp.Threading.Tasks.Linq
return new _DistinctUntilChangedAwait(source, keySelector, comparer, cancellationToken); return new _DistinctUntilChangedAwait(source, keySelector, comparer, cancellationToken);
} }
class _DistinctUntilChangedAwait : AsyncEnumeratorAwaitSelectorBase<TSource, TSource, TKey> sealed class _DistinctUntilChangedAwait : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IEqualityComparer<TKey> comparer; readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, UniTask<TKey>> keySelector; readonly Func<TSource, UniTask<TKey>> keySelector;
readonly IEqualityComparer<TKey> comparer;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<TKey>.Awaiter awaiter2;
Action moveNextAction;
TSource enumeratorCurrent;
TKey prev; TKey prev;
bool first;
public _DistinctUntilChangedAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) public _DistinctUntilChangedAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TKey>> keySelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.comparer = comparer; this.source = source;
this.keySelector = keySelector; this.keySelector = keySelector;
this.first = true; this.comparer = comparer;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<TKey> TransformAsync(TSource sourceCurrent) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return keySelector(sourceCurrent); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(TKey key, out bool terminateIteration) void MoveNext()
{ {
if (first || !comparer.Equals(prev, key)) REPEAT:
try
{ {
first = false; switch (state)
prev = key; {
Current = SourceCurrent; case -1: // init
terminateIteration = false; enumerator = source.GetAsyncEnumerator(cancellationToken);
return true; awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case -3;
} }
else else
{ {
terminateIteration = false; state = -3;
return false; awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case -3: // first
if (awaiter.GetResult())
{
Current = enumerator.Current;
goto CONTINUE;
}
else
{
goto DONE;
}
case 0: // normal
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
enumeratorCurrent = enumerator.Current;
awaiter2 = keySelector(enumeratorCurrent).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
}
}
else
{
goto DONE;
}
case 2:
var key = awaiter2.GetResult();
if (!comparer.Equals(prev, key))
{
prev = key;
Current = enumeratorCurrent;
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
case -2:
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -255,42 +520,142 @@ namespace Cysharp.Threading.Tasks.Linq
return new _DistinctUntilChangedAwaitWithCancellation(source, keySelector, comparer, cancellationToken); return new _DistinctUntilChangedAwaitWithCancellation(source, keySelector, comparer, cancellationToken);
} }
class _DistinctUntilChangedAwaitWithCancellation : AsyncEnumeratorAwaitSelectorBase<TSource, TSource, TKey> sealed class _DistinctUntilChangedAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IEqualityComparer<TKey> comparer; readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, CancellationToken, UniTask<TKey>> keySelector; readonly Func<TSource, CancellationToken, UniTask<TKey>> keySelector;
readonly IEqualityComparer<TKey> comparer;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<TKey>.Awaiter awaiter2;
Action moveNextAction;
TSource enumeratorCurrent;
TKey prev; TKey prev;
bool first;
public _DistinctUntilChangedAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken) public _DistinctUntilChangedAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TKey>> keySelector, IEqualityComparer<TKey> comparer, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.comparer = comparer; this.source = source;
this.keySelector = keySelector; this.keySelector = keySelector;
this.first = true; this.comparer = comparer;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<TKey> TransformAsync(TSource sourceCurrent) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return keySelector(sourceCurrent, cancellationToken); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(TKey key, out bool terminateIteration) void MoveNext()
{ {
if (first || !comparer.Equals(prev, key)) REPEAT:
try
{ {
first = false; switch (state)
prev = key; {
Current = SourceCurrent; case -1: // init
terminateIteration = false; enumerator = source.GetAsyncEnumerator(cancellationToken);
return true; awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case -3;
} }
else else
{ {
terminateIteration = false; state = -3;
return false; awaiter.UnsafeOnCompleted(moveNextAction);
} return;
}
case -3: // first
if (awaiter.GetResult())
{
Current = enumerator.Current;
goto CONTINUE;
}
else
{
goto DONE;
}
case 0: // normal
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
enumeratorCurrent = enumerator.Current;
awaiter2 = keySelector(enumeratorCurrent, cancellationToken).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
}
}
else
{
goto DONE;
}
case 2:
var key = awaiter2.GetResult();
if (!comparer.Equals(prev, key))
{
prev = key;
Current = enumeratorCurrent;
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
case -2:
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }

View File

@@ -115,6 +115,8 @@ namespace Cysharp.Threading.Tasks.Linq
} }
public TSource Current { get; private set; } public TSource Current { get; private set; }
ITriggerHandler<TSource> ITriggerHandler<TSource>.Prev { get; set; }
ITriggerHandler<TSource> ITriggerHandler<TSource>.Next { get; set; }
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {

View File

@@ -71,29 +71,92 @@ namespace Cysharp.Threading.Tasks.Linq
return new _Select(source, selector, cancellationToken); return new _Select(source, selector, cancellationToken);
} }
sealed class _Select : AsyncEnumeratorBase<TSource, TResult> sealed class _Select : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, TResult> selector; readonly Func<TSource, TResult> selector;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
Action moveNextAction;
public _Select(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken) public _Select(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, TResult> selector, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.selector = selector; this.selector = selector;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) public TResult Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
if (sourceHasCurrent) if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
}
void MoveNext()
{ {
Current = selector(SourceCurrent); try
result = true; {
return true; switch (state)
{
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
result = false; state = 1;
return true; awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case 1:
if (awaiter.GetResult())
{
Current = selector(enumerator.Current);
goto CONTINUE;
}
else
{
goto DONE;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -111,33 +174,96 @@ namespace Cysharp.Threading.Tasks.Linq
public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _SelectInt(source, selector, cancellationToken); return new _Select(source, selector, cancellationToken);
} }
sealed class _SelectInt : AsyncEnumeratorBase<TSource, TResult> sealed class _Select : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, int, TResult> selector; readonly Func<TSource, int, TResult> selector;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
Action moveNextAction;
int index; int index;
public _SelectInt(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, TResult> selector, CancellationToken cancellationToken) public _Select(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, TResult> selector, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.selector = selector; this.selector = selector;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) public TResult Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
if (sourceHasCurrent) if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
}
void MoveNext()
{ {
Current = selector(SourceCurrent, checked(index++)); try
result = true; {
return true; switch (state)
{
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
result = false; state = 1;
return true; awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case 1:
if (awaiter.GetResult())
{
Current = selector(enumerator.Current, checked(index++));
goto CONTINUE;
}
else
{
goto DONE;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -158,26 +284,105 @@ namespace Cysharp.Threading.Tasks.Linq
return new _SelectAwait(source, selector, cancellationToken); return new _SelectAwait(source, selector, cancellationToken);
} }
sealed class _SelectAwait : AsyncEnumeratorAwaitSelectorBase<TSource, TResult, TResult> sealed class _SelectAwait : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, UniTask<TResult>> selector; readonly Func<TSource, UniTask<TResult>> selector;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<TResult>.Awaiter awaiter2;
Action moveNextAction;
public _SelectAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TResult>> selector, CancellationToken cancellationToken) public _SelectAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<TResult>> selector, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.selector = selector; this.selector = selector;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<TResult> TransformAsync(TSource sourceCurrent) public TResult Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return selector(sourceCurrent); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(TResult awaitResult, out bool terminateIteration) void MoveNext()
{ {
Current = awaitResult; try
terminateIteration = false; {
return true; switch (state)
{
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
awaiter2 = selector(enumerator.Current).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
}
}
else
{
goto DONE;
}
case 2:
Current = awaiter2.GetResult();
goto CONTINUE;
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -195,30 +400,109 @@ namespace Cysharp.Threading.Tasks.Linq
public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _SelectIntAwait(source, selector, cancellationToken); return new _SelectAwait(source, selector, cancellationToken);
} }
sealed class _SelectIntAwait : AsyncEnumeratorAwaitSelectorBase<TSource, TResult, TResult> sealed class _SelectAwait : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, int, UniTask<TResult>> selector; readonly Func<TSource, int, UniTask<TResult>> selector;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<TResult>.Awaiter awaiter2;
Action moveNextAction;
int index; int index;
public _SelectIntAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, UniTask<TResult>> selector, CancellationToken cancellationToken) public _SelectAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, UniTask<TResult>> selector, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.selector = selector; this.selector = selector;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<TResult> TransformAsync(TSource sourceCurrent) public TResult Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return selector(sourceCurrent, checked(index++)); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(TResult awaitResult, out bool terminateIteration) void MoveNext()
{ {
Current = awaitResult; try
terminateIteration = false; {
return true; switch (state)
{
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
awaiter2 = selector(enumerator.Current, checked(index++)).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
}
}
else
{
goto DONE;
}
case 2:
Current = awaiter2.GetResult();
goto CONTINUE;
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -239,26 +523,105 @@ namespace Cysharp.Threading.Tasks.Linq
return new _SelectAwaitWithCancellation(source, selector, cancellationToken); return new _SelectAwaitWithCancellation(source, selector, cancellationToken);
} }
sealed class _SelectAwaitWithCancellation : AsyncEnumeratorAwaitSelectorBase<TSource, TResult, TResult> sealed class _SelectAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, CancellationToken, UniTask<TResult>> selector; readonly Func<TSource, CancellationToken, UniTask<TResult>> selector;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<TResult>.Awaiter awaiter2;
Action moveNextAction;
public _SelectAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TResult>> selector, CancellationToken cancellationToken) public _SelectAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<TResult>> selector, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.selector = selector; this.selector = selector;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<TResult> TransformAsync(TSource sourceCurrent) public TResult Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return selector(sourceCurrent, cancellationToken); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(TResult awaitResult, out bool terminateIteration) void MoveNext()
{ {
Current = awaitResult; try
terminateIteration = false; {
return true; switch (state)
{
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
awaiter2 = selector(enumerator.Current, cancellationToken).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
}
}
else
{
goto DONE;
}
case 2:
Current = awaiter2.GetResult();
goto CONTINUE;
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -276,32 +639,110 @@ namespace Cysharp.Threading.Tasks.Linq
public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TResult> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _SelectIntAwaitWithCancellation(source, selector, cancellationToken); return new _SelectAwaitWithCancellation(source, selector, cancellationToken);
} }
sealed class _SelectIntAwaitWithCancellation : AsyncEnumeratorAwaitSelectorBase<TSource, TResult, TResult> sealed class _SelectAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator<TResult>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, int, CancellationToken, UniTask<TResult>> selector; readonly Func<TSource, int, CancellationToken, UniTask<TResult>> selector;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<TResult>.Awaiter awaiter2;
Action moveNextAction;
int index; int index;
public _SelectIntAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, CancellationToken, UniTask<TResult>> selector, CancellationToken cancellationToken) public _SelectAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, CancellationToken, UniTask<TResult>> selector, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.selector = selector; this.selector = selector;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<TResult> TransformAsync(TSource sourceCurrent) public TResult Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return selector(sourceCurrent, checked(index++), cancellationToken); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(TResult awaitResult, out bool terminateIteration) void MoveNext()
{ {
Current = awaitResult; try
terminateIteration = false; {
return true; switch (state)
{
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
}
else
{
state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
awaiter2 = selector(enumerator.Current, checked(index++), cancellationToken).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
} }
} }
else
{
goto DONE;
}
case 2:
Current = awaiter2.GetResult();
goto CONTINUE;
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
} }
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
}
}
}
} }

View File

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

View File

@@ -0,0 +1,15 @@
{
"name": "UniTask.Linq",
"references": [
"UniTask"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5c01796d064528144a599661eaab93a6
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Threading; using System.Threading;
using UnityEngine;
namespace Cysharp.Threading.Tasks.Linq namespace Cysharp.Threading.Tasks.Linq
{ {
@@ -22,16 +23,34 @@ namespace Cysharp.Threading.Tasks.Linq
public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
{ {
if (dueTimeFrameCount < 0)
{
throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. dueTimeFrameCount:" + dueTimeFrameCount);
}
return new TimerFrame(dueTimeFrameCount, null, updateTiming); return new TimerFrame(dueTimeFrameCount, null, updateTiming);
} }
public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
{ {
if (dueTimeFrameCount < 0)
{
throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. dueTimeFrameCount:" + dueTimeFrameCount);
}
if (periodFrameCount < 0)
{
throw new ArgumentOutOfRangeException("Delay does not allow minus periodFrameCount. periodFrameCount:" + dueTimeFrameCount);
}
return new TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming); return new TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming);
} }
public static IUniTaskAsyncEnumerable<AsyncUnit> IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) public static IUniTaskAsyncEnumerable<AsyncUnit> IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update)
{ {
if (intervalFrameCount < 0)
{
throw new ArgumentOutOfRangeException("Delay does not allow minus intervalFrameCount. intervalFrameCount:" + intervalFrameCount);
}
return new TimerFrame(intervalFrameCount, intervalFrameCount, updateTiming); return new TimerFrame(intervalFrameCount, intervalFrameCount, updateTiming);
} }
} }
@@ -64,6 +83,7 @@ namespace Cysharp.Threading.Tasks.Linq
readonly bool ignoreTimeScale; readonly bool ignoreTimeScale;
CancellationToken cancellationToken; CancellationToken cancellationToken;
int initialFrame;
float elapsed; float elapsed;
bool dueTimePhase; bool dueTimePhase;
bool completed; bool completed;
@@ -80,6 +100,7 @@ namespace Cysharp.Threading.Tasks.Linq
if (this.period <= 0) this.period = 1; if (this.period <= 0) this.period = 1;
} }
this.initialFrame = Time.frameCount;
this.dueTimePhase = true; this.dueTimePhase = true;
this.updateTiming = updateTiming; this.updateTiming = updateTiming;
this.ignoreTimeScale = ignoreTimeScale; this.ignoreTimeScale = ignoreTimeScale;
@@ -119,9 +140,19 @@ namespace Cysharp.Threading.Tasks.Linq
return false; return false;
} }
elapsed += (ignoreTimeScale) ? UnityEngine.Time.unscaledDeltaTime : UnityEngine.Time.deltaTime;
if (dueTimePhase) if (dueTimePhase)
{ {
if (elapsed == 0)
{
// skip in initial frame.
if (initialFrame == Time.frameCount)
{
return true;
}
}
elapsed += (ignoreTimeScale) ? UnityEngine.Time.unscaledDeltaTime : UnityEngine.Time.deltaTime;
if (elapsed >= dueTime) if (elapsed >= dueTime)
{ {
dueTimePhase = false; dueTimePhase = false;
@@ -137,6 +168,8 @@ namespace Cysharp.Threading.Tasks.Linq
return false; return false;
} }
elapsed += (ignoreTimeScale) ? UnityEngine.Time.unscaledDeltaTime : UnityEngine.Time.deltaTime;
if (elapsed >= period) if (elapsed >= period)
{ {
completionSource.TrySetResult(true); completionSource.TrySetResult(true);
@@ -172,6 +205,7 @@ namespace Cysharp.Threading.Tasks.Linq
readonly int? periodFrameCount; readonly int? periodFrameCount;
CancellationToken cancellationToken; CancellationToken cancellationToken;
int initialFrame;
int currentFrame; int currentFrame;
bool dueTimePhase; bool dueTimePhase;
bool completed; bool completed;
@@ -185,6 +219,7 @@ namespace Cysharp.Threading.Tasks.Linq
if (periodFrameCount <= 0) periodFrameCount = 1; if (periodFrameCount <= 0) periodFrameCount = 1;
} }
this.initialFrame = Time.frameCount;
this.dueTimePhase = true; this.dueTimePhase = true;
this.dueTimeFrameCount = dueTimeFrameCount; this.dueTimeFrameCount = dueTimeFrameCount;
this.periodFrameCount = periodFrameCount; this.periodFrameCount = periodFrameCount;
@@ -228,10 +263,29 @@ namespace Cysharp.Threading.Tasks.Linq
if (dueTimePhase) if (dueTimePhase)
{ {
if (currentFrame++ >= dueTimeFrameCount) if (currentFrame == 0)
{
if (dueTimeFrameCount == 0)
{ {
dueTimePhase = false; dueTimePhase = false;
completionSource.TrySetResult(true); completionSource.TrySetResult(true);
return true;
}
// skip in initial frame.
if (initialFrame == Time.frameCount)
{
return true;
}
}
if (++currentFrame >= dueTimeFrameCount)
{
dueTimePhase = false;
completionSource.TrySetResult(true);
}
else
{
} }
} }
else else

View File

@@ -71,36 +71,101 @@ namespace Cysharp.Threading.Tasks.Linq
return new _Where(source, predicate, cancellationToken); return new _Where(source, predicate, cancellationToken);
} }
class _Where : AsyncEnumeratorBase<TSource, TSource> sealed class _Where : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, bool> predicate; readonly Func<TSource, bool> predicate;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
Action moveNextAction;
public _Where(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken) public _Where(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, bool> predicate, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.predicate = predicate; this.predicate = predicate;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
if (sourceHasCurrent) if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
}
void MoveNext()
{ {
if (predicate(SourceCurrent)) REPEAT:
try
{ {
Current = SourceCurrent; switch (state)
result = true; {
return true; case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
result = default; state = 1;
return false; awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case 1:
if (awaiter.GetResult())
{
Current = enumerator.Current;
if (predicate(Current))
{
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
}
else
{
goto DONE;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
} }
result = false; DONE:
return true; state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -118,40 +183,105 @@ namespace Cysharp.Threading.Tasks.Linq
public IUniTaskAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _WhereInt(source, predicate, cancellationToken); return new _Where(source, predicate, cancellationToken);
} }
class _WhereInt : AsyncEnumeratorBase<TSource, TSource> sealed class _Where : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, int, bool> predicate; readonly Func<TSource, int, bool> predicate;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
Action moveNextAction;
int index; int index;
public _WhereInt(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, bool> predicate, CancellationToken cancellationToken) public _Where(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, bool> predicate, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.predicate = predicate; this.predicate = predicate;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override bool TryMoveNextCore(bool sourceHasCurrent, out bool result) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
if (sourceHasCurrent) if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
}
void MoveNext()
{ {
if (predicate(SourceCurrent, checked(index++))) REPEAT:
try
{ {
Current = SourceCurrent; switch (state)
result = true; {
return true; case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
result = default; state = 1;
return false; awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case 1:
if (awaiter.GetResult())
{
Current = enumerator.Current;
if (predicate(Current, checked(index++)))
{
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
}
else
{
goto DONE;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
} }
result = false; DONE:
return true; state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -172,34 +302,115 @@ namespace Cysharp.Threading.Tasks.Linq
return new _WhereAwait(source, predicate, cancellationToken); return new _WhereAwait(source, predicate, cancellationToken);
} }
class _WhereAwait : AsyncEnumeratorAwaitSelectorBase<TSource, TSource, bool> sealed class _WhereAwait : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, UniTask<bool>> predicate; readonly Func<TSource, UniTask<bool>> predicate;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<bool>.Awaiter awaiter2;
Action moveNextAction;
public _WhereAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<bool>> predicate, CancellationToken cancellationToken) public _WhereAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, UniTask<bool>> predicate, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.predicate = predicate; this.predicate = predicate;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<bool> TransformAsync(TSource sourceCurrent) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return predicate(sourceCurrent); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(bool awaitResult, out bool terminateIteration) void MoveNext()
{ {
terminateIteration = false; REPEAT:
if (awaitResult) try
{ {
Current = SourceCurrent; switch (state)
return true; {
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
return false; state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case 1:
if (awaiter.GetResult())
{
Current = enumerator.Current;
awaiter2 = predicate(Current).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
}
}
else
{
goto DONE;
}
case 2:
if (awaiter2.GetResult())
{
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -217,43 +428,122 @@ namespace Cysharp.Threading.Tasks.Linq
public IUniTaskAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _WhereIntAwait(source, predicate, cancellationToken); return new _WhereAwait(source, predicate, cancellationToken);
} }
class _WhereIntAwait : AsyncEnumeratorAwaitSelectorBase<TSource, TSource, bool> sealed class _WhereAwait : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, int, UniTask<bool>> predicate; readonly Func<TSource, int, UniTask<bool>> predicate;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<bool>.Awaiter awaiter2;
Action moveNextAction;
int index; int index;
public _WhereIntAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, UniTask<bool>> predicate, CancellationToken cancellationToken) public _WhereAwait(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, UniTask<bool>> predicate, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.predicate = predicate; this.predicate = predicate;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<bool> TransformAsync(TSource sourceCurrent) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return predicate(sourceCurrent, checked(index++)); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(bool awaitResult, out bool terminateIteration) void MoveNext()
{ {
terminateIteration = false; REPEAT:
if (awaitResult) try
{ {
Current = SourceCurrent; switch (state)
return true; {
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
return false; state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
Current = enumerator.Current;
awaiter2 = predicate(Current, checked(index++)).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
} }
} }
else
{
goto DONE;
} }
case 2:
if (awaiter2.GetResult())
{
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
} }
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
}
}
}
internal sealed class WhereAwaitWithCancellation<TSource> : IUniTaskAsyncEnumerable<TSource> internal sealed class WhereAwaitWithCancellation<TSource> : IUniTaskAsyncEnumerable<TSource>
{ {
@@ -271,34 +561,115 @@ namespace Cysharp.Threading.Tasks.Linq
return new _WhereAwaitWithCancellation(source, predicate, cancellationToken); return new _WhereAwaitWithCancellation(source, predicate, cancellationToken);
} }
class _WhereAwaitWithCancellation : AsyncEnumeratorAwaitSelectorBase<TSource, TSource, bool> sealed class _WhereAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, CancellationToken, UniTask<bool>> predicate; readonly Func<TSource, CancellationToken, UniTask<bool>> predicate;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<bool>.Awaiter awaiter2;
Action moveNextAction;
public _WhereAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<bool>> predicate, CancellationToken cancellationToken) public _WhereAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, CancellationToken, UniTask<bool>> predicate, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.predicate = predicate; this.predicate = predicate;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<bool> TransformAsync(TSource sourceCurrent) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return predicate(sourceCurrent, cancellationToken); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(bool awaitResult, out bool terminateIteration) void MoveNext()
{ {
terminateIteration = false; REPEAT:
if (awaitResult) try
{ {
Current = SourceCurrent; switch (state)
return true; {
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
return false; state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
} }
case 1:
if (awaiter.GetResult())
{
Current = enumerator.Current;
awaiter2 = predicate(Current, cancellationToken).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
}
}
else
{
goto DONE;
}
case 2:
if (awaiter2.GetResult())
{
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
}
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
} }
} }
} }
@@ -316,40 +687,120 @@ namespace Cysharp.Threading.Tasks.Linq
public IUniTaskAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TSource> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _WhereIntAwaitWithCancellation(source, predicate, cancellationToken); return new _WhereAwaitWithCancellation(source, predicate, cancellationToken);
} }
class _WhereIntAwaitWithCancellation : AsyncEnumeratorAwaitSelectorBase<TSource, TSource, bool> sealed class _WhereAwaitWithCancellation : MoveNextSource, IUniTaskAsyncEnumerator<TSource>
{ {
readonly IUniTaskAsyncEnumerable<TSource> source;
readonly Func<TSource, int, CancellationToken, UniTask<bool>> predicate; readonly Func<TSource, int, CancellationToken, UniTask<bool>> predicate;
readonly CancellationToken cancellationToken;
int state = -1;
IUniTaskAsyncEnumerator<TSource> enumerator;
UniTask<bool>.Awaiter awaiter;
UniTask<bool>.Awaiter awaiter2;
Action moveNextAction;
int index; int index;
public _WhereIntAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, CancellationToken, UniTask<bool>> predicate, CancellationToken cancellationToken) public _WhereAwaitWithCancellation(IUniTaskAsyncEnumerable<TSource> source, Func<TSource, int, CancellationToken, UniTask<bool>> predicate, CancellationToken cancellationToken)
: base(source, cancellationToken)
{ {
this.source = source;
this.predicate = predicate; this.predicate = predicate;
this.cancellationToken = cancellationToken;
this.moveNextAction = MoveNext;
} }
protected override UniTask<bool> TransformAsync(TSource sourceCurrent) public TSource Current { get; private set; }
public UniTask<bool> MoveNextAsync()
{ {
return predicate(sourceCurrent, checked(index++), cancellationToken); if (state == -2) return default;
completionSource.Reset();
MoveNext();
return new UniTask<bool>(this, completionSource.Version);
} }
protected override bool TrySetCurrentCore(bool awaitResult, out bool terminateIteration) void MoveNext()
{ {
terminateIteration = false; REPEAT:
if (awaitResult) try
{ {
Current = SourceCurrent; switch (state)
return true; {
case -1: // init
enumerator = source.GetAsyncEnumerator(cancellationToken);
goto case 0;
case 0:
awaiter = enumerator.MoveNextAsync().GetAwaiter();
if (awaiter.IsCompleted)
{
goto case 1;
} }
else else
{ {
return false; state = 1;
awaiter.UnsafeOnCompleted(moveNextAction);
return;
}
case 1:
if (awaiter.GetResult())
{
Current = enumerator.Current;
awaiter2 = predicate(Current, checked(index++), cancellationToken).GetAwaiter();
if (awaiter2.IsCompleted)
{
goto case 2;
}
else
{
state = 2;
awaiter2.UnsafeOnCompleted(moveNextAction);
return;
} }
} }
else
{
goto DONE;
} }
case 2:
if (awaiter2.GetResult())
{
goto CONTINUE;
}
else
{
state = 0;
goto REPEAT;
}
default:
goto DONE;
}
}
catch (Exception ex)
{
state = -2;
completionSource.TrySetException(ex);
return;
} }
DONE:
state = -2;
completionSource.TrySetResult(false);
return;
CONTINUE:
state = 0;
completionSource.TrySetResult(true);
return;
}
public UniTask DisposeAsync()
{
return enumerator.DisposeAsync();
}
}
}
} }

View File

@@ -0,0 +1,63 @@
using System;
namespace Cysharp.Threading.Tasks
{
public abstract class MoveNextSource : IUniTaskSource<bool>
{
protected UniTaskCompletionSourceCore<bool> completionSource;
public bool GetResult(short token)
{
return completionSource.GetResult(token);
}
public UniTaskStatus GetStatus(short token)
{
return completionSource.GetStatus(token);
}
public void OnCompleted(Action<object> continuation, object state, short token)
{
completionSource.OnCompleted(continuation, state, token);
}
public UniTaskStatus UnsafeGetStatus()
{
return completionSource.UnsafeGetStatus();
}
void IUniTaskSource.GetResult(short token)
{
completionSource.GetResult(token);
}
protected bool TryGetResult<T>(UniTask<T>.Awaiter awaiter, out T result)
{
try
{
result = awaiter.GetResult();
return true;
}
catch (Exception ex)
{
completionSource.TrySetException(ex);
result = default;
return false;
}
}
protected bool TryGetResult(UniTask.Awaiter awaiter)
{
try
{
awaiter.GetResult();
return true;
}
catch (Exception ex)
{
completionSource.TrySetException(ex);
return false;
}
}
}
}

View File

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

View File

@@ -92,8 +92,10 @@ namespace Cysharp.Threading.Tasks
{ {
public static SynchronizationContext UnitySynchronizationContext => unitySynchronizationContetext; public static SynchronizationContext UnitySynchronizationContext => unitySynchronizationContetext;
public static int MainThreadId => mainThreadId; public static int MainThreadId => mainThreadId;
internal static string ApplicationDataPath => applicationDataPath;
static int mainThreadId; static int mainThreadId;
static string applicationDataPath;
static SynchronizationContext unitySynchronizationContetext; static SynchronizationContext unitySynchronizationContetext;
static ContinuationQueue[] yielders; static ContinuationQueue[] yielders;
static PlayerLoopRunner[] runners; static PlayerLoopRunner[] runners;
@@ -177,6 +179,11 @@ namespace Cysharp.Threading.Tasks
// capture default(unity) sync-context. // capture default(unity) sync-context.
unitySynchronizationContetext = SynchronizationContext.Current; unitySynchronizationContetext = SynchronizationContext.Current;
mainThreadId = Thread.CurrentThread.ManagedThreadId; mainThreadId = Thread.CurrentThread.ManagedThreadId;
try
{
applicationDataPath = Application.dataPath;
}
catch { }
#if UNITY_EDITOR && UNITY_2019_3_OR_NEWER #if UNITY_EDITOR && UNITY_2019_3_OR_NEWER
// When domain reload is disabled, re-initialization is required when entering play mode; // When domain reload is disabled, re-initialization is required when entering play mode;
@@ -200,6 +207,7 @@ namespace Cysharp.Threading.Tasks
#if UNITY_EDITOR #if UNITY_EDITOR
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
static void InitOnEditor() static void InitOnEditor()
{ {
@@ -212,15 +220,30 @@ namespace Cysharp.Threading.Tasks
private static void ForceEditorPlayerLoopUpdate() private static void ForceEditorPlayerLoopUpdate()
{ {
if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling || if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling || EditorApplication.isUpdating)
EditorApplication.isUpdating)
{ {
// Not in Edit mode, don't interfere // Not in Edit mode, don't interfere
return; return;
} }
//force unity to update PlayerLoop callbacks // EditorApplication.QueuePlayerLoopUpdate causes performance issue, don't call directly.
EditorApplication.QueuePlayerLoopUpdate(); // EditorApplication.QueuePlayerLoopUpdate();
if (yielders != null)
{
foreach (var item in yielders)
{
if (item != null) item.Run();
}
}
if (runners != null)
{
foreach (var item in runners)
{
if (item != null) item.Run();
}
}
} }
#endif #endif

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace Cysharp.Threading.Tasks
{
// internaly used but public, allow to user create custom operator with pooling.
public static class TaskPool
{
internal static int MaxPoolSize;
static ConcurrentDictionary<Type, Func<int>> sizes = new ConcurrentDictionary<Type, Func<int>>();
static TaskPool()
{
try
{
var value = Environment.GetEnvironmentVariable("UNITASK_MAX_POOLSIZE");
if (value != null)
{
if (int.TryParse(value, out var size))
{
MaxPoolSize = size;
return;
}
}
}
catch { }
MaxPoolSize = int.MaxValue;
}
public static void SetMaxPoolSize(int maxPoolSize)
{
MaxPoolSize = maxPoolSize;
}
public static IEnumerable<(Type, int)> GetCacheSizeInfo()
{
foreach (var item in sizes)
{
yield return (item.Key, item.Value());
}
}
public static void RegisterSizeGetter(Type type, Func<int> getSize)
{
sizes[type] = getSize;
}
}
public interface ITaskPoolNode<T>
{
T NextNode { get; set; }
}
// mutable struct, don't mark readonly.
[StructLayout(LayoutKind.Auto)]
public struct TaskPool<T>
where T : class, ITaskPoolNode<T>
{
int gate;
int size;
T root;
public int Size => size;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPop(out T result)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
var v = root;
if (!(v is null))
{
root = v.NextNode;
v.NextNode = null;
size--;
result = v;
Volatile.Write(ref gate, 0);
return true;
}
Volatile.Write(ref gate, 0);
}
result = default;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T item)
{
if (Interlocked.CompareExchange(ref gate, 1, 0) == 0)
{
if (size < TaskPool.MaxPoolSize)
{
item.NextNode = root;
root = item;
size++;
Volatile.Write(ref gate, 0);
return true;
}
else
{
Volatile.Write(ref gate, 0);
}
}
return false;
}
}
}

View File

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

View File

@@ -1,5 +1,4 @@
using Cysharp.Threading.Tasks.Internal; using System;
using System;
using System.Threading; using System.Threading;
namespace Cysharp.Threading.Tasks namespace Cysharp.Threading.Tasks
@@ -10,341 +9,302 @@ namespace Cysharp.Threading.Tasks
void OnError(Exception ex); void OnError(Exception ex);
void OnCompleted(); void OnCompleted();
void OnCanceled(CancellationToken cancellationToken); void OnCanceled(CancellationToken cancellationToken);
// set/get from TriggerEvent<T>
ITriggerHandler<T> Prev { get; set; }
ITriggerHandler<T> Next { get; set; }
} }
// be careful to use, itself is struct. // be careful to use, itself is struct.
public struct TriggerEvent<T> public struct TriggerEvent<T>
{ {
// optimize: many cases, handler is single. ITriggerHandler<T> head; // head.prev is last
ITriggerHandler<T> singleHandler; ITriggerHandler<T> iteratingHead;
ITriggerHandler<T>[] handlers; bool preserveRemoveSelf;
ITriggerHandler<T> iteratingNode;
// when running(in TrySetResult), does not add immediately(trampoline). void LogError(Exception ex)
bool isRunning; {
ITriggerHandler<T> waitHandler; #if UNITY_2018_3_OR_NEWER
MinimumQueue<ITriggerHandler<T>> waitQueue; UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
}
public void SetResult(T value) public void SetResult(T value)
{ {
isRunning = true; if (iteratingNode != null)
if (singleHandler != null)
{ {
throw new InvalidOperationException("Can not trigger itself in iterating.");
}
var h = head;
while (h != null)
{
iteratingNode = h;
try try
{ {
singleHandler.OnNext(value); h.OnNext(value);
} }
catch (Exception ex) catch (Exception ex)
{ {
#if UNITY_2018_3_OR_NEWER LogError(ex);
UnityEngine.Debug.LogException(ex); Remove(h);
#else }
Console.WriteLine(ex);
#endif if (preserveRemoveSelf)
{
preserveRemoveSelf = false;
iteratingNode = null;
var next = h.Next;
Remove(h);
h = next;
}
else
{
h = h.Next;
} }
} }
if (handlers != null) iteratingNode = null;
if (iteratingHead != null)
{ {
for (int i = 0; i < handlers.Length; i++) Add(iteratingHead);
{ iteratingHead = null;
if (handlers[i] != null)
{
try
{
handlers[i].OnNext(value);
}
catch (Exception ex)
{
handlers[i] = null;
#if UNITY_2018_3_OR_NEWER
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
}
}
}
}
isRunning = false;
if (waitHandler != null)
{
var h = waitHandler;
waitHandler = null;
Add(h);
}
if (waitQueue != null)
{
while (waitQueue.Count != 0)
{
Add(waitQueue.Dequeue());
}
} }
} }
public void SetCanceled(CancellationToken cancellationToken) public void SetCanceled(CancellationToken cancellationToken)
{ {
isRunning = true; if (iteratingNode != null)
if (singleHandler != null)
{ {
throw new InvalidOperationException("Can not trigger itself in iterating.");
}
var h = head;
while (h != null)
{
iteratingNode = h;
try try
{ {
(singleHandler).OnCanceled(cancellationToken); h.OnCanceled(cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
#if UNITY_2018_3_OR_NEWER LogError(ex);
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
}
} }
if (handlers != null) preserveRemoveSelf = false;
{ iteratingNode = null;
for (int i = 0; i < handlers.Length; i++) var next = h.Next;
{ Remove(h);
if (handlers[i] != null) h = next;
{
try
{
(handlers[i]).OnCanceled(cancellationToken);
}
catch (Exception ex)
{
#if UNITY_2018_3_OR_NEWER
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
handlers[i] = null;
}
}
}
} }
isRunning = false; iteratingNode = null;
if (iteratingHead != null)
if (waitHandler != null)
{ {
var h = waitHandler; Add(iteratingHead);
waitHandler = null; iteratingHead = null;
Add(h);
}
if (waitQueue != null)
{
while (waitQueue.Count != 0)
{
Add(waitQueue.Dequeue());
}
} }
} }
public void SetCompleted() public void SetCompleted()
{ {
isRunning = true; if (iteratingNode != null)
if (singleHandler != null)
{ {
throw new InvalidOperationException("Can not trigger itself in iterating.");
}
var h = head;
while (h != null)
{
iteratingNode = h;
try try
{ {
(singleHandler).OnCompleted(); h.OnCompleted();
} }
catch (Exception ex) catch (Exception ex)
{ {
#if UNITY_2018_3_OR_NEWER LogError(ex);
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
}
} }
if (handlers != null) preserveRemoveSelf = false;
{ iteratingNode = null;
for (int i = 0; i < handlers.Length; i++) var next = h.Next;
{ Remove(h);
if (handlers[i] != null) h = next;
{
try
{
(handlers[i]).OnCompleted();
}
catch (Exception ex)
{
#if UNITY_2018_3_OR_NEWER
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
handlers[i] = null;
}
}
}
} }
isRunning = false; iteratingNode = null;
if (iteratingHead != null)
if (waitHandler != null)
{ {
var h = waitHandler; Add(iteratingHead);
waitHandler = null; iteratingHead = null;
Add(h);
}
if (waitQueue != null)
{
while (waitQueue.Count != 0)
{
Add(waitQueue.Dequeue());
}
} }
} }
public void SetError(Exception exception) public void SetError(Exception exception)
{ {
isRunning = true; if (iteratingNode != null)
if (singleHandler != null)
{ {
throw new InvalidOperationException("Can not trigger itself in iterating.");
}
var h = head;
while (h != null)
{
iteratingNode = h;
try try
{ {
singleHandler.OnError(exception); h.OnError(exception);
} }
catch (Exception ex) catch (Exception ex)
{ {
#if UNITY_2018_3_OR_NEWER LogError(ex);
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
}
} }
if (handlers != null) preserveRemoveSelf = false;
{ iteratingNode = null;
for (int i = 0; i < handlers.Length; i++) var next = h.Next;
{ Remove(h);
if (handlers[i] != null) h = next;
{
try
{
handlers[i].OnError(exception);
}
catch (Exception ex)
{
handlers[i] = null;
#if UNITY_2018_3_OR_NEWER
UnityEngine.Debug.LogException(ex);
#else
Console.WriteLine(ex);
#endif
}
}
}
} }
isRunning = false; iteratingNode = null;
if (iteratingHead != null)
if (waitHandler != null)
{ {
var h = waitHandler; Add(iteratingHead);
waitHandler = null; iteratingHead = null;
Add(h);
}
if (waitQueue != null)
{
while (waitQueue.Count != 0)
{
Add(waitQueue.Dequeue());
}
} }
} }
public void Add(ITriggerHandler<T> handler) public void Add(ITriggerHandler<T> handler)
{ {
if (isRunning) if (handler == null) throw new ArgumentNullException(nameof(handler));
// zero node.
if (head == null)
{ {
if (waitHandler == null) head = handler;
{
waitHandler = handler;
return; return;
} }
if (waitQueue == null) if (iteratingNode != null)
{ {
waitQueue = new MinimumQueue<ITriggerHandler<T>>(4); if (iteratingHead == null)
} {
waitQueue.Enqueue(handler); iteratingHead = handler;
return; return;
} }
if (singleHandler == null) var last = iteratingHead.Prev;
if (last == null)
{ {
singleHandler = handler; // single node.
iteratingHead.Prev = handler;
iteratingHead.Next = handler;
handler.Prev = iteratingHead;
} }
else else
{ {
if (handlers == null) // multi node
iteratingHead.Prev = handler;
last.Next = handler;
handler.Prev = last;
}
}
else
{ {
handlers = new ITriggerHandler<T>[4]; var last = head.Prev;
} if (last == null)
// check empty
for (int i = 0; i < handlers.Length; i++)
{ {
if (handlers[i] == null) // single node.
head.Prev = handler;
head.Next = handler;
handler.Prev = head;
}
else
{ {
handlers[i] = handler; // multi node
return; head.Prev = handler;
last.Next = handler;
handler.Prev = last;
} }
} }
// full, ensure capacity
var last = handlers.Length;
{
EnsureCapacity(ref handlers);
handlers[last] = handler;
}
}
}
static void EnsureCapacity(ref ITriggerHandler<T>[] array)
{
var newSize = array.Length * 2;
var newArray = new ITriggerHandler<T>[newSize];
Array.Copy(array, 0, newArray, 0, array.Length);
array = newArray;
} }
public void Remove(ITriggerHandler<T> handler) public void Remove(ITriggerHandler<T> handler)
{ {
if (singleHandler == handler) if (handler == null) throw new ArgumentNullException(nameof(handler));
if (iteratingNode != null && iteratingNode == handler)
{ {
singleHandler = null; // if remove self, reserve remove self after invoke completed.
preserveRemoveSelf = true;
} }
else else
{ {
if (handlers != null) var prev = handler.Prev;
var next = handler.Next;
if (next != null)
{ {
for (int i = 0; i < handlers.Length; i++) next.Prev = prev;
}
if (handler == head)
{ {
if (handlers[i] == handler) head = next;
}
else if (handler == iteratingHead)
{ {
// fill null. iteratingHead = next;
handlers[i] = null; }
return; else
} {
} // when handler is head, prev indicate last so don't use it.
} if (prev != null)
{
prev.Next = next;
}
}
if (head != null)
{
if (head.Prev == handler)
{
if (prev != head)
{
head.Prev = prev;
}
else
{
head.Prev = null;
}
}
}
if (iteratingHead != null)
{
if (iteratingHead.Prev == handler)
{
if (prev != iteratingHead.Prev)
{
iteratingHead.Prev = prev;
}
else
{
iteratingHead.Prev = null;
}
}
}
handler.Prev = null;
handler.Next = null;
} }
} }
} }

View File

@@ -1,6 +1,5 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using Cysharp.Threading.Tasks.Linq;
using System; using System;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;
@@ -103,6 +102,8 @@ namespace Cysharp.Threading.Tasks.Triggers
} }
public T Current { get; private set; } public T Current { get; private set; }
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {
@@ -189,6 +190,9 @@ namespace Cysharp.Threading.Tasks.Triggers
internal CancellationToken CancellationToken => cancellationToken; internal CancellationToken CancellationToken => cancellationToken;
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
internal AsyncTriggerHandler(AsyncTriggerBase<T> trigger, bool callOnce) internal AsyncTriggerHandler(AsyncTriggerBase<T> trigger, bool callOnce)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)

View File

@@ -3,7 +3,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using Cysharp.Threading.Tasks.Internal;
using UnityEngine; using UnityEngine;
namespace Cysharp.Threading.Tasks namespace Cysharp.Threading.Tasks
@@ -21,6 +20,46 @@ namespace Cysharp.Threading.Tasks
return new UniTask(YieldPromise.Create(timing, cancellationToken, out var token), token); return new UniTask(YieldPromise.Create(timing, cancellationToken, out var token), token);
} }
/// <summary>
/// Similar as UniTask.Yield but guaranteed run on next frame.
/// </summary>
public static UniTask NextFrame(PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default)
{
return new UniTask(NextFramePromise.Create(timing, cancellationToken, out var token), token);
}
/// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate).
/// </summary>
public static YieldAwaitable WaitForEndOfFrame()
{
return UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
}
/// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken).
/// </summary>
public static UniTask WaitForEndOfFrame(CancellationToken cancellationToken)
{
return UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken);
}
/// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.FixedUpdate).
/// </summary>
public static YieldAwaitable WaitForFixedUpdate()
{
return UniTask.Yield(PlayerLoopTiming.FixedUpdate);
}
/// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.FixedUpdate, cancellationToken).
/// </summary>
public static UniTask WaitForFixedUpdate(CancellationToken cancellationToken)
{
return UniTask.Yield(PlayerLoopTiming.FixedUpdate, cancellationToken);
}
public static UniTask DelayFrame(int delayFrameCount, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask DelayFrame(int delayFrameCount, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
{ {
if (delayFrameCount < 0) if (delayFrameCount < 0)
@@ -56,9 +95,15 @@ namespace Cysharp.Threading.Tasks
: new UniTask(DelayPromise.Create(delayTimeSpan, delayTiming, cancellationToken, out token), token); : new UniTask(DelayPromise.Create(delayTimeSpan, delayTiming, cancellationToken, out token), token);
} }
sealed class YieldPromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class YieldPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<YieldPromise>
{ {
static readonly PromisePool<YieldPromise> pool = new PromisePool<YieldPromise>(); static TaskPool<YieldPromise> pool;
public YieldPromise NextNode { get; set; }
static YieldPromise()
{
TaskPool.RegisterSizeGetter(typeof(YieldPromise), () => pool.Size);
}
CancellationToken cancellationToken; CancellationToken cancellationToken;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> core;
@@ -74,7 +119,11 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new YieldPromise(); if (!pool.TryPop(out var result))
{
result = new YieldPromise();
}
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
@@ -90,12 +139,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -126,30 +174,141 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~YieldPromise() ~YieldPromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
} }
} }
sealed class DelayFramePromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class NextFramePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<NextFramePromise>
{ {
static readonly PromisePool<DelayFramePromise> pool = new PromisePool<DelayFramePromise>(); static TaskPool<NextFramePromise> pool;
public NextFramePromise NextNode { get; set; }
static NextFramePromise()
{
TaskPool.RegisterSizeGetter(typeof(NextFramePromise), () => pool.Size);
}
int frameCount;
CancellationToken cancellationToken;
UniTaskCompletionSourceCore<AsyncUnit> core;
NextFramePromise()
{
}
public static IUniTaskSource Create(PlayerLoopTiming timing, CancellationToken cancellationToken, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new NextFramePromise();
}
result.frameCount = Time.frameCount;
result.cancellationToken = cancellationToken;
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result);
token = result.core.Version;
return result;
}
public void GetResult(short token)
{
try
{
core.GetResult(token);
}
finally
{
TryReturn();
}
}
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;
}
if (frameCount == Time.frameCount)
{
return true;
}
core.TrySetResult(AsyncUnit.Default);
return false;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
cancellationToken = default;
return pool.TryPush(this);
}
~NextFramePromise()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
}
sealed class DelayFramePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<DelayFramePromise>
{
static TaskPool<DelayFramePromise> pool;
public DelayFramePromise NextNode { get; set; }
static DelayFramePromise()
{
TaskPool.RegisterSizeGetter(typeof(DelayFramePromise), () => pool.Size);
}
int initialFrame;
int delayFrameCount; int delayFrameCount;
CancellationToken cancellationToken; CancellationToken cancellationToken;
int currentFrameCount; int currentFrameCount;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<AsyncUnit> core;
DelayFramePromise() DelayFramePromise()
{ {
@@ -162,10 +321,14 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new DelayFramePromise(); if (!pool.TryPop(out var result))
{
result = new DelayFramePromise();
}
result.delayFrameCount = delayFrameCount; result.delayFrameCount = delayFrameCount;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.initialFrame = Time.frameCount;
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
@@ -179,12 +342,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -211,37 +373,60 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
if (currentFrameCount == delayFrameCount) if (currentFrameCount == 0)
{ {
core.TrySetResult(null); if (delayFrameCount == 0) // same as Yield
{
core.TrySetResult(AsyncUnit.Default);
return false;
}
// skip in initial frame.
if (initialFrame == Time.frameCount)
{
return true;
}
}
if (++currentFrameCount >= delayFrameCount)
{
core.TrySetResult(AsyncUnit.Default);
return false; return false;
} }
currentFrameCount++;
return true; return true;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
currentFrameCount = default; currentFrameCount = default;
delayFrameCount = default; delayFrameCount = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~DelayFramePromise() ~DelayFramePromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
} }
} }
sealed class DelayPromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class DelayPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<DelayPromise>
{ {
static readonly PromisePool<DelayPromise> pool = new PromisePool<DelayPromise>(); static TaskPool<DelayPromise> pool;
public DelayPromise NextNode { get; set; }
static DelayPromise()
{
TaskPool.RegisterSizeGetter(typeof(DelayPromise), () => pool.Size);
}
int initialFrame;
float delayFrameTimeSpan; float delayFrameTimeSpan;
float elapsed; float elapsed;
CancellationToken cancellationToken; CancellationToken cancellationToken;
@@ -259,11 +444,15 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new DelayPromise(); if (!pool.TryPop(out var result))
{
result = new DelayPromise();
}
result.elapsed = 0.0f; result.elapsed = 0.0f;
result.delayFrameTimeSpan = (float)delayFrameTimeSpan.TotalSeconds; result.delayFrameTimeSpan = (float)delayFrameTimeSpan.TotalSeconds;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.initialFrame = Time.frameCount;
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
@@ -277,12 +466,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -309,6 +497,14 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
if (elapsed == 0.0f)
{
if (initialFrame == Time.frameCount)
{
return true;
}
}
elapsed += Time.deltaTime; elapsed += Time.deltaTime;
if (elapsed >= delayFrameTimeSpan) if (elapsed >= delayFrameTimeSpan)
{ {
@@ -319,29 +515,38 @@ namespace Cysharp.Threading.Tasks
return true; return true;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
delayFrameTimeSpan = default; delayFrameTimeSpan = default;
elapsed = default; elapsed = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~DelayPromise() ~DelayPromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
} }
} }
sealed class DelayIgnoreTimeScalePromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class DelayIgnoreTimeScalePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<DelayIgnoreTimeScalePromise>
{ {
static readonly PromisePool<DelayIgnoreTimeScalePromise> pool = new PromisePool<DelayIgnoreTimeScalePromise>(); static TaskPool<DelayIgnoreTimeScalePromise> pool;
public DelayIgnoreTimeScalePromise NextNode { get; set; }
static DelayIgnoreTimeScalePromise()
{
TaskPool.RegisterSizeGetter(typeof(DelayIgnoreTimeScalePromise), () => pool.Size);
}
float delayFrameTimeSpan; float delayFrameTimeSpan;
float elapsed; float elapsed;
int initialFrame;
CancellationToken cancellationToken; CancellationToken cancellationToken;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> core;
@@ -357,10 +562,14 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new DelayIgnoreTimeScalePromise(); if (!pool.TryPop(out var result))
{
result = new DelayIgnoreTimeScalePromise();
}
result.elapsed = 0.0f; result.elapsed = 0.0f;
result.delayFrameTimeSpan = (float)delayFrameTimeSpan.TotalSeconds; result.delayFrameTimeSpan = (float)delayFrameTimeSpan.TotalSeconds;
result.initialFrame = Time.frameCount;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
@@ -375,12 +584,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -407,6 +615,14 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
if (elapsed == 0.0f)
{
if (initialFrame == Time.frameCount)
{
return true;
}
}
elapsed += Time.unscaledDeltaTime; elapsed += Time.unscaledDeltaTime;
if (elapsed >= delayFrameTimeSpan) if (elapsed >= delayFrameTimeSpan)
{ {
@@ -417,17 +633,19 @@ namespace Cysharp.Threading.Tasks
return true; return true;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
delayFrameTimeSpan = default; delayFrameTimeSpan = default;
elapsed = default; elapsed = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~DelayIgnoreTimeScalePromise() ~DelayIgnoreTimeScalePromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }

View File

@@ -1,6 +1,8 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using Cysharp.Threading.Tasks.Internal;
using System; using System;
using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
namespace Cysharp.Threading.Tasks namespace Cysharp.Threading.Tasks
@@ -102,8 +104,7 @@ namespace Cysharp.Threading.Tasks
} }
/// <summary> /// <summary>
/// helper of create add UniTaskVoid to delegate. /// helper of fire and forget void action.
/// For example: FooEvent += () => UniTask.Void(async () => { /* */ })
/// </summary> /// </summary>
public static void Void(Func<UniTaskVoid> asyncAction) public static void Void(Func<UniTaskVoid> asyncAction)
{ {
@@ -111,7 +112,7 @@ namespace Cysharp.Threading.Tasks
} }
/// <summary> /// <summary>
/// helper of create add UniTaskVoid to delegate. /// helper of fire and forget void action.
/// </summary> /// </summary>
public static void Void(Func<CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken) public static void Void(Func<CancellationToken, UniTaskVoid> asyncAction, CancellationToken cancellationToken)
{ {
@@ -119,8 +120,7 @@ namespace Cysharp.Threading.Tasks
} }
/// <summary> /// <summary>
/// helper of create add UniTaskVoid to delegate. /// helper of fire and forget void action.
/// For example: FooEvent += (sender, e) => UniTask.Void(async arg => { /* */ }, (sender, e))
/// </summary> /// </summary>
public static void Void<T>(Func<T, UniTaskVoid> asyncAction, T state) public static void Void<T>(Func<T, UniTaskVoid> asyncAction, T state)
{ {
@@ -204,7 +204,7 @@ namespace Cysharp.Threading.Tasks
if (f == null) throw new InvalidOperationException("Can't call twice."); if (f == null) throw new InvalidOperationException("Can't call twice.");
task = f(); task = f();
awaiter = f().GetAwaiter(); awaiter = task.GetAwaiter();
return task.Status; return task.Status;
} }
@@ -246,7 +246,7 @@ namespace Cysharp.Threading.Tasks
if (f == null) throw new InvalidOperationException("Can't call twice."); if (f == null) throw new InvalidOperationException("Can't call twice.");
task = f(); task = f();
awaiter = f().GetAwaiter(); awaiter = task.GetAwaiter();
return task.Status; return task.Status;
} }

View File

@@ -17,7 +17,31 @@ namespace Cysharp.Threading.Tasks
/// </summary> /// </summary>
public static SwitchToMainThreadAwaitable SwitchToMainThread() public static SwitchToMainThreadAwaitable SwitchToMainThread()
{ {
return new SwitchToMainThreadAwaitable(); return new SwitchToMainThreadAwaitable(PlayerLoopTiming.Update);
}
/// <summary>
/// If running on mainthread, do nothing. Otherwise, same as UniTask.Yield(timing).
/// </summary>
public static SwitchToMainThreadAwaitable SwitchToMainThread(PlayerLoopTiming timing)
{
return new SwitchToMainThreadAwaitable(timing);
}
/// <summary>
/// Return to mainthread(same as await SwitchToMainThread) after using scope is closed.
/// </summary>
public static ReturnToMainThread ReturnToMainThread()
{
return new ReturnToMainThread(PlayerLoopTiming.Update);
}
/// <summary>
/// Return to mainthread(same as await SwitchToMainThread) after using scope is closed.
/// </summary>
public static ReturnToMainThread ReturnToMainThread(PlayerLoopTiming timing)
{
return new ReturnToMainThread(timing);
} }
#endif #endif
@@ -27,15 +51,28 @@ namespace Cysharp.Threading.Tasks
return new SwitchToThreadPoolAwaitable(); return new SwitchToThreadPoolAwaitable();
} }
/// <summary>
/// Note: use SwitchToThreadPool is recommended.
/// </summary>
public static SwitchToTaskPoolAwaitable SwitchToTaskPool() public static SwitchToTaskPoolAwaitable SwitchToTaskPool()
{ {
return new SwitchToTaskPoolAwaitable(); return new SwitchToTaskPoolAwaitable();
} }
public static SwitchToSynchronizationContextAwaitable SwitchToSynchronizationContext(SynchronizationContext syncContext) public static SwitchToSynchronizationContextAwaitable SwitchToSynchronizationContext(SynchronizationContext synchronizationContext)
{ {
Error.ThrowArgumentNullException(syncContext, nameof(syncContext)); Error.ThrowArgumentNullException(synchronizationContext, nameof(synchronizationContext));
return new SwitchToSynchronizationContextAwaitable(syncContext); return new SwitchToSynchronizationContextAwaitable(synchronizationContext);
}
public static ReturnToSynchronizationContext ReturnToSynchronizationContext(SynchronizationContext synchronizationContext)
{
return new ReturnToSynchronizationContext(synchronizationContext, false);
}
public static ReturnToSynchronizationContext ReturnToCurrentSynchronizationContext(bool dontPostWhenSameContext = true)
{
return new ReturnToSynchronizationContext(SynchronizationContext.Current, dontPostWhenSameContext);
} }
} }
@@ -43,10 +80,24 @@ namespace Cysharp.Threading.Tasks
public struct SwitchToMainThreadAwaitable public struct SwitchToMainThreadAwaitable
{ {
public Awaiter GetAwaiter() => new Awaiter(); readonly PlayerLoopTiming playerLoopTiming;
public SwitchToMainThreadAwaitable(PlayerLoopTiming playerLoopTiming)
{
this.playerLoopTiming = playerLoopTiming;
}
public Awaiter GetAwaiter() => new Awaiter(playerLoopTiming);
public struct Awaiter : ICriticalNotifyCompletion public struct Awaiter : ICriticalNotifyCompletion
{ {
readonly PlayerLoopTiming playerLoopTiming;
public Awaiter(PlayerLoopTiming playerLoopTiming)
{
this.playerLoopTiming = playerLoopTiming;
}
public bool IsCompleted public bool IsCompleted
{ {
get get
@@ -67,12 +118,53 @@ namespace Cysharp.Threading.Tasks
public void OnCompleted(Action continuation) public void OnCompleted(Action continuation)
{ {
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.Update, continuation); PlayerLoopHelper.AddContinuation(playerLoopTiming, continuation);
} }
public void UnsafeOnCompleted(Action continuation) public void UnsafeOnCompleted(Action continuation)
{ {
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.Update, continuation); PlayerLoopHelper.AddContinuation(playerLoopTiming, continuation);
}
}
}
public struct ReturnToMainThread
{
readonly PlayerLoopTiming playerLoopTiming;
public ReturnToMainThread(PlayerLoopTiming playerLoopTiming)
{
this.playerLoopTiming = playerLoopTiming;
}
public Awaiter DisposeAsync()
{
return new Awaiter(playerLoopTiming); // run immediate.
}
public readonly struct Awaiter : ICriticalNotifyCompletion
{
readonly PlayerLoopTiming timing;
public Awaiter(PlayerLoopTiming timing)
{
this.timing = timing;
}
public Awaiter GetAwaiter() => this;
public bool IsCompleted => PlayerLoopHelper.MainThreadId == System.Threading.Thread.CurrentThread.ManagedThreadId;
public void GetResult() { }
public void OnCompleted(Action continuation)
{
PlayerLoopHelper.AddContinuation(timing, continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
PlayerLoopHelper.AddContinuation(timing, continuation);
} }
} }
} }
@@ -92,12 +184,16 @@ namespace Cysharp.Threading.Tasks
public void OnCompleted(Action continuation) public void OnCompleted(Action continuation)
{ {
ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation); ThreadPool.QueueUserWorkItem(switchToCallback, continuation);
} }
public void UnsafeOnCompleted(Action continuation) public void UnsafeOnCompleted(Action continuation)
{ {
#if NETCOREAPP3_1
ThreadPool.UnsafeQueueUserWorkItem(ThreadPoolWorkItem.Create(continuation), false);
#else
ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation); ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation);
#endif
} }
static void Callback(object state) static void Callback(object state)
@@ -106,6 +202,47 @@ namespace Cysharp.Threading.Tasks
continuation(); continuation();
} }
} }
#if NETCOREAPP3_1
sealed class ThreadPoolWorkItem : IThreadPoolWorkItem, ITaskPoolNode<ThreadPoolWorkItem>
{
static TaskPool<ThreadPoolWorkItem> pool;
public ThreadPoolWorkItem NextNode { get; set; }
static ThreadPoolWorkItem()
{
TaskPool.RegisterSizeGetter(typeof(ThreadPoolWorkItem), () => pool.Size);
}
Action continuation;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ThreadPoolWorkItem Create(Action continuation)
{
if (!pool.TryPop(out var item))
{
item = new ThreadPoolWorkItem();
}
item.continuation = continuation;
return item;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute()
{
var call = continuation;
continuation = null;
if (call != null)
{
pool.TryPush(this);
call.Invoke();
}
}
}
#endif
} }
public struct SwitchToTaskPoolAwaitable public struct SwitchToTaskPoolAwaitable
@@ -178,5 +315,73 @@ namespace Cysharp.Threading.Tasks
} }
} }
} }
public struct ReturnToSynchronizationContext
{
readonly SynchronizationContext syncContext;
readonly bool dontPostWhenSameContext;
public ReturnToSynchronizationContext(SynchronizationContext syncContext, bool dontPostWhenSameContext)
{
this.syncContext = syncContext;
this.dontPostWhenSameContext = dontPostWhenSameContext;
} }
public Awaiter DisposeAsync()
{
return new Awaiter(syncContext, dontPostWhenSameContext);
}
public struct Awaiter : ICriticalNotifyCompletion
{
static readonly SendOrPostCallback switchToCallback = Callback;
readonly SynchronizationContext synchronizationContext;
readonly bool dontPostWhenSameContext;
public Awaiter(SynchronizationContext synchronizationContext, bool dontPostWhenSameContext)
{
this.synchronizationContext = synchronizationContext;
this.dontPostWhenSameContext = dontPostWhenSameContext;
}
public Awaiter GetAwaiter() => this;
public bool IsCompleted
{
get
{
if (!dontPostWhenSameContext) return false;
var current = SynchronizationContext.Current;
if (current == synchronizationContext)
{
return true;
}
else
{
return false;
}
}
}
public void GetResult() { }
public void OnCompleted(Action continuation)
{
synchronizationContext.Post(switchToCallback, continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
synchronizationContext.Post(switchToCallback, continuation);
}
static void Callback(object state)
{
var continuation = (Action)state;
continuation();
}
}
}
}

View File

@@ -35,9 +35,15 @@ namespace Cysharp.Threading.Tasks
: WaitUntilValueChangedStandardObjectPromise<T, U>.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, out token), token); : WaitUntilValueChangedStandardObjectPromise<T, U>.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, out token), token);
} }
sealed class WaitUntilPromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class WaitUntilPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitUntilPromise>
{ {
static readonly PromisePool<WaitUntilPromise> pool = new PromisePool<WaitUntilPromise>(); static TaskPool<WaitUntilPromise> pool;
public WaitUntilPromise NextNode { get; set; }
static WaitUntilPromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitUntilPromise), () => pool.Size);
}
Func<bool> predicate; Func<bool> predicate;
CancellationToken cancellationToken; CancellationToken cancellationToken;
@@ -55,7 +61,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new WaitUntilPromise(); if (!pool.TryPop(out var result))
{
result = new WaitUntilPromise();
}
result.predicate = predicate; result.predicate = predicate;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
@@ -72,12 +81,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -121,25 +129,33 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
predicate = default; predicate = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~WaitUntilPromise() ~WaitUntilPromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
} }
} }
sealed class WaitWhilePromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class WaitWhilePromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitWhilePromise>
{ {
static readonly PromisePool<WaitWhilePromise> pool = new PromisePool<WaitWhilePromise>(); static TaskPool<WaitWhilePromise> pool;
public WaitWhilePromise NextNode { get; set; }
static WaitWhilePromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitWhilePromise), () => pool.Size);
}
Func<bool> predicate; Func<bool> predicate;
CancellationToken cancellationToken; CancellationToken cancellationToken;
@@ -157,7 +173,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new WaitWhilePromise(); if (!pool.TryPop(out var result))
{
result = new WaitWhilePromise();
}
result.predicate = predicate; result.predicate = predicate;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
@@ -174,12 +193,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -223,25 +241,33 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
predicate = default; predicate = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~WaitWhilePromise() ~WaitWhilePromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
} }
} }
sealed class WaitUntilCanceledPromise : IUniTaskSource, IPlayerLoopItem, IPromisePoolItem sealed class WaitUntilCanceledPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitUntilCanceledPromise>
{ {
static readonly PromisePool<WaitUntilCanceledPromise> pool = new PromisePool<WaitUntilCanceledPromise>(); static TaskPool<WaitUntilCanceledPromise> pool;
public WaitUntilCanceledPromise NextNode { get; set; }
static WaitUntilCanceledPromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitUntilCanceledPromise), () => pool.Size);
}
CancellationToken cancellationToken; CancellationToken cancellationToken;
@@ -258,7 +284,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new WaitUntilCanceledPromise(); if (!pool.TryPop(out var result))
{
result = new WaitUntilCanceledPromise();
}
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
@@ -274,12 +303,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -309,15 +337,17 @@ namespace Cysharp.Threading.Tasks
return true; return true;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~WaitUntilCanceledPromise() ~WaitUntilCanceledPromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
@@ -325,9 +355,15 @@ namespace Cysharp.Threading.Tasks
} }
// where T : UnityEngine.Object, can not add constraint // where T : UnityEngine.Object, can not add constraint
sealed class WaitUntilValueChangedUnityObjectPromise<T, U> : IUniTaskSource<U>, IPlayerLoopItem, IPromisePoolItem sealed class WaitUntilValueChangedUnityObjectPromise<T, U> : IUniTaskSource<U>, IPlayerLoopItem, ITaskPoolNode<WaitUntilValueChangedUnityObjectPromise<T, U>>
{ {
static readonly PromisePool<WaitUntilValueChangedUnityObjectPromise<T, U>> pool = new PromisePool<WaitUntilValueChangedUnityObjectPromise<T, U>>(); static TaskPool<WaitUntilValueChangedUnityObjectPromise<T, U>> pool;
public WaitUntilValueChangedUnityObjectPromise<T, U> NextNode { get; set; }
static WaitUntilValueChangedUnityObjectPromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitUntilValueChangedUnityObjectPromise<T, U>), () => pool.Size);
}
T target; T target;
UnityEngine.Object targetAsUnityObject; UnityEngine.Object targetAsUnityObject;
@@ -349,7 +385,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource<U>.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource<U>.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new WaitUntilValueChangedUnityObjectPromise<T, U>(); if (!pool.TryPop(out var result))
{
result = new WaitUntilValueChangedUnityObjectPromise<T, U>();
}
result.target = target; result.target = target;
result.targetAsUnityObject = target as UnityEngine.Object; result.targetAsUnityObject = target as UnityEngine.Object;
@@ -370,12 +409,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
return core.GetResult(token); return core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -426,29 +464,37 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
target = default; target = default;
currentValue = default; currentValue = default;
monitorFunction = default; monitorFunction = default;
equalityComparer = default; equalityComparer = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~WaitUntilValueChangedUnityObjectPromise() ~WaitUntilValueChangedUnityObjectPromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }
} }
} }
sealed class WaitUntilValueChangedStandardObjectPromise<T, U> : IUniTaskSource<U>, IPlayerLoopItem, IPromisePoolItem sealed class WaitUntilValueChangedStandardObjectPromise<T, U> : IUniTaskSource<U>, IPlayerLoopItem, ITaskPoolNode<WaitUntilValueChangedStandardObjectPromise<T, U>>
where T : class where T : class
{ {
static readonly PromisePool<WaitUntilValueChangedStandardObjectPromise<T, U>> pool = new PromisePool<WaitUntilValueChangedStandardObjectPromise<T, U>>(); static TaskPool<WaitUntilValueChangedStandardObjectPromise<T, U>> pool;
public WaitUntilValueChangedStandardObjectPromise<T, U> NextNode { get; set; }
static WaitUntilValueChangedStandardObjectPromise()
{
TaskPool.RegisterSizeGetter(typeof(WaitUntilValueChangedStandardObjectPromise<T, U>), () => pool.Size);
}
WeakReference<T> target; WeakReference<T> target;
U currentValue; U currentValue;
@@ -469,7 +515,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource<U>.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource<U>.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new WaitUntilValueChangedStandardObjectPromise<T, U>(); if (!pool.TryPop(out var result))
{
result = new WaitUntilValueChangedStandardObjectPromise<T, U>();
}
result.target = new WeakReference<T>(target, false); // wrap in WeakReference. result.target = new WeakReference<T>(target, false); // wrap in WeakReference.
result.monitorFunction = monitorFunction; result.monitorFunction = monitorFunction;
@@ -489,12 +538,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
return core.GetResult(token); return core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -545,19 +593,21 @@ namespace Cysharp.Threading.Tasks
return false; return false;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
target = default; target = default;
currentValue = default; currentValue = default;
monitorFunction = default; monitorFunction = default;
equalityComparer = default; equalityComparer = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~WaitUntilValueChangedStandardObjectPromise() ~WaitUntilValueChangedStandardObjectPromise()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }

View File

@@ -1,22 +1,22 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
#pragma warning disable CS0436 #pragma warning disable CS0436
using Cysharp.Threading.Tasks.CompilerServices;
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using Cysharp.Threading.Tasks.CompilerServices; using System.Runtime.InteropServices;
using Cysharp.Threading.Tasks.Internal;
namespace Cysharp.Threading.Tasks namespace Cysharp.Threading.Tasks
{ {
internal static class AwaiterActions internal static class AwaiterActions
{ {
internal static readonly Action<object> InvokeActionDelegate = InvokeAction; internal static readonly Action<object> InvokeContinuationDelegate = Continuation;
[DebuggerHidden] [DebuggerHidden]
static void InvokeAction(object state) [MethodImpl(MethodImplOptions.AggressiveInlining)]
static void Continuation(object state)
{ {
((Action)state).Invoke(); ((Action)state).Invoke();
} }
@@ -26,6 +26,7 @@ namespace Cysharp.Threading.Tasks
/// Lightweight unity specified task-like object. /// Lightweight unity specified task-like object.
/// </summary> /// </summary>
[AsyncMethodBuilder(typeof(AsyncUniTaskMethodBuilder))] [AsyncMethodBuilder(typeof(AsyncUniTaskMethodBuilder))]
[StructLayout(LayoutKind.Auto)]
public readonly partial struct UniTask public readonly partial struct UniTask
{ {
readonly IUniTaskSource source; readonly IUniTaskSource source;
@@ -68,6 +69,24 @@ namespace Cysharp.Threading.Tasks
return new UniTask<bool>(new IsCanceledSource(source), token); return new UniTask<bool>(new IsCanceledSource(source), token);
} }
#if !UNITY_2018_3_OR_NEWER
public static implicit operator System.Threading.Tasks.ValueTask(in UniTask self)
{
if (self.source == null)
{
return default;
}
#if NETSTANDARD2_0
return self.AsValueTask();
#else
return new System.Threading.Tasks.ValueTask(self.source, self.token);
#endif
}
#endif
public override string ToString() public override string ToString()
{ {
if (source == null) return "()"; if (source == null) return "()";
@@ -298,7 +317,7 @@ namespace Cysharp.Threading.Tasks
} }
else else
{ {
task.source.OnCompleted(AwaiterActions.InvokeActionDelegate, continuation, task.token); task.source.OnCompleted(AwaiterActions.InvokeContinuationDelegate, continuation, task.token);
} }
} }
@@ -312,7 +331,7 @@ namespace Cysharp.Threading.Tasks
} }
else else
{ {
task.source.OnCompleted(AwaiterActions.InvokeActionDelegate, continuation, task.token); task.source.OnCompleted(AwaiterActions.InvokeContinuationDelegate, continuation, task.token);
} }
} }
@@ -339,6 +358,7 @@ namespace Cysharp.Threading.Tasks
/// Lightweight unity specified task-like object. /// Lightweight unity specified task-like object.
/// </summary> /// </summary>
[AsyncMethodBuilder(typeof(AsyncUniTaskMethodBuilder<>))] [AsyncMethodBuilder(typeof(AsyncUniTaskMethodBuilder<>))]
[StructLayout(LayoutKind.Auto)]
public readonly struct UniTask<T> public readonly struct UniTask<T>
{ {
readonly IUniTaskSource<T> source; readonly IUniTaskSource<T> source;
@@ -414,6 +434,24 @@ namespace Cysharp.Threading.Tasks
return self.AsUniTask(); return self.AsUniTask();
} }
#if !UNITY_2018_3_OR_NEWER
public static implicit operator System.Threading.Tasks.ValueTask<T>(in UniTask<T> self)
{
if (self.source == null)
{
return new System.Threading.Tasks.ValueTask<T>(self.result);
}
#if NETSTANDARD2_0
return self.AsValueTask();
#else
return new System.Threading.Tasks.ValueTask<T>(self.source, self.token);
#endif
}
#endif
/// <summary> /// <summary>
/// returns (bool IsCanceled, T Result) instead of throws OperationCanceledException. /// returns (bool IsCanceled, T Result) instead of throws OperationCanceledException.
/// </summary> /// </summary>
@@ -621,7 +659,7 @@ namespace Cysharp.Threading.Tasks
} }
else else
{ {
s.OnCompleted(AwaiterActions.InvokeActionDelegate, continuation, task.token); s.OnCompleted(AwaiterActions.InvokeContinuationDelegate, continuation, task.token);
} }
} }
@@ -636,7 +674,7 @@ namespace Cysharp.Threading.Tasks
} }
else else
{ {
s.OnCompleted(AwaiterActions.InvokeActionDelegate, continuation, task.token); s.OnCompleted(AwaiterActions.InvokeContinuationDelegate, continuation, task.token);
} }
} }

View File

@@ -271,7 +271,7 @@ namespace Cysharp.Threading.Tasks
{ {
if (token != version) if (token != version)
{ {
throw new InvalidOperationException("token version is not matched, can not await twice."); throw new InvalidOperationException("Token version is not matched, can not await twice or get Status after await.");
} }
} }
} }
@@ -375,9 +375,15 @@ namespace Cysharp.Threading.Tasks
} }
} }
public class AutoResetUniTaskCompletionSource : IUniTaskSource, IPromisePoolItem, IPromise public class AutoResetUniTaskCompletionSource : IUniTaskSource, ITaskPoolNode<AutoResetUniTaskCompletionSource>, IPromise
{ {
static readonly PromisePool<AutoResetUniTaskCompletionSource> pool = new PromisePool<AutoResetUniTaskCompletionSource>(); static TaskPool<AutoResetUniTaskCompletionSource> pool;
public AutoResetUniTaskCompletionSource NextNode { get; set; }
static AutoResetUniTaskCompletionSource()
{
TaskPool.RegisterSizeGetter(typeof(AutoResetUniTaskCompletionSource), () => pool.Size);
}
UniTaskCompletionSourceCore<AsyncUnit> core; UniTaskCompletionSourceCore<AsyncUnit> core;
@@ -388,9 +394,12 @@ namespace Cysharp.Threading.Tasks
[DebuggerHidden] [DebuggerHidden]
public static AutoResetUniTaskCompletionSource Create() public static AutoResetUniTaskCompletionSource Create()
{ {
var value = pool.TryRent() ?? new AutoResetUniTaskCompletionSource(); if (!pool.TryPop(out var result))
TaskTracker.TrackActiveTask(value, 2); {
return value; result = new AutoResetUniTaskCompletionSource();
}
TaskTracker.TrackActiveTask(result, 2);
return result;
} }
[DebuggerHidden] [DebuggerHidden]
@@ -452,12 +461,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
core.GetResult(token); core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -481,14 +489,16 @@ namespace Cysharp.Threading.Tasks
} }
[DebuggerHidden] [DebuggerHidden]
void IPromisePoolItem.Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
return pool.TryPush(this);
} }
~AutoResetUniTaskCompletionSource() ~AutoResetUniTaskCompletionSource()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
return; return;
@@ -591,9 +601,15 @@ namespace Cysharp.Threading.Tasks
} }
} }
public class AutoResetUniTaskCompletionSource<T> : IUniTaskSource<T>, IPromisePoolItem, IPromise<T> public class AutoResetUniTaskCompletionSource<T> : IUniTaskSource<T>, ITaskPoolNode<AutoResetUniTaskCompletionSource<T>>, IPromise<T>
{ {
static readonly PromisePool<AutoResetUniTaskCompletionSource<T>> pool = new PromisePool<AutoResetUniTaskCompletionSource<T>>(); static TaskPool<AutoResetUniTaskCompletionSource<T>> pool;
public AutoResetUniTaskCompletionSource<T> NextNode { get; set; }
static AutoResetUniTaskCompletionSource()
{
TaskPool.RegisterSizeGetter(typeof(AutoResetUniTaskCompletionSource<T>), () => pool.Size);
}
UniTaskCompletionSourceCore<T> core; UniTaskCompletionSourceCore<T> core;
@@ -604,7 +620,10 @@ namespace Cysharp.Threading.Tasks
[DebuggerHidden] [DebuggerHidden]
public static AutoResetUniTaskCompletionSource<T> Create() public static AutoResetUniTaskCompletionSource<T> Create()
{ {
var result = pool.TryRent() ?? new AutoResetUniTaskCompletionSource<T>(); if (!pool.TryPop(out var result))
{
result = new AutoResetUniTaskCompletionSource<T>();
}
TaskTracker.TrackActiveTask(result, 2); TaskTracker.TrackActiveTask(result, 2);
return result; return result;
} }
@@ -668,12 +687,11 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
return core.GetResult(token); return core.GetResult(token);
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -702,14 +720,17 @@ namespace Cysharp.Threading.Tasks
} }
[DebuggerHidden] [DebuggerHidden]
void IPromisePoolItem.Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
return pool.TryPush(this);
} }
~AutoResetUniTaskCompletionSource() ~AutoResetUniTaskCompletionSource()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }

View File

@@ -571,7 +571,7 @@ namespace Cysharp.Threading.Tasks
#if UNITY_2018_3_OR_NEWER #if UNITY_2018_3_OR_NEWER
class ToCoroutineEnumerator : IEnumerator sealed class ToCoroutineEnumerator : IEnumerator
{ {
bool completed; bool completed;
UniTask task; UniTask task;
@@ -621,20 +621,19 @@ namespace Cysharp.Threading.Tasks
if (exception != null) if (exception != null)
{ {
// throw exception on iterator (main)thread. exception.Throw();
// unfortunately unity test-runner can not handle throw exception on hand-write IEnumerator.MoveNext. return false;
UnityEngine.Debug.LogException(exception.SourceException);
} }
return !completed; return !completed;
} }
public void Reset() void IEnumerator.Reset()
{ {
} }
} }
class ToCoroutineEnumerator<T> : IEnumerator sealed class ToCoroutineEnumerator<T> : IEnumerator
{ {
bool completed; bool completed;
Action<T> resultHandler = null; Action<T> resultHandler = null;
@@ -692,15 +691,14 @@ namespace Cysharp.Threading.Tasks
if (exception != null) if (exception != null)
{ {
// throw exception on iterator (main)thread. exception.Throw();
// unfortunately unity test-runner can not handle throw exception on hand-write IEnumerator.MoveNext. return false;
UnityEngine.Debug.LogException(exception.SourceException);
} }
return !completed; return !completed;
} }
public void Reset() void IEnumerator.Reset()
{ {
} }
} }

View File

@@ -9,7 +9,7 @@ namespace Cysharp.Threading.Tasks
{ {
public static class UniTaskObservableExtensions public static class UniTaskObservableExtensions
{ {
public static UniTask<T> ToUniTask<T>(this IObservable<T> source, CancellationToken cancellationToken = default(CancellationToken), bool useFirstValue = false) public static UniTask<T> ToUniTask<T>(this IObservable<T> source, bool useFirstValue = false, CancellationToken cancellationToken = default)
{ {
var promise = new UniTaskCompletionSource<T>(); var promise = new UniTaskCompletionSource<T>();
var disposable = new SingleAssignmentDisposable(); var disposable = new SingleAssignmentDisposable();

View File

@@ -9,41 +9,11 @@ using Cysharp.Threading.Tasks.CompilerServices;
namespace Cysharp.Threading.Tasks namespace Cysharp.Threading.Tasks
{ {
[AsyncMethodBuilder(typeof(AsyncUniTaskVoidMethodBuilder))] [AsyncMethodBuilder(typeof(AsyncUniTaskVoidMethodBuilder))]
public struct UniTaskVoid public readonly struct UniTaskVoid
{ {
public void Forget() public void Forget()
{ {
} }
[DebuggerHidden]
public Awaiter GetAwaiter()
{
return new Awaiter();
}
public struct Awaiter : ICriticalNotifyCompletion
{
[DebuggerHidden]
public bool IsCompleted => true;
[DebuggerHidden]
public void GetResult()
{
#if UNITY_2018_3_OR_NEWER
UnityEngine.Debug.LogWarning("UniTaskVoid can't await, always fire-and-forget. use Forget instead of await.");
#endif
}
[DebuggerHidden]
public void OnCompleted(Action continuation)
{
}
[DebuggerHidden]
public void UnsafeOnCompleted(Action continuation)
{
}
}
} }
} }

View File

@@ -9,10 +9,11 @@ namespace Cysharp.Threading.Tasks
{ {
public static partial class UnityAsyncExtensions public static partial class UnityAsyncExtensions
{ {
public static async UniTask WaitAsync(this JobHandle jobHandle, PlayerLoopTiming waitTiming) public static async UniTask WaitAsync(this JobHandle jobHandle, PlayerLoopTiming waitTiming, CancellationToken cancellationToken = default)
{ {
await UniTask.Yield(waitTiming); await UniTask.Yield(waitTiming);
jobHandle.Complete(); jobHandle.Complete();
cancellationToken.ThrowIfCancellationRequested(); // call cancel after Complete.
} }
public static UniTask.Awaiter GetAwaiter(this JobHandle jobHandle) public static UniTask.Awaiter GetAwaiter(this JobHandle jobHandle)
@@ -29,6 +30,8 @@ namespace Cysharp.Threading.Tasks
return new UniTask(handler, token).GetAwaiter(); return new UniTask(handler, token).GetAwaiter();
} }
// can not pass CancellationToken because can't handle JobHandle's Complete and NativeArray.Dispose.
public static UniTask ToUniTask(this JobHandle jobHandle, PlayerLoopTiming waitTiming) public static UniTask ToUniTask(this JobHandle jobHandle, PlayerLoopTiming waitTiming)
{ {
var handler = JobHandlePromise.Create(jobHandle, out var token); var handler = JobHandlePromise.Create(jobHandle, out var token);

View File

@@ -45,18 +45,18 @@ namespace Cysharp.Threading.Tasks
return new <#= t.typeName #>Awaiter(asyncOperation); return new <#= t.typeName #>Awaiter(asyncOperation);
} }
public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation) public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, CancellationToken cancellationToken)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>;
return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, PlayerLoopTiming.Update, null, CancellationToken.None, out var token), token); return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>WithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token);
} }
public static <#= ToUniTaskReturnType(t.returnType) #> ConfigureAwait(this <#= t.typeName #> asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellation = default(CancellationToken)) public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>;
return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, timing, progress, cancellation, out var token), token); return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token);
} }
public struct <#= t.typeName #>Awaiter : ICriticalNotifyCompletion public struct <#= t.typeName #>Awaiter : ICriticalNotifyCompletion
@@ -106,14 +106,153 @@ namespace Cysharp.Threading.Tasks
public void UnsafeOnCompleted(Action continuation) public void UnsafeOnCompleted(Action continuation)
{ {
Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction); Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction);
continuationAction = continuation.AsFuncOfT<AsyncOperation>(); // allocate delegate. continuationAction = PooledDelegate<AsyncOperation>.Create(continuation);
asyncOperation.completed += continuationAction; asyncOperation.completed += continuationAction;
} }
} }
class <#= t.typeName #>ConfiguredSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, IPromisePoolItem sealed class <#= t.typeName #>WithCancellationSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>WithCancellationSource>
{ {
static readonly PromisePool<<#= t.typeName #>ConfiguredSource> pool = new PromisePool<<#= t.typeName #>ConfiguredSource>(); static TaskPool<<#= t.typeName #>WithCancellationSource> pool;
public <#= t.typeName #>WithCancellationSource NextNode { get; set; }
static <#= t.typeName #>WithCancellationSource()
{
TaskPool.RegisterSizeGetter(typeof(<#= t.typeName #>WithCancellationSource), () => pool.Size);
}
readonly Action<AsyncOperation> continuationAction;
<#= t.typeName #> asyncOperation;
CancellationToken cancellationToken;
bool completed;
UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core;
<#= t.typeName #>WithCancellationSource()
{
continuationAction = Continuation;
}
public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, CancellationToken cancellationToken, out short token)
{
if (cancellationToken.IsCancellationRequested)
{
return AutoResetUniTaskCompletionSource<#= IsVoid(t) ? "" : $"<{t.returnType}>" #>.CreateFromCanceled(cancellationToken, out token);
}
if (!pool.TryPop(out var result))
{
result = new <#= t.typeName #>WithCancellationSource();
}
result.asyncOperation = asyncOperation;
result.cancellationToken = cancellationToken;
result.completed = false;
TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result);
asyncOperation.completed += result.continuationAction;
token = result.core.Version;
return result;
}
void Continuation(AsyncOperation _)
{
asyncOperation.completed -= continuationAction;
if (completed)
{
TryReturn();
}
else
{
completed = true;
core.TrySetResult(<#= IsVoid(t) ? "AsyncUnit.Default" : $"asyncOperation.{t.returnField}" #>);
}
}
public <#= t.returnType #> GetResult(short token)
{
<# if (!IsVoid(t)) { #>
return core.GetResult(token);
<# } else { #>
core.GetResult(token);
<# } #>
}
<# if (!IsVoid(t)) { #>
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()
{
if (completed)
{
TryReturn();
return false;
}
if (cancellationToken.IsCancellationRequested)
{
completed = true;
<# if(t.returnType == "UnityWebRequest") { #>
asyncOperation.webRequest.Abort();
<# } #>
core.TrySetCanceled(cancellationToken);
return false;
}
return true;
}
bool TryReturn()
{
TaskTracker.RemoveTracking(this);
core.Reset();
asyncOperation = default;
cancellationToken = default;
return pool.TryPush(this);
}
~<#= t.typeName #>WithCancellationSource()
{
if (TryReturn())
{
GC.ReRegisterForFinalize(this);
}
}
}
sealed class <#= t.typeName #>ConfiguredSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>ConfiguredSource>
{
static TaskPool<<#= t.typeName #>ConfiguredSource> pool;
public <#= t.typeName #>ConfiguredSource NextNode { get; set; }
static <#= t.typeName #>ConfiguredSource()
{
TaskPool.RegisterSizeGetter(typeof(<#= t.typeName #>ConfiguredSource), () => pool.Size);
}
<#= t.typeName #> asyncOperation; <#= t.typeName #> asyncOperation;
IProgress<float> progress; IProgress<float> progress;
@@ -133,7 +272,10 @@ namespace Cysharp.Threading.Tasks
return AutoResetUniTaskCompletionSource<#= IsVoid(t) ? "" : $"<{t.returnType}>" #>.CreateFromCanceled(cancellationToken, out token); return AutoResetUniTaskCompletionSource<#= IsVoid(t) ? "" : $"<{t.returnType}>" #>.CreateFromCanceled(cancellationToken, out token);
} }
var result = pool.TryRent() ?? new <#= t.typeName #>ConfiguredSource(); if (!pool.TryPop(out var result))
{
result = new <#= t.typeName #>ConfiguredSource();
}
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
@@ -151,8 +293,6 @@ namespace Cysharp.Threading.Tasks
{ {
try try
{ {
TaskTracker.RemoveTracking(this);
<# if (!IsVoid(t)) { #> <# if (!IsVoid(t)) { #>
return core.GetResult(token); return core.GetResult(token);
<# } else { #> <# } else { #>
@@ -161,7 +301,7 @@ namespace Cysharp.Threading.Tasks
} }
finally finally
{ {
pool.TryReturn(this); TryReturn();
} }
} }
@@ -191,6 +331,9 @@ namespace Cysharp.Threading.Tasks
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
<# if(t.returnType == "UnityWebRequest") { #>
asyncOperation.webRequest.Abort();
<# } #>
core.TrySetCanceled(cancellationToken); core.TrySetCanceled(cancellationToken);
return false; return false;
} }
@@ -209,17 +352,19 @@ namespace Cysharp.Threading.Tasks
return true; return true;
} }
public void Reset() bool TryReturn()
{ {
TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
return pool.TryPush(this);
} }
~<#= t.typeName #>ConfiguredSource() ~<#= t.typeName #>ConfiguredSource()
{ {
if (pool.TryReturn(this)) if (TryReturn())
{ {
GC.ReRegisterForFinalize(this); GC.ReRegisterForFinalize(this);
} }

View File

@@ -1,6 +1,5 @@
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using Cysharp.Threading.Tasks.Linq;
using System; using System;
using System.Threading; using System.Threading;
using UnityEngine; using UnityEngine;

View File

@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("UniTask.Linq")]

View File

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

View File

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

View File

@@ -21,13 +21,15 @@ public class ExceptionExamples : MonoBehaviour
private void Start() private void Start()
{ {
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; UnityEngine.Debug.Log("ExceptionScene, LoopType:" + PlayerLoopInfo.CurrentLoopType + ":" + Time.frameCount);
ThrowFromAsyncVoid(); //TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
_ = ThrowFromTask();
_ = ThrowFromUniTask();
ThrowFromNonAsync(); //ThrowFromAsyncVoid();
//_ = ThrowFromTask();
//_ = ThrowFromUniTask();
//ThrowFromNonAsync();
} }
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)

View File

@@ -1,4 +1,5 @@
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using System.Linq;
using Cysharp.Threading.Tasks.Linq; using Cysharp.Threading.Tasks.Linq;
using Cysharp.Threading.Tasks.Triggers; using Cysharp.Threading.Tasks.Triggers;
using System; using System;
@@ -10,7 +11,10 @@ using System.Threading.Tasks;
using Unity.Collections; using Unity.Collections;
using Unity.Jobs; using Unity.Jobs;
using UnityEngine; using UnityEngine;
using UnityEngine.LowLevel;
using UnityEngine.Networking;
using UnityEngine.UI; using UnityEngine.UI;
using UnityEngine.SceneManagement;
// using DG.Tweening; // using DG.Tweening;
@@ -192,6 +196,8 @@ public class SandboxMain : MonoBehaviour
async UniTask RunJobAsync() async UniTask RunJobAsync()
{ {
var job = new MyJob() { loopCount = 999, inOut = new NativeArray<int>(1, Allocator.TempJob) }; var job = new MyJob() { loopCount = 999, inOut = new NativeArray<int>(1, Allocator.TempJob) };
try
{
JobHandle.ScheduleBatchedJobs(); JobHandle.ScheduleBatchedJobs();
var scheduled = job.Schedule(); var scheduled = job.Schedule();
@@ -199,9 +205,12 @@ public class SandboxMain : MonoBehaviour
UnityEngine.Debug.Log("OK"); UnityEngine.Debug.Log("OK");
await scheduled; // .ConfigureAwait(PlayerLoopTiming.Update); // .WaitAsync(PlayerLoopTiming.Update); await scheduled; // .ConfigureAwait(PlayerLoopTiming.Update); // .WaitAsync(PlayerLoopTiming.Update);
UnityEngine.Debug.Log("OK2"); UnityEngine.Debug.Log("OK2");
}
finally
{
job.inOut.Dispose(); job.inOut.Dispose();
} }
}
async UniTaskVoid Update2() async UniTaskVoid Update2()
@@ -243,9 +252,256 @@ public class SandboxMain : MonoBehaviour
public int MyProperty { get; set; } public int MyProperty { get; set; }
} }
async Task Test1()
{
var r = await TcsAsync("https://bing.com/");
Debug.Log("TASKASYNC");
}
async UniTaskVoid Test2()
{
try
{
//var cts = new CancellationTokenSource();
//var r = UniAsync("https://bing.com/", cts.Token);
//cts.Cancel();
//await r;
Debug.Log("SendWebRequestDone:" + PlayerLoopInfo.CurrentLoopType);
// var foo = await UnityWebRequest.Get("https://bing.com/").SendWebRequest();
// foo.downloadHandler.text;
//
_ = await UnityWebRequest.Get("https://bing.com/").SendWebRequest().WithCancellation(CancellationToken.None);
Debug.Log("SendWebRequestWithCancellationDone:" + PlayerLoopInfo.CurrentLoopType);
}
catch
{
Debug.Log("Canceled");
}
}
IEnumerator Test3(string url)
{
var req = UnityWebRequest.Get(url).SendWebRequest();
yield return req;
Debug.Log("COROUTINE");
}
static async Task<UnityWebRequest> TcsAsync(string url)
{
var req = await UnityWebRequest.Get(url).SendWebRequest();
return req;
}
static async UniTask<UnityWebRequest> UniAsync(string url, CancellationToken cancellationToken)
{
var req = await UnityWebRequest.Get(url).SendWebRequest().WithCancellation(cancellationToken);
return req;
}
async Task<int> Test()
{
await Task.Yield();
return 10;
}
async UniTask<int> Ex()
{
await UniTask.Yield();
//throw new Exception();
await UniTask.Delay(TimeSpan.FromSeconds(15));
return 0;
}
IEnumerator CoroutineRun()
{
UnityEngine.Debug.Log("Before Coroutine yield return null," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
yield return null;
UnityEngine.Debug.Log("After Coroutine yield return null," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
}
IEnumerator CoroutineRun2()
{
UnityEngine.Debug.Log("Before Coroutine yield return WaitForEndOfFrame," + Time.frameCount);
yield return new WaitForEndOfFrame();
UnityEngine.Debug.Log("After Coroutine yield return WaitForEndOfFrame," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
yield return new WaitForEndOfFrame();
UnityEngine.Debug.Log("Onemore After Coroutine yield return WaitForEndOfFrame," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
}
async UniTaskVoid AsyncRun()
{
UnityEngine.Debug.Log("Before async Yield(default)," + Time.frameCount);
await UniTask.Yield();
UnityEngine.Debug.Log("After async Yield(default)," + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
}
async UniTaskVoid AsyncLastUpdate()
{
UnityEngine.Debug.Log("Before async Yield(LastUpdate)," + Time.frameCount);
await UniTask.Yield(PlayerLoopTiming.LastUpdate);
UnityEngine.Debug.Log("After async Yield(LastUpdate)," + Time.frameCount);
}
async UniTaskVoid AsyncLastLast()
{
UnityEngine.Debug.Log("Before async Yield(LastPostLateUpdate)," + Time.frameCount);
await UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate);
UnityEngine.Debug.Log("After async Yield(LastPostLateUpdate)," + Time.frameCount);
}
async UniTaskVoid Yieldding()
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
StartCoroutine(CoroutineRun());
}
async UniTaskVoid AsyncFixedUpdate()
{
while (true)
{
await UniTask.WaitForFixedUpdate();
Debug.Log("Async:" + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
}
}
IEnumerator CoroutineFixedUpdate()
{
while (true)
{
yield return new WaitForFixedUpdate();
Debug.Log("Coroutine:" + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
}
}
private void FixedUpdate()
{
// Debug.Log("FixedUpdate:" + Time.frameCount + ", " + PlayerLoopInfo.CurrentLoopType);
}
async UniTaskVoid DelayFrame3_Pre()
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
Debug.Log("Before framecount:" + Time.frameCount);
await UniTask.DelayFrame(3);
Debug.Log("After framecount:" + Time.frameCount);
}
async UniTaskVoid DelayFrame3_Post()
{
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
Debug.Log("Before framecount:" + Time.frameCount);
await UniTask.DelayFrame(3);
Debug.Log("After framecount:" + Time.frameCount);
}
async UniTask TestCoroutine()
{
await UniTask.Yield();
throw new Exception("foobarbaz");
}
async UniTask DelayCheck()
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
Debug.Log("before");
var t = UniTask.Delay(TimeSpan.FromSeconds(1), ignoreTimeScale: false);
await t;
Debug.Log("after");
}
void Start() void Start()
{ {
PlayerLoopInfo.Inject();
//_ = AsyncFixedUpdate();
//StartCoroutine(CoroutineFixedUpdate());
//StartCoroutine(TestCoroutine().ToCoroutine());
Application.logMessageReceived += Application_logMessageReceived;
okButton.onClick.AddListener(UniTask.UnityAction(async () =>
{
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
Debug.Log("------------------");
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
//await DelayCheck();
/*
UnityEngine.Debug.Log("click:" + PlayerLoopInfo.CurrentLoopType);
StartCoroutine(CoroutineRun());
StartCoroutine(CoroutineRun2());
_ = AsyncRun();
_ = AsyncLastUpdate();
_ = AsyncLastLast();
*/
//await UniTask.Yield();
//_ = Test2();
// EarlyUpdate.ExecuteMainThreadJobs
// _ = Test2();
//var t = await Resources.LoadAsync<TextAsset>(Application.streamingAssetsPath + "test.txt");
//Debug.Log("LoadEnd" + PlayerLoopInfo.CurrentLoopType + ", " + (t != null));
//Debug.Log("LoadEnd" + PlayerLoopInfo.CurrentLoopType + ", " + ((TextAsset)t).text);
//await UniTask.Yield(PlayerLoopTiming.LastUpdate);
//UnityEngine.Debug.Log("after update:" + Time.frameCount);
////await UniTask.NextFrame();
////await UniTask.Yield();
////UnityEngine.Debug.Log("after update nextframe:" + Time.frameCount);
//StartCoroutine(CoroutineRun2());
////StartCoroutine(CoroutineRun());
//UnityEngine.Debug.Log("FOO?");
//_ = DelayFrame3_Pre();
//await UniTask.Yield();
}));
cancelButton.onClick.AddListener(UniTask.UnityAction(async () =>
{
_ = DelayFrame3_Post();
await UniTask.Yield();
//await UniTask.Yield(PlayerLoopTiming.LastPreUpdate);
//UnityEngine.Debug.Log("before update:" + Time.frameCount);
//await UniTask.NextFrame();
//await UniTask.Yield();
//UnityEngine.Debug.Log("before update nextframe:" + Time.frameCount);
//StartCoroutine(CoroutineRun());
//UnityEngine.Debug.Log("click:" + PlayerLoopInfo.CurrentLoopType);
//_ = Yieldding();
//var cts = new CancellationTokenSource();
//UnityEngine.Debug.Log("click:" + PlayerLoopInfo.CurrentLoopType + ":" + Time.frameCount);
//var la = SceneManager.LoadSceneAsync("Scenes/ExceptionExamples").WithCancellation(cts.Token);
////cts.Cancel();
//await la;
//UnityEngine.Debug.Log("End LoadSceneAsync" + PlayerLoopInfo.CurrentLoopType + ":" + Time.frameCount);
}));
//return;
//await UniTask.SwitchToMainThread();
//UniTaskAsyncEnumerable.EveryValueChanged(mcc, x => x.MyProperty) //UniTaskAsyncEnumerable.EveryValueChanged(mcc, x => x.MyProperty)
// .Do(_ => { }, () => Debug.Log("COMPLETED")) // .Do(_ => { }, () => Debug.Log("COMPLETED"))
// .ForEachAsync(x => // .ForEachAsync(x =>
@@ -254,53 +510,190 @@ public class SandboxMain : MonoBehaviour
// }) // })
// .Forget(); // .Forget();
//_ = Test1();
//Test2().Forget();
//StartCoroutine(Test3("https://bing.com/"));
//bool flip = false;
//var rect = cancelButton.GetComponent<RectTransform>();
//var cts = new CancellationTokenSource();
//var ct = cts.Token;
//okButton.onClick.AddListener(UniTask.UnityAction(async () =>
//{
// await rect.DOMoveX(10f * (flip ? -1 : 1), 3).OnUpdate(() => { Debug.Log("UPDATE YEAH"); }).WithCancellation(ct);
// flip = !flip;
// // ok.
//}));
//cancelButton.onClick.AddListener(() =>
//{
// cts.Cancel();
//});
// DG.Tweening.Core.TweenerCore<int> // DG.Tweening.Core.TweenerCore<int>
//okButton.GetComponent<RectTransform>().DOMoveX(10.2f, 30); //Debug.Log("GO MOVEX");
//await okButton.GetComponent<RectTransform>().DOMoveX(-10.2f, 3).WithCancellation(CancellationToken.None);
//Debug.Log("END MOVEX");
// DOTween.To( //Debug.Log("AGAIN MOVE");
//await okButton.GetComponent<RectTransform>().DOMoveY(10.2f, 3).WithCancellation(CancellationToken.None);
//Debug.Log("AGAIN END MOVE");
var cts = new CancellationTokenSource(); //Debug.Log(Test().GetType().FullName);
//var tween = okButton.GetComponent<RectTransform>().DOLocalMoveX(100, 5.0f);
cancelButton.OnClickAsAsyncEnumerable().ForEachAsync(_ =>
// check stacktrace
// await UniTaskAsyncEnumerable.EveryUpdate().Where((x, i) => i % 2 == 0).Select(x => x).DistinctUntilChanged().ForEachAsync(x =>
//{
// Debug.Log("test");
//});
//// DOTween.To(
//var cts = new CancellationTokenSource();
////var tween = okButton.GetComponent<RectTransform>().DOLocalMoveX(100, 5.0f);
//cancelButton.OnClickAsAsyncEnumerable().ForEachAsync(_ =>
//{
// cts.Cancel();
//}).Forget();
//// await tween.ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, cts.Token);
////tween.SetRecyclable(true);
//Debug.Log("END");
//// tween.Play();
//// DOTween.
//// DOVirtual.Float(0, 1, 1, x => { }).ToUniTask();
//await foreach (var _ in UniTaskAsyncEnumerable.EveryUpdate())
//{
// Debug.Log("Update() " + Time.frameCount);
//}
//await okButton.OnClickAsAsyncEnumerable().Where((x, i) => i % 2 == 0).ForEachAsync(_ =>
//{
//});
//okButton.OnClickAsAsyncEnumerable().ForEachAsync(_ =>
//{
//foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
//{
// Debug.Log(type + ":" + size);
//}
//}).Forget();
//CloseAsync(this.GetCancellationTokenOnDestroy()).Forget();
//okButton.onClick.AddListener(UniTask.UnityAction(async () => await UniTask.Yield()));
//UpdateUniTask().Forget();
//StartCoroutine(Coroutine());
//await UniTask.Delay(TimeSpan.FromSeconds(1));
// _ = ReturnToMainThreadTest();
//GameObject.Destroy(this.gameObject);
}
private void Application_logMessageReceived2(string condition, string stackTrace, LogType type)
{ {
cts.Cancel(); throw new NotImplementedException();
}).Forget(); }
private void Application_logMessageReceived1(string condition, string stackTrace, LogType type)
// await tween.ToUniTask(TweenCancelBehaviour.KillAndCancelAwait, cts.Token);
//tween.SetRecyclable(true);
Debug.Log("END");
// tween.Play();
// DOTween.
// DOVirtual.Float(0, 1, 1, x => { }).ToUniTask();
okButton.OnClickAsAsyncEnumerable().ForEachAsync(_ =>
{ {
throw new NotImplementedException();
}
async UniTaskVoid UpdateUniTask()
{
while (true)
{
await UniTask.Yield();
UnityEngine.Debug.Log("UniTaskYield:" + PlayerLoopInfo.CurrentLoopType);
}
}
async UniTaskVoid ReturnToMainThreadTest()
{
var d = UniTask.ReturnToCurrentSynchronizationContext();
try
{
UnityEngine.Debug.Log("In MainThread?" + Thread.CurrentThread.ManagedThreadId);
UnityEngine.Debug.Log("SyncContext is null?" + (SynchronizationContext.Current == null));
await UniTask.SwitchToThreadPool();
UnityEngine.Debug.Log("In ThreadPool?" + Thread.CurrentThread.ManagedThreadId);
UnityEngine.Debug.Log("SyncContext is null?" + (SynchronizationContext.Current == null));
}
finally
{
await d.DisposeAsync();
}
UnityEngine.Debug.Log("In ThreadPool?" + Thread.CurrentThread.ManagedThreadId);
UnityEngine.Debug.Log("SyncContext is null2" + (SynchronizationContext.Current == null));
}
private void Update()
{
// UnityEngine.Debug.Log("Update:" + PlayerLoopInfo.CurrentLoopType);
}
}).Forget(); IEnumerator Coroutine()
{
try
{
okButton.onClick.AddListener(UniTask.UnityAction(async () => await UniTask.Yield())); while (true)
{
yield return null;
//UnityEngine.Debug.Log("Coroutine null:" + PlayerLoopInfo.CurrentLoopType);
}
}
finally
{
UnityEngine.Debug.Log("Coroutine Finally");
}
} }
async UniTaskVoid CloseAsync(CancellationToken cancellationToken = default) async UniTaskVoid CloseAsync(CancellationToken cancellationToken = default)
{ {
await UniTask.Yield(); while (true)
{
await UniTask.Yield(PlayerLoopTiming.Update, cancellationToken);
}
} }
async UniTaskVoid Running(CancellationToken ct) async UniTaskVoid Running(CancellationToken ct)
@@ -533,7 +926,7 @@ public class SandboxMain : MonoBehaviour
} }
} }
public class ShowPlayerLoop public class PlayerLoopInfo
{ {
// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] // [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
static void Init() static void Init()
@@ -564,4 +957,45 @@ public class ShowPlayerLoop
UnityEngine.Debug.Log(sb.ToString()); UnityEngine.Debug.Log(sb.ToString());
} }
public static Type CurrentLoopType { get; private set; }
public static void Inject()
{
var system = PlayerLoop.GetCurrentPlayerLoop();
for (int i = 0; i < system.subSystemList.Length; i++)
{
var loop = system.subSystemList[i].subSystemList.SelectMany(x =>
{
var t = typeof(WrapLoop<>).MakeGenericType(x.type);
var instance = (ILoopRunner)Activator.CreateInstance(t, x.type);
return new[] { new PlayerLoopSystem { type = t, updateDelegate = instance.Run }, x };
}).ToArray();
system.subSystemList[i].subSystemList = loop;
}
PlayerLoop.SetPlayerLoop(system);
}
interface ILoopRunner
{
void Run();
}
class WrapLoop<T> : ILoopRunner
{
readonly Type type;
public WrapLoop(Type type)
{
this.type = type;
}
public void Run()
{
CurrentLoopType = type;
}
}
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -126,15 +126,15 @@ namespace Cysharp.Threading.TasksTests
await ToaruCoroutineEnumerator(); // wait 5 frame:) await ToaruCoroutineEnumerator(); // wait 5 frame:)
}); });
[UnityTest] //[UnityTest]
public IEnumerator JobSystem() => UniTask.ToCoroutine(async () => //public IEnumerator JobSystem() => UniTask.ToCoroutine(async () =>
{ //{
var job = new MyJob() { loopCount = 999, inOut = new NativeArray<int>(1, Allocator.TempJob) }; // var job = new MyJob() { loopCount = 999, inOut = new NativeArray<int>(1, Allocator.TempJob) };
JobHandle.ScheduleBatchedJobs(); // JobHandle.ScheduleBatchedJobs();
await job.Schedule(); // await job.Schedule();
job.inOut[0].Should().Be(999); // job.inOut[0].Should().Be(999);
job.inOut.Dispose(); // job.inOut.Dispose();
}); //});
class MyMyClass class MyMyClass
{ {
@@ -269,7 +269,8 @@ namespace Cysharp.Threading.TasksTests
var first = Time.frameCount; var first = Time.frameCount;
var canceled = await UniTask.DelayFrame(100, cancellationToken: cts.Token).SuppressCancellationThrow(); var canceled = await UniTask.DelayFrame(100, cancellationToken: cts.Token).SuppressCancellationThrow();
(Time.frameCount - first).Should().Be(11); // 10 frame canceled var r = (Time.frameCount - first);
(9 < r && r < 11).Should().BeTrue();
canceled.Should().Be(true); canceled.Should().Be(true);
}); });
@@ -369,6 +370,24 @@ namespace Cysharp.Threading.TasksTests
throw new Exception("MyException"); throw new Exception("MyException");
} }
[UnityTest]
public IEnumerator NextFrame1() => UniTask.ToCoroutine(async () =>
{
await UniTask.Yield(PlayerLoopTiming.LastUpdate);
var frame = Time.frameCount;
await UniTask.NextFrame();
Time.frameCount.Should().Be(frame + 1);
});
[UnityTest]
public IEnumerator NextFrame2() => UniTask.ToCoroutine(async () =>
{
await UniTask.Yield(PlayerLoopTiming.PreUpdate);
var frame = Time.frameCount;
await UniTask.NextFrame();
Time.frameCount.Should().Be(frame + 1);
});
[UnityTest] [UnityTest]
public IEnumerator NestedEnumerator() => UniTask.ToCoroutine(async () => public IEnumerator NestedEnumerator() => UniTask.ToCoroutine(async () =>
{ {

View File

@@ -0,0 +1,97 @@
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Linq;
using FluentAssertions;
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.TestTools;
namespace Cysharp.Threading.TasksTests
{
public class Cachelike
{
[UnityTest]
public IEnumerator Check() => UniTask.ToCoroutine(async () =>
{
{
var v = await CachedCheck("foo", 10);
v.Should().Be(10);
var v2 = await CachedCheck("bar", 20);
v2.Should().Be(20);
var v3 = await CachedCheck("baz", 30);
v3.Should().Be(30);
}
{
var v = await CachedCheck("foo", 10);
v.Should().Be(10);
var v2 = await CachedCheck("bar", 20);
v2.Should().Be(20);
var v3 = await CachedCheck("baz", 30);
v3.Should().Be(30);
}
{
var v = CachedCheck("foo", 10);
var v2 = CachedCheck("bar", 20);
var v3 = CachedCheck("baz", 30);
(await v).Should().Be(10);
(await v2).Should().Be(20);
(await v3).Should().Be(30);
}
{
var v = CachedCheck("foo", 10, true);
var v2 = CachedCheck("bar", 20, true);
var v3 = CachedCheck("baz", 30, true);
(await v).Should().Be(10);
(await v2).Should().Be(20);
(await v3).Should().Be(30);
}
});
static Dictionary<string, int> cacheDict = new Dictionary<string, int>();
async UniTask<int> CachedCheck(string cache, int value, bool yield = false)
{
if (!cacheDict.ContainsKey(cache))
{
await UniTask.Yield();
}
if (yield)
{
await UniTask.Yield();
}
if (cacheDict.TryGetValue(cache, out var v))
{
return v;
}
cacheDict.Add(cache, value);
return value;
}
}
}

View File

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

View File

@@ -0,0 +1,394 @@
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks.Linq;
using FluentAssertions;
using NUnit.Framework;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.TestTools;
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);
// }
// 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);
//});
[UnityTest]
public IEnumerator TimerFrameSinglePre2() => UniTask.ToCoroutine(async () =>
{
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
//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 () =>
{
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
Debug.Log("------------------");
{
var xs = await UniTaskAsyncEnumerable.TimerFrame(1).ToArrayAsync();
}
});
}
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

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

View File

@@ -1,400 +1,400 @@
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2) //#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 //#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using UnityEngine; //using UnityEngine;
using System; //using System;
using System.Collections; //using System.Collections;
using System.Collections.Generic; //using System.Collections.Generic;
using System.Linq; //using System.Linq;
using System.Text; //using System.Text;
using UnityEngine.UI; //using UnityEngine.UI;
using UnityEngine.Scripting; //using UnityEngine.Scripting;
using Cysharp.Threading.Tasks; //using Cysharp.Threading.Tasks;
using UnityEngine.SceneManagement; //using UnityEngine.SceneManagement;
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) //#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
using System.Threading.Tasks; //using System.Threading.Tasks;
#endif //#endif
using UnityEngine.Networking; //using UnityEngine.Networking;
#if !UNITY_2019_3_OR_NEWER //#if !UNITY_2019_3_OR_NEWER
using UnityEngine.Experimental.LowLevel; //using UnityEngine.Experimental.LowLevel;
#else //#else
using UnityEngine.LowLevel; //using UnityEngine.LowLevel;
#endif //#endif
#if !UNITY_WSA //#if !UNITY_WSA
using Unity.Jobs; //using Unity.Jobs;
#endif //#endif
using Unity.Collections; //using Unity.Collections;
using System.Threading; //using System.Threading;
using NUnit.Framework; //using NUnit.Framework;
using UnityEngine.TestTools; //using UnityEngine.TestTools;
using FluentAssertions; //using FluentAssertions;
namespace Cysharp.Threading.TasksTests //namespace Cysharp.Threading.TasksTests
{ //{
public class AsyncTest // public class AsyncTest
{ // {
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) //#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
#if !UNITY_WSA //#if !UNITY_WSA
public struct MyJob : IJob // public struct MyJob : IJob
{ // {
public int loopCount; // public int loopCount;
public NativeArray<int> inOut; // public NativeArray<int> inOut;
public int result; // public int result;
public void Execute() // public void Execute()
{ // {
result = 0; // result = 0;
for (int i = 0; i < loopCount; i++) // for (int i = 0; i < loopCount; i++)
{ // {
result++; // result++;
} // }
inOut[0] = result; // inOut[0] = result;
} // }
} // }
[UnityTest] // [UnityTest]
public IEnumerator DelayAnd() => UniTask.ToCoroutine(async () => // public IEnumerator DelayAnd() => UniTask.ToCoroutine(async () =>
{ // {
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); // await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
var time = Time.realtimeSinceStartup; // var time = Time.realtimeSinceStartup;
Time.timeScale = 0.5f; // Time.timeScale = 0.5f;
try // try
{ // {
await UniTask.Delay(TimeSpan.FromSeconds(3)); // await UniTask.Delay(TimeSpan.FromSeconds(3));
var elapsed = Time.realtimeSinceStartup - time; // var elapsed = Time.realtimeSinceStartup - time;
((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(6); // ((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(6);
} // }
finally // finally
{ // {
Time.timeScale = 1.0f; // Time.timeScale = 1.0f;
} // }
}); // });
[UnityTest] // [UnityTest]
public IEnumerator DelayIgnore() => UniTask.ToCoroutine(async () => // public IEnumerator DelayIgnore() => UniTask.ToCoroutine(async () =>
{ // {
var time = Time.realtimeSinceStartup; // var time = Time.realtimeSinceStartup;
Time.timeScale = 0.5f; // Time.timeScale = 0.5f;
try // try
{ // {
await UniTask.Delay(TimeSpan.FromSeconds(3), ignoreTimeScale: true); // await UniTask.Delay(TimeSpan.FromSeconds(3), ignoreTimeScale: true);
var elapsed = Time.realtimeSinceStartup - time; // var elapsed = Time.realtimeSinceStartup - time;
((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(3); // ((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(3);
} // }
finally // finally
{ // {
Time.timeScale = 1.0f; // Time.timeScale = 1.0f;
} // }
}); // });
[UnityTest] // [UnityTest]
public IEnumerator WhenAll() => UniTask.ToCoroutine(async () => // public IEnumerator WhenAll() => UniTask.ToCoroutine(async () =>
{ // {
var a = UniTask.FromResult(999); // var a = UniTask.FromResult(999);
var b = UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None).AsAsyncUnitUniTask(); // var b = UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None).AsAsyncUnitUniTask();
var c = UniTask.DelayFrame(99).AsAsyncUnitUniTask(); // var c = UniTask.DelayFrame(99).AsAsyncUnitUniTask();
var (a2, b2, c2) = await UniTask.WhenAll(a, b, c); // var (a2, b2, c2) = await UniTask.WhenAll(a, b, c);
a2.Should().Be(999); // a2.Should().Be(999);
b2.Should().Be(AsyncUnit.Default); // b2.Should().Be(AsyncUnit.Default);
c2.Should().Be(AsyncUnit.Default); // c2.Should().Be(AsyncUnit.Default);
}); // });
[UnityTest] // [UnityTest]
public IEnumerator WhenAny() => UniTask.ToCoroutine(async () => // public IEnumerator WhenAny() => UniTask.ToCoroutine(async () =>
{ // {
var a = UniTask.FromResult(999); // var a = UniTask.FromResult(999);
var b = UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None).AsAsyncUnitUniTask(); // var b = UniTask.Yield(PlayerLoopTiming.Update, CancellationToken.None).AsAsyncUnitUniTask();
var c = UniTask.DelayFrame(99).AsAsyncUnitUniTask(); // var c = UniTask.DelayFrame(99).AsAsyncUnitUniTask();
var (win, a2, b2, c2) = await UniTask.WhenAny(a, b, c); // var (win, a2, b2, c2) = await UniTask.WhenAny(a, b, c);
win.Should().Be(0); // win.Should().Be(0);
a2.Should().Be(999); // a2.Should().Be(999);
}); // });
[UnityTest] // [UnityTest]
public IEnumerator BothEnumeratorCheck() => UniTask.ToCoroutine(async () => // public IEnumerator BothEnumeratorCheck() => UniTask.ToCoroutine(async () =>
{ // {
await ToaruCoroutineEnumerator(); // wait 5 frame:) // await ToaruCoroutineEnumerator(); // wait 5 frame:)
}); // });
[UnityTest] // [UnityTest]
public IEnumerator JobSystem() => UniTask.ToCoroutine(async () => // public IEnumerator JobSystem() => UniTask.ToCoroutine(async () =>
{ // {
var job = new MyJob() { loopCount = 999, inOut = new NativeArray<int>(1, Allocator.TempJob) }; // var job = new MyJob() { loopCount = 999, inOut = new NativeArray<int>(1, Allocator.TempJob) };
JobHandle.ScheduleBatchedJobs(); // JobHandle.ScheduleBatchedJobs();
await job.Schedule(); // await job.Schedule();
job.inOut[0].Should().Be(999); // job.inOut[0].Should().Be(999);
job.inOut.Dispose(); // job.inOut.Dispose();
}); // });
class MyMyClass // class MyMyClass
{ // {
public int MyProperty { get; set; } // public int MyProperty { get; set; }
} // }
[UnityTest] // [UnityTest]
public IEnumerator WaitUntil() => UniTask.ToCoroutine(async () => // public IEnumerator WaitUntil() => UniTask.ToCoroutine(async () =>
{ // {
bool t = false; // bool t = false;
await UniTask.Yield(PlayerLoopTiming.PostLateUpdate); // await UniTask.Yield(PlayerLoopTiming.PostLateUpdate);
UniTask.DelayFrame(10,PlayerLoopTiming.PostLateUpdate).ContinueWith(() => t = true).Forget(); // UniTask.DelayFrame(10,PlayerLoopTiming.PostLateUpdate).ContinueWith(() => t = true).Forget();
var startFrame = Time.frameCount; // var startFrame = Time.frameCount;
await UniTask.WaitUntil(() => t, PlayerLoopTiming.EarlyUpdate); // await UniTask.WaitUntil(() => t, PlayerLoopTiming.EarlyUpdate);
var diff = Time.frameCount - startFrame; // var diff = Time.frameCount - startFrame;
diff.Should().Be(11); // diff.Should().Be(11);
}); // });
[UnityTest] // [UnityTest]
public IEnumerator WaitWhile() => UniTask.ToCoroutine(async () => // public IEnumerator WaitWhile() => UniTask.ToCoroutine(async () =>
{ // {
bool t = true; // bool t = true;
UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => t = false).Forget(); // UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => t = false).Forget();
var startFrame = Time.frameCount; // var startFrame = Time.frameCount;
await UniTask.WaitWhile(() => t, PlayerLoopTiming.EarlyUpdate); // await UniTask.WaitWhile(() => t, PlayerLoopTiming.EarlyUpdate);
var diff = Time.frameCount - startFrame; // var diff = Time.frameCount - startFrame;
diff.Should().Be(11); // diff.Should().Be(11);
}); // });
[UnityTest] // [UnityTest]
public IEnumerator WaitUntilValueChanged() => UniTask.ToCoroutine(async () => // public IEnumerator WaitUntilValueChanged() => UniTask.ToCoroutine(async () =>
{ // {
var v = new MyMyClass { MyProperty = 99 }; // var v = new MyMyClass { MyProperty = 99 };
UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => v.MyProperty = 1000).Forget(); // UniTask.DelayFrame(10, PlayerLoopTiming.PostLateUpdate).ContinueWith(() => v.MyProperty = 1000).Forget();
var startFrame = Time.frameCount; // var startFrame = Time.frameCount;
await UniTask.WaitUntilValueChanged(v, x => x.MyProperty, PlayerLoopTiming.EarlyUpdate); // await UniTask.WaitUntilValueChanged(v, x => x.MyProperty, PlayerLoopTiming.EarlyUpdate);
var diff = Time.frameCount - startFrame; // var diff = Time.frameCount - startFrame;
diff.Should().Be(11); // diff.Should().Be(11);
}); // });
[UnityTest] // [UnityTest]
public IEnumerator SwitchTo() => UniTask.ToCoroutine(async () => // public IEnumerator SwitchTo() => UniTask.ToCoroutine(async () =>
{ // {
await UniTask.Yield(); // await UniTask.Yield();
var currentThreadId = Thread.CurrentThread.ManagedThreadId; // var currentThreadId = Thread.CurrentThread.ManagedThreadId;
await UniTask.SwitchToThreadPool();
//await UniTask.SwitchToThreadPool();
// await UniTask.SwitchToThreadPool(); // await UniTask.SwitchToThreadPool();
// //await UniTask.SwitchToThreadPool();
// //await UniTask.SwitchToThreadPool();
var switchedThreadId = Thread.CurrentThread.ManagedThreadId; // var switchedThreadId = Thread.CurrentThread.ManagedThreadId;
currentThreadId.Should().NotBe(switchedThreadId); // currentThreadId.Should().NotBe(switchedThreadId);
await UniTask.Yield(); // await UniTask.Yield();
var switchedThreadId2 = Thread.CurrentThread.ManagedThreadId; // var switchedThreadId2 = Thread.CurrentThread.ManagedThreadId;
currentThreadId.Should().Be(switchedThreadId2); // currentThreadId.Should().Be(switchedThreadId2);
}); // });
// //[UnityTest]
// //public IEnumerator ObservableConversion() => UniTask.ToCoroutine(async () =>
// //{
// // var v = await Observable.Range(1, 10).ToUniTask();
// // v.Is(10);
// // v = await Observable.Range(1, 10).ToUniTask(useFirstValue: true);
// // v.Is(1);
// // v = await UniTask.DelayFrame(10).ToObservable().ToTask();
// // v.Is(10);
// // v = await UniTask.FromResult(99).ToObservable();
// // v.Is(99);
// //});
// //[UnityTest]
// //public IEnumerator AwaitableReactiveProperty() => UniTask.ToCoroutine(async () =>
// //{
// // var rp1 = new ReactiveProperty<int>(99);
// // UniTask.DelayFrame(100).ContinueWith(x => rp1.Value = x).Forget();
// // await rp1;
// // rp1.Value.Is(100);
// // // var delay2 = UniTask.DelayFrame(10);
// // // var (a, b ) = await UniTask.WhenAll(rp1.WaitUntilValueChangedAsync(), delay2);
// //});
// //[UnityTest]
// //public IEnumerator AwaitableReactiveCommand() => UniTask.ToCoroutine(async () =>
// //{
// // var rc = new ReactiveCommand<int>();
// // UniTask.DelayFrame(100).ContinueWith(x => rc.Execute(x)).Forget();
// // var v = await rc;
// // v.Is(100);
// //});
// [UnityTest] // [UnityTest]
//public IEnumerator ObservableConversion() => UniTask.ToCoroutine(async () => // public IEnumerator ExceptionlessCancellation() => UniTask.ToCoroutine(async () =>
// { // {
// var v = await Observable.Range(1, 10).ToUniTask(); // var cts = new CancellationTokenSource();
// v.Is(10);
// v = await Observable.Range(1, 10).ToUniTask(useFirstValue: true); // UniTask.DelayFrame(10).ContinueWith(() => cts.Cancel()).Forget();
// v.Is(1);
// v = await UniTask.DelayFrame(10).ToObservable().ToTask(); // var first = Time.frameCount;
// v.Is(10); // var canceled = await UniTask.DelayFrame(100, cancellationToken: cts.Token).SuppressCancellationThrow();
// v = await UniTask.FromResult(99).ToObservable(); // (Time.frameCount - first).Should().Be(11); // 10 frame canceled
// v.Is(99); // canceled.Should().Be(true);
// }); // });
// [UnityTest] // [UnityTest]
//public IEnumerator AwaitableReactiveProperty() => UniTask.ToCoroutine(async () => // public IEnumerator ExceptionCancellation() => UniTask.ToCoroutine(async () =>
// { // {
// var rp1 = new ReactiveProperty<int>(99); // var cts = new CancellationTokenSource();
// UniTask.DelayFrame(100).ContinueWith(x => rp1.Value = x).Forget(); // UniTask.DelayFrame(10).ContinueWith(() => cts.Cancel()).Forget();
// await rp1; // bool occur = false;
// try
// {
// await UniTask.DelayFrame(100, cancellationToken: cts.Token);
// }
// catch (OperationCanceledException)
// {
// occur = true;
// }
// occur.Should().BeTrue();
// });
// rp1.Value.Is(100); // IEnumerator ToaruCoroutineEnumerator()
// {
// yield return null;
// yield return null;
// yield return null;
// yield return null;
// yield return null;
// }
// // var delay2 = UniTask.DelayFrame(10); // [UnityTest]
// // var (a, b ) = await UniTask.WhenAll(rp1.WaitUntilValueChangedAsync(), delay2); // public IEnumerator ExceptionUnobserved1() => UniTask.ToCoroutine(async () =>
// {
// bool calledEx = false;
// Action<Exception> action = exx =>
// {
// calledEx = true;
// exx.Message.Should().Be("MyException");
// };
// UniTaskScheduler.UnobservedTaskException += action;
// var ex = InException1();
// ex = default(UniTask);
// await UniTask.DelayFrame(3);
// GC.Collect();
// GC.WaitForPendingFinalizers();
// GC.Collect();
// await UniTask.DelayFrame(1);
// calledEx.Should().BeTrue();
// UniTaskScheduler.UnobservedTaskException -= action;
// }); // });
// [UnityTest] // [UnityTest]
//public IEnumerator AwaitableReactiveCommand() => UniTask.ToCoroutine(async () => // public IEnumerator ExceptionUnobserved2() => UniTask.ToCoroutine(async () =>
// { // {
// var rc = new ReactiveCommand<int>(); // bool calledEx = false;
// Action<Exception> action = exx =>
// {
// calledEx = true;
// exx.Message.Should().Be("MyException");
// };
// UniTask.DelayFrame(100).ContinueWith(x => rc.Execute(x)).Forget(); // UniTaskScheduler.UnobservedTaskException += action;
// var v = await rc; // var ex = InException2();
// ex = default(UniTask<int>);
// v.Is(100); // await UniTask.DelayFrame(3);
// GC.Collect();
// GC.WaitForPendingFinalizers();
// GC.Collect();
// await UniTask.DelayFrame(1);
// calledEx.Should().BeTrue();
// UniTaskScheduler.UnobservedTaskException -= action;
// }); // });
[UnityTest] // async UniTask InException1()
public IEnumerator ExceptionlessCancellation() => UniTask.ToCoroutine(async () => // {
{ // await UniTask.Yield();
var cts = new CancellationTokenSource(); // throw new Exception("MyException");
// }
UniTask.DelayFrame(10).ContinueWith(() => cts.Cancel()).Forget(); // async UniTask<int> InException2()
// {
// await UniTask.Yield();
// throw new Exception("MyException");
// }
var first = Time.frameCount; // [UnityTest]
var canceled = await UniTask.DelayFrame(100, cancellationToken: cts.Token).SuppressCancellationThrow(); // public IEnumerator NestedEnumerator() => UniTask.ToCoroutine(async () =>
// {
// var time = Time.realtimeSinceStartup;
(Time.frameCount - first).Should().Be(11); // 10 frame canceled // await ParentCoroutineEnumerator();
canceled.Should().Be(true);
});
[UnityTest] // var elapsed = Time.realtimeSinceStartup - time;
public IEnumerator ExceptionCancellation() => UniTask.ToCoroutine(async () => // ((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(3);
{ // });
var cts = new CancellationTokenSource();
UniTask.DelayFrame(10).ContinueWith(() => cts.Cancel()).Forget(); // IEnumerator ParentCoroutineEnumerator()
// {
// yield return ChildCoroutineEnumerator();
// }
bool occur = false; // IEnumerator ChildCoroutineEnumerator()
try // {
{ // yield return new WaitForSeconds(3);
await UniTask.DelayFrame(100, cancellationToken: cts.Token); // }
}
catch (OperationCanceledException)
{
occur = true;
}
occur.Should().BeTrue();
});
IEnumerator ToaruCoroutineEnumerator() //#endif
{ //#endif
yield return null; // }
yield return null; //}
yield return null;
yield return null;
yield return null;
}
[UnityTest] //#endif
public IEnumerator ExceptionUnobserved1() => UniTask.ToCoroutine(async () =>
{
bool calledEx = false;
Action<Exception> action = exx =>
{
calledEx = true;
exx.Message.Should().Be("MyException");
};
UniTaskScheduler.UnobservedTaskException += action;
var ex = InException1();
ex = default(UniTask);
await UniTask.DelayFrame(3);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
await UniTask.DelayFrame(1);
calledEx.Should().BeTrue();
UniTaskScheduler.UnobservedTaskException -= action;
});
[UnityTest]
public IEnumerator ExceptionUnobserved2() => UniTask.ToCoroutine(async () =>
{
bool calledEx = false;
Action<Exception> action = exx =>
{
calledEx = true;
exx.Message.Should().Be("MyException");
};
UniTaskScheduler.UnobservedTaskException += action;
var ex = InException2();
ex = default(UniTask<int>);
await UniTask.DelayFrame(3);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
await UniTask.DelayFrame(1);
calledEx.Should().BeTrue();
UniTaskScheduler.UnobservedTaskException -= action;
});
async UniTask InException1()
{
await UniTask.Yield();
throw new Exception("MyException");
}
async UniTask<int> InException2()
{
await UniTask.Yield();
throw new Exception("MyException");
}
[UnityTest]
public IEnumerator NestedEnumerator() => UniTask.ToCoroutine(async () =>
{
var time = Time.realtimeSinceStartup;
await ParentCoroutineEnumerator();
var elapsed = Time.realtimeSinceStartup - time;
((int)Math.Round(TimeSpan.FromSeconds(elapsed).TotalSeconds, MidpointRounding.ToEven)).Should().Be(3);
});
IEnumerator ParentCoroutineEnumerator()
{
yield return ChildCoroutineEnumerator();
}
IEnumerator ChildCoroutineEnumerator()
{
yield return new WaitForSeconds(3);
}
#endif
#endif
}
}
#endif

View File

@@ -1,95 +1,95 @@
#if !(UNITY_4_5 || UNITY_4_6 || UNITY_4_7 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2) //#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 //#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using UnityEngine; //using UnityEngine;
using System; //using System;
using System.Collections; //using System.Collections;
using System.Collections.Generic; //using System.Collections.Generic;
using System.Linq; //using System.Linq;
using System.Text; //using System.Text;
using UnityEngine.UI; //using UnityEngine.UI;
using UnityEngine.Scripting; //using UnityEngine.Scripting;
using Cysharp.Threading.Tasks; //using Cysharp.Threading.Tasks;
using UnityEngine.SceneManagement; //using UnityEngine.SceneManagement;
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) //#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
using System.Threading.Tasks; //using System.Threading.Tasks;
#endif //#endif
using UnityEngine.Networking; //using UnityEngine.Networking;
#if !UNITY_2019_3_OR_NEWER //#if !UNITY_2019_3_OR_NEWER
using UnityEngine.Experimental.LowLevel; //using UnityEngine.Experimental.LowLevel;
#else //#else
using UnityEngine.LowLevel; //using UnityEngine.LowLevel;
#endif //#endif
#if !UNITY_WSA //#if !UNITY_WSA
using Unity.Jobs; //using Unity.Jobs;
#endif //#endif
using Unity.Collections; //using Unity.Collections;
using System.Threading; //using System.Threading;
using NUnit.Framework; //using NUnit.Framework;
using UnityEngine.TestTools; //using UnityEngine.TestTools;
using FluentAssertions; //using FluentAssertions;
namespace Cysharp.Threading.TasksTests //namespace Cysharp.Threading.TasksTests
{ //{
public class RunTest // public class RunTest
{ // {
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) //#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
#if !UNITY_WSA //#if !UNITY_WSA
// //[UnityTest]
// //public IEnumerator RunThread() => UniTask.ToCoroutine(async () =>
// //{
// // var main = Thread.CurrentThread.ManagedThreadId;
// // var v = await UniTask.Run(() => { return System.Threading.Thread.CurrentThread.ManagedThreadId; }, false);
// // UnityEngine.Debug.Log("Ret Value is:" + v);
// // UnityEngine.Debug.Log("Run Here and id:" + System.Threading.Thread.CurrentThread.ManagedThreadId);
// // //v.Should().Be(3);
// // main.Should().NotBe(Thread.CurrentThread.ManagedThreadId);
// //});
// [UnityTest] // [UnityTest]
//public IEnumerator RunThread() => UniTask.ToCoroutine(async () => // public IEnumerator RunThreadConfigure() => UniTask.ToCoroutine(async () =>
// { // {
// var main = Thread.CurrentThread.ManagedThreadId; // var main = Thread.CurrentThread.ManagedThreadId;
// var v = await UniTask.Run(() => { return System.Threading.Thread.CurrentThread.ManagedThreadId; }, false); // var v = await UniTask.Run(() => 3, true);
// UnityEngine.Debug.Log("Ret Value is:" + v); // v.Should().Be(3);
// UnityEngine.Debug.Log("Run Here and id:" + System.Threading.Thread.CurrentThread.ManagedThreadId); // main.Should().Be(Thread.CurrentThread.ManagedThreadId);
// //v.Should().Be(3);
// main.Should().NotBe(Thread.CurrentThread.ManagedThreadId);
// }); // });
[UnityTest] // //[UnityTest]
public IEnumerator RunThreadConfigure() => UniTask.ToCoroutine(async () => // //public IEnumerator RunThreadException() => UniTask.ToCoroutine(async () =>
{ // //{
var main = Thread.CurrentThread.ManagedThreadId; // // var main = Thread.CurrentThread.ManagedThreadId;
var v = await UniTask.Run(() => 3, true); // // try
v.Should().Be(3); // // {
main.Should().Be(Thread.CurrentThread.ManagedThreadId); // // await UniTask.Run<int>(() => throw new Exception(), false);
}); // // }
// // catch
// // {
// // main.Should().NotBe(Thread.CurrentThread.ManagedThreadId);
// // }
// //});
// [UnityTest] // [UnityTest]
//public IEnumerator RunThreadException() => UniTask.ToCoroutine(async () => // public IEnumerator RunThreadExceptionConfigure() => UniTask.ToCoroutine(async () =>
// { // {
// var main = Thread.CurrentThread.ManagedThreadId; // var main = Thread.CurrentThread.ManagedThreadId;
// try // try
// { // {
// await UniTask.Run<int>(() => throw new Exception(), false); // await UniTask.Run<int>(() => throw new Exception(), true);
// } // }
// catch // catch
// { // {
// main.Should().NotBe(Thread.CurrentThread.ManagedThreadId); // main.Should().Be(Thread.CurrentThread.ManagedThreadId);
// } // }
// }); // });
[UnityTest]
public IEnumerator RunThreadExceptionConfigure() => UniTask.ToCoroutine(async () =>
{
var main = Thread.CurrentThread.ManagedThreadId;
try
{
await UniTask.Run<int>(() => throw new Exception(), true);
}
catch
{
main.Should().Be(Thread.CurrentThread.ManagedThreadId);
}
});
//#endif
//#endif
// }
//}
#endif //#endif
#endif
}
}
#endif

View File

@@ -1,43 +1,43 @@
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) //#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member //#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using UnityEngine; //using UnityEngine;
using System; //using System;
using System.Collections; //using System.Collections;
using System.Collections.Generic; //using System.Collections.Generic;
using System.Linq; //using System.Linq;
using System.Text; //using System.Text;
using UnityEngine.UI; //using UnityEngine.UI;
using UnityEngine.Scripting; //using UnityEngine.Scripting;
using Cysharp.Threading.Tasks; //using Cysharp.Threading.Tasks;
using Unity.Collections; //using Unity.Collections;
using System.Threading; //using System.Threading;
using NUnit.Framework; //using NUnit.Framework;
using UnityEngine.TestTools; //using UnityEngine.TestTools;
using FluentAssertions; //using FluentAssertions;
namespace Cysharp.Threading.TasksTests //namespace Cysharp.Threading.TasksTests
{ //{
public class WhenAnyTest // public class WhenAnyTest
{ // {
[UnityTest] // [UnityTest]
public IEnumerator WhenAnyCanceled() => UniTask.ToCoroutine(async () => // public IEnumerator WhenAnyCanceled() => UniTask.ToCoroutine(async () =>
{ // {
var cts = new CancellationTokenSource(); // var cts = new CancellationTokenSource();
var successDelayTask = UniTask.Delay(TimeSpan.FromSeconds(1)); // var successDelayTask = UniTask.Delay(TimeSpan.FromSeconds(1));
var cancelTask = UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cts.Token); // var cancelTask = UniTask.Delay(TimeSpan.FromSeconds(1), cancellationToken: cts.Token);
cts.CancelAfterSlim(200); // cts.CancelAfterSlim(200);
try // try
{ // {
var r = await UniTask.WhenAny(new[] { successDelayTask, cancelTask }); // var r = await UniTask.WhenAny(new[] { successDelayTask, cancelTask });
} // }
catch (Exception ex) // catch (Exception ex)
{ // {
ex.Should().BeAssignableTo<OperationCanceledException>(); // ex.Should().BeAssignableTo<OperationCanceledException>();
} // }
}); // });
} // }
} //}
#endif //#endif

View File

@@ -0,0 +1,165 @@
#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6))
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine.UI;
using UnityEngine.Scripting;
using Cysharp.Threading.Tasks;
using Unity.Collections;
using System.Threading;
using NUnit.Framework;
using UnityEngine.TestTools;
using FluentAssertions;
namespace Cysharp.Threading.TasksTests
{
public class GenericsWhenAllAny
{
[UnityTest]
public IEnumerator WhenAllT15() => UniTask.ToCoroutine(async () =>
{
var t01 = Tes<int>();
var t02 = Tes<int>();
var t03 = Tes<int>();
var t04 = Tes<int>();
var t05 = Tes<int>();
var t06 = Tes<int>();
var t07 = Tes<int>();
var t08 = Tes<int>();
var t09 = Tes<int>();
var t10 = Tes<int>();
var t11 = Tes<int>();
var t12 = Tes<int>();
var t13 = Tes<int>();
var t14 = Tes<int>();
var t15 = Tes<int>();
await UniTask.WhenAll(t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15);
});
[UnityTest]
public IEnumerator WhenAllT01_Generics1() => UniTask.ToCoroutine(async () =>
{
var t01 = Tes<MyGenerics<int>>();
await UniTask.WhenAll(t01);
});
[UnityTest]
public IEnumerator WhenAllT02_Generics1() => UniTask.ToCoroutine(async () =>
{
var t01 = Tes<MyGenerics<int>>();
var t02 = Tes<MyGenerics<int>>();
await UniTask.WhenAll(t01, t02);
});
[UnityTest]
public IEnumerator WhenAllT03_Generics1() => UniTask.ToCoroutine(async () =>
{
var t01 = Tes<MyGenerics<int>>();
var t02 = Tes<MyGenerics<int>>();
var t03 = Tes<MyGenerics<int>>();
await UniTask.WhenAll(t01, t02, t03);
});
[UnityTest]
public IEnumerator WhenAllT04_Generics1() => UniTask.ToCoroutine(async () =>
{
var t01 = Tes<MyGenerics<int>>();
var t02 = Tes<MyGenerics<int>>();
var t03 = Tes<MyGenerics<int>>();
var t04 = Tes<MyGenerics<int>>();
await UniTask.WhenAll(t01, t02, t03, t04);
});
// will fail.
//[UnityTest]
//public IEnumerator WhenAllT05_Generics1() => UniTask.ToCoroutine(async () =>
//{
// var t01 = Tes<MyGenerics<int>>();
// var t02 = Tes<MyGenerics<int>>();
// var t03 = Tes<MyGenerics<int>>();
// var t04 = Tes<MyGenerics<int>>();
// var t05 = Tes<MyGenerics<int>>();
// await UniTask.WhenAll(t01, t02, t03, t04, t05);
//});
//[UnityTest]
//public IEnumerator WhenAllT06_Generics1() => UniTask.ToCoroutine(async () =>
//{
// var t01 = Tes<MyGenerics<int>>();
// var t02 = Tes<MyGenerics<int>>();
// var t03 = Tes<MyGenerics<int>>();
// var t04 = Tes<MyGenerics<int>>();
// var t05 = Tes<MyGenerics<int>>();
// var t06 = Tes<MyGenerics<int>>();
// await UniTask.WhenAll(t01, t02, t03, t04, t05, t06);
//});
//[UnityTest]
//public IEnumerator WhenAllT07_Generics1() => UniTask.ToCoroutine(async () =>
//{
// var t01 = Tes<MyGenerics<int>>();
// var t02 = Tes<MyGenerics<int>>();
// var t03 = Tes<MyGenerics<int>>();
// var t04 = Tes<MyGenerics<int>>();
// var t05 = Tes<MyGenerics<int>>();
// var t06 = Tes<MyGenerics<int>>();
// var t07 = Tes<MyGenerics<int>>();
// await UniTask.WhenAll(t01, t02, t03, t04, t05, t06, t07);
//});
//[UnityTest]
//public IEnumerator WhenAllT15_Generics1() => UniTask.ToCoroutine(async () =>
//{
// var t01 = Tes<MyGenerics<int>>();
// var t02 = Tes<MyGenerics<int>>();
// var t03 = Tes<MyGenerics<int>>();
// var t04 = Tes<MyGenerics<int>>();
// var t05 = Tes<MyGenerics<int>>();
// var t06 = Tes<MyGenerics<int>>();
// var t07 = Tes<MyGenerics<int>>();
// var t08 = Tes<MyGenerics<int>>();
// var t09 = Tes<MyGenerics<int>>();
// var t10 = Tes<MyGenerics<int>>();
// var t11 = Tes<MyGenerics<int>>();
// var t12 = Tes<MyGenerics<int>>();
// var t13 = Tes<MyGenerics<int>>();
// var t14 = Tes<MyGenerics<int>>();
// var t15 = Tes<MyGenerics<int>>();
// await UniTask.WhenAll(t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15);
//});
async UniTask<T> Tes<T>()
{
await UniTask.Yield();
return default;
}
}
public class MyGenerics<T>
{
}
public class MyGenerics2
{
}
}
#endif

View File

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

View File

@@ -6,13 +6,20 @@ using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.TestTools; using UnityEngine.TestTools;
namespace Cysharp.Threading.TasksTests namespace Cysharp.Threading.TasksTests
{ {
public class Preserve public class Preserve
{ {
public Preserve()
{
// TaskPool.SetMaxPoolSize(0);
}
[UnityTest] [UnityTest]
public IEnumerator AwaitTwice() => UniTask.ToCoroutine(async () => public IEnumerator AwaitTwice() => UniTask.ToCoroutine(async () =>
{ {
@@ -33,12 +40,17 @@ namespace Cysharp.Threading.TasksTests
[UnityTest] [UnityTest]
public IEnumerator PreserveAllowTwice() => UniTask.ToCoroutine(async () => public IEnumerator PreserveAllowTwice() => UniTask.ToCoroutine(async () =>
{ {
await UniTask.Yield(PlayerLoopTiming.Update);
var delay = UniTask.DelayFrame(5, PlayerLoopTiming.PostLateUpdate).Preserve(); var delay = UniTask.DelayFrame(5, PlayerLoopTiming.PostLateUpdate).Preserve();
var before = UnityEngine.Time.frameCount;
var before = UnityEngine.Time.frameCount; // 0
await delay; await delay;
var afterOne = UnityEngine.Time.frameCount; var afterOne = UnityEngine.Time.frameCount; // 5
await delay; await delay;
var afterTwo = UnityEngine.Time.frameCount; var afterTwo = UnityEngine.Time.frameCount; // 5
(afterOne - before).Should().Be(5); (afterOne - before).Should().Be(5);
afterOne.Should().Be(afterTwo); afterOne.Should().Be(afterTwo);

View File

@@ -5,7 +5,8 @@
"UnityEditor.TestRunner", "UnityEditor.TestRunner",
"UniTask", "UniTask",
"Unity.ResourceManager", "Unity.ResourceManager",
"DOTween.Modules" "DOTween.Modules",
"UniTask.Linq"
], ],
"includePlatforms": [], "includePlatforms": [],
"excludePlatforms": [], "excludePlatforms": [],

View File

@@ -8,7 +8,7 @@ EditorBuildSettings:
- enabled: 1 - enabled: 1
path: Assets/Scenes/SandboxMain.unity path: Assets/Scenes/SandboxMain.unity
guid: 2cda990e2423bbf4892e6590ba056729 guid: 2cda990e2423bbf4892e6590ba056729
- enabled: 0 - enabled: 1
path: path: Assets/Scenes/ExceptionExamples.unity
guid: 00000000000000000000000000000000 guid: b5fed17e3ece238439bc796d8747df5d
m_configObjects: {} m_configObjects: {}