MotionBuilder.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using Unity.Collections;
  4. using UnityEngine;
  5. using LitMotion.Collections;
  6. namespace LitMotion
  7. {
  8. internal class MotionBuilderBuffer<TValue, TOptions>
  9. where TValue : unmanaged
  10. where TOptions : unmanaged, IMotionOptions
  11. {
  12. static MotionBuilderBuffer<TValue, TOptions> PoolRoot = new();
  13. public static MotionBuilderBuffer<TValue, TOptions> Rent()
  14. {
  15. MotionBuilderBuffer<TValue, TOptions> result;
  16. if (PoolRoot == null)
  17. {
  18. result = new();
  19. }
  20. else
  21. {
  22. result = PoolRoot;
  23. PoolRoot = PoolRoot.NextNode;
  24. result.NextNode = null;
  25. }
  26. return result;
  27. }
  28. public static void Return(MotionBuilderBuffer<TValue, TOptions> buffer)
  29. {
  30. buffer.Version++;
  31. buffer.Data = MotionData<TValue, TOptions>.Default;
  32. buffer.CallbackData = MotionCallbackData.Default;
  33. buffer.AnimationCurve = default;
  34. buffer.Scheduler = default;
  35. buffer.IsPreserved = default;
  36. buffer.BindOnSchedule = default;
  37. if (buffer.Version != ushort.MaxValue)
  38. {
  39. buffer.NextNode = PoolRoot;
  40. PoolRoot = buffer;
  41. }
  42. }
  43. public ushort Version;
  44. public MotionBuilderBuffer<TValue, TOptions> NextNode;
  45. public bool IsPreserved;
  46. public bool BindOnSchedule;
  47. public MotionData<TValue, TOptions> Data = MotionData<TValue, TOptions>.Default;
  48. public MotionCallbackData CallbackData = MotionCallbackData.Default;
  49. public AnimationCurve AnimationCurve;
  50. public IMotionScheduler Scheduler;
  51. }
  52. /// <summary>
  53. /// Supports construction, scheduling, and binding of motion entities.
  54. /// </summary>
  55. /// <typeparam name="TValue">The type of value to animate</typeparam>
  56. /// <typeparam name="TOptions">The type of special parameters given to the motion data</typeparam>
  57. /// <typeparam name="TAdapter">The type of adapter that support value animation</typeparam>
  58. public struct MotionBuilder<TValue, TOptions, TAdapter> : IDisposable
  59. where TValue : unmanaged
  60. where TOptions : unmanaged, IMotionOptions
  61. where TAdapter : unmanaged, IMotionAdapter<TValue, TOptions>
  62. {
  63. internal MotionBuilder(MotionBuilderBuffer<TValue, TOptions> buffer)
  64. {
  65. this.buffer = buffer;
  66. this.version = buffer.Version;
  67. }
  68. internal ushort version;
  69. internal MotionBuilderBuffer<TValue, TOptions> buffer;
  70. /// <summary>
  71. /// Specify easing for motion.
  72. /// </summary>
  73. /// <param name="ease">The type of easing</param>
  74. /// <returns>This builder to allow chaining multiple method calls.</returns>
  75. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  76. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithEase(Ease ease)
  77. {
  78. CheckEaseType(ease);
  79. CheckBuffer();
  80. buffer.Data.Core.Ease = ease;
  81. return this;
  82. }
  83. /// <summary>
  84. /// Specify easing for motion.
  85. /// </summary>
  86. /// <param name="animationCurve">Animation curve</param>
  87. /// <returns>This builder to allow chaining multiple method calls.</returns>
  88. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  89. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithEase(AnimationCurve animationCurve)
  90. {
  91. CheckBuffer();
  92. buffer.AnimationCurve = animationCurve;
  93. buffer.Data.Core.Ease = Ease.CustomAnimationCurve;
  94. return this;
  95. }
  96. /// <summary>
  97. /// Specify the delay time when the motion starts.
  98. /// </summary>
  99. /// <param name="delay">Delay time (seconds)</param>
  100. /// <param name="delayType">Delay type</param>
  101. /// <param name="skipValuesDuringDelay">Whether to skip updating values during the delay time</param>
  102. /// <returns>This builder to allow chaining multiple method calls.</returns>
  103. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  104. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithDelay(float delay, DelayType delayType = DelayType.FirstLoop, bool skipValuesDuringDelay = true)
  105. {
  106. CheckBuffer();
  107. buffer.Data.Core.Delay = delay;
  108. buffer.Data.Core.DelayType = delayType;
  109. buffer.CallbackData.SkipValuesDuringDelay = skipValuesDuringDelay;
  110. return this;
  111. }
  112. /// <summary>
  113. /// Specify the number of times the motion is repeated. If specified as less than 0, the motion will continue to play until manually completed or canceled.
  114. /// </summary>
  115. /// <param name="loops">Number of loops</param>
  116. /// <param name="loopType">Behavior at the end of each loop</param>
  117. /// <returns>This builder to allow chaining multiple method calls.</returns>
  118. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  119. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithLoops(int loops, LoopType loopType = LoopType.Restart)
  120. {
  121. CheckBuffer();
  122. buffer.Data.Core.Loops = loops;
  123. buffer.Data.Core.LoopType = loopType;
  124. return this;
  125. }
  126. /// <summary>
  127. /// Specify special parameters for each motion data.
  128. /// </summary>
  129. /// <param name="options">Option value to specify</param>
  130. /// <returns>This builder to allow chaining multiple method calls.</returns>
  131. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  132. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithOptions(TOptions options)
  133. {
  134. CheckBuffer();
  135. buffer.Data.Options = options;
  136. return this;
  137. }
  138. /// <summary>
  139. /// Specify the callback when canceled.
  140. /// </summary>
  141. /// <param name="callback">Callback when canceled</param>
  142. /// <returns>This builder to allow chaining multiple method calls.</returns>
  143. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  144. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithOnCancel(Action callback)
  145. {
  146. CheckBuffer();
  147. buffer.CallbackData.OnCancelAction += callback;
  148. return this;
  149. }
  150. /// <summary>
  151. /// Specify the callback when playback ends.
  152. /// </summary>
  153. /// <param name="callback">Callback when playback ends</param>
  154. /// <returns>This builder to allow chaining multiple method calls.</returns>
  155. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  156. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithOnComplete(Action callback)
  157. {
  158. CheckBuffer();
  159. buffer.CallbackData.OnCompleteAction += callback;
  160. return this;
  161. }
  162. /// <summary>
  163. /// Cancel Motion when an exception occurs during Bind processing.
  164. /// </summary>
  165. /// <param name="cancelOnError">Whether to cancel on error</param>
  166. /// <returns>This builder to allow chaining multiple method calls.</returns>
  167. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  168. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithCancelOnError(bool cancelOnError = true)
  169. {
  170. CheckBuffer();
  171. buffer.CallbackData.CancelOnError = cancelOnError;
  172. return this;
  173. }
  174. /// <summary>
  175. /// Bind values when scheduling the motion.
  176. /// </summary>
  177. /// <param name="bindOnSchedule">Whether to bind on sheduling</param>
  178. /// <returns>This builder to allow chaining multiple method calls.</returns>
  179. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  180. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithBindOnSchedule(bool bindOnSchedule = true)
  181. {
  182. CheckBuffer();
  183. buffer.BindOnSchedule = bindOnSchedule;
  184. return this;
  185. }
  186. /// <summary>
  187. /// Specifies the scheduler that schedule the motion.
  188. /// </summary>
  189. /// <param name="scheduler">Scheduler</param>
  190. /// <returns>This builder to allow chaining multiple method calls.</returns>
  191. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  192. public readonly MotionBuilder<TValue, TOptions, TAdapter> WithScheduler(IMotionScheduler scheduler)
  193. {
  194. CheckBuffer();
  195. buffer.Scheduler = scheduler;
  196. return this;
  197. }
  198. /// <summary>
  199. /// Create motion and play it without binding it to a specific object.
  200. /// </summary>
  201. /// <returns>Handle of the created motion data.</returns>
  202. public MotionHandle RunWithoutBinding()
  203. {
  204. CheckBuffer();
  205. SetMotionData();
  206. var scheduler = buffer.Scheduler;
  207. return Schedule(scheduler, ref buffer.Data, ref buffer.CallbackData);
  208. }
  209. /// <summary>
  210. /// Create motion and bind it to a specific object, property, etc.
  211. /// </summary>
  212. /// <param name="action">Action that handles binding</param>
  213. /// <returns>Handle of the created motion data.</returns>
  214. public MotionHandle Bind(Action<TValue> action)
  215. {
  216. CheckBuffer();
  217. SetMotionData();
  218. SetCallbackData(action);
  219. var scheduler = buffer.Scheduler;
  220. return Schedule(scheduler, ref buffer.Data, ref buffer.CallbackData);
  221. }
  222. /// <summary>
  223. /// Create motion and bind it to a specific object. Unlike the regular Bind method, it avoids allocation by closure by passing an object.
  224. /// </summary>
  225. /// <typeparam name="TState">Type of state</typeparam>
  226. /// <param name="state">Motion state</param>
  227. /// <param name="action">Action that handles binding</param>
  228. /// <returns>Handle of the created motion data.</returns>
  229. public MotionHandle BindWithState<TState>(TState state, Action<TValue, TState> action) where TState : class
  230. {
  231. CheckBuffer();
  232. SetMotionData();
  233. SetCallbackData(state, action);
  234. var scheduler = buffer.Scheduler;
  235. return Schedule(scheduler, ref buffer.Data, ref buffer.CallbackData);
  236. }
  237. /// <summary>
  238. /// Create motion and bind it to a specific object. Unlike the regular Bind method, it avoids allocation by closure by passing an object.
  239. /// </summary>
  240. /// <typeparam name="TState1">Type of state</typeparam>
  241. /// <typeparam name="TState2">Type of state</typeparam>
  242. /// <param name="state">Motion state</param>
  243. /// <param name="action">Action that handles binding</param>
  244. /// <returns>Handle of the created motion data.</returns>
  245. public MotionHandle BindWithState<TState1, TState2>(TState1 state1, TState2 state2, Action<TValue, TState1, TState2> action)
  246. where TState1 : class
  247. where TState2 : class
  248. {
  249. CheckBuffer();
  250. SetMotionData();
  251. SetCallbackData(state1, state2, action);
  252. var scheduler = buffer.Scheduler;
  253. return Schedule(scheduler, ref buffer.Data, ref buffer.CallbackData);
  254. }
  255. /// <summary>
  256. /// Create motion and bind it to a specific object. Unlike the regular Bind method, it avoids allocation by closure by passing an object.
  257. /// </summary>
  258. /// <typeparam name="TState1">Type of state</typeparam>
  259. /// <typeparam name="TState2">Type of state</typeparam>
  260. /// <typeparam name="TState3">Type of state</typeparam>
  261. /// <param name="state">Motion state</param>
  262. /// <param name="action">Action that handles binding</param>
  263. /// <returns>Handle of the created motion data.</returns>
  264. public MotionHandle BindWithState<TState1, TState2, TState3>(TState1 state1, TState2 state2, TState3 state3, Action<TValue, TState1, TState2, TState3> action)
  265. where TState1 : class
  266. where TState2 : class
  267. where TState3 : class
  268. {
  269. CheckBuffer();
  270. SetMotionData();
  271. SetCallbackData(state1, state2, state3, action);
  272. var scheduler = buffer.Scheduler;
  273. return Schedule(scheduler, ref buffer.Data, ref buffer.CallbackData);
  274. }
  275. /// <summary>
  276. /// Preserves the internal buffer and prevents the builder from being automatically destroyed after creating the motion data.
  277. /// Calling this allows you to create the motion multiple times, but you must call the Dispose method to destroy the builder after use.
  278. /// </summary>
  279. /// <returns>This builder to allow chaining multiple method calls.</returns>
  280. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  281. public readonly MotionBuilder<TValue, TOptions, TAdapter> Preserve()
  282. {
  283. CheckBuffer();
  284. buffer.IsPreserved = true;
  285. return this;
  286. }
  287. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  288. internal MotionHandle Schedule(IMotionScheduler scheduler, ref MotionData<TValue, TOptions> data, ref MotionCallbackData callbackData)
  289. {
  290. if (buffer.BindOnSchedule && callbackData.UpdateAction != null)
  291. {
  292. callbackData.InvokeUnsafe(
  293. default(TAdapter).Evaluate(
  294. ref data.StartValue,
  295. ref data.EndValue,
  296. ref data.Options,
  297. new() { Progress = data.Core.Ease switch
  298. {
  299. Ease.CustomAnimationCurve => data.Core.AnimationCurve.Evaluate(0f),
  300. _ => EaseUtility.Evaluate(0f, data.Core.Ease)
  301. }
  302. }
  303. ));
  304. }
  305. MotionHandle handle;
  306. if (scheduler == null)
  307. {
  308. #if UNITY_EDITOR
  309. if (!UnityEditor.EditorApplication.isPlaying)
  310. {
  311. handle = EditorMotionDispatcher.Schedule<TValue, TOptions, TAdapter>(data, callbackData);
  312. }
  313. else if (MotionScheduler.DefaultScheduler == MotionScheduler.Update) // avoid virtual method call
  314. {
  315. handle = MotionDispatcher.Schedule<TValue, TOptions, TAdapter>(data, callbackData, PlayerLoopTiming.Update);
  316. }
  317. else
  318. {
  319. handle = MotionScheduler.DefaultScheduler.Schedule<TValue, TOptions, TAdapter>(ref data, ref callbackData);
  320. }
  321. #else
  322. if (MotionScheduler.DefaultScheduler == MotionScheduler.Update) // avoid virtual method call
  323. {
  324. handle = MotionDispatcher.Schedule<TValue, TOptions, TAdapter>(data, callbackData, PlayerLoopTiming.Update);
  325. }
  326. else
  327. {
  328. handle = MotionScheduler.DefaultScheduler.Schedule<TValue, TOptions, TAdapter>(ref data, ref callbackData);
  329. }
  330. #endif
  331. }
  332. else
  333. {
  334. handle = scheduler.Schedule<TValue, TOptions, TAdapter>(ref data, ref callbackData);
  335. }
  336. if (MotionTracker.EnableTracking)
  337. {
  338. MotionTracker.AddTracking(handle, scheduler);
  339. }
  340. if (!buffer.IsPreserved) Dispose();
  341. return handle;
  342. }
  343. /// <summary>
  344. /// Dispose this builder. You need to call this manually after calling Preserve or if you have never created a motion data.
  345. /// </summary>
  346. public void Dispose()
  347. {
  348. if (buffer == null) return;
  349. MotionBuilderBuffer<TValue, TOptions>.Return(buffer);
  350. buffer = null;
  351. }
  352. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  353. internal readonly void SetMotionData()
  354. {
  355. buffer.Data.Core.Status = MotionStatus.Scheduled;
  356. if (buffer.AnimationCurve != null)
  357. {
  358. #if LITMOTION_COLLECTIONS_2_0_OR_NEWER
  359. buffer.Data.Core.AnimationCurve = new NativeAnimationCurve(buffer.AnimationCurve, Allocator.Temp);
  360. #else
  361. buffer.Data.Core.AnimationCurve = new UnsafeAnimationCurve(buffer.AnimationCurve, Allocator.Temp);
  362. #endif
  363. }
  364. }
  365. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  366. internal readonly void SetCallbackData(Action<TValue> action)
  367. {
  368. buffer.CallbackData.UpdateAction = action;
  369. }
  370. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  371. internal readonly void SetCallbackData<TState>(TState state, Action<TValue, TState> action)
  372. where TState : class
  373. {
  374. buffer.CallbackData.StateCount = 1;
  375. buffer.CallbackData.State1 = state;
  376. buffer.CallbackData.UpdateAction = action;
  377. }
  378. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  379. internal readonly void SetCallbackData<TState1, TState2>(TState1 state1, TState2 state2, Action<TValue, TState1, TState2> action)
  380. where TState1 : class
  381. where TState2 : class
  382. {
  383. buffer.CallbackData.StateCount = 2;
  384. buffer.CallbackData.State1 = state1;
  385. buffer.CallbackData.State2 = state2;
  386. buffer.CallbackData.UpdateAction = action;
  387. }
  388. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  389. internal readonly void SetCallbackData<TState1, TState2, TState3>(TState1 state1, TState2 state2, TState3 state3, Action<TValue, TState1, TState2, TState3> action)
  390. where TState1 : class
  391. where TState2 : class
  392. where TState3 : class
  393. {
  394. buffer.CallbackData.StateCount = 3;
  395. buffer.CallbackData.State1 = state1;
  396. buffer.CallbackData.State2 = state2;
  397. buffer.CallbackData.State3 = state3;
  398. buffer.CallbackData.UpdateAction = action;
  399. }
  400. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  401. readonly void CheckBuffer()
  402. {
  403. if (buffer == null || buffer.Version != version) throw new InvalidOperationException("MotionBuilder is either not initialized or has already run a Build (or Bind). If you want to build or bind multiple times, call Preseve() for MotionBuilder.");
  404. }
  405. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  406. readonly void CheckEaseType(Ease ease)
  407. {
  408. if (ease is Ease.CustomAnimationCurve) throw new ArgumentException($"Ease.{ease} cannot be specified directly.");
  409. }
  410. }
  411. }