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
}