MotionStorage.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using LitMotion.Collections;
  4. using Unity.Collections;
  5. using Unity.Collections.LowLevel.Unsafe;
  6. // TODO: Constantize the exception message
  7. namespace LitMotion
  8. {
  9. internal struct StorageEntry : IEquatable<StorageEntry>
  10. {
  11. public int? Next;
  12. public int DenseIndex;
  13. public int Version;
  14. public readonly bool Equals(StorageEntry other)
  15. {
  16. return other.Next == Next && other.DenseIndex == DenseIndex && other.Version == Version;
  17. }
  18. public override readonly bool Equals(object obj)
  19. {
  20. if (obj is StorageEntry entry) return Equals(entry);
  21. return false;
  22. }
  23. public override readonly int GetHashCode()
  24. {
  25. return HashCode.Combine(Next, DenseIndex, Version);
  26. }
  27. }
  28. internal unsafe interface IMotionStorage
  29. {
  30. bool IsActive(MotionHandle handle);
  31. void Cancel(MotionHandle handle);
  32. void Complete(MotionHandle handle);
  33. ref MotionDataCore GetDataRef(MotionHandle handle);
  34. ref MotionCallbackData GetCallbackDataRef(MotionHandle handle);
  35. void Reset();
  36. }
  37. internal sealed class StorageEntryList
  38. {
  39. public StorageEntryList(int initialCapacity = 32)
  40. {
  41. entries = new StorageEntry[initialCapacity];
  42. Reset();
  43. }
  44. StorageEntry[] entries;
  45. int? freeEntry;
  46. public StorageEntry this[int index]
  47. {
  48. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  49. get => entries[index];
  50. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  51. set => entries[index] = value;
  52. }
  53. public void EnsureCapacity(int capacity)
  54. {
  55. var currentLength = entries.Length;
  56. if (currentLength >= capacity) return;
  57. Array.Resize(ref entries, capacity);
  58. for (int i = currentLength; i < entries.Length; i++)
  59. {
  60. entries[i] = new() { Next = i == capacity - 1 ? freeEntry : i + 1, DenseIndex = -1, Version = 1 };
  61. }
  62. freeEntry = currentLength;
  63. }
  64. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  65. public StorageEntry Alloc(int denseIndex, out int entryIndex)
  66. {
  67. // Ensure array capacity
  68. if (freeEntry == null)
  69. {
  70. var currentLength = entries.Length;
  71. EnsureCapacity(entries.Length * 2);
  72. freeEntry = currentLength;
  73. }
  74. // Find free entry
  75. entryIndex = freeEntry.Value;
  76. var entry = entries[entryIndex];
  77. freeEntry = entry.Next;
  78. entry.Next = null;
  79. entry.DenseIndex = denseIndex;
  80. entries[entryIndex] = entry;
  81. return entry;
  82. }
  83. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  84. public void Free(int index)
  85. {
  86. var entry = entries[index];
  87. entry.Next = freeEntry;
  88. entry.Version++;
  89. entries[index] = entry;
  90. freeEntry = index;
  91. }
  92. public void Reset()
  93. {
  94. for (int i = 0; i < entries.Length; i++)
  95. {
  96. entries[i] = new() { Next = i == entries.Length - 1 ? null : i + 1, DenseIndex = -1, Version = 1 };
  97. }
  98. freeEntry = 0;
  99. }
  100. }
  101. internal sealed class MotionStorage<TValue, TOptions, TAdapter> : IMotionStorage
  102. where TValue : unmanaged
  103. where TOptions : unmanaged, IMotionOptions
  104. where TAdapter : unmanaged, IMotionAdapter<TValue, TOptions>
  105. {
  106. public MotionStorage(int id)
  107. {
  108. StorageId = id;
  109. AllocatorHelper = RewindableAllocatorFactory.CreateAllocator();
  110. }
  111. // Entry
  112. readonly StorageEntryList entries = new(InitialCapacity);
  113. // Data
  114. public int?[] toEntryIndex = new int?[InitialCapacity];
  115. public MotionData<TValue, TOptions>[] dataArray = new MotionData<TValue, TOptions>[InitialCapacity];
  116. public MotionCallbackData[] callbacksArray = new MotionCallbackData[InitialCapacity];
  117. // Allocator
  118. AllocatorHelper<RewindableAllocator> AllocatorHelper;
  119. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  120. public Span<MotionData<TValue, TOptions>> GetDataSpan() => dataArray.AsSpan(0, tail);
  121. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  122. public Span<MotionCallbackData> GetCallbacksSpan() => callbacksArray.AsSpan(0, tail);
  123. int tail;
  124. const int InitialCapacity = 8;
  125. public int StorageId { get; }
  126. public int Count => tail;
  127. public (int EntryIndex, int Version) Append(in MotionData<TValue, TOptions> data, in MotionCallbackData callbacks)
  128. {
  129. if (tail == dataArray.Length)
  130. {
  131. var newLength = tail * 2;
  132. Array.Resize(ref toEntryIndex, newLength);
  133. Array.Resize(ref dataArray, newLength);
  134. Array.Resize(ref callbacksArray, newLength);
  135. }
  136. var entry = entries.Alloc(tail, out var entryIndex);
  137. #if LITMOTION_ENABLE_MOTION_LOG
  138. UnityEngine.Debug.Log("[Add] Entry:" + entryIndex + " Dense:" + entry.DenseIndex + " Version:" + entry.Version);
  139. #endif
  140. var prevAnimationCurve = dataArray[tail].Core.AnimationCurve;
  141. toEntryIndex[tail] = entryIndex;
  142. dataArray[tail] = data;
  143. callbacksArray[tail] = callbacks;
  144. if (data.Core.Ease == Ease.CustomAnimationCurve)
  145. {
  146. if (!prevAnimationCurve.IsCreated)
  147. {
  148. #if LITMOTION_COLLECTIONS_2_0_OR_NEWER
  149. prevAnimationCurve = new NativeAnimationCurve(AllocatorHelper.Allocator.Handle);
  150. #else
  151. prevAnimationCurve = new UnsafeAnimationCurve(AllocatorHelper.Allocator.Handle);
  152. #endif
  153. }
  154. prevAnimationCurve.CopyFrom(data.Core.AnimationCurve);
  155. dataArray[tail].Core.AnimationCurve = prevAnimationCurve;
  156. }
  157. tail++;
  158. return (entryIndex, entry.Version);
  159. }
  160. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  161. void RemoveAt(int denseIndex)
  162. {
  163. tail--;
  164. // swap elements
  165. dataArray[denseIndex] = dataArray[tail];
  166. // dataArray[tail] = default;
  167. callbacksArray[denseIndex] = callbacksArray[tail];
  168. // callbacksArray[tail] = default;
  169. // swap entry indexes
  170. var prevEntryIndex = toEntryIndex[denseIndex];
  171. var currentEntryIndex = toEntryIndex[denseIndex] = toEntryIndex[tail];
  172. // toEntryIndex[tail] = default;
  173. // update entry
  174. if (currentEntryIndex != null)
  175. {
  176. var index = (int)currentEntryIndex;
  177. var entry = entries[index];
  178. entry.DenseIndex = denseIndex;
  179. entries[index] = entry;
  180. }
  181. // free entry
  182. if (prevEntryIndex != null)
  183. {
  184. entries.Free((int)prevEntryIndex);
  185. }
  186. #if LITMOTION_ENABLE_MOTION_LOG
  187. var v = entries[(int)prevEntryIndex].Version - 1;
  188. UnityEngine.Debug.Log("[Remove] Entry:" + prevEntryIndex + " Dense:" + denseIndex + " Version:" + v);
  189. #endif
  190. }
  191. public void RemoveAll(NativeList<int> indexes)
  192. {
  193. var entryIndexes = new NativeArray<int>(indexes.Length, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
  194. var lastCallbacksSpan = GetCallbacksSpan();
  195. for (int i = 0; i < entryIndexes.Length; i++)
  196. {
  197. entryIndexes[i] = (int)toEntryIndex[indexes[i]];
  198. }
  199. for (int i = 0; i < entryIndexes.Length; i++)
  200. {
  201. RemoveAt(entries[entryIndexes[i]].DenseIndex);
  202. }
  203. // Avoid Memory leak
  204. lastCallbacksSpan[tail..].Clear();
  205. entryIndexes.Dispose();
  206. }
  207. public void EnsureCapacity(int capacity)
  208. {
  209. if (capacity > dataArray.Length)
  210. {
  211. Array.Resize(ref toEntryIndex, capacity);
  212. Array.Resize(ref dataArray, capacity);
  213. Array.Resize(ref callbacksArray, capacity);
  214. entries.EnsureCapacity(capacity);
  215. }
  216. }
  217. public void Cancel(MotionHandle handle)
  218. {
  219. var entry = entries[handle.Index];
  220. var denseIndex = entry.DenseIndex;
  221. if (denseIndex < 0 || denseIndex >= dataArray.Length)
  222. {
  223. throw new ArgumentException("Motion has been destroyed or no longer exists.");
  224. }
  225. ref var motion = ref GetDataSpan()[denseIndex];
  226. var version = entry.Version;
  227. if (version <= 0 || version != handle.Version || motion.Core.Status == MotionStatus.None)
  228. {
  229. throw new ArgumentException("Motion has been destroyed or no longer exists.");
  230. }
  231. motion.Core.Status = MotionStatus.Canceled;
  232. ref var callbackData = ref GetCallbacksSpan()[denseIndex];
  233. try
  234. {
  235. callbackData.OnCancelAction?.Invoke();
  236. }
  237. catch (Exception ex)
  238. {
  239. MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
  240. }
  241. }
  242. public void Complete(MotionHandle handle)
  243. {
  244. var entry = entries[handle.Index];
  245. var denseIndex = entry.DenseIndex;
  246. if (denseIndex < 0 || denseIndex >= tail)
  247. {
  248. throw new ArgumentException("Motion has been destroyed or no longer exists.");
  249. }
  250. ref var motion = ref GetDataSpan()[denseIndex];
  251. var version = entry.Version;
  252. if (version <= 0 || version != handle.Version || motion.Core.Status == MotionStatus.None)
  253. {
  254. throw new ArgumentException("Motion has been destroyed or no longer exists.");
  255. }
  256. if (motion.Core.Loops < 0)
  257. {
  258. UnityEngine.Debug.LogWarning("[LitMotion] Complete was ignored because it is not possible to complete a motion that loops infinitely. If you want to end the motion, call Cancel() instead.");
  259. return;
  260. }
  261. ref var callbackData = ref GetCallbacksSpan()[denseIndex];
  262. if (callbackData.IsCallbackRunning)
  263. {
  264. throw new InvalidOperationException("Recursion of Complete call was detected.");
  265. }
  266. callbackData.IsCallbackRunning = true;
  267. // To avoid duplication of Complete processing, it is treated as canceled internally.
  268. motion.Core.Status = MotionStatus.Canceled;
  269. var endProgress = motion.Core.LoopType switch
  270. {
  271. LoopType.Restart => 1f,
  272. LoopType.Yoyo => motion.Core.Loops % 2 == 0 ? 0f : 1f,
  273. LoopType.Incremental => motion.Core.Loops,
  274. _ => 1f
  275. };
  276. var easedEndProgress = motion.Core.Ease switch
  277. {
  278. Ease.CustomAnimationCurve => motion.Core.AnimationCurve.Evaluate(endProgress),
  279. _ => EaseUtility.Evaluate(endProgress, motion.Core.Ease),
  280. };
  281. var endValue = default(TAdapter).Evaluate(
  282. ref motion.StartValue,
  283. ref motion.EndValue,
  284. ref motion.Options,
  285. new() { Progress = easedEndProgress }
  286. );
  287. try
  288. {
  289. callbackData.InvokeUnsafe(endValue);
  290. }
  291. catch (Exception ex)
  292. {
  293. MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
  294. }
  295. try
  296. {
  297. callbackData.OnCompleteAction?.Invoke();
  298. }
  299. catch (Exception ex)
  300. {
  301. MotionDispatcher.GetUnhandledExceptionHandler()?.Invoke(ex);
  302. }
  303. callbackData.IsCallbackRunning = false;
  304. }
  305. public bool IsActive(MotionHandle handle)
  306. {
  307. var entry = entries[handle.Index];
  308. var denseIndex = entry.DenseIndex;
  309. if (denseIndex < 0 || denseIndex >= dataArray.Length) return false;
  310. var version = entry.Version;
  311. if (version <= 0 || version != handle.Version) return false;
  312. var motion = dataArray[denseIndex];
  313. return motion.Core.Status is MotionStatus.Scheduled or MotionStatus.Delayed or MotionStatus.Playing;
  314. }
  315. public ref MotionCallbackData GetCallbackDataRef(MotionHandle handle)
  316. {
  317. CheckIndex(handle);
  318. return ref callbacksArray[entries[handle.Index].DenseIndex];
  319. }
  320. public ref MotionDataCore GetDataRef(MotionHandle handle)
  321. {
  322. CheckIndex(handle);
  323. return ref UnsafeUtility.As<MotionData<TValue, TOptions>, MotionDataCore>(ref dataArray[entries[handle.Index].DenseIndex]);
  324. }
  325. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  326. void CheckIndex(MotionHandle handle)
  327. {
  328. var entry = entries[handle.Index];
  329. var denseIndex = entry.DenseIndex;
  330. if (denseIndex < 0 || denseIndex >= dataArray.Length)
  331. {
  332. throw new ArgumentException("Motion has been destroyed or no longer exists.");
  333. }
  334. var version = entry.Version;
  335. if (version <= 0 || version != handle.Version || dataArray[denseIndex].Core.Status == MotionStatus.None)
  336. {
  337. throw new ArgumentException("Motion has been destroyed or no longer exists.");
  338. }
  339. }
  340. public void Reset()
  341. {
  342. entries.Reset();
  343. toEntryIndex.AsSpan().Clear();
  344. dataArray.AsSpan().Clear();
  345. callbacksArray.AsSpan().Clear();
  346. tail = 0;
  347. AllocatorHelper.Allocator.Rewind();
  348. }
  349. }
  350. }