HTTPUpdateDelegator.cs 13 KB

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