using System; using System.Threading; using BestHTTP.PlatformSupport.Threading; using UnityEngine; #if NETFX_CORE using System.Threading.Tasks; #endif namespace BestHTTP { /// /// Threading mode the plugin will use to call HTTPManager.OnUpdate(). /// public enum ThreadingMode : int { /// /// HTTPManager.OnUpdate() is called from the HTTPUpdateDelegator's Update functions (Unity's main thread). /// UnityUpdate, /// /// The plugin starts a dedicated thread to call HTTPManager.OnUpdate() periodically. /// Threaded, /// /// HTTPManager.OnUpdate() will not be called automatically. /// None } /// /// Will route some U3D calls to the HTTPManager. /// [ExecuteInEditMode] [BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute] public sealed class HTTPUpdateDelegator : MonoBehaviour { #region Public Properties /// /// The singleton instance of the HTTPUpdateDelegator /// public static HTTPUpdateDelegator Instance { get; private set; } /// /// True, if the Instance property should hold a valid value. /// public static bool IsCreated { get; private set; } /// /// Set it true before any CheckInstance() call, or before any request sent to dispatch callbacks on another thread. /// public static bool IsThreaded { get; set; } /// /// It's true if the dispatch thread running. /// public static bool IsThreadRunning { get; private set; } public ThreadingMode CurrentThreadingMode { get { return _currentThreadingMode; } set { SetThreadingMode(value); } } private ThreadingMode _currentThreadingMode = ThreadingMode.UnityUpdate; /// /// How much time the plugin should wait between two update call. Its default value 100 ms. /// public static int ThreadFrequencyInMS { get; set; } /// /// Called in the OnApplicationQuit function. If this function returns False, the plugin will not start to /// shut down itself. /// public static System.Func OnBeforeApplicationQuit; /// /// Called when the Unity application's foreground state changed. /// public static System.Action OnApplicationForegroundStateChanged; #endregion private static bool isSetupCalled; private int isHTTPManagerOnUpdateRunning; private AutoResetEvent pingEvent = new AutoResetEvent(false); private int updateThreadCount = 0; #if UNITY_EDITOR /// /// Called after scene loaded to support Configurable Enter Play Mode (https://docs.unity3d.com/2019.3/Documentation/Manual/ConfigurableEnterPlayMode.html) /// #if UNITY_2019_3_OR_NEWER [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] #endif static void ResetSetup() { isSetupCalled = false; HTTPManager.Logger.Information("HTTPUpdateDelegator", "Reset called!"); } #endif static HTTPUpdateDelegator() { ThreadFrequencyInMS = 100; } /// /// Will create the HTTPUpdateDelegator instance and set it up. /// public static void CheckInstance() { try { if (!IsCreated) { GameObject go = GameObject.Find("HTTP Update Delegator"); if (go != null) Instance = go.GetComponent(); if (Instance == null) { go = new GameObject("HTTP Update Delegator"); go.hideFlags = HideFlags.HideAndDontSave; Instance = go.AddComponent(); } IsCreated = true; #if UNITY_EDITOR if (!UnityEditor.EditorApplication.isPlaying) { UnityEditor.EditorApplication.update -= Instance.Update; UnityEditor.EditorApplication.update += Instance.Update; } #if UNITY_2017_2_OR_NEWER UnityEditor.EditorApplication.playModeStateChanged -= Instance.OnPlayModeStateChanged; UnityEditor.EditorApplication.playModeStateChanged += Instance.OnPlayModeStateChanged; #else UnityEditor.EditorApplication.playmodeStateChanged -= Instance.OnPlayModeStateChanged; UnityEditor.EditorApplication.playmodeStateChanged += Instance.OnPlayModeStateChanged; #endif #endif // https://docs.unity3d.com/ScriptReference/Application-wantsToQuit.html Application.wantsToQuit -= UnityApplication_WantsToQuit; Application.wantsToQuit += UnityApplication_WantsToQuit; HTTPManager.Logger.Information("HTTPUpdateDelegator", "Instance Created!"); } } catch { HTTPManager.Logger.Error("HTTPUpdateDelegator", "Please call the BestHTTP.HTTPManager.Setup() from one of Unity's event(eg. awake, start) before you send any request!"); } } private void Setup() { if (isSetupCalled) return; isSetupCalled = true; HTTPManager.Logger.Information("HTTPUpdateDelegator", string.Format("Setup called Threading Mode: {0}, IsThreaded: {1}", _currentThreadingMode, IsThreaded)); HTTPManager.Setup(); #if UNITY_WEBGL && !UNITY_EDITOR // Threads are not implemented in WEBGL builds, disable it for now. IsThreaded = false; #endif SetThreadingMode(IsThreaded ? ThreadingMode.Threaded : ThreadingMode.UnityUpdate); // Unity doesn't tolerate well if the DontDestroyOnLoad called when purely in editor mode. So, we will set the flag // only when we are playing, or not in the editor. if (!Application.isEditor || Application.isPlaying) GameObject.DontDestroyOnLoad(this.gameObject); HTTPManager.Logger.Information("HTTPUpdateDelegator", "Setup done!"); } /// /// Set directly the threading mode to use. /// public void SetThreadingMode(ThreadingMode mode) { if (_currentThreadingMode == mode) return; HTTPManager.Logger.Information("HTTPUpdateDelegator", "SetThreadingMode: " + mode); _currentThreadingMode = mode; #if !UNITY_WEBGL || UNITY_EDITOR switch (_currentThreadingMode) { case ThreadingMode.UnityUpdate: case ThreadingMode.None: IsThreadRunning = false; PingUpdateThread(); break; case ThreadingMode.Threaded: ThreadedRunner.RunLongLiving(ThreadFunc); break; } #endif } /// /// Swaps threading mode between Unity's Update function or a distinct thread. /// public void SwapThreadingMode() => SetThreadingMode(_currentThreadingMode == ThreadingMode.Threaded ? ThreadingMode.UnityUpdate : ThreadingMode.Threaded); /// /// Pings the update thread to call HTTPManager.OnUpdate immediately. /// /// Works only when the current threading mode is Threaded! public void PingUpdateThread() => pingEvent.Set(); void ThreadFunc() { HTTPManager.Logger.Information("HTTPUpdateDelegator", "Update Thread Started"); ThreadedRunner.SetThreadName("BestHTTP.Update Thread"); try { if (Interlocked.Increment(ref updateThreadCount) > 1) { HTTPManager.Logger.Information("HTTPUpdateDelegator", "An update thread already started."); return; } // Threading mode might be already changed, so set IsThreadRunning to IsThreaded's value. IsThreadRunning = CurrentThreadingMode == ThreadingMode.Threaded; while (IsThreadRunning) { CallOnUpdate(); pingEvent.WaitOne(ThreadFrequencyInMS); } } finally { Interlocked.Decrement(ref updateThreadCount); HTTPManager.Logger.Information("HTTPUpdateDelegator", "Update Thread Ended"); } } void Update() { if (!isSetupCalled) Setup(); if (CurrentThreadingMode == ThreadingMode.UnityUpdate) CallOnUpdate(); } private void CallOnUpdate() { // Prevent overlapping call of OnUpdate from unity's main thread and a separate thread if (Interlocked.CompareExchange(ref isHTTPManagerOnUpdateRunning, 1, 0) == 0) { try { HTTPManager.OnUpdate(); } finally { Interlocked.Exchange(ref isHTTPManagerOnUpdateRunning, 0); } } } #if UNITY_EDITOR #if UNITY_2017_2_OR_NEWER void OnPlayModeStateChanged(UnityEditor.PlayModeStateChange playMode) { if (playMode == UnityEditor.PlayModeStateChange.EnteredPlayMode) { UnityEditor.EditorApplication.update -= Update; } else if (playMode == UnityEditor.PlayModeStateChange.EnteredEditMode) { UnityEditor.EditorApplication.update -= Update; UnityEditor.EditorApplication.update += Update; HTTPUpdateDelegator.ResetSetup(); HTTPManager.ResetSetup(); } } #else void OnPlayModeStateChanged() { if (UnityEditor.EditorApplication.isPlaying) UnityEditor.EditorApplication.update -= Update; else if (!UnityEditor.EditorApplication.isPlaying) UnityEditor.EditorApplication.update += Update; } #endif #endif void OnDisable() { HTTPManager.Logger.Information("HTTPUpdateDelegator", "OnDisable Called!"); #if UNITY_EDITOR if (UnityEditor.EditorApplication.isPlaying) #endif UnityApplication_WantsToQuit(); } void OnApplicationPause(bool isPaused) { HTTPManager.Logger.Information("HTTPUpdateDelegator", "OnApplicationPause isPaused: " + isPaused); if (HTTPUpdateDelegator.OnApplicationForegroundStateChanged != null) HTTPUpdateDelegator.OnApplicationForegroundStateChanged(isPaused); } private static bool UnityApplication_WantsToQuit() { HTTPManager.Logger.Information("HTTPUpdateDelegator", "UnityApplication_WantsToQuit Called!"); if (OnBeforeApplicationQuit != null) { try { if (!OnBeforeApplicationQuit()) { HTTPManager.Logger.Information("HTTPUpdateDelegator", "OnBeforeApplicationQuit call returned false, postponing plugin and application shutdown."); return false; } } catch (System.Exception ex) { HTTPManager.Logger.Exception("HTTPUpdateDelegator", string.Empty, ex); } } IsThreadRunning = false; Instance.PingUpdateThread(); if (!IsCreated) return true; IsCreated = false; HTTPManager.OnQuit(); return true; } } }