You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
359 lines
12 KiB
359 lines
12 KiB
using System; |
|
using System.Threading; |
|
|
|
using BestHTTP.PlatformSupport.Threading; |
|
|
|
using UnityEngine; |
|
|
|
#if NETFX_CORE |
|
using System.Threading.Tasks; |
|
#endif |
|
|
|
namespace BestHTTP |
|
{ |
|
/// <summary> |
|
/// Threading mode the plugin will use to call HTTPManager.OnUpdate(). |
|
/// </summary> |
|
public enum ThreadingMode : int |
|
{ |
|
/// <summary> |
|
/// HTTPManager.OnUpdate() is called from the HTTPUpdateDelegator's Update functions (Unity's main thread). |
|
/// </summary> |
|
UnityUpdate, |
|
|
|
/// <summary> |
|
/// The plugin starts a dedicated thread to call HTTPManager.OnUpdate() periodically. |
|
/// </summary> |
|
Threaded, |
|
|
|
/// <summary> |
|
/// HTTPManager.OnUpdate() will not be called automatically. |
|
/// </summary> |
|
None |
|
} |
|
|
|
/// <summary> |
|
/// Will route some U3D calls to the HTTPManager. |
|
/// </summary> |
|
[ExecuteInEditMode] |
|
[BestHTTP.PlatformSupport.IL2CPP.Il2CppEagerStaticClassConstructionAttribute] |
|
public sealed class HTTPUpdateDelegator : MonoBehaviour |
|
{ |
|
#region Public Properties |
|
|
|
/// <summary> |
|
/// The singleton instance of the HTTPUpdateDelegator |
|
/// </summary> |
|
public static HTTPUpdateDelegator Instance { get; private set; } |
|
|
|
/// <summary> |
|
/// True, if the Instance property should hold a valid value. |
|
/// </summary> |
|
public static bool IsCreated { get; private set; } |
|
|
|
/// <summary> |
|
/// Set it true before any CheckInstance() call, or before any request sent to dispatch callbacks on another thread. |
|
/// </summary> |
|
public static bool IsThreaded { get; set; } |
|
|
|
/// <summary> |
|
/// It's true if the dispatch thread running. |
|
/// </summary> |
|
public static bool IsThreadRunning { get; private set; } |
|
|
|
public ThreadingMode CurrentThreadingMode { get { return _currentThreadingMode; } set { SetThreadingMode(value); } } |
|
private ThreadingMode _currentThreadingMode = ThreadingMode.UnityUpdate; |
|
|
|
/// <summary> |
|
/// How much time the plugin should wait between two update call. Its default value 100 ms. |
|
/// </summary> |
|
public static int ThreadFrequencyInMS { get; set; } |
|
|
|
/// <summary> |
|
/// Called in the OnApplicationQuit function. If this function returns False, the plugin will not start to |
|
/// shut down itself. |
|
/// </summary> |
|
public static System.Func<bool> OnBeforeApplicationQuit; |
|
|
|
/// <summary> |
|
/// Called when the Unity application's foreground state changed. |
|
/// </summary> |
|
public static System.Action<bool> OnApplicationForegroundStateChanged; |
|
|
|
#endregion |
|
|
|
private static bool isSetupCalled; |
|
private int isHTTPManagerOnUpdateRunning; |
|
private AutoResetEvent pingEvent = new AutoResetEvent(false); |
|
private int updateThreadCount = 0; |
|
|
|
#if UNITY_EDITOR |
|
/// <summary> |
|
/// Called after scene loaded to support Configurable Enter Play Mode (https://docs.unity3d.com/2019.3/Documentation/Manual/ConfigurableEnterPlayMode.html) |
|
/// </summary> |
|
#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; |
|
} |
|
|
|
/// <summary> |
|
/// Will create the HTTPUpdateDelegator instance and set it up. |
|
/// </summary> |
|
public static void CheckInstance() |
|
{ |
|
try |
|
{ |
|
if (!IsCreated) |
|
{ |
|
GameObject go = GameObject.Find("HTTP Update Delegator"); |
|
|
|
if (go != null) |
|
Instance = go.GetComponent<HTTPUpdateDelegator>(); |
|
|
|
if (Instance == null) |
|
{ |
|
go = new GameObject("HTTP Update Delegator"); |
|
go.hideFlags = HideFlags.HideAndDontSave; |
|
|
|
Instance = go.AddComponent<HTTPUpdateDelegator>(); |
|
} |
|
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!"); |
|
} |
|
|
|
/// <summary> |
|
/// Set directly the threading mode to use. |
|
/// </summary> |
|
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 |
|
} |
|
|
|
/// <summary> |
|
/// Swaps threading mode between Unity's Update function or a distinct thread. |
|
/// </summary> |
|
public void SwapThreadingMode() => SetThreadingMode(_currentThreadingMode == ThreadingMode.Threaded ? ThreadingMode.UnityUpdate : ThreadingMode.Threaded); |
|
|
|
/// <summary> |
|
/// Pings the update thread to call HTTPManager.OnUpdate immediately. |
|
/// </summary> |
|
/// <remarks>Works only when the current threading mode is Threaded!</remarks> |
|
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; |
|
} |
|
} |
|
}
|
|
|