using System.Collections; using UnityEngine; using System.Threading; namespace UnityAsync { [AddComponentMenu("")] // don't show in menu public partial class AsyncManager : MonoBehaviour { /// /// The frame count in the currently active update loop. /// public static int CurrentFrameCount { get; private set; } /// /// The time in the currently active update loop. /// public static float CurrentTime { get; private set; } /// /// The unscaled time in the currently active update loop. /// public static float CurrentUnscaledTime { get; private set; } /// /// Unity's . /// public static SynchronizationContext UnitySyncContext { get; private set; } /// /// Background (thread pool) . /// public static SynchronizationContext BackgroundSyncContext { get; private set; } /// /// Returns true if called from Unity's . /// public static bool InUnityContext => Thread.CurrentThread.ManagedThreadId == unityThreadId; public static AsyncManager Instance { get; private set; } static int unityThreadId, updateCount, lateCount, fixedCount; static ContinuationProcessorGroup updates, lateUpdates, fixedUpdates; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Initialize() { unityThreadId = Thread.CurrentThread.ManagedThreadId; UnitySyncContext = SynchronizationContext.Current; BackgroundSyncContext = new SynchronizationContext(); // TODO: confirm this produces desired behaviour updates = new ContinuationProcessorGroup(); lateUpdates = new ContinuationProcessorGroup(); fixedUpdates = new ContinuationProcessorGroup(); Instance = new GameObject("Async Manager").AddComponent(); DontDestroyOnLoad(Instance); } /// /// Initializes the manager explicitly. Typically used when running Unity Editor tests (NUnit unit tests). /// public static void InitializeForEditorTests() { Initialize(); // Do not run tasks in background when testing: BackgroundSyncContext = UnitySyncContext; } /// /// Queues a continuation. /// Intended for internal use only - you shouldn't need to invoke this. /// public static void AddContinuation(in T cont) where T : IAwaitInstructionAwaiter { switch(cont.Scheduler) { case FrameScheduler.Update: updates.Add(cont); break; case FrameScheduler.LateUpdate: lateUpdates.Add(cont); break; case FrameScheduler.FixedUpdate: fixedUpdates.Add(cont); break; } } /// /// Start a coroutine from any context without requiring a MonoBehaviour. /// public new static Coroutine StartCoroutine(IEnumerator coroutine) => ((MonoBehaviour)Instance).StartCoroutine(coroutine); void Update() { CurrentFrameCount = ++updateCount; if(CurrentFrameCount <= 1) return; CurrentTime = Time.time; CurrentUnscaledTime = Time.unscaledTime; updates.Process(); } void LateUpdate() { CurrentFrameCount = ++lateCount; if(CurrentFrameCount <= 1) return; lateUpdates.Process(); } void FixedUpdate() { CurrentFrameCount = ++fixedCount; if(CurrentFrameCount <= 1) return; fixedUpdates.Process(); } } }