MainThreadDispatcher.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. #if !(UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_4 || UNITY_4_5 || UNITY_4_6 || UNITY_5_0 || UNITY_5_1 || UNITY_5_2)
  2. #define SupportCustomYieldInstruction
  3. #endif
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Reflection;
  8. using System.Threading;
  9. using UniRx.InternalUtil;
  10. using UnityEngine;
  11. namespace UniRx
  12. {
  13. public sealed class MainThreadDispatcher : MonoBehaviour
  14. {
  15. public enum CullingMode
  16. {
  17. /// <summary>
  18. /// Won't remove any MainThreadDispatchers.
  19. /// </summary>
  20. Disabled,
  21. /// <summary>
  22. /// Checks if there is an existing MainThreadDispatcher on Awake(). If so, the new dispatcher removes itself.
  23. /// </summary>
  24. Self,
  25. /// <summary>
  26. /// Search for excess MainThreadDispatchers and removes them all on Awake().
  27. /// </summary>
  28. All
  29. }
  30. public static CullingMode cullingMode = CullingMode.Self;
  31. #if UNITY_EDITOR
  32. // In UnityEditor's EditorMode can't instantiate and work MonoBehaviour.Update.
  33. // EditorThreadDispatcher use EditorApplication.update instead of MonoBehaviour.Update.
  34. class EditorThreadDispatcher
  35. {
  36. static object gate = new object();
  37. static EditorThreadDispatcher instance;
  38. public static EditorThreadDispatcher Instance
  39. {
  40. get
  41. {
  42. // Activate EditorThreadDispatcher is dangerous, completely Lazy.
  43. lock (gate)
  44. {
  45. if (instance == null)
  46. {
  47. instance = new EditorThreadDispatcher();
  48. }
  49. return instance;
  50. }
  51. }
  52. }
  53. ThreadSafeQueueWorker editorQueueWorker = new ThreadSafeQueueWorker();
  54. EditorThreadDispatcher()
  55. {
  56. UnityEditor.EditorApplication.update += Update;
  57. }
  58. public void Enqueue(Action<object> action, object state)
  59. {
  60. editorQueueWorker.Enqueue(action, state);
  61. }
  62. public void UnsafeInvoke(Action action)
  63. {
  64. try
  65. {
  66. action();
  67. }
  68. catch (Exception ex)
  69. {
  70. Debug.LogException(ex);
  71. }
  72. }
  73. public void UnsafeInvoke<T>(Action<T> action, T state)
  74. {
  75. try
  76. {
  77. action(state);
  78. }
  79. catch (Exception ex)
  80. {
  81. Debug.LogException(ex);
  82. }
  83. }
  84. public void PseudoStartCoroutine(IEnumerator routine)
  85. {
  86. editorQueueWorker.Enqueue(_ => ConsumeEnumerator(routine), null);
  87. }
  88. void Update()
  89. {
  90. editorQueueWorker.ExecuteAll(x => Debug.LogException(x));
  91. }
  92. void ConsumeEnumerator(IEnumerator routine)
  93. {
  94. if (routine.MoveNext())
  95. {
  96. var current = routine.Current;
  97. if (current == null)
  98. {
  99. goto ENQUEUE;
  100. }
  101. var type = current.GetType();
  102. #if UNITY_2018_3_OR_NEWER
  103. #pragma warning disable CS0618
  104. #endif
  105. if (type == typeof(WWW))
  106. {
  107. var www = (WWW)current;
  108. editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapWaitWWW(www, routine)), null);
  109. return;
  110. }
  111. #if UNITY_2018_3_OR_NEWER
  112. #pragma warning restore CS0618
  113. #endif
  114. else if (type == typeof(AsyncOperation))
  115. {
  116. var asyncOperation = (AsyncOperation)current;
  117. editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapWaitAsyncOperation(asyncOperation, routine)), null);
  118. return;
  119. }
  120. else if (type == typeof(WaitForSeconds))
  121. {
  122. var waitForSeconds = (WaitForSeconds)current;
  123. var accessor = typeof(WaitForSeconds).GetField("m_Seconds", BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic);
  124. var second = (float)accessor.GetValue(waitForSeconds);
  125. editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapWaitForSeconds(second, routine)), null);
  126. return;
  127. }
  128. else if (type == typeof(Coroutine))
  129. {
  130. Debug.Log("Can't wait coroutine on UnityEditor");
  131. goto ENQUEUE;
  132. }
  133. #if SupportCustomYieldInstruction
  134. else if (current is IEnumerator)
  135. {
  136. var enumerator = (IEnumerator)current;
  137. editorQueueWorker.Enqueue(_ => ConsumeEnumerator(UnwrapEnumerator(enumerator, routine)), null);
  138. return;
  139. }
  140. #endif
  141. ENQUEUE:
  142. editorQueueWorker.Enqueue(_ => ConsumeEnumerator(routine), null); // next update
  143. }
  144. }
  145. #if UNITY_2018_3_OR_NEWER
  146. #pragma warning disable CS0618
  147. #endif
  148. IEnumerator UnwrapWaitWWW(WWW www, IEnumerator continuation)
  149. {
  150. while (!www.isDone)
  151. {
  152. yield return null;
  153. }
  154. ConsumeEnumerator(continuation);
  155. }
  156. #if UNITY_2018_3_OR_NEWER
  157. #pragma warning restore CS0618
  158. #endif
  159. IEnumerator UnwrapWaitAsyncOperation(AsyncOperation asyncOperation, IEnumerator continuation)
  160. {
  161. while (!asyncOperation.isDone)
  162. {
  163. yield return null;
  164. }
  165. ConsumeEnumerator(continuation);
  166. }
  167. IEnumerator UnwrapWaitForSeconds(float second, IEnumerator continuation)
  168. {
  169. var startTime = DateTimeOffset.UtcNow;
  170. while (true)
  171. {
  172. yield return null;
  173. var elapsed = (DateTimeOffset.UtcNow - startTime).TotalSeconds;
  174. if (elapsed >= second)
  175. {
  176. break;
  177. }
  178. };
  179. ConsumeEnumerator(continuation);
  180. }
  181. IEnumerator UnwrapEnumerator(IEnumerator enumerator, IEnumerator continuation)
  182. {
  183. while (enumerator.MoveNext())
  184. {
  185. yield return null;
  186. }
  187. ConsumeEnumerator(continuation);
  188. }
  189. }
  190. #endif
  191. /// <summary>Dispatch Asyncrhonous action.</summary>
  192. public static void Post(Action<object> action, object state)
  193. {
  194. #if UNITY_EDITOR
  195. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.Enqueue(action, state); return; }
  196. #endif
  197. var dispatcher = Instance;
  198. if (!isQuitting && !object.ReferenceEquals(dispatcher, null))
  199. {
  200. dispatcher.queueWorker.Enqueue(action, state);
  201. }
  202. }
  203. /// <summary>Dispatch Synchronous action if possible.</summary>
  204. public static void Send(Action<object> action, object state)
  205. {
  206. #if UNITY_EDITOR
  207. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.Enqueue(action, state); return; }
  208. #endif
  209. if (mainThreadToken != null)
  210. {
  211. try
  212. {
  213. action(state);
  214. }
  215. catch (Exception ex)
  216. {
  217. var dispatcher = MainThreadDispatcher.Instance;
  218. if (dispatcher != null)
  219. {
  220. dispatcher.unhandledExceptionCallback(ex);
  221. }
  222. }
  223. }
  224. else
  225. {
  226. Post(action, state);
  227. }
  228. }
  229. /// <summary>Run Synchronous action.</summary>
  230. public static void UnsafeSend(Action action)
  231. {
  232. #if UNITY_EDITOR
  233. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.UnsafeInvoke(action); return; }
  234. #endif
  235. try
  236. {
  237. action();
  238. }
  239. catch (Exception ex)
  240. {
  241. var dispatcher = MainThreadDispatcher.Instance;
  242. if (dispatcher != null)
  243. {
  244. dispatcher.unhandledExceptionCallback(ex);
  245. }
  246. }
  247. }
  248. /// <summary>Run Synchronous action.</summary>
  249. public static void UnsafeSend<T>(Action<T> action, T state)
  250. {
  251. #if UNITY_EDITOR
  252. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.UnsafeInvoke(action, state); return; }
  253. #endif
  254. try
  255. {
  256. action(state);
  257. }
  258. catch (Exception ex)
  259. {
  260. var dispatcher = MainThreadDispatcher.Instance;
  261. if (dispatcher != null)
  262. {
  263. dispatcher.unhandledExceptionCallback(ex);
  264. }
  265. }
  266. }
  267. /// <summary>ThreadSafe StartCoroutine.</summary>
  268. public static void SendStartCoroutine(IEnumerator routine)
  269. {
  270. if (mainThreadToken != null)
  271. {
  272. StartCoroutine(routine);
  273. }
  274. else
  275. {
  276. #if UNITY_EDITOR
  277. // call from other thread
  278. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
  279. #endif
  280. var dispatcher = Instance;
  281. if (!isQuitting && !object.ReferenceEquals(dispatcher, null))
  282. {
  283. dispatcher.queueWorker.Enqueue(_ =>
  284. {
  285. var dispacher2 = Instance;
  286. if (dispacher2 != null)
  287. {
  288. (dispacher2 as MonoBehaviour).StartCoroutine(routine);
  289. }
  290. }, null);
  291. }
  292. }
  293. }
  294. public static void StartUpdateMicroCoroutine(IEnumerator routine)
  295. {
  296. #if UNITY_EDITOR
  297. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
  298. #endif
  299. var dispatcher = Instance;
  300. if (dispatcher != null)
  301. {
  302. dispatcher.updateMicroCoroutine.AddCoroutine(routine);
  303. }
  304. }
  305. public static void StartFixedUpdateMicroCoroutine(IEnumerator routine)
  306. {
  307. #if UNITY_EDITOR
  308. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
  309. #endif
  310. var dispatcher = Instance;
  311. if (dispatcher != null)
  312. {
  313. dispatcher.fixedUpdateMicroCoroutine.AddCoroutine(routine);
  314. }
  315. }
  316. public static void StartEndOfFrameMicroCoroutine(IEnumerator routine)
  317. {
  318. #if UNITY_EDITOR
  319. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return; }
  320. #endif
  321. var dispatcher = Instance;
  322. if (dispatcher != null)
  323. {
  324. dispatcher.endOfFrameMicroCoroutine.AddCoroutine(routine);
  325. }
  326. }
  327. new public static Coroutine StartCoroutine(IEnumerator routine)
  328. {
  329. #if UNITY_EDITOR
  330. if (!ScenePlaybackDetector.IsPlaying) { EditorThreadDispatcher.Instance.PseudoStartCoroutine(routine); return null; }
  331. #endif
  332. var dispatcher = Instance;
  333. if (dispatcher != null)
  334. {
  335. return (dispatcher as MonoBehaviour).StartCoroutine(routine);
  336. }
  337. else
  338. {
  339. return null;
  340. }
  341. }
  342. public static void RegisterUnhandledExceptionCallback(Action<Exception> exceptionCallback)
  343. {
  344. if (exceptionCallback == null)
  345. {
  346. // do nothing
  347. Instance.unhandledExceptionCallback = Stubs<Exception>.Ignore;
  348. }
  349. else
  350. {
  351. Instance.unhandledExceptionCallback = exceptionCallback;
  352. }
  353. }
  354. ThreadSafeQueueWorker queueWorker = new ThreadSafeQueueWorker();
  355. Action<Exception> unhandledExceptionCallback = ex => Debug.LogException(ex); // default
  356. MicroCoroutine updateMicroCoroutine = null;
  357. MicroCoroutine fixedUpdateMicroCoroutine = null;
  358. MicroCoroutine endOfFrameMicroCoroutine = null;
  359. static MainThreadDispatcher instance;
  360. static bool initialized;
  361. static bool isQuitting = false;
  362. public static string InstanceName
  363. {
  364. get
  365. {
  366. if (instance == null)
  367. {
  368. throw new NullReferenceException("MainThreadDispatcher is not initialized.");
  369. }
  370. return instance.name;
  371. }
  372. }
  373. public static bool IsInitialized
  374. {
  375. get { return initialized && instance != null; }
  376. }
  377. [ThreadStatic]
  378. static object mainThreadToken;
  379. static MainThreadDispatcher Instance
  380. {
  381. get
  382. {
  383. Initialize();
  384. return instance;
  385. }
  386. }
  387. public static void Initialize()
  388. {
  389. if (!initialized)
  390. {
  391. #if UNITY_EDITOR
  392. // Don't try to add a GameObject when the scene is not playing. Only valid in the Editor, EditorView.
  393. if (!ScenePlaybackDetector.IsPlaying) return;
  394. #endif
  395. MainThreadDispatcher dispatcher = null;
  396. try
  397. {
  398. dispatcher = GameObject.FindObjectOfType<MainThreadDispatcher>();
  399. }
  400. catch
  401. {
  402. // Throw exception when calling from a worker thread.
  403. var ex = new Exception("UniRx requires a MainThreadDispatcher component created on the main thread. Make sure it is added to the scene before calling UniRx from a worker thread.");
  404. UnityEngine.Debug.LogException(ex);
  405. throw ex;
  406. }
  407. if (isQuitting)
  408. {
  409. // don't create new instance after quitting
  410. // avoid "Some objects were not cleaned up when closing the scene find target" error.
  411. return;
  412. }
  413. if (dispatcher == null)
  414. {
  415. // awake call immediately from UnityEngine
  416. new GameObject("MainThreadDispatcher").AddComponent<MainThreadDispatcher>();
  417. }
  418. else
  419. {
  420. dispatcher.Awake(); // force awake
  421. }
  422. }
  423. }
  424. public static bool IsInMainThread
  425. {
  426. get
  427. {
  428. return (mainThreadToken != null);
  429. }
  430. }
  431. void Awake()
  432. {
  433. if (instance == null)
  434. {
  435. instance = this;
  436. mainThreadToken = new object();
  437. initialized = true;
  438. updateMicroCoroutine = new MicroCoroutine(ex => unhandledExceptionCallback(ex));
  439. fixedUpdateMicroCoroutine = new MicroCoroutine(ex => unhandledExceptionCallback(ex));
  440. endOfFrameMicroCoroutine = new MicroCoroutine(ex => unhandledExceptionCallback(ex));
  441. StartCoroutine(RunUpdateMicroCoroutine());
  442. StartCoroutine(RunFixedUpdateMicroCoroutine());
  443. StartCoroutine(RunEndOfFrameMicroCoroutine());
  444. DontDestroyOnLoad(gameObject);
  445. }
  446. else
  447. {
  448. if (this != instance)
  449. {
  450. if (cullingMode == CullingMode.Self)
  451. {
  452. // Try to destroy this dispatcher if there's already one in the scene.
  453. Debug.LogWarning("There is already a MainThreadDispatcher in the scene. Removing myself...");
  454. DestroyDispatcher(this);
  455. }
  456. else if (cullingMode == CullingMode.All)
  457. {
  458. Debug.LogWarning("There is already a MainThreadDispatcher in the scene. Cleaning up all excess dispatchers...");
  459. CullAllExcessDispatchers();
  460. }
  461. else
  462. {
  463. Debug.LogWarning("There is already a MainThreadDispatcher in the scene.");
  464. }
  465. }
  466. }
  467. }
  468. IEnumerator RunUpdateMicroCoroutine()
  469. {
  470. while (true)
  471. {
  472. yield return null;
  473. updateMicroCoroutine.Run();
  474. }
  475. }
  476. IEnumerator RunFixedUpdateMicroCoroutine()
  477. {
  478. while (true)
  479. {
  480. yield return YieldInstructionCache.WaitForFixedUpdate;
  481. fixedUpdateMicroCoroutine.Run();
  482. }
  483. }
  484. IEnumerator RunEndOfFrameMicroCoroutine()
  485. {
  486. while (true)
  487. {
  488. yield return YieldInstructionCache.WaitForEndOfFrame;
  489. endOfFrameMicroCoroutine.Run();
  490. }
  491. }
  492. static void DestroyDispatcher(MainThreadDispatcher aDispatcher)
  493. {
  494. if (aDispatcher != instance)
  495. {
  496. // Try to remove game object if it's empty
  497. var components = aDispatcher.gameObject.GetComponents<Component>();
  498. if (aDispatcher.gameObject.transform.childCount == 0 && components.Length == 2)
  499. {
  500. if (components[0] is Transform && components[1] is MainThreadDispatcher)
  501. {
  502. Destroy(aDispatcher.gameObject);
  503. }
  504. }
  505. else
  506. {
  507. // Remove component
  508. MonoBehaviour.Destroy(aDispatcher);
  509. }
  510. }
  511. }
  512. public static void CullAllExcessDispatchers()
  513. {
  514. var dispatchers = GameObject.FindObjectsOfType<MainThreadDispatcher>();
  515. for (int i = 0; i < dispatchers.Length; i++)
  516. {
  517. DestroyDispatcher(dispatchers[i]);
  518. }
  519. }
  520. void OnDestroy()
  521. {
  522. if (instance == this)
  523. {
  524. instance = GameObject.FindObjectOfType<MainThreadDispatcher>();
  525. initialized = instance != null;
  526. /*
  527. // Although `this` still refers to a gameObject, it won't be found.
  528. var foundDispatcher = GameObject.FindObjectOfType<MainThreadDispatcher>();
  529. if (foundDispatcher != null)
  530. {
  531. // select another game object
  532. Debug.Log("new instance: " + foundDispatcher.name);
  533. instance = foundDispatcher;
  534. initialized = true;
  535. }
  536. */
  537. }
  538. }
  539. void Update()
  540. {
  541. if (update != null)
  542. {
  543. try
  544. {
  545. update.OnNext(Unit.Default);
  546. }
  547. catch (Exception ex)
  548. {
  549. unhandledExceptionCallback(ex);
  550. }
  551. }
  552. queueWorker.ExecuteAll(unhandledExceptionCallback);
  553. }
  554. // for Lifecycle Management
  555. Subject<Unit> update;
  556. public static IObservable<Unit> UpdateAsObservable()
  557. {
  558. return Instance.update ?? (Instance.update = new Subject<Unit>());
  559. }
  560. Subject<Unit> lateUpdate;
  561. void LateUpdate()
  562. {
  563. if (lateUpdate != null) lateUpdate.OnNext(Unit.Default);
  564. }
  565. public static IObservable<Unit> LateUpdateAsObservable()
  566. {
  567. return Instance.lateUpdate ?? (Instance.lateUpdate = new Subject<Unit>());
  568. }
  569. Subject<bool> onApplicationFocus;
  570. void OnApplicationFocus(bool focus)
  571. {
  572. if (onApplicationFocus != null) onApplicationFocus.OnNext(focus);
  573. }
  574. public static IObservable<bool> OnApplicationFocusAsObservable()
  575. {
  576. return Instance.onApplicationFocus ?? (Instance.onApplicationFocus = new Subject<bool>());
  577. }
  578. Subject<bool> onApplicationPause;
  579. void OnApplicationPause(bool pause)
  580. {
  581. if (onApplicationPause != null) onApplicationPause.OnNext(pause);
  582. }
  583. public static IObservable<bool> OnApplicationPauseAsObservable()
  584. {
  585. return Instance.onApplicationPause ?? (Instance.onApplicationPause = new Subject<bool>());
  586. }
  587. Subject<Unit> onApplicationQuit;
  588. void OnApplicationQuit()
  589. {
  590. isQuitting = true;
  591. if (onApplicationQuit != null) onApplicationQuit.OnNext(Unit.Default);
  592. }
  593. public static IObservable<Unit> OnApplicationQuitAsObservable()
  594. {
  595. return Instance.onApplicationQuit ?? (Instance.onApplicationQuit = new Subject<Unit>());
  596. }
  597. }
  598. }