PlayerLoopTimer.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
  2. using System.Threading;
  3. using System;
  4. using Cysharp.Threading.Tasks.Internal;
  5. using UnityEngine;
  6. namespace Cysharp.Threading.Tasks
  7. {
  8. public abstract class PlayerLoopTimer : IDisposable, IPlayerLoopItem
  9. {
  10. readonly CancellationToken cancellationToken;
  11. readonly Action<object> timerCallback;
  12. readonly object state;
  13. readonly PlayerLoopTiming playerLoopTiming;
  14. readonly bool periodic;
  15. bool isRunning;
  16. bool tryStop;
  17. bool isDisposed;
  18. protected PlayerLoopTimer(bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
  19. {
  20. this.periodic = periodic;
  21. this.playerLoopTiming = playerLoopTiming;
  22. this.cancellationToken = cancellationToken;
  23. this.timerCallback = timerCallback;
  24. this.state = state;
  25. }
  26. public static PlayerLoopTimer Create(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
  27. {
  28. #if UNITY_EDITOR
  29. // force use Realtime.
  30. if (PlayerLoopHelper.IsMainThread && !UnityEditor.EditorApplication.isPlaying)
  31. {
  32. delayType = DelayType.Realtime;
  33. }
  34. #endif
  35. switch (delayType)
  36. {
  37. case DelayType.UnscaledDeltaTime:
  38. return new IgnoreTimeScalePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
  39. case DelayType.Realtime:
  40. return new RealtimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
  41. case DelayType.DeltaTime:
  42. default:
  43. return new DeltaTimePlayerLoopTimer(interval, periodic, playerLoopTiming, cancellationToken, timerCallback, state);
  44. }
  45. }
  46. public static PlayerLoopTimer StartNew(TimeSpan interval, bool periodic, DelayType delayType, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
  47. {
  48. var timer = Create(interval, periodic, delayType, playerLoopTiming, cancellationToken, timerCallback, state);
  49. timer.Restart();
  50. return timer;
  51. }
  52. /// <summary>
  53. /// Restart(Reset and Start) timer.
  54. /// </summary>
  55. public void Restart()
  56. {
  57. if (isDisposed) throw new ObjectDisposedException(null);
  58. ResetCore(null); // init state
  59. if (!isRunning)
  60. {
  61. isRunning = true;
  62. PlayerLoopHelper.AddAction(playerLoopTiming, this);
  63. }
  64. tryStop = false;
  65. }
  66. /// <summary>
  67. /// Restart(Reset and Start) and change interval.
  68. /// </summary>
  69. public void Restart(TimeSpan interval)
  70. {
  71. if (isDisposed) throw new ObjectDisposedException(null);
  72. ResetCore(interval); // init state
  73. if (!isRunning)
  74. {
  75. isRunning = true;
  76. PlayerLoopHelper.AddAction(playerLoopTiming, this);
  77. }
  78. tryStop = false;
  79. }
  80. /// <summary>
  81. /// Stop timer.
  82. /// </summary>
  83. public void Stop()
  84. {
  85. tryStop = true;
  86. }
  87. protected abstract void ResetCore(TimeSpan? newInterval);
  88. public void Dispose()
  89. {
  90. isDisposed = true;
  91. }
  92. bool IPlayerLoopItem.MoveNext()
  93. {
  94. if (isDisposed)
  95. {
  96. isRunning = false;
  97. return false;
  98. }
  99. if (tryStop)
  100. {
  101. isRunning = false;
  102. return false;
  103. }
  104. if (cancellationToken.IsCancellationRequested)
  105. {
  106. isRunning = false;
  107. return false;
  108. }
  109. if (!MoveNextCore())
  110. {
  111. timerCallback(state);
  112. if (periodic)
  113. {
  114. ResetCore(null);
  115. return true;
  116. }
  117. else
  118. {
  119. isRunning = false;
  120. return false;
  121. }
  122. }
  123. return true;
  124. }
  125. protected abstract bool MoveNextCore();
  126. }
  127. sealed class DeltaTimePlayerLoopTimer : PlayerLoopTimer
  128. {
  129. int initialFrame;
  130. float elapsed;
  131. float interval;
  132. public DeltaTimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
  133. : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
  134. {
  135. ResetCore(interval);
  136. }
  137. protected override bool MoveNextCore()
  138. {
  139. if (elapsed == 0.0f)
  140. {
  141. if (initialFrame == Time.frameCount)
  142. {
  143. return true;
  144. }
  145. }
  146. elapsed += Time.deltaTime;
  147. if (elapsed >= interval)
  148. {
  149. return false;
  150. }
  151. return true;
  152. }
  153. protected override void ResetCore(TimeSpan? interval)
  154. {
  155. this.elapsed = 0.0f;
  156. this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
  157. if (interval != null)
  158. {
  159. this.interval = (float)interval.Value.TotalSeconds;
  160. }
  161. }
  162. }
  163. sealed class IgnoreTimeScalePlayerLoopTimer : PlayerLoopTimer
  164. {
  165. int initialFrame;
  166. float elapsed;
  167. float interval;
  168. public IgnoreTimeScalePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
  169. : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
  170. {
  171. ResetCore(interval);
  172. }
  173. protected override bool MoveNextCore()
  174. {
  175. if (elapsed == 0.0f)
  176. {
  177. if (initialFrame == Time.frameCount)
  178. {
  179. return true;
  180. }
  181. }
  182. elapsed += Time.unscaledDeltaTime;
  183. if (elapsed >= interval)
  184. {
  185. return false;
  186. }
  187. return true;
  188. }
  189. protected override void ResetCore(TimeSpan? interval)
  190. {
  191. this.elapsed = 0.0f;
  192. this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
  193. if (interval != null)
  194. {
  195. this.interval = (float)interval.Value.TotalSeconds;
  196. }
  197. }
  198. }
  199. sealed class RealtimePlayerLoopTimer : PlayerLoopTimer
  200. {
  201. ValueStopwatch stopwatch;
  202. long intervalTicks;
  203. public RealtimePlayerLoopTimer(TimeSpan interval, bool periodic, PlayerLoopTiming playerLoopTiming, CancellationToken cancellationToken, Action<object> timerCallback, object state)
  204. : base(periodic, playerLoopTiming, cancellationToken, timerCallback, state)
  205. {
  206. ResetCore(interval);
  207. }
  208. protected override bool MoveNextCore()
  209. {
  210. if (stopwatch.ElapsedTicks >= intervalTicks)
  211. {
  212. return false;
  213. }
  214. return true;
  215. }
  216. protected override void ResetCore(TimeSpan? interval)
  217. {
  218. this.stopwatch = ValueStopwatch.StartNew();
  219. if (interval != null)
  220. {
  221. this.intervalTicks = interval.Value.Ticks;
  222. }
  223. }
  224. }
  225. }