diff --git a/Package/UnityHelpers/2018.3/Internal/PromiseBehaviour.cs b/Package/UnityHelpers/2018.3/Internal/PromiseBehaviour.cs index 2e4bbaf4..96a0a51a 100644 --- a/Package/UnityHelpers/2018.3/Internal/PromiseBehaviour.cs +++ b/Package/UnityHelpers/2018.3/Internal/PromiseBehaviour.cs @@ -17,21 +17,26 @@ namespace Proto.Promises #endif internal static partial class InternalHelper { - // AppDomain reload could be disabled in editor, so we need to explicitly reset static fields. - // See https://github.com/timcassell/ProtoPromise/issues/204 - // https://docs.unity3d.com/Manual/DomainReloading.html [RuntimeInitializeOnLoadMethod((RuntimeInitializeLoadType) 4)] // SubsystemRegistration - internal static void ResetStaticState() - => PromiseBehaviour.ResetStaticState(); + internal static void InitSubsystemRegistration() + => PromiseBehaviour.Initialize(); - // We initialize the config as early as possible. Ideally we would just do this in static constructors of Promise() and Promise.Config, - // but since this is in a separate assembly, that's not possible. - // Also, using static constructors would slightly slow down promises in IL2CPP where it would have to check if it already ran on every call. - // We can't use SubsystemRegistration or AfterAssembliesLoaded which run before BeforeSceneLoad, because it forcibly destroys the MonoBehaviour. +#if !UNITY_2019_2_OR_NEWER + // SubsystemRegistration was added in 2019.2, but it still runs on older Unity versions in a different order. + // To initialize as early as possible, we simply use all of the RuntimeInitializeLoadTypes, and all calls after the first will be ignored. [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - internal static void InitializePromiseConfig() + internal static void InitBeforeSceneLoad() + => PromiseBehaviour.Initialize(); + + [RuntimeInitializeOnLoadMethod((RuntimeInitializeLoadType) 2)] + internal static void InitAfterAssembliesLoaded() => PromiseBehaviour.Initialize(); + [RuntimeInitializeOnLoadMethod((RuntimeInitializeLoadType) 3)] + internal static void InitBeforeSplashScreen() + => PromiseBehaviour.Initialize(); +#endif + #if !PROTO_PROMISE_DEVELOPER_MODE [DebuggerNonUserCode, StackTraceHidden] #endif @@ -51,19 +56,44 @@ internal static PromiseBehaviour Instance private SynchronizationContext _oldContext; private bool _isApplicationQuitting = false; + private static void MaybeResetStaticState() + { + // Check if the singleton instance is true null. + // If it's not, it means the editor is running with AppDomain reload disabled, so we need to reset static fields. + // #204 https://docs.unity3d.com/Manual/DomainReloading.html + if (s_instance is null) + { + return; + } + ResetProcessors(); + s_instance.ResetConfig(); + } + internal static void Initialize() { - // Create a PromiseBehaviour instance before any promise actions are made. + // Check if the singleton instance is alive. + if (s_instance != null) + { + return; + } + + MaybeResetStaticState(); + + // Create a PromiseBehaviour instance and initialize it. // Unity will throw if this is not ran on the main thread. - new GameObject("Proto.Promises.Unity.PromiseBehaviour") + new GameObject("Proto.Promises.UnityHelpers.PromiseBehaviour") .AddComponent() - .InitializeConfig(); - - StaticInit(); + .Init(); } - private void InitializeConfig() + private void Init() { + s_instance = this; + DontDestroyOnLoad(gameObject); + gameObject.hideFlags = HideFlags.HideAndDontSave; // Don't show in hierarchy and don't destroy. + + StaticInit(); + // Even though we try to initialize this as early as possible, it is possible for other code to run before this. // So we need to be careful to not overwrite non-default values. @@ -93,28 +123,23 @@ private void InitializeConfig() } } - private void Start() + private void ResetConfig() { - if (s_instance != null) + if (Promise.Config.ForegroundContext == _syncContext) { - UnityEngine.Debug.LogWarning("There can only be one instance of PromiseBehaviour. Destroying new instance."); - Destroy(this); - return; + Promise.Config.ForegroundContext = null; } - DontDestroyOnLoad(gameObject); - gameObject.hideFlags = HideFlags.HideAndDontSave; // Don't show in hierarchy and don't destroy. - s_instance = this; - Init(); - } - - // This should never be called except when the application is shutting down. - // Users would have to go out of their way to find and destroy the PromiseBehaviour instance. - private void OnDestroy() - { - if (!_isApplicationQuitting & s_instance == this) + if (Promise.Config.UncaughtRejectionHandler == HandleRejection) { - UnityEngine.Debug.LogError("PromiseBehaviour destroyed! Removing PromiseSynchronizationContext from Promise.Config.ForegroundContext. PromiseYielder functions will stop working."); - ResetStaticState(); + Promise.Config.UncaughtRejectionHandler = null; + } + if (Promise.Manager.ThreadStaticSynchronizationContext == _syncContext) + { + Promise.Manager.ThreadStaticSynchronizationContext = null; + } + if (SynchronizationContext.Current == _syncContext) + { + SynchronizationContext.SetSynchronizationContext(_oldContext); } } @@ -128,6 +153,17 @@ private void HandleRejection(UnhandledException exception) } } + private void Start() + { + if (s_instance != this) + { + UnityEngine.Debug.LogWarning("There can only be one instance of PromiseBehaviour. Destroying new instance."); + Destroy(this); + return; + } + StartCoroutines(); + } + private void Update() { try @@ -179,36 +215,15 @@ private void OnApplicationQuit() } } - private void ResetConfig() - { - if (Promise.Config.ForegroundContext == _syncContext) - { - Promise.Config.ForegroundContext = null; - } - if (Promise.Config.UncaughtRejectionHandler == HandleRejection) - { - Promise.Config.UncaughtRejectionHandler = null; - } - if (Promise.Manager.ThreadStaticSynchronizationContext == _syncContext) - { - Promise.Manager.ThreadStaticSynchronizationContext = null; - } - if (SynchronizationContext.Current == _syncContext) - { - SynchronizationContext.SetSynchronizationContext(_oldContext); - } - } - - internal static void ResetStaticState() + // This should never be called except when the application is shutting down. + // Users would have to go out of their way to find and destroy the PromiseBehaviour instance. + private void OnDestroy() { - if (s_instance is null) + if (!_isApplicationQuitting & s_instance == this) { - return; + UnityEngine.Debug.LogError("PromiseBehaviour destroyed! Removing PromiseSynchronizationContext from Promise.Config.ForegroundContext. PromiseYielder functions will stop working."); + ResetConfig(); } - - ResetProcessors(); - s_instance.ResetConfig(); - s_instance = null; } } } diff --git a/Package/UnityHelpers/2018.3/Internal/PromiseYielderInternal.cs b/Package/UnityHelpers/2018.3/Internal/PromiseYielderInternal.cs index 8b640498..a25f45a6 100644 --- a/Package/UnityHelpers/2018.3/Internal/PromiseYielderInternal.cs +++ b/Package/UnityHelpers/2018.3/Internal/PromiseYielderInternal.cs @@ -81,13 +81,6 @@ static private void StaticInit() SetTimeValues(); } - private void Init() - { - StartCoroutine(UpdateRoutine()); - StartCoroutine(FixedUpdateRoutine()); - StartCoroutine(EndOfFrameRoutine()); - } - private static void ResetProcessors() { s_waitOneFrameProcessor.Clear(); @@ -98,6 +91,13 @@ private static void ResetProcessors() s_genericProcessor.ResetProcessors(); } + private void StartCoroutines() + { + StartCoroutine(UpdateRoutine()); + StartCoroutine(FixedUpdateRoutine()); + StartCoroutine(EndOfFrameRoutine()); + } + private IEnumerator UpdateRoutine() { while (true)