MicroCoroutine.cs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace UniRx.InternalUtil
  5. {
  6. /// <summary>
  7. /// Simple supports(only yield return null) lightweight, threadsafe coroutine dispatcher.
  8. /// </summary>
  9. public class MicroCoroutine
  10. {
  11. const int InitialSize = 16;
  12. readonly object runningAndQueueLock = new object();
  13. readonly object arrayLock = new object();
  14. readonly Action<Exception> unhandledExceptionCallback;
  15. int tail = 0;
  16. bool running = false;
  17. IEnumerator[] coroutines = new IEnumerator[InitialSize];
  18. Queue<IEnumerator> waitQueue = new Queue<IEnumerator>();
  19. public MicroCoroutine(Action<Exception> unhandledExceptionCallback)
  20. {
  21. this.unhandledExceptionCallback = unhandledExceptionCallback;
  22. }
  23. public void AddCoroutine(IEnumerator enumerator)
  24. {
  25. lock (runningAndQueueLock)
  26. {
  27. if (running)
  28. {
  29. waitQueue.Enqueue(enumerator);
  30. return;
  31. }
  32. }
  33. // worst case at multi threading, wait lock until finish Run() but it is super rarely.
  34. lock (arrayLock)
  35. {
  36. // Ensure Capacity
  37. if (coroutines.Length == tail)
  38. {
  39. Array.Resize(ref coroutines, checked(tail * 2));
  40. }
  41. coroutines[tail++] = enumerator;
  42. }
  43. }
  44. public void Run()
  45. {
  46. lock (runningAndQueueLock)
  47. {
  48. running = true;
  49. }
  50. lock (arrayLock)
  51. {
  52. var j = tail - 1;
  53. // eliminate array-bound check for i
  54. for (int i = 0; i < coroutines.Length; i++)
  55. {
  56. var coroutine = coroutines[i];
  57. if (coroutine != null)
  58. {
  59. try
  60. {
  61. if (!coroutine.MoveNext())
  62. {
  63. coroutines[i] = null;
  64. }
  65. else
  66. {
  67. #if UNITY_EDITOR
  68. // validation only on Editor.
  69. if (coroutine.Current != null)
  70. {
  71. UnityEngine.Debug.LogWarning("MicroCoroutine supports only yield return null. return value = " + coroutine.Current);
  72. }
  73. #endif
  74. continue; // next i
  75. }
  76. }
  77. catch (Exception ex)
  78. {
  79. coroutines[i] = null;
  80. try
  81. {
  82. unhandledExceptionCallback(ex);
  83. }
  84. catch { }
  85. }
  86. }
  87. // find null, loop from tail
  88. while (i < j)
  89. {
  90. var fromTail = coroutines[j];
  91. if (fromTail != null)
  92. {
  93. try
  94. {
  95. if (!fromTail.MoveNext())
  96. {
  97. coroutines[j] = null;
  98. j--;
  99. continue; // next j
  100. }
  101. else
  102. {
  103. #if UNITY_EDITOR
  104. // validation only on Editor.
  105. if (fromTail.Current != null)
  106. {
  107. UnityEngine.Debug.LogWarning("MicroCoroutine supports only yield return null. return value = " + coroutine.Current);
  108. }
  109. #endif
  110. // swap
  111. coroutines[i] = fromTail;
  112. coroutines[j] = null;
  113. j--;
  114. goto NEXT_LOOP; // next i
  115. }
  116. }
  117. catch (Exception ex)
  118. {
  119. coroutines[j] = null;
  120. j--;
  121. try
  122. {
  123. unhandledExceptionCallback(ex);
  124. }
  125. catch { }
  126. continue; // next j
  127. }
  128. }
  129. else
  130. {
  131. j--;
  132. }
  133. }
  134. tail = i; // loop end
  135. break; // LOOP END
  136. NEXT_LOOP:
  137. continue;
  138. }
  139. lock (runningAndQueueLock)
  140. {
  141. running = false;
  142. while (waitQueue.Count != 0)
  143. {
  144. if (coroutines.Length == tail)
  145. {
  146. Array.Resize(ref coroutines, checked(tail * 2));
  147. }
  148. coroutines[tail++] = waitQueue.Dequeue();
  149. }
  150. }
  151. }
  152. }
  153. }
  154. }