From a9baa523095ddb6fbb804e7a85a210abe9910e84 Mon Sep 17 00:00:00 2001 From: neuecc Date: Sun, 31 May 2020 04:30:03 +0900 Subject: [PATCH] Add ReturnToMainThread, ReturnToSynchronizationContext, ReturnToCurrentSynchronizationContext --- .../UniTask/Runtime/UniTask.Threading.cs | 169 +++++++++++++++- src/UniTask/Assets/Scenes/SandboxMain.cs | 186 +++++++++++++++--- 2 files changed, 319 insertions(+), 36 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs index 6ed1ef8..6f49276 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Threading.cs @@ -17,7 +17,31 @@ namespace Cysharp.Threading.Tasks /// public static SwitchToMainThreadAwaitable SwitchToMainThread() { - return new SwitchToMainThreadAwaitable(); + return new SwitchToMainThreadAwaitable(PlayerLoopTiming.Update); + } + + /// + /// If running on mainthread, do nothing. Otherwise, same as UniTask.Yield(timing). + /// + public static SwitchToMainThreadAwaitable SwitchToMainThread(PlayerLoopTiming timing) + { + return new SwitchToMainThreadAwaitable(timing); + } + + /// + /// Return to mainthread(same as await SwitchToMainThread) after using scope is closed. + /// + public static ReturnToMainThread ReturnToMainThread() + { + return new ReturnToMainThread(PlayerLoopTiming.Update); + } + + /// + /// Return to mainthread(same as await SwitchToMainThread) after using scope is closed. + /// + public static ReturnToMainThread ReturnToMainThread(PlayerLoopTiming timing) + { + return new ReturnToMainThread(timing); } #endif @@ -27,15 +51,28 @@ namespace Cysharp.Threading.Tasks return new SwitchToThreadPoolAwaitable(); } + /// + /// Note: use SwitchToThreadPool is recommended. + /// public static SwitchToTaskPoolAwaitable SwitchToTaskPool() { return new SwitchToTaskPoolAwaitable(); } - public static SwitchToSynchronizationContextAwaitable SwitchToSynchronizationContext(SynchronizationContext syncContext) + public static SwitchToSynchronizationContextAwaitable SwitchToSynchronizationContext(SynchronizationContext synchronizationContext) { - Error.ThrowArgumentNullException(syncContext, nameof(syncContext)); - return new SwitchToSynchronizationContextAwaitable(syncContext); + Error.ThrowArgumentNullException(synchronizationContext, nameof(synchronizationContext)); + return new SwitchToSynchronizationContextAwaitable(synchronizationContext); + } + + public static ReturnToSynchronizationContext ReturnToSynchronizationContext(SynchronizationContext synchronizationContext) + { + return new ReturnToSynchronizationContext(synchronizationContext); + } + + public static ReturnToSynchronizationContext ReturnToCurrentSynchronizationContext() + { + return new ReturnToSynchronizationContext(SynchronizationContext.Current); } } @@ -43,10 +80,24 @@ namespace Cysharp.Threading.Tasks 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 { + readonly PlayerLoopTiming playerLoopTiming; + + public Awaiter(PlayerLoopTiming playerLoopTiming) + { + this.playerLoopTiming = playerLoopTiming; + } + public bool IsCompleted { get @@ -67,12 +118,53 @@ namespace Cysharp.Threading.Tasks public void OnCompleted(Action continuation) { - PlayerLoopHelper.AddContinuation(PlayerLoopTiming.Update, continuation); + PlayerLoopHelper.AddContinuation(playerLoopTiming, 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) { - ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation); + ThreadPool.QueueUserWorkItem(switchToCallback, continuation); } public void UnsafeOnCompleted(Action continuation) { +#if NETCOREAPP3_1 + ThreadPool.UnsafeQueueUserWorkItem(ThreadPoolWorkItem.Create(continuation), false); +#else ThreadPool.UnsafeQueueUserWorkItem(switchToCallback, continuation); +#endif } static void Callback(object state) @@ -106,6 +202,47 @@ namespace Cysharp.Threading.Tasks continuation(); } } + +#if NETCOREAPP3_1 + + sealed class ThreadPoolWorkItem : IThreadPoolWorkItem, ITaskPoolNode + { + static TaskPool 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 @@ -178,5 +315,19 @@ namespace Cysharp.Threading.Tasks } } } -} + public struct ReturnToSynchronizationContext + { + readonly SynchronizationContext syncContext; + + public ReturnToSynchronizationContext(SynchronizationContext syncContext) + { + this.syncContext = syncContext; + } + + public SwitchToSynchronizationContextAwaitable DisposeAsync() + { + return UniTask.SwitchToSynchronizationContext(syncContext); + } + } +} diff --git a/src/UniTask/Assets/Scenes/SandboxMain.cs b/src/UniTask/Assets/Scenes/SandboxMain.cs index 6953bf5..beaf08e 100644 --- a/src/UniTask/Assets/Scenes/SandboxMain.cs +++ b/src/UniTask/Assets/Scenes/SandboxMain.cs @@ -1,4 +1,5 @@ using Cysharp.Threading.Tasks; +using System.Linq; using Cysharp.Threading.Tasks.Linq; using Cysharp.Threading.Tasks.Triggers; using System; @@ -10,6 +11,7 @@ using System.Threading.Tasks; using Unity.Collections; using Unity.Jobs; using UnityEngine; +using UnityEngine.LowLevel; using UnityEngine.Networking; using UnityEngine.UI; @@ -320,43 +322,132 @@ public class SandboxMain : MonoBehaviour //Debug.Log("AGAIN END MOVE"); - await UniTask.Yield(); - // DOTween.To( - var cts = new CancellationTokenSource(); - //var tween = okButton.GetComponent().DOLocalMoveX(100, 5.0f); - cancelButton.OnClickAsAsyncEnumerable().ForEachAsync(_ => + + + + //// DOTween.To( + + //var cts = new CancellationTokenSource(); + + ////var tween = okButton.GetComponent().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(_ => + //{ + + + + + + //}).Forget(); + + //CloseAsync(this.GetCancellationTokenOnDestroy()).Forget(); + + //okButton.onClick.AddListener(UniTask.UnityAction(async () => await UniTask.Yield())); + + PlayerLoopInfo.Inject(); + + //UpdateUniTask().Forget(); + + //StartCoroutine(Coroutine()); + + //await UniTask.Delay(TimeSpan.FromSeconds(1)); + + + _ = ReturnToMainThreadTest(); + + //GameObject.Destroy(this.gameObject); + + + SynchronizationContext.Current.Post(_ => { - cts.Cancel(); - }).Forget(); + //UnityEngine.Debug.Log("Post:" + PlayerLoopInfo.CurrentLoopType); + }, null); + } - - // 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(_ => + 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(); - - CloseAsync(this.GetCancellationTokenOnDestroy()).Forget(); - - okButton.onClick.AddListener(UniTask.UnityAction(async () => await UniTask.Yield())); + IEnumerator Coroutine() + { + try + { + while (true) + { + yield return null; + //UnityEngine.Debug.Log("Coroutine null:" + PlayerLoopInfo.CurrentLoopType); + } + } + finally + { + UnityEngine.Debug.Log("Coroutine Finally"); + } } async UniTaskVoid CloseAsync(CancellationToken cancellationToken = default) @@ -597,7 +688,7 @@ public class SandboxMain : MonoBehaviour } } -public class ShowPlayerLoop +public class PlayerLoopInfo { // [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] static void Init() @@ -628,4 +719,45 @@ public class ShowPlayerLoop 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 : ILoopRunner + { + readonly Type type; + + public WrapLoop(Type type) + { + this.type = type; + } + + public void Run() + { + CurrentLoopType = type; + } + } } \ No newline at end of file