using System; using UnityEngine; namespace Cysharp.Threading.Tasks.Internal { internal sealed class PlayerLoopRunner { const int InitialSize = 16; readonly PlayerLoopTiming timing; readonly object runningAndQueueLock = new object(); readonly object arrayLock = new object(); readonly Action unhandledExceptionCallback; int tail = 0; bool running = false; IPlayerLoopItem[] loopItems = new IPlayerLoopItem[InitialSize]; MinimumQueue waitQueue = new MinimumQueue(InitialSize); public PlayerLoopRunner(PlayerLoopTiming timing) { this.unhandledExceptionCallback = ex => Debug.LogException(ex); this.timing = timing; } public void AddAction(IPlayerLoopItem item) { lock (runningAndQueueLock) { if (running) { waitQueue.Enqueue(item); return; } } lock (arrayLock) { // Ensure Capacity if (loopItems.Length == tail) { Array.Resize(ref loopItems, checked(tail * 2)); } loopItems[tail++] = item; } } public int Clear() { lock (arrayLock) { var rest = 0; for (var index = 0; index < loopItems.Length; index++) { if (loopItems[index] != null) { rest++; } loopItems[index] = null; } tail = 0; return rest; } } // delegate entrypoint. public void Run() { // for debugging, create named stacktrace. #if DEBUG switch (timing) { case PlayerLoopTiming.Initialization: Initialization(); break; case PlayerLoopTiming.LastInitialization: LastInitialization(); break; case PlayerLoopTiming.EarlyUpdate: EarlyUpdate(); break; case PlayerLoopTiming.LastEarlyUpdate: LastEarlyUpdate(); break; case PlayerLoopTiming.FixedUpdate: FixedUpdate(); break; case PlayerLoopTiming.LastFixedUpdate: LastFixedUpdate(); break; case PlayerLoopTiming.PreUpdate: PreUpdate(); break; case PlayerLoopTiming.LastPreUpdate: LastPreUpdate(); break; case PlayerLoopTiming.Update: Update(); break; case PlayerLoopTiming.LastUpdate: LastUpdate(); break; case PlayerLoopTiming.PreLateUpdate: PreLateUpdate(); break; case PlayerLoopTiming.LastPreLateUpdate: LastPreLateUpdate(); break; case PlayerLoopTiming.PostLateUpdate: PostLateUpdate(); break; case PlayerLoopTiming.LastPostLateUpdate: LastPostLateUpdate(); break; #if UNITY_2020_2_OR_NEWER case PlayerLoopTiming.TimeUpdate: TimeUpdate(); break; case PlayerLoopTiming.LastTimeUpdate: LastTimeUpdate(); break; #endif default: break; } #else RunCore(); #endif } void Initialization() => RunCore(); void LastInitialization() => RunCore(); void EarlyUpdate() => RunCore(); void LastEarlyUpdate() => RunCore(); void FixedUpdate() => RunCore(); void LastFixedUpdate() => RunCore(); void PreUpdate() => RunCore(); void LastPreUpdate() => RunCore(); void Update() => RunCore(); void LastUpdate() => RunCore(); void PreLateUpdate() => RunCore(); void LastPreLateUpdate() => RunCore(); void PostLateUpdate() => RunCore(); void LastPostLateUpdate() => RunCore(); #if UNITY_2020_2_OR_NEWER void TimeUpdate() => RunCore(); void LastTimeUpdate() => RunCore(); #endif [System.Diagnostics.DebuggerHidden] void RunCore() { lock (runningAndQueueLock) { running = true; } lock (arrayLock) { var j = tail - 1; for (int i = 0; i < loopItems.Length; i++) { var action = loopItems[i]; if (action != null) { try { if (!action.MoveNext()) { loopItems[i] = null; } else { continue; // next i } } catch (Exception ex) { loopItems[i] = null; try { unhandledExceptionCallback(ex); } catch { } } } // find null, loop from tail while (i < j) { var fromTail = loopItems[j]; if (fromTail != null) { try { if (!fromTail.MoveNext()) { loopItems[j] = null; j--; continue; // next j } else { // swap loopItems[i] = fromTail; loopItems[j] = null; j--; goto NEXT_LOOP; // next i } } catch (Exception ex) { loopItems[j] = null; j--; try { unhandledExceptionCallback(ex); } catch { } continue; // next j } } else { j--; } } tail = i; // loop end break; // LOOP END NEXT_LOOP: continue; } lock (runningAndQueueLock) { running = false; while (waitQueue.Count != 0) { if (loopItems.Length == tail) { Array.Resize(ref loopItems, checked(tail * 2)); } loopItems[tail++] = waitQueue.Dequeue(); } } } } } }