#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member using System; using System.Linq; using UnityEngine; using Cysharp.Threading.Tasks.Internal; using System.Threading; #if UNITY_2019_3_OR_NEWER using UnityEngine.LowLevel; using PlayerLoopType = UnityEngine.PlayerLoop; #else using UnityEngine.Experimental.LowLevel; using PlayerLoopType = UnityEngine.Experimental.PlayerLoop; #endif #if UNITY_EDITOR using UnityEditor; #endif namespace Cysharp.Threading.Tasks { public static class UniTaskLoopRunners { public struct UniTaskLoopRunnerInitialization { }; public struct UniTaskLoopRunnerEarlyUpdate { }; public struct UniTaskLoopRunnerFixedUpdate { }; public struct UniTaskLoopRunnerPreUpdate { }; public struct UniTaskLoopRunnerUpdate { }; public struct UniTaskLoopRunnerPreLateUpdate { }; public struct UniTaskLoopRunnerPostLateUpdate { }; // Last public struct UniTaskLoopRunnerLastInitialization { }; public struct UniTaskLoopRunnerLastEarlyUpdate { }; public struct UniTaskLoopRunnerLastFixedUpdate { }; public struct UniTaskLoopRunnerLastPreUpdate { }; public struct UniTaskLoopRunnerLastUpdate { }; public struct UniTaskLoopRunnerLastPreLateUpdate { }; public struct UniTaskLoopRunnerLastPostLateUpdate { }; // Yield public struct UniTaskLoopRunnerYieldInitialization { }; public struct UniTaskLoopRunnerYieldEarlyUpdate { }; public struct UniTaskLoopRunnerYieldFixedUpdate { }; public struct UniTaskLoopRunnerYieldPreUpdate { }; public struct UniTaskLoopRunnerYieldUpdate { }; public struct UniTaskLoopRunnerYieldPreLateUpdate { }; public struct UniTaskLoopRunnerYieldPostLateUpdate { }; // Yield Last public struct UniTaskLoopRunnerLastYieldInitialization { }; public struct UniTaskLoopRunnerLastYieldEarlyUpdate { }; public struct UniTaskLoopRunnerLastYieldFixedUpdate { }; public struct UniTaskLoopRunnerLastYieldPreUpdate { }; public struct UniTaskLoopRunnerLastYieldUpdate { }; public struct UniTaskLoopRunnerLastYieldPreLateUpdate { }; public struct UniTaskLoopRunnerLastYieldPostLateUpdate { }; #if UNITY_2020_2_OR_NEWER public struct UniTaskLoopRunnerTimeUpdate { }; public struct UniTaskLoopRunnerLastTimeUpdate { }; public struct UniTaskLoopRunnerYieldTimeUpdate { }; public struct UniTaskLoopRunnerLastYieldTimeUpdate { }; #endif } 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, #if UNITY_2020_2_OR_NEWER // Unity 2020.2 added TimeUpdate https://docs.unity3d.com/2020.2/Documentation/ScriptReference/PlayerLoop.TimeUpdate.html TimeUpdate = 14, LastTimeUpdate = 15, #endif } [Flags] public enum InjectPlayerLoopTimings { /// /// Preset: All loops(default). /// All = Initialization | LastInitialization | EarlyUpdate | LastEarlyUpdate | FixedUpdate | LastFixedUpdate | PreUpdate | LastPreUpdate | Update | LastUpdate | PreLateUpdate | LastPreLateUpdate | PostLateUpdate | LastPostLateUpdate #if UNITY_2020_2_OR_NEWER | TimeUpdate | LastTimeUpdate, #else , #endif /// /// Preset: All without last except LastPostLateUpdate. /// Standard = Initialization | EarlyUpdate | FixedUpdate | PreUpdate | Update | PreLateUpdate | PostLateUpdate | LastPostLateUpdate #if UNITY_2020_2_OR_NEWER | TimeUpdate #endif , /// /// Preset: Minimum pattern, Update | FixedUpdate | LastPostLateUpdate /// Minimum = Update | FixedUpdate | LastPostLateUpdate, // PlayerLoopTiming Initialization = 1, LastInitialization = 2, EarlyUpdate = 4, LastEarlyUpdate = 8, FixedUpdate = 16, LastFixedUpdate = 32, PreUpdate = 64, LastPreUpdate = 128, Update = 256, LastUpdate = 512, PreLateUpdate = 1024, LastPreLateUpdate = 2048, PostLateUpdate = 4096, LastPostLateUpdate = 8192 #if UNITY_2020_2_OR_NEWER , // Unity 2020.2 added TimeUpdate https://docs.unity3d.com/2020.2/Documentation/ScriptReference/PlayerLoop.TimeUpdate.html TimeUpdate = 16384, LastTimeUpdate = 32768 #endif } public interface IPlayerLoopItem { bool MoveNext(); } public static class PlayerLoopHelper { static readonly ContinuationQueue ThrowMarkerContinuationQueue = new ContinuationQueue(PlayerLoopTiming.Initialization); static readonly PlayerLoopRunner ThrowMarkerPlayerLoopRunner = new PlayerLoopRunner(PlayerLoopTiming.Initialization); public static SynchronizationContext UnitySynchronizationContext => unitySynchronizationContext; public static int MainThreadId => mainThreadId; internal static string ApplicationDataPath => applicationDataPath; public static bool IsMainThread => Thread.CurrentThread.ManagedThreadId == mainThreadId; static int mainThreadId; static string applicationDataPath; static SynchronizationContext unitySynchronizationContext; static ContinuationQueue[] yielders; static PlayerLoopRunner[] runners; internal static bool IsEditorApplicationQuitting { get; private set; } static PlayerLoopSystem[] InsertRunner(PlayerLoopSystem loopSystem, bool injectOnFirst, Type loopRunnerYieldType, ContinuationQueue cq, Type loopRunnerType, PlayerLoopRunner runner) { #if UNITY_EDITOR EditorApplication.playModeStateChanged += (state) => { if (state == PlayModeStateChange.EnteredEditMode || state == PlayModeStateChange.ExitingEditMode) { IsEditorApplicationQuitting = true; // run rest action before clear. if (runner != null) { runner.Run(); runner.Clear(); } if (cq != null) { cq.Run(); cq.Clear(); } IsEditorApplicationQuitting = false; } }; #endif var yieldLoop = new PlayerLoopSystem { type = loopRunnerYieldType, updateDelegate = cq.Run }; var runnerLoop = new PlayerLoopSystem { type = loopRunnerType, updateDelegate = runner.Run }; // Remove items from previous initializations. var source = RemoveRunner(loopSystem, loopRunnerYieldType, loopRunnerType); var dest = new PlayerLoopSystem[source.Length + 2]; Array.Copy(source, 0, dest, injectOnFirst ? 2 : 0, source.Length); if (injectOnFirst) { dest[0] = yieldLoop; dest[1] = runnerLoop; } else { dest[dest.Length - 2] = yieldLoop; dest[dest.Length - 1] = runnerLoop; } return dest; } static PlayerLoopSystem[] RemoveRunner(PlayerLoopSystem loopSystem, Type loopRunnerYieldType, Type loopRunnerType) { return loopSystem.subSystemList .Where(ls => ls.type != loopRunnerYieldType && ls.type != loopRunnerType) .ToArray(); } 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(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)] static void Init() { // capture default(unity) sync-context. unitySynchronizationContext = SynchronizationContext.Current; mainThreadId = Thread.CurrentThread.ManagedThreadId; try { applicationDataPath = Application.dataPath; } catch { } #if UNITY_EDITOR && UNITY_2019_3_OR_NEWER // When domain reload is disabled, re-initialization is required when entering play mode; // otherwise, pending tasks will leak between play mode sessions. var domainReloadDisabled = UnityEditor.EditorSettings.enterPlayModeOptionsEnabled && UnityEditor.EditorSettings.enterPlayModeOptions.HasFlag(UnityEditor.EnterPlayModeOptions.DisableDomainReload); if (!domainReloadDisabled && runners != null) return; #else if (runners != null) return; // already initialized #endif var playerLoop = #if UNITY_2019_3_OR_NEWER PlayerLoop.GetCurrentPlayerLoop(); #else PlayerLoop.GetDefaultPlayerLoop(); #endif Initialize(ref playerLoop); } #if UNITY_EDITOR [InitializeOnLoadMethod] static void InitOnEditor() { // Execute the play mode init method Init(); // register an Editor update delegate, used to forcing playerLoop update EditorApplication.update += ForceEditorPlayerLoopUpdate; } private static void ForceEditorPlayerLoopUpdate() { if (EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling || EditorApplication.isUpdating) { // Not in Edit mode, don't interfere return; } // EditorApplication.QueuePlayerLoopUpdate causes performance issue, don't call directly. // 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(); } } UniTaskSynchronizationContext.Run(); } #endif private static int FindLoopSystemIndex(PlayerLoopSystem[] playerLoopList, Type systemType) { for (int i = 0; i < playerLoopList.Length; i++) { if (playerLoopList[i].type == systemType) { return i; } } throw new Exception("Target PlayerLoopSystem does not found. Type:" + systemType.FullName); } static void InsertLoop(PlayerLoopSystem[] copyList, InjectPlayerLoopTimings injectTimings, Type loopType, InjectPlayerLoopTimings targetTimings, int index, bool injectOnFirst, Type loopRunnerYieldType, Type loopRunnerType, PlayerLoopTiming playerLoopTiming) { var i = FindLoopSystemIndex(copyList, loopType); if ((injectTimings & targetTimings) == targetTimings) { copyList[i].subSystemList = InsertRunner(copyList[i], injectOnFirst, loopRunnerYieldType, yielders[index] = new ContinuationQueue(playerLoopTiming), loopRunnerType, runners[index] = new PlayerLoopRunner(playerLoopTiming)); } else { copyList[i].subSystemList = RemoveRunner(copyList[i], loopRunnerYieldType, loopRunnerType); } } public static void Initialize(ref PlayerLoopSystem playerLoop, InjectPlayerLoopTimings injectTimings = InjectPlayerLoopTimings.All) { #if UNITY_2020_2_OR_NEWER yielders = new ContinuationQueue[16]; runners = new PlayerLoopRunner[16]; #else yielders = new ContinuationQueue[14]; runners = new PlayerLoopRunner[14]; #endif var copyList = playerLoop.subSystemList.ToArray(); // Initialization InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.Initialization), InjectPlayerLoopTimings.Initialization, 0, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldInitialization), typeof(UniTaskLoopRunners.UniTaskLoopRunnerInitialization), PlayerLoopTiming.Initialization); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.Initialization), InjectPlayerLoopTimings.LastInitialization, 1, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldInitialization), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastInitialization), PlayerLoopTiming.LastInitialization); // EarlyUpdate InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.EarlyUpdate), InjectPlayerLoopTimings.EarlyUpdate, 2, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldEarlyUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerEarlyUpdate), PlayerLoopTiming.EarlyUpdate); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.EarlyUpdate), InjectPlayerLoopTimings.LastEarlyUpdate, 3, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldEarlyUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastEarlyUpdate), PlayerLoopTiming.LastEarlyUpdate); // FixedUpdate InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.FixedUpdate), InjectPlayerLoopTimings.FixedUpdate, 4, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldFixedUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerFixedUpdate), PlayerLoopTiming.FixedUpdate); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.FixedUpdate), InjectPlayerLoopTimings.LastFixedUpdate, 5, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldFixedUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastFixedUpdate), PlayerLoopTiming.LastFixedUpdate); // PreUpdate InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.PreUpdate), InjectPlayerLoopTimings.PreUpdate, 6, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldPreUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerPreUpdate), PlayerLoopTiming.PreUpdate); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.PreUpdate), InjectPlayerLoopTimings.LastPreUpdate, 7, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldPreUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastPreUpdate), PlayerLoopTiming.LastPreUpdate); // Update InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.Update), InjectPlayerLoopTimings.Update, 8, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerUpdate), PlayerLoopTiming.Update); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.Update), InjectPlayerLoopTimings.LastUpdate, 9, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastUpdate), PlayerLoopTiming.LastUpdate); // PreLateUpdate InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.PreLateUpdate), InjectPlayerLoopTimings.PreLateUpdate, 10, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldPreLateUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerPreLateUpdate), PlayerLoopTiming.PreLateUpdate); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.PreLateUpdate), InjectPlayerLoopTimings.LastPreLateUpdate, 11, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldPreLateUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastPreLateUpdate), PlayerLoopTiming.LastPreLateUpdate); // PostLateUpdate InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.PostLateUpdate), InjectPlayerLoopTimings.PostLateUpdate, 12, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldPostLateUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerPostLateUpdate), PlayerLoopTiming.PostLateUpdate); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.PostLateUpdate), InjectPlayerLoopTimings.LastPostLateUpdate, 13, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldPostLateUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastPostLateUpdate), PlayerLoopTiming.LastPostLateUpdate); #if UNITY_2020_2_OR_NEWER // TimeUpdate InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.TimeUpdate), InjectPlayerLoopTimings.TimeUpdate, 14, true, typeof(UniTaskLoopRunners.UniTaskLoopRunnerYieldTimeUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerTimeUpdate), PlayerLoopTiming.TimeUpdate); InsertLoop(copyList, injectTimings, typeof(PlayerLoopType.TimeUpdate), InjectPlayerLoopTimings.LastTimeUpdate, 15, false, typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastYieldTimeUpdate), typeof(UniTaskLoopRunners.UniTaskLoopRunnerLastTimeUpdate), PlayerLoopTiming.LastTimeUpdate); #endif // Insert UniTaskSynchronizationContext to Update loop var i = FindLoopSystemIndex(copyList, typeof(PlayerLoopType.Update)); copyList[i].subSystemList = InsertUniTaskSynchronizationContext(copyList[i]); playerLoop.subSystemList = copyList; PlayerLoop.SetPlayerLoop(playerLoop); } public static void AddAction(PlayerLoopTiming timing, IPlayerLoopItem action) { var runner = runners[(int)timing]; if (runner == null) { ThrowInvalidLoopTiming(timing); } runner.AddAction(action); } static void ThrowInvalidLoopTiming(PlayerLoopTiming playerLoopTiming) { throw new InvalidOperationException("Target playerLoopTiming is not injected. Please check PlayerLoopHelper.Initialize. PlayerLoopTiming:" + playerLoopTiming); } public static void AddContinuation(PlayerLoopTiming timing, Action continuation) { var q = yielders[(int)timing]; if (q == null) { ThrowInvalidLoopTiming(timing); } q.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 } }