using System; using System.Runtime.CompilerServices; using UnityEngine; using LitMotion.Collections; #if UNITY_EDITOR using UnityEditor; #endif namespace LitMotion { /// /// Motion dispatcher. /// public static class MotionDispatcher { static class StorageCache where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { public static MotionStorage initialization; public static MotionStorage earlyUpdate; public static MotionStorage fixedUpdate; public static MotionStorage preUpdate; public static MotionStorage update; public static MotionStorage preLateUpdate; public static MotionStorage postLateUpdate; public static MotionStorage timeUpdate; public static MotionStorage GetOrCreate(PlayerLoopTiming playerLoopTiming) { return playerLoopTiming switch { PlayerLoopTiming.Initialization => CreateIfNull(ref initialization), PlayerLoopTiming.EarlyUpdate => CreateIfNull(ref earlyUpdate), PlayerLoopTiming.FixedUpdate => CreateIfNull(ref fixedUpdate), PlayerLoopTiming.PreUpdate => CreateIfNull(ref preUpdate), PlayerLoopTiming.Update => CreateIfNull(ref update), PlayerLoopTiming.PreLateUpdate => CreateIfNull(ref preLateUpdate), PlayerLoopTiming.PostLateUpdate => CreateIfNull(ref postLateUpdate), PlayerLoopTiming.TimeUpdate => CreateIfNull(ref timeUpdate), _ => null, }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] static MotionStorage CreateIfNull(ref MotionStorage storage) { if (storage == null) { storage = new MotionStorage(MotionStorageManager.CurrentStorageId); MotionStorageManager.AddStorage(storage); } return storage; } } static class RunnerCache where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { public static UpdateRunner initialization; public static UpdateRunner earlyUpdate; public static UpdateRunner fixedUpdate; public static UpdateRunner preUpdate; public static UpdateRunner update; public static UpdateRunner preLateUpdate; public static UpdateRunner postLateUpdate; public static UpdateRunner timeUpdate; public static (UpdateRunner runner, bool isCreated) GetOrCreate(PlayerLoopTiming playerLoopTiming, MotionStorage storage) { return playerLoopTiming switch { PlayerLoopTiming.Initialization => CreateIfNull(playerLoopTiming, ref initialization, storage), PlayerLoopTiming.EarlyUpdate => CreateIfNull(playerLoopTiming, ref earlyUpdate, storage), PlayerLoopTiming.FixedUpdate => CreateIfNull(playerLoopTiming, ref fixedUpdate, storage), PlayerLoopTiming.PreUpdate => CreateIfNull(playerLoopTiming, ref preUpdate, storage), PlayerLoopTiming.Update => CreateIfNull(playerLoopTiming, ref update, storage), PlayerLoopTiming.PreLateUpdate => CreateIfNull(playerLoopTiming, ref preLateUpdate, storage), PlayerLoopTiming.PostLateUpdate => CreateIfNull(playerLoopTiming, ref postLateUpdate, storage), PlayerLoopTiming.TimeUpdate => CreateIfNull(playerLoopTiming, ref timeUpdate, storage), _ => default, }; } [MethodImpl(MethodImplOptions.AggressiveInlining)] static (UpdateRunner, bool) CreateIfNull(PlayerLoopTiming playerLoopTiming, ref UpdateRunner runner, MotionStorage storage) { if (runner == null) { if (playerLoopTiming == PlayerLoopTiming.FixedUpdate) { runner = new UpdateRunner(storage, Time.fixedTimeAsDouble, Time.fixedUnscaledTimeAsDouble, Time.realtimeSinceStartupAsDouble); } else { runner = new UpdateRunner(storage, Time.timeAsDouble, Time.unscaledTimeAsDouble, Time.realtimeSinceStartupAsDouble); } GetRunnerList(playerLoopTiming).Add(runner); return (runner, true); } return (runner, false); } } static FastListCore initializationRunners; static FastListCore earlyUpdateRunners; static FastListCore fixedUpdateRunners; static FastListCore preUpdateRunners; static FastListCore updateRunners; static FastListCore preLateUpdateRunners; static FastListCore postLateUpdateRunners; static FastListCore timeUpdateRunners; internal static FastListCore EmptyList = FastListCore.Empty; [MethodImpl(MethodImplOptions.AggressiveInlining)] static ref FastListCore GetRunnerList(PlayerLoopTiming playerLoopTiming) { // FastListCore must be passed as ref switch (playerLoopTiming) { default: return ref EmptyList; case PlayerLoopTiming.Initialization: return ref initializationRunners; case PlayerLoopTiming.EarlyUpdate: return ref earlyUpdateRunners; case PlayerLoopTiming.FixedUpdate: return ref fixedUpdateRunners; case PlayerLoopTiming.PreUpdate: return ref preUpdateRunners; case PlayerLoopTiming.Update: return ref updateRunners; case PlayerLoopTiming.PreLateUpdate: return ref preLateUpdateRunners; case PlayerLoopTiming.PostLateUpdate: return ref postLateUpdateRunners; case PlayerLoopTiming.TimeUpdate: return ref timeUpdateRunners; }; } static Action unhandledException = DefaultUnhandledExceptionHandler; static readonly PlayerLoopTiming[] playerLoopTimings = (PlayerLoopTiming[])Enum.GetValues(typeof(PlayerLoopTiming)); [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] static void Init() { Clear(); } /// /// Set handling of unhandled exceptions. /// public static void RegisterUnhandledExceptionHandler(Action unhandledExceptionHandler) { unhandledException = unhandledExceptionHandler; } /// /// Get handling of unhandled exceptions. /// public static Action GetUnhandledExceptionHandler() { return unhandledException; } static void DefaultUnhandledExceptionHandler(Exception exception) { Debug.LogException(exception); } /// /// Cancel all motions. /// public static void Clear() { foreach (var playerLoopTiming in playerLoopTimings) { var span = GetRunnerList(playerLoopTiming).AsSpan(); for (int i = 0; i < span.Length; i++) { span[i].Reset(); } } } /// /// Ensures the storage capacity until it reaches at least `capacity`. /// /// The minimum capacity to ensure. public static void EnsureStorageCapacity(int capacity) where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { foreach (var playerLoopTiming in playerLoopTimings) { StorageCache.GetOrCreate(playerLoopTiming).EnsureCapacity(capacity); } #if UNITY_EDITOR EditorMotionDispatcher.EnsureStorageCapacity(capacity); #endif } internal static MotionHandle Schedule(in MotionData data, in MotionCallbackData callbackData, PlayerLoopTiming playerLoopTiming) where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { var storage = StorageCache.GetOrCreate(playerLoopTiming); RunnerCache.GetOrCreate(playerLoopTiming, storage); var (EntryIndex, Version) = storage.Append(data, callbackData); return new MotionHandle() { StorageId = storage.StorageId, Index = EntryIndex, Version = Version }; } internal static void Update(PlayerLoopTiming playerLoopTiming) { #if UNITY_EDITOR if (!EditorApplication.isPlaying) return; #endif var span = GetRunnerList(playerLoopTiming).AsSpan(); if (playerLoopTiming == PlayerLoopTiming.FixedUpdate) { for (int i = 0; i < span.Length; i++) span[i].Update(Time.fixedTimeAsDouble, Time.fixedUnscaledTimeAsDouble, Time.realtimeSinceStartupAsDouble); } else { for (int i = 0; i < span.Length; i++) span[i].Update(Time.timeAsDouble, Time.unscaledTimeAsDouble, Time.realtimeSinceStartupAsDouble); } } } #if UNITY_EDITOR internal static class EditorMotionDispatcher { static class Cache where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { static MotionStorage storage; static UpdateRunner updateRunner; public static MotionStorage GetOrCreateStorage() { if (storage == null) { storage = new MotionStorage(MotionStorageManager.CurrentStorageId); MotionStorageManager.AddStorage(storage); } return storage; } public static void InitUpdateRunner() { if (updateRunner == null) { var time = EditorApplication.timeSinceStartup; updateRunner = new UpdateRunner(storage, time, time, time); updateRunners.Add(updateRunner); } } } static FastListCore updateRunners; public static MotionHandle Schedule(in MotionData data, in MotionCallbackData callbackData) where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { var storage = Cache.GetOrCreateStorage(); Cache.InitUpdateRunner(); var (EntryIndex, Version) = storage.Append(data, callbackData); return new MotionHandle() { StorageId = storage.StorageId, Index = EntryIndex, Version = Version }; } public static void EnsureStorageCapacity(int capacity) where TValue : unmanaged where TOptions : unmanaged, IMotionOptions where TAdapter : unmanaged, IMotionAdapter { Cache.GetOrCreateStorage().EnsureCapacity(capacity); } [InitializeOnLoadMethod] static void Init() { EditorApplication.update += Update; } static void Update() { var span = updateRunners.AsSpan(); for (int i = 0; i < span.Length; i++) { span[i].Update(EditorApplication.timeSinceStartup, EditorApplication.timeSinceStartup, Time.realtimeSinceStartupAsDouble); } } } #endif }