Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix so that “DontDestroyOnLoad” takes precedence when there are multiple LifetimeScope of the same class. #703

Closed
wants to merge 1 commit into from

Conversation

IShix-g
Copy link
Contributor

@IShix-g IShix-g commented Aug 28, 2024

What happened?

When I use FindAnyObjectByType in LifetimeScope.Find, the order of acquisition is not guaranteed and sometimes Configure is not called. Also, I am using a singleton LifetimeScope, which is a rare case, but I would appreciate your consideration.

static LifetimeScope Find(Type type)
{
#if UNITY_2022_1_OR_NEWER
return (LifetimeScope)FindAnyObjectByType(type);
#else
return (LifetimeScope)FindObjectOfType(type);
#endif
}

Reproducing the problem

  • Unity uses 2022.1 or higher
  • Create a singleton LifetimeScope
  • Create a LifetimeScope with a singleton LifetimeScope as Parent
  • Attach the created script to a different game object and place it in the scene
  • Create a script that repeats the scene transitions and attach it to the appropriate object.
  • Wait until Assert of ReloadTestLifetimeScope fails.
src

Code

DontDestroyOnLoadLifetimeScope.cs

singleton LifetimeScope

using UnityEngine.Assertions;
using VContainer;
using VContainer.Unity;

public sealed class DontDestroyOnLoadLifetimeScope : LifetimeScope
{
    static DontDestroyOnLoadLifetimeScope s_instance;
    
    bool _isCalled;
    
    protected override void Awake()
    {
        if (s_instance == null)
        {
            base.Awake();
            s_instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else if(s_instance != this)
        {
            Destroy(gameObject);
        }
    }
    
    void Start() => Assert.IsTrue(_isCalled, "Configure was NOT called: " + GetType().Name);
    
    protected override void OnDestroy()
    {
        if (s_instance == this)
        {
            base.OnDestroy();
        }
    }
    
    protected override void Configure(IContainerBuilder builder) => _isCalled = true;
}

ReloadTestLifetimeScope.cs

a LifetimeScope with a singleton LifetimeScope as Parent

using UnityEngine.Assertions;
using VContainer;
using VContainer.Unity;

public sealed class ReloadTestLifetimeScope : LifetimeScope
{
    bool _isCalled;
    
    void Start() => Assert.IsTrue(_isCalled, "Configure was NOT called: " + GetType().Name);
    
    protected override void Configure(IContainerBuilder builder) => _isCalled = true;

    void Reset() => parentReference = ParentReference.Create<DontDestroyOnLoadLifetimeScope>();
}

ReloadScene.cs

Scene reloading.

using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement;

public sealed class ReloadScene : MonoBehaviour
{
    async void Start()
    {
        try
        {
            await Task.Delay(1000, cancellationToken:destroyCancellationToken);
            SceneManager.LoadScene(SceneManager.GetActiveScene().name);
        }
        catch
        {
            // ignore
        }
    }
}

Problem solved

Fixed so that if multiple LifetimeScope of the same class exist, the LifetimeScope with DontDestroyOnLoad is returned. The reason is that there are no other situations where multiple LifetimeScope exist. Also, if there are no more than one, the first one in the array is returned. Internally, this is exactly the same code as the previous code.

Current

static LifetimeScope Find(Type type)
{
#if UNITY_2022_1_OR_NEWER
return (LifetimeScope)FindAnyObjectByType(type);
#else
return (LifetimeScope)FindObjectOfType(type);
#endif
}

After

static LifetimeScope Find(Type type)
{
#if UNITY_2022_1_OR_NEWER
var objects = FindObjectsByType(type, FindObjectsInactive.Exclude, FindObjectsSortMode.None);
#else
var objects = FindObjectsOfType(type);
#endif
if (objects.Length > 1)
{
foreach (var obj in objects)
{
if (obj is LifetimeScope lifetimeScope
&& lifetimeScope.gameObject.scene.name == "DontDestroyOnLoad")
{
return lifetimeScope;
}
}
}
return (LifetimeScope)objects[0];
}

About performance

As explained, performance is the same as before when there is no more than one. The internal implementation of FindAnyObjectByType and FindObjectOfType used is as follows.

public static Object FindAnyObjectByType(System.Type type)
{
  Object[] objectsByType = Object.FindObjectsByType(type, FindObjectsInactive.Exclude, FindObjectsSortMode.None);
  return objectsByType.Length != 0 ? objectsByType[0] : (Object) null;
}


[TypeInferenceRule(TypeInferenceRules.TypeReferencedByFirstArgument)]
public static Object FindObjectOfType(System.Type type)
{
  Object[] objectsOfType = Object.FindObjectsOfType(type, false);
  return objectsOfType.Length != 0 ? objectsOfType[0] : (Object) null;
}

FindObjectsOfType handling

In my testing, FindObjectOfType was always stored in the array in the order it was created, as opposed to FindAnyObjectByType, but since there seems to be no guarantee of order, I made the same modification to give priority to DontDestroyOnLoad The same modification has been made to prioritize DontDestroyOnLoad.

Copy link

vercel bot commented Aug 28, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
vcontainer ✅ Ready (Inspect) Visit Preview 💬 Add feedback Aug 28, 2024 2:25pm

@IShix-g IShix-g changed the title fix If there are multiple LifetimeScope, DontDestroyOnLoad takes prec… fix If there are multiple LifetimeScope, DontDestroyOnLoad takes precedence. Aug 28, 2024
@IShix-g IShix-g changed the title fix If there are multiple LifetimeScope, DontDestroyOnLoad takes precedence. Fix DontDestroyOnLoad to take precedence when there are multiple LifetimeScope of the same class. Aug 29, 2024
@IShix-g IShix-g changed the title Fix DontDestroyOnLoad to take precedence when there are multiple LifetimeScope of the same class. Fix "DontDestroyOnLoad" to take precedence when there are multiple LifetimeScope of the same class. Aug 29, 2024
@IShix-g IShix-g changed the title Fix "DontDestroyOnLoad" to take precedence when there are multiple LifetimeScope of the same class. Fix so that “DontDestroyOnLoad” takes precedence when there are multiple LifetimeScope of the same class. Aug 29, 2024
@IShix-g IShix-g closed this Nov 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant