Add UniTaskSynchronizationContext, PlayerLoopHelper.IsInjectedUniTaskPlayerLoop, DumpCurrentPlayerLoop

master
neuecc 2020-07-30 08:11:07 +09:00
parent 1999d94b33
commit db7ddba735
4 changed files with 277 additions and 0 deletions

View File

@ -384,6 +384,16 @@ var playerLoop = ScriptBehaviourUpdateOrder.CurrentPlayerLoop;
PlayerLoopHelper.Initialize(ref playerLoop); PlayerLoopHelper.Initialize(ref playerLoop);
``` ```
You can diagnostic UniTask's player loop is ready by `PlayerLoopHelper.IsInjectedUniTaskPlayerLoop()`. And also `PlayerLoopHelper.DumpCurrentPlayerLoop` shows current all playerloop to console.
```csharp
void Start()
{
UnityEngine.Debug.Log("UniTaskPlayerLoop ready? " + PlayerLoopHelper.IsInjectedUniTaskPlayerLoop());
PlayerLoopHelper.DumpCurrentPlayerLoop();
}
```
async void vs async UniTaskVoid async void vs async UniTaskVoid
--- ---
`async void` is a standard C# task 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()`. `async void` is a standard C# task 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()`.
@ -826,6 +836,23 @@ foreach (var (type, size) in TaskPool.GetCacheSizeInfo())
> In UnityEditor profiler shows allocation of compiler generated AsyncStateMachine but it only occurs in debug(development) build. C# Compiler generate AsyncStateMachine as class on Debug build and as struct on Release build. > In UnityEditor profiler shows allocation of compiler generated AsyncStateMachine but it only occurs in debug(development) build. C# Compiler generate AsyncStateMachine as class on Debug build and as struct on Release build.
UniTaskSynchronizationContext
---
Unity's default SynchronizationContext(`UnitySynchronizationContext`) is poor implementation for performance. UniTask itself is bypass `SynchronizationContext`(and `ExecutionContext`) so does not use it but if exists in `async Task`, still used it. `UniTaskSynchronizationContext` is replacement of `UnitySynchronizationContext`, it is better for performance.
```csharp
public class SyncContextInjecter
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
public static void Inject()
{
SynchronizationContext.SetSynchronizationContext(new UniTaskSynchronizationContext());
}
}
```
This is an optional choice and is not always recommended; `UniTaskSynchronizationContext` is less performance than `async UniTask` and is not a complete UniTask replacement. It also does not guarantee full behavioral compatibility with the `UnitySynchronizationContext`.
API References 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). 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).

View File

@ -175,6 +175,32 @@ namespace Cysharp.Threading.Tasks
return dest; return dest;
} }
static PlayerLoopSystem[] InsertUniTaskSynchronizationContext(PlayerLoopSystem loopSystem)
{
var loop = new PlayerLoopSystem
{
type = typeof(UniTaskSynchronizationContext),
updateDelegate = UniTaskSynchronizationContext.Run
};
// Remove items from previous initializations.
var source = loopSystem.subSystemList
.Where(ls => ls.type != typeof(UniTaskSynchronizationContext))
.ToArray();
var dest = new System.Collections.Generic.List<PlayerLoopSystem>(source);
var index = dest.FindIndex(x => x.type.Name == "ScriptRunDelayedTasks");
if (index == -1)
{
index = dest.FindIndex(x => x.type.Name == "UniTaskLoopRunnerUpdate");
}
dest.Insert(index + 1, loop);
return dest.ToArray();
}
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void Init() static void Init()
{ {
@ -246,6 +272,8 @@ namespace Cysharp.Threading.Tasks
if (item != null) item.Run(); if (item != null) item.Run();
} }
} }
UniTaskSynchronizationContext.Run();
} }
#endif #endif
@ -293,6 +321,9 @@ namespace Cysharp.Threading.Tasks
typeof(UniTaskLoopRunners.UniTaskLoopRunnerPostLateUpdate), runners[12] = new PlayerLoopRunner(PlayerLoopTiming.PostLateUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerPostLateUpdate), runners[12] = new PlayerLoopRunner(PlayerLoopTiming.PostLateUpdate),
typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastPostLateUpdate), runners[13] = new PlayerLoopRunner(PlayerLoopTiming.LastPostLateUpdate)); typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastPostLateUpdate), runners[13] = new PlayerLoopRunner(PlayerLoopTiming.LastPostLateUpdate));
// Insert UniTaskSynchronizationContext to Update loop
copyList[4].subSystemList = InsertUniTaskSynchronizationContext(copyList[4]);
playerLoop.subSystemList = copyList; playerLoop.subSystemList = copyList;
PlayerLoop.SetPlayerLoop(playerLoop); PlayerLoop.SetPlayerLoop(playerLoop);
} }
@ -306,6 +337,56 @@ namespace Cysharp.Threading.Tasks
{ {
yielders[(int)timing].Enqueue(continuation); yielders[(int)timing].Enqueue(continuation);
} }
// Diagnostics helper
#if UNITY_2019_3_OR_NEWER
public static void DumpCurrentPlayerLoop()
{
var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop();
var sb = new System.Text.StringBuilder();
sb.AppendLine($"PlayerLoop List");
foreach (var header in playerLoop.subSystemList)
{
sb.AppendFormat("------{0}------", header.type.Name);
sb.AppendLine();
foreach (var subSystem in header.subSystemList)
{
sb.AppendFormat("{0}", subSystem.type.Name);
sb.AppendLine();
if (subSystem.subSystemList != null)
{
UnityEngine.Debug.LogWarning("More Subsystem:" + subSystem.subSystemList.Length);
}
}
}
UnityEngine.Debug.Log(sb.ToString());
}
public static bool IsInjectedUniTaskPlayerLoop()
{
var playerLoop = UnityEngine.LowLevel.PlayerLoop.GetCurrentPlayerLoop();
foreach (var header in playerLoop.subSystemList)
{
foreach (var subSystem in header.subSystemList)
{
if (subSystem.type == typeof(UniTaskLoopRunners.UniTaskLoopRunnerInitialization))
{
return true;
}
}
}
return false;
}
#endif
} }
} }

View File

@ -0,0 +1,158 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
namespace Cysharp.Threading.Tasks
{
public class UniTaskSynchronizationContext : SynchronizationContext
{
const int MaxArrayLength = 0X7FEFFFFF;
const int InitialSize = 16;
static SpinLock gate = new SpinLock();
static bool dequing = false;
static int actionListCount = 0;
static Callback[] actionList = new Callback[InitialSize];
static int waitingListCount = 0;
static Callback[] waitingList = new Callback[InitialSize];
static int opCount;
public override void Send(SendOrPostCallback d, object state)
{
d(state);
}
public override void Post(SendOrPostCallback d, object state)
{
bool lockTaken = false;
try
{
gate.Enter(ref lockTaken);
if (dequing)
{
// Ensure Capacity
if (waitingList.Length == waitingListCount)
{
var newLength = waitingListCount * 2;
if ((uint)newLength > MaxArrayLength) newLength = MaxArrayLength;
var newArray = new Callback[newLength];
Array.Copy(waitingList, newArray, waitingListCount);
waitingList = newArray;
}
waitingList[waitingListCount] = new Callback(d, state);
waitingListCount++;
}
else
{
// Ensure Capacity
if (actionList.Length == actionListCount)
{
var newLength = actionListCount * 2;
if ((uint)newLength > MaxArrayLength) newLength = MaxArrayLength;
var newArray = new Callback[newLength];
Array.Copy(actionList, newArray, actionListCount);
actionList = newArray;
}
actionList[actionListCount] = new Callback(d, state);
actionListCount++;
}
}
finally
{
if (lockTaken) gate.Exit(false);
}
}
public override void OperationStarted()
{
Interlocked.Increment(ref opCount);
}
public override void OperationCompleted()
{
Interlocked.Decrement(ref opCount);
}
public override SynchronizationContext CreateCopy()
{
return this;
}
// delegate entrypoint.
internal static void Run()
{
{
bool lockTaken = false;
try
{
gate.Enter(ref lockTaken);
if (actionListCount == 0) return;
dequing = true;
}
finally
{
if (lockTaken) gate.Exit(false);
}
}
for (int i = 0; i < actionListCount; i++)
{
var action = actionList[i];
actionList[i] = default;
action.Invoke();
}
{
bool lockTaken = false;
try
{
gate.Enter(ref lockTaken);
dequing = false;
var swapTempActionList = actionList;
actionListCount = waitingListCount;
actionList = waitingList;
waitingListCount = 0;
waitingList = swapTempActionList;
}
finally
{
if (lockTaken) gate.Exit(false);
}
}
}
[StructLayout(LayoutKind.Auto)]
readonly struct Callback
{
readonly SendOrPostCallback callback;
readonly object state;
public Callback(SendOrPostCallback callback, object state)
{
this.callback = callback;
this.state = state;
}
public void Invoke()
{
try
{
callback(state);
}
catch (Exception ex)
{
UnityEngine.Debug.LogException(ex);
}
}
}
}
}

View File

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