HTTPUpdateDelegator.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. using System.Threading;
  2. using Best.HTTP.Shared.PlatformSupport.Threading;
  3. using UnityEngine;
  4. namespace Best.HTTP.Shared
  5. {
  6. /// <summary>
  7. /// Threading mode the plugin will use to call HTTPManager.OnUpdate().
  8. /// </summary>
  9. public enum ThreadingMode : int
  10. {
  11. /// <summary>
  12. /// HTTPManager.OnUpdate() is called from the HTTPUpdateDelegator's Update functions (Unity's main thread).
  13. /// </summary>
  14. UnityUpdate,
  15. /// <summary>
  16. /// The plugin starts a dedicated thread to call HTTPManager.OnUpdate() periodically.
  17. /// </summary>
  18. Threaded,
  19. /// <summary>
  20. /// HTTPManager.OnUpdate() will not be called automatically.
  21. /// </summary>
  22. None
  23. }
  24. /// <summary>
  25. /// Will route some U3D calls to the HTTPManager.
  26. /// </summary>
  27. [ExecuteInEditMode]
  28. [Best.HTTP.Shared.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute]
  29. public sealed class HTTPUpdateDelegator : MonoBehaviour
  30. {
  31. #region Public Properties
  32. /// <summary>
  33. /// The singleton instance of the HTTPUpdateDelegator
  34. /// </summary>
  35. public static HTTPUpdateDelegator Instance { get { return CheckInstance(); } }
  36. private volatile static HTTPUpdateDelegator instance;
  37. /// <summary>
  38. /// True, if the Instance property should hold a valid value.
  39. /// </summary>
  40. public static bool IsCreated { get; private set; }
  41. /// <summary>
  42. /// It's true if the dispatch thread running.
  43. /// </summary>
  44. public static bool IsThreadRunning { get; private set; }
  45. /// <summary>
  46. /// The current threading mode the plugin is in.
  47. /// </summary>
  48. public ThreadingMode CurrentThreadingMode { get { return _currentThreadingMode; } set { SetThreadingMode(value); } }
  49. private ThreadingMode _currentThreadingMode = ThreadingMode.UnityUpdate;
  50. /// <summary>
  51. /// How much time the plugin should wait between two update call. Its default value 100 ms.
  52. /// </summary>
  53. public static int ThreadFrequencyInMS { get; set; }
  54. /// <summary>
  55. /// Called in the OnApplicationQuit function. If this function returns False, the plugin will not start to
  56. /// shut down itself.
  57. /// </summary>
  58. public static System.Func<bool> OnBeforeApplicationQuit;
  59. /// <summary>
  60. /// Called when the Unity application's foreground state changed.
  61. /// </summary>
  62. public static System.Action<bool> OnApplicationForegroundStateChanged;
  63. public int MainThreadId { get => this.mainThreadId; }
  64. #endregion
  65. private static bool isSetupCalled;
  66. private int isHTTPManagerOnUpdateRunning;
  67. private AutoResetEvent pingEvent = new AutoResetEvent(false);
  68. private int updateThreadCount = 0;
  69. private int mainThreadId;
  70. #if UNITY_EDITOR
  71. /// <summary>
  72. /// Called after scene loaded to support Configurable Enter Play Mode (https://docs.unity3d.com/2019.3/Documentation/Manual/ConfigurableEnterPlayMode.html)
  73. /// </summary>
  74. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
  75. static void ResetSetup()
  76. {
  77. isSetupCalled = false;
  78. instance?.SetThreadingMode(ThreadingMode.UnityUpdate);
  79. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "Reset called!");
  80. }
  81. #endif
  82. static HTTPUpdateDelegator()
  83. {
  84. ThreadFrequencyInMS = 100;
  85. }
  86. /// <summary>
  87. /// Will create the HTTPUpdateDelegator instance and set it up.
  88. /// </summary>
  89. public static HTTPUpdateDelegator CheckInstance()
  90. {
  91. try
  92. {
  93. if (!IsCreated)
  94. {
  95. GameObject go = GameObject.Find("HTTP Update Delegator");
  96. if (go != null)
  97. instance = go.GetComponent<HTTPUpdateDelegator>();
  98. if (instance == null)
  99. {
  100. go = new GameObject("HTTP Update Delegator");
  101. go.hideFlags = HideFlags.HideAndDontSave;
  102. instance = go.AddComponent<HTTPUpdateDelegator>();
  103. }
  104. IsCreated = true;
  105. #if UNITY_EDITOR
  106. if (!UnityEditor.EditorApplication.isPlaying)
  107. {
  108. UnityEditor.EditorApplication.update -= instance.Update;
  109. UnityEditor.EditorApplication.update += instance.Update;
  110. }
  111. UnityEditor.EditorApplication.playModeStateChanged -= instance.OnPlayModeStateChanged;
  112. UnityEditor.EditorApplication.playModeStateChanged += instance.OnPlayModeStateChanged;
  113. #endif
  114. // https://docs.unity3d.com/ScriptReference/Application-wantsToQuit.html
  115. Application.wantsToQuit -= UnityApplication_WantsToQuit;
  116. Application.wantsToQuit += UnityApplication_WantsToQuit;
  117. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "Instance Created!");
  118. }
  119. }
  120. catch
  121. {
  122. HTTPManager.Logger.Error(nameof(HTTPUpdateDelegator), "Please call the Best.HTTPManager.Setup() from one of Unity's event(eg. awake, start) before you send any request!");
  123. }
  124. return instance;
  125. }
  126. private void Setup()
  127. {
  128. if (isSetupCalled)
  129. return;
  130. isSetupCalled = true;
  131. // Setup is expected to be called on the Unity main thread only.
  132. mainThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
  133. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), $"Setup called Threading Mode: {this._currentThreadingMode}, MainThreadId: {this.mainThreadId}");
  134. HTTPManager.Setup();
  135. SetThreadingMode(this._currentThreadingMode);
  136. // Unity doesn't tolerate well if the DontDestroyOnLoad called when purely in editor mode. So, we will set the flag
  137. // only when we are playing, or not in the editor.
  138. if (!Application.isEditor || Application.isPlaying)
  139. GameObject.DontDestroyOnLoad(this.gameObject);
  140. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "Setup done!");
  141. }
  142. /// <summary>
  143. /// Return true if the call happens on the Unity main thread. Setup must be called before to save the thread id!
  144. /// </summary>
  145. public bool IsMainThread() => System.Threading.Thread.CurrentThread.ManagedThreadId == mainThreadId;
  146. /// <summary>
  147. /// Set directly the threading mode to use.
  148. /// </summary>
  149. public void SetThreadingMode(ThreadingMode mode)
  150. {
  151. if (_currentThreadingMode == mode)
  152. return;
  153. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), $"SetThreadingMode({mode}, {isSetupCalled})");
  154. _currentThreadingMode = mode;
  155. if (!isSetupCalled)
  156. Setup();
  157. switch (_currentThreadingMode)
  158. {
  159. case ThreadingMode.UnityUpdate:
  160. case ThreadingMode.None:
  161. IsThreadRunning = false;
  162. PingUpdateThread();
  163. break;
  164. case ThreadingMode.Threaded:
  165. #if !UNITY_WEBGL || UNITY_EDITOR
  166. ThreadedRunner.RunLongLiving(ThreadFunc);
  167. #else
  168. HTTPManager.Logger.Warning(nameof(HTTPUpdateDelegator), "Threading mode set to ThreadingMode.Threaded, but threads aren't supported under WebGL!");
  169. #endif
  170. break;
  171. }
  172. }
  173. /// <summary>
  174. /// Swaps threading mode between Unity's Update function or a distinct thread.
  175. /// </summary>
  176. public void SwapThreadingMode() => SetThreadingMode(_currentThreadingMode == ThreadingMode.Threaded ? ThreadingMode.UnityUpdate : ThreadingMode.Threaded);
  177. /// <summary>
  178. /// Pings the update thread to call HTTPManager.OnUpdate immediately.
  179. /// </summary>
  180. /// <remarks>Works only when the current threading mode is Threaded!</remarks>
  181. public void PingUpdateThread() => pingEvent.Set();
  182. void ThreadFunc()
  183. {
  184. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "Update Thread Started");
  185. ThreadedRunner.SetThreadName("Best.HTTP.Update Thread");
  186. try
  187. {
  188. if (Interlocked.Increment(ref updateThreadCount) > 1)
  189. {
  190. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "An update thread already started.");
  191. return;
  192. }
  193. // Threading mode might be already changed, so set IsThreadRunning to IsThreaded's value.
  194. IsThreadRunning = CurrentThreadingMode == ThreadingMode.Threaded;
  195. while (IsThreadRunning)
  196. {
  197. CallOnUpdate();
  198. pingEvent.WaitOne(ThreadFrequencyInMS);
  199. }
  200. }
  201. finally
  202. {
  203. Interlocked.Decrement(ref updateThreadCount);
  204. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "Update Thread Ended");
  205. }
  206. }
  207. void Update()
  208. {
  209. if (!isSetupCalled)
  210. Setup();
  211. if (CurrentThreadingMode == ThreadingMode.UnityUpdate)
  212. CallOnUpdate();
  213. }
  214. private void CallOnUpdate()
  215. {
  216. // Prevent overlapping call of OnUpdate from unity's main thread and a separate thread
  217. if (Interlocked.CompareExchange(ref isHTTPManagerOnUpdateRunning, 1, 0) == 0)
  218. {
  219. try
  220. {
  221. HTTPManager.OnUpdate();
  222. }
  223. finally
  224. {
  225. Interlocked.Exchange(ref isHTTPManagerOnUpdateRunning, 0);
  226. }
  227. }
  228. }
  229. #if UNITY_EDITOR
  230. void OnPlayModeStateChanged(UnityEditor.PlayModeStateChange playMode)
  231. {
  232. if (playMode == UnityEditor.PlayModeStateChange.EnteredPlayMode)
  233. {
  234. UnityEditor.EditorApplication.update -= Update;
  235. }
  236. else if (playMode == UnityEditor.PlayModeStateChange.EnteredEditMode)
  237. {
  238. UnityEditor.EditorApplication.update -= Update;
  239. UnityEditor.EditorApplication.update += Update;
  240. HTTPUpdateDelegator.ResetSetup();
  241. HTTPManager.ResetSetup();
  242. }
  243. }
  244. #endif
  245. void OnDisable()
  246. {
  247. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "OnDisable Called!");
  248. #if UNITY_EDITOR
  249. if (UnityEditor.EditorApplication.isPlaying)
  250. #endif
  251. UnityApplication_WantsToQuit();
  252. }
  253. void OnApplicationPause(bool isPaused)
  254. {
  255. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "OnApplicationPause isPaused: " + isPaused);
  256. if (HTTPUpdateDelegator.OnApplicationForegroundStateChanged != null)
  257. HTTPUpdateDelegator.OnApplicationForegroundStateChanged(isPaused);
  258. }
  259. private static bool UnityApplication_WantsToQuit()
  260. {
  261. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "UnityApplication_WantsToQuit Called!");
  262. if (OnBeforeApplicationQuit != null)
  263. {
  264. try
  265. {
  266. if (!OnBeforeApplicationQuit())
  267. {
  268. HTTPManager.Logger.Information(nameof(HTTPUpdateDelegator), "OnBeforeApplicationQuit call returned false, postponing plugin and application shutdown.");
  269. return false;
  270. }
  271. }
  272. catch (System.Exception ex)
  273. {
  274. HTTPManager.Logger.Exception(nameof(HTTPUpdateDelegator), string.Empty, ex);
  275. }
  276. }
  277. IsThreadRunning = false;
  278. Instance.PingUpdateThread();
  279. if (!IsCreated)
  280. return true;
  281. IsCreated = false;
  282. HTTPManager.OnQuit();
  283. return true;
  284. }
  285. }
  286. }