hadashiA / VContainer

The extra fast, minimum code size, GC-free DI (Dependency Injection) library running on Unity Game Engine.
https://vcontainer.hadashikick.jp
MIT License
1.92k stars 167 forks source link

VContainerSettings collide with Unity RuntimeTests #689

Open mixedHans opened 3 months ago

mixedHans commented 3 months ago

I recognized, that when you set a LifetimeScope as the RootLifetimeScope through VContainerSettings, the RootLifetimeScope gets also instantiated, when you run RuntimeTests through Unitys TestRunner. Since this is not what you want most of the time, I would like to make an improvement to the VContainerSettings class.

Add a serialized field, where you give the user the chance to decide, whether to instantiate the root lifetimescope during test runs or not

[SerializeField]
[Tooltip("Instantiates the RootLifetimeScope even in Runtime Tests.")]
public static bool UseInRuntimeTests;

And insert this check somewhere, to prevent the lifetimescope to be instantiated. This check is also beeing used from unity in the XRDeviceSimulatorLoader to prevent automatic instantiation of the XR device simulator. So I guess this would be the way to go right now. Also the upcoming TestFramework 2.0 doesn't seem to have an option to check, if a runtime test is running right now.

var scene = SceneManager.GetActiveScene();
if(scene.IsValid() && scene.name.StartsWith("InitTestScene") && UseInRuntimeTests == false)
     return null;

My custom VContainerSettings class looks like this now. I think it would be nice, if we would see something like that in some of the the next updates.

using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace VContainer.Unity
{
    public sealed class VContainerSettings : ScriptableObject
    {
        public static VContainerSettings Instance { get; private set; }
        public static bool DiagnosticsEnabled => Instance != null && Instance.EnableDiagnostics;

        static LifetimeScope rootLifetimeScopeInstance;

        [SerializeField]
        [Tooltip("Set the Prefab to be the parent of the entire Project.")]
        public LifetimeScope RootLifetimeScope;

        [SerializeField]
        [Tooltip("Enables the collection of information that can be viewed in the VContainerDiagnosticsWindow. Note: Performance degradation")]
        public bool EnableDiagnostics;

        [SerializeField]
        [Tooltip("Disables script modification for LifetimeScope scripts.")]
        public bool DisableScriptModifier;

        [SerializeField]
        [Tooltip("Removes (Clone) postfix in IObjectResolver.Instantiate() and IContainerBuilder.RegisterComponentInNewPrefab().")]
        public bool RemoveClonePostfix;

        [SerializeField]
        [Tooltip("Instantiates the RootLifetimeScope in Runtime Tests scenes.")]
        public bool UseInRuntimeTests;

#if UNITY_EDITOR
        [UnityEditor.MenuItem("Assets/Create/VContainer/Custom VContainer Settings")]
        public static void CreateAsset()
        {
            var path = UnityEditor.EditorUtility.SaveFilePanelInProject(
                "Save VContainerSettings",
                "VContainerSettings",
                "asset",
                string.Empty);

            if (string.IsNullOrEmpty(path))
                return;

            var newSettings = CreateInstance<VContainerSettings>();
            UnityEditor.AssetDatabase.CreateAsset(newSettings, path);

            var preloadedAssets = UnityEditor.PlayerSettings.GetPreloadedAssets().ToList();
            preloadedAssets.RemoveAll(x => x is VContainerSettings or VContainerSettings);
            preloadedAssets.Add(newSettings);
            UnityEditor.PlayerSettings.SetPreloadedAssets(preloadedAssets.ToArray());
        }

        public static void LoadInstanceFromPreloadAssets()
        {
            var preloadAsset = UnityEditor.PlayerSettings.GetPreloadedAssets().FirstOrDefault(x => x is VContainerSettings);
            if (preloadAsset is VContainerSettings instance)
            {
                instance.OnDisable();
                instance.OnEnable();
            }
        }

        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        static void RuntimeInitialize()
        {
            // For editor, we need to load the Preload asset manually.
            LoadInstanceFromPreloadAssets();
        }
#endif

        public LifetimeScope GetOrCreateRootLifetimeScopeInstance()
        {
            var scene = SceneManager.GetActiveScene();
            if(scene.IsValid() && scene.name.StartsWith("InitTestScene") && UseInRuntimeTests == false)
                return null;

            if (RootLifetimeScope != null && rootLifetimeScopeInstance == null)
            {
                var activeBefore = RootLifetimeScope.gameObject.activeSelf;
                RootLifetimeScope.gameObject.SetActive(false);

                rootLifetimeScopeInstance = Instantiate(RootLifetimeScope);
                DontDestroyOnLoad(rootLifetimeScopeInstance);
                rootLifetimeScopeInstance.gameObject.SetActive(true);

                RootLifetimeScope.gameObject.SetActive(activeBefore);
            }
            return rootLifetimeScopeInstance;
        }

        public bool IsRootLifetimeScopeInstance(LifetimeScope lifetimeScope) =>
            RootLifetimeScope == lifetimeScope || rootLifetimeScopeInstance == lifetimeScope;

        void OnEnable()
        {
            if (Application.isPlaying)
            {
                Instance = this;

                var activeScene = SceneManager.GetActiveScene();
                if (activeScene.isLoaded)
                {
                    OnFirstSceneLoaded(activeScene, default);
                }
                else
                {
                    SceneManager.sceneLoaded -= OnFirstSceneLoaded;
                    SceneManager.sceneLoaded += OnFirstSceneLoaded;
                }
            }
        }

        void OnDisable()
        {
            Instance = null;
        }

        void OnFirstSceneLoaded(Scene scene, LoadSceneMode mode)
        {
            if (RootLifetimeScope != null &&
                RootLifetimeScope.autoRun &&
                (rootLifetimeScopeInstance == null || rootLifetimeScopeInstance.Container == null))
            {
                GetOrCreateRootLifetimeScopeInstance();
            }
            SceneManager.sceneLoaded -= OnFirstSceneLoaded;
        }
    }
}