Skip to content

Commit

Permalink
Set PromiseBehaviour.s_instance when it's created instead of delaye…
Browse files Browse the repository at this point in the history
…d to `Start`.

Initialize earlier.
Combine static reset and initialization calls.
  • Loading branch information
timcassell committed Jan 4, 2025
1 parent 465cc94 commit 18d75cb
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 68 deletions.
136 changes: 75 additions & 61 deletions Package/UnityHelpers/2018.3/Internal/PromiseBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(<T>) 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
Expand All @@ -51,19 +56,43 @@ 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.
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<PromiseBehaviour>()
.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.

Expand Down Expand Up @@ -93,28 +122,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);
}
}

Expand All @@ -128,6 +152,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
Expand Down Expand Up @@ -179,36 +214,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;
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions Package/UnityHelpers/2018.3/Internal/PromiseYielderInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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)
Expand Down

0 comments on commit 18d75cb

Please sign in to comment.