You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

355 lines
14 KiB

  1. using System;
  2. using System.Threading;
  3. using UnityEngine;
  4. namespace Cysharp.Threading.Tasks.Linq
  5. {
  6. public static partial class UniTaskAsyncEnumerable
  7. {
  8. public static IUniTaskAsyncEnumerable<AsyncUnit> Timer(TimeSpan dueTime, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false, bool cancelImmediately = false)
  9. {
  10. return new Timer(dueTime, null, updateTiming, ignoreTimeScale, cancelImmediately);
  11. }
  12. public static IUniTaskAsyncEnumerable<AsyncUnit> Timer(TimeSpan dueTime, TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false, bool cancelImmediately = false)
  13. {
  14. return new Timer(dueTime, period, updateTiming, ignoreTimeScale, cancelImmediately);
  15. }
  16. public static IUniTaskAsyncEnumerable<AsyncUnit> Interval(TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false, bool cancelImmediately = false)
  17. {
  18. return new Timer(period, period, updateTiming, ignoreTimeScale, cancelImmediately);
  19. }
  20. public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false)
  21. {
  22. if (dueTimeFrameCount < 0)
  23. {
  24. throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. dueTimeFrameCount:" + dueTimeFrameCount);
  25. }
  26. return new TimerFrame(dueTimeFrameCount, null, updateTiming, cancelImmediately);
  27. }
  28. public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false)
  29. {
  30. if (dueTimeFrameCount < 0)
  31. {
  32. throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. dueTimeFrameCount:" + dueTimeFrameCount);
  33. }
  34. if (periodFrameCount < 0)
  35. {
  36. throw new ArgumentOutOfRangeException("Delay does not allow minus periodFrameCount. periodFrameCount:" + dueTimeFrameCount);
  37. }
  38. return new TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming, cancelImmediately);
  39. }
  40. public static IUniTaskAsyncEnumerable<AsyncUnit> IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false)
  41. {
  42. if (intervalFrameCount < 0)
  43. {
  44. throw new ArgumentOutOfRangeException("Delay does not allow minus intervalFrameCount. intervalFrameCount:" + intervalFrameCount);
  45. }
  46. return new TimerFrame(intervalFrameCount, intervalFrameCount, updateTiming, cancelImmediately);
  47. }
  48. }
  49. internal class Timer : IUniTaskAsyncEnumerable<AsyncUnit>
  50. {
  51. readonly PlayerLoopTiming updateTiming;
  52. readonly TimeSpan dueTime;
  53. readonly TimeSpan? period;
  54. readonly bool ignoreTimeScale;
  55. readonly bool cancelImmediately;
  56. public Timer(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale, bool cancelImmediately)
  57. {
  58. this.updateTiming = updateTiming;
  59. this.dueTime = dueTime;
  60. this.period = period;
  61. this.ignoreTimeScale = ignoreTimeScale;
  62. this.cancelImmediately = cancelImmediately;
  63. }
  64. public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default)
  65. {
  66. return new _Timer(dueTime, period, updateTiming, ignoreTimeScale, cancellationToken, cancelImmediately);
  67. }
  68. class _Timer : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem
  69. {
  70. readonly float dueTime;
  71. readonly float? period;
  72. readonly PlayerLoopTiming updateTiming;
  73. readonly bool ignoreTimeScale;
  74. readonly CancellationToken cancellationToken;
  75. readonly CancellationTokenRegistration cancellationTokenRegistration;
  76. int initialFrame;
  77. float elapsed;
  78. bool dueTimePhase;
  79. bool completed;
  80. bool disposed;
  81. public _Timer(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale, CancellationToken cancellationToken, bool cancelImmediately)
  82. {
  83. this.dueTime = (float)dueTime.TotalSeconds;
  84. this.period = (period == null) ? null : (float?)period.Value.TotalSeconds;
  85. if (this.dueTime <= 0) this.dueTime = 0;
  86. if (this.period != null)
  87. {
  88. if (this.period <= 0) this.period = 1;
  89. }
  90. this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
  91. this.dueTimePhase = true;
  92. this.updateTiming = updateTiming;
  93. this.ignoreTimeScale = ignoreTimeScale;
  94. this.cancellationToken = cancellationToken;
  95. if (cancelImmediately && cancellationToken.CanBeCanceled)
  96. {
  97. cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
  98. {
  99. var source = (_Timer)state;
  100. source.completionSource.TrySetCanceled(source.cancellationToken);
  101. }, this);
  102. }
  103. TaskTracker.TrackActiveTask(this, 2);
  104. PlayerLoopHelper.AddAction(updateTiming, this);
  105. }
  106. public AsyncUnit Current => default;
  107. public UniTask<bool> MoveNextAsync()
  108. {
  109. // return false instead of throw
  110. if (disposed || completed) return CompletedTasks.False;
  111. // reset value here.
  112. this.elapsed = 0;
  113. completionSource.Reset();
  114. if (cancellationToken.IsCancellationRequested)
  115. {
  116. completionSource.TrySetCanceled(cancellationToken);
  117. }
  118. return new UniTask<bool>(this, completionSource.Version);
  119. }
  120. public UniTask DisposeAsync()
  121. {
  122. if (!disposed)
  123. {
  124. cancellationTokenRegistration.Dispose();
  125. disposed = true;
  126. TaskTracker.RemoveTracking(this);
  127. }
  128. return default;
  129. }
  130. public bool MoveNext()
  131. {
  132. if (disposed)
  133. {
  134. completionSource.TrySetResult(false);
  135. return false;
  136. }
  137. if (cancellationToken.IsCancellationRequested)
  138. {
  139. completionSource.TrySetCanceled(cancellationToken);
  140. return false;
  141. }
  142. if (dueTimePhase)
  143. {
  144. if (elapsed == 0)
  145. {
  146. // skip in initial frame.
  147. if (initialFrame == Time.frameCount)
  148. {
  149. return true;
  150. }
  151. }
  152. elapsed += (ignoreTimeScale) ? UnityEngine.Time.unscaledDeltaTime : UnityEngine.Time.deltaTime;
  153. if (elapsed >= dueTime)
  154. {
  155. dueTimePhase = false;
  156. completionSource.TrySetResult(true);
  157. }
  158. }
  159. else
  160. {
  161. if (period == null)
  162. {
  163. completed = true;
  164. completionSource.TrySetResult(false);
  165. return false;
  166. }
  167. elapsed += (ignoreTimeScale) ? UnityEngine.Time.unscaledDeltaTime : UnityEngine.Time.deltaTime;
  168. if (elapsed >= period)
  169. {
  170. completionSource.TrySetResult(true);
  171. }
  172. }
  173. return true;
  174. }
  175. }
  176. }
  177. internal class TimerFrame : IUniTaskAsyncEnumerable<AsyncUnit>
  178. {
  179. readonly PlayerLoopTiming updateTiming;
  180. readonly int dueTimeFrameCount;
  181. readonly int? periodFrameCount;
  182. readonly bool cancelImmediately;
  183. public TimerFrame(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming, bool cancelImmediately)
  184. {
  185. this.updateTiming = updateTiming;
  186. this.dueTimeFrameCount = dueTimeFrameCount;
  187. this.periodFrameCount = periodFrameCount;
  188. this.cancelImmediately = cancelImmediately;
  189. }
  190. public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default)
  191. {
  192. return new _TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming, cancellationToken, cancelImmediately);
  193. }
  194. class _TimerFrame : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem
  195. {
  196. readonly int dueTimeFrameCount;
  197. readonly int? periodFrameCount;
  198. readonly CancellationToken cancellationToken;
  199. readonly CancellationTokenRegistration cancellationTokenRegistration;
  200. int initialFrame;
  201. int currentFrame;
  202. bool dueTimePhase;
  203. bool completed;
  204. bool disposed;
  205. public _TimerFrame(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming, CancellationToken cancellationToken, bool cancelImmediately)
  206. {
  207. if (dueTimeFrameCount <= 0) dueTimeFrameCount = 0;
  208. if (periodFrameCount != null)
  209. {
  210. if (periodFrameCount <= 0) periodFrameCount = 1;
  211. }
  212. this.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
  213. this.dueTimePhase = true;
  214. this.dueTimeFrameCount = dueTimeFrameCount;
  215. this.periodFrameCount = periodFrameCount;
  216. this.cancellationToken = cancellationToken;
  217. if (cancelImmediately && cancellationToken.CanBeCanceled)
  218. {
  219. cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
  220. {
  221. var source = (_TimerFrame)state;
  222. source.completionSource.TrySetCanceled(source.cancellationToken);
  223. }, this);
  224. }
  225. TaskTracker.TrackActiveTask(this, 2);
  226. PlayerLoopHelper.AddAction(updateTiming, this);
  227. }
  228. public AsyncUnit Current => default;
  229. public UniTask<bool> MoveNextAsync()
  230. {
  231. if (disposed || completed) return CompletedTasks.False;
  232. if (cancellationToken.IsCancellationRequested)
  233. {
  234. completionSource.TrySetCanceled(cancellationToken);
  235. }
  236. // reset value here.
  237. this.currentFrame = 0;
  238. completionSource.Reset();
  239. return new UniTask<bool>(this, completionSource.Version);
  240. }
  241. public UniTask DisposeAsync()
  242. {
  243. if (!disposed)
  244. {
  245. cancellationTokenRegistration.Dispose();
  246. disposed = true;
  247. TaskTracker.RemoveTracking(this);
  248. }
  249. return default;
  250. }
  251. public bool MoveNext()
  252. {
  253. if (cancellationToken.IsCancellationRequested)
  254. {
  255. completionSource.TrySetCanceled(cancellationToken);
  256. return false;
  257. }
  258. if (disposed)
  259. {
  260. completionSource.TrySetResult(false);
  261. return false;
  262. }
  263. if (dueTimePhase)
  264. {
  265. if (currentFrame == 0)
  266. {
  267. if (dueTimeFrameCount == 0)
  268. {
  269. dueTimePhase = false;
  270. completionSource.TrySetResult(true);
  271. return true;
  272. }
  273. // skip in initial frame.
  274. if (initialFrame == Time.frameCount)
  275. {
  276. return true;
  277. }
  278. }
  279. if (++currentFrame >= dueTimeFrameCount)
  280. {
  281. dueTimePhase = false;
  282. completionSource.TrySetResult(true);
  283. }
  284. else
  285. {
  286. }
  287. }
  288. else
  289. {
  290. if (periodFrameCount == null)
  291. {
  292. completed = true;
  293. completionSource.TrySetResult(false);
  294. return false;
  295. }
  296. if (++currentFrame >= periodFrameCount)
  297. {
  298. completionSource.TrySetResult(true);
  299. }
  300. }
  301. return true;
  302. }
  303. }
  304. }
  305. }