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.89k stars 165 forks source link

[Question] Factory #551

Closed ATHellboy closed 11 months ago

ATHellboy commented 12 months ago

Hey there, Recently I'm trying to replace Zenject with VContainer. Sorry about creating a new issue for asking my question. Unfortunately I couldn't find anywhere to ask my question. Probably there is Discord channel or Gitter group for that ?

anyway, In Zenject, I've implemented factory method and instantiation in this way:

    public class ZenjectResourceFactory : IResourceFactory
    {
        private readonly DiContainer _container;

        public ZenjectResourceFactory(DiContainer container)
        {
            _container = container;
        }

        public Object Instantiate(Object @object)
        {
            Object instance = Object.Instantiate(@object);
            _container.Inject(instance);
            return instance;
        }

        public Transform Instantiate(Transform transform)
        {
            Transform obj = _container.InstantiatePrefab(transform).transform;
            return obj;
        }

        public Transform Instantiate(Transform transform, Transform parent)
        {
            Transform obj = _container.InstantiatePrefab(transform, parent).transform;
            return obj;
        }

        public Transform Instantiate(Transform transform, Vector3 position)
        {
            Transform obj = _container.InstantiatePrefab(transform).transform;
            obj.transform.position = position;
            return obj;
        }

        public Transform Instantiate(Transform transform, Vector3 position, Transform parent)
        {
            Transform obj = _container.InstantiatePrefab(transform, parent).transform;
            obj.transform.position = position;
            return obj;
        }

        public Transform Instantiate(Transform transform, Vector3 position, Quaternion rotation, Transform parent)
        {
            Transform obj = _container.InstantiatePrefab(transform, parent).transform;
            obj.transform.position = position;
            obj.transform.rotation = rotation;
            return obj;
        }
    }

I'm curious to know if I can achieve same thing in Vcontainer ? Probably I should use this ? https://vcontainer.hadashikick.jp/registering/register-factory#registering-factory-methods Or there is another way like accessing container by injection and use methods there like I was using Zenject ?

ATHellboy commented 12 months ago

Probably it is in this way actually. https://vcontainer.hadashikick.jp/resolving/container-api Exactly like Zenject. Just I should replace InstantiatePrefab with Instantiate. But because I'm still trying to figure out things in VContainer so I've not gotten the result.

ATHellboy commented 11 months ago

How can I have LifetimeScope on a prefab when using IObjectResolver.Instantiate ? Looks like when I use this method, actually it registers LifetimeScope component too (Because of registering the root gameObject and all the descendents) and it makes an error on the parent which calls instantiating. The error:

VContainerException: Failed to resolve System.Object : No such registration of type: Character.CharacterContext
VContainer.Internal.ReflectionInjector.InjectMethods (System.Object obj, VContainer.IObjectResolver resolver, System.Collections.Generic.IReadOnlyList`1[T] parameters) (at ./Library/PackageCache/jp.hadashikick.vcontainer@af7bd4ecab/Runtime/Internal/ReflectionInjector.cs:106)
VContainer.Internal.ReflectionInjector.Inject (System.Object instance, VContainer.IObjectResolver resolver, System.Collections.Generic.IReadOnlyList`1[T] parameters) (at ./Library/PackageCache/jp.hadashikick.vcontainer@af7bd4ecab/Runtime/Internal/ReflectionInjector.cs:28)
VContainer.Container.Inject (System.Object instance) (at ./Library/PackageCache/jp.hadashikick.vcontainer@af7bd4ecab/Runtime/Container.cs:220)
VContainer.Unity.ObjectResolverUnityExtensions.<InjectGameObject>g__InjectGameObjectRecursive|0_0 (UnityEngine.GameObject current, VContainer.Unity.ObjectResolverUnityExtensions+<>c__DisplayClass0_0& ) (at ./Library/PackageCache/jp.hadashikick.vcontainer@af7bd4ecab/Runtime/Unity/ObjectResolverUnityExtensions.cs:21)
VContainer.Unity.ObjectResolverUnityExtensions.InjectGameObject (VContainer.IObjectResolver resolver, UnityEngine.GameObject gameObject) (at ./Library/PackageCache/jp.hadashikick.vcontainer@af7bd4ecab/Runtime/Unity/ObjectResolverUnityExtensions.cs:34)
VContainer.Unity.ObjectResolverUnityExtensions.InjectUnityEngineObject[T] (VContainer.IObjectResolver resolver, T instance) (at ./Library/PackageCache/jp.hadashikick.vcontainer@af7bd4ecab/Runtime/Unity/ObjectResolverUnityExtensions.cs:130)
VContainer.Unity.ObjectResolverUnityExtensions.Instantiate[T] (VContainer.IObjectResolver resolver, T prefab, UnityEngine.Vector3 position, UnityEngine.Quaternion rotation, UnityEngine.Transform parent) (at ./Library/PackageCache/jp.hadashikick.vcontainer@af7bd4ecab/Runtime/Unity/ObjectResolverUnityExtensions.cs:117)

This is a partial screenshot from the prefab I'm talking about. (Gameobjects children are not included): image

Reproduction project VContainerReproduction.zip

Bezarius commented 11 months ago

@ATHellboy I'm not sure, maybe I've already encountered this problem, but your case is poorly described. I advise you to write a reproduction.

ATHellboy commented 11 months ago

@ATHellboy I'm not sure, maybe I've already encountered this problem, but your case is poorly described. I advise you to write a reproduction.

Thanks for the reply. I just want to instantiate a prefab which contains LifetimeScope component without throwing any errors. btw, injection is done correctly in debugging process but there is an error about not finding registeration for that injected class too. I'm creating minimal reproduction project for showing the issue. I'll post it here.

Bezarius commented 11 months ago

@ATHellboy mb this example will help u: image

ATHellboy commented 11 months ago

@Bezarius Reproduction project VContainerReproduction.zip

ATHellboy commented 11 months ago

@ATHellboy mb this example will help u: image

In one of my tests, I've tried to use CreateChildFromPrefab from Player LifetimeScope (The prefab just contained CharacterLifetimeScope and Auto Run was disabled) then using IObjectResolver.Instantiate on Character prefab without CharacterLifetimeScope and then Build on newly created LifetimeScope which was created by CreateChildFromPrefab. The resault was no injection at all on the Character and same error. Is it the same approach like above ?

Bezarius commented 11 months ago

@ATHellboy my bad, I forgot to show the part with the injection: image

Bezarius commented 11 months ago

@ATHellboy dependency injection occurs at the moment of instantiation before dependencies from CharacterLifetimeScope are resolved. To solve this problem, you need to manually manage this process. Unfortunately, VContainer does not know how to do this out of the box.

ATHellboy commented 11 months ago

@ATHellboy dependency injection occurs at the moment of instantiation before dependencies from CharacterLifetimeScope are resolved. To solve this problem, you need to manually manage this process. Unfortunately, VContainer does not know how to do this out of the box.

Thanks for the reply and all the helps here. Last night I figured out how to inject dependencies in this specific situation by these lines:

LifetimeScope characterLifetimeScope = _lifetimeScope.CreateChildFromPrefab(_characterLifetimeScope);
Transform characterInstance = UnityEngine.Object.Instantiate(_characterPrefab, pos, Quaternion.identity, characterLifetimeScope.transform);
characterLifetimeScope.Build();

But this morning I noticed Awake and OnEnable are called before method injection. So changed that to this:

LifetimeScope characterLifetimeScope = _lifetimeScope.CreateChildFromPrefab(_characterLifetimeScope);
_characterPrefab.gameObject.SetActive(false);
Transform characterInstance = UnityEngine.Object.Instantiate(_characterPrefab, pos, Quaternion.identity, characterLifetimeScope.transform);
characterLifetimeScope.Build();
characterInstance.gameObject.SetActive(true);
_characterPrefab.gameObject.SetActive(true);

Does it make sense ?

Bezarius commented 11 months ago

@ATHellboy Yes, in general it looks like it is correct. In my example, there are just some specifics, so there is more code and it looks more complicated.

ATHellboy commented 11 months ago

@ATHellboy Yes, in general it looks like it is correct. In my example, there are just some specifics, so there is more code and it looks more complicated.

I just noticed by using UnityEngine.Object.Instantiate on the prefab which has LifetimeScope, eveything works correctly. It made me nut tbh. I didn't need to use Factory or generting scope at all. I think it must be mentioned somewhere on doc. It took me so much time to figure it out.