Skip to content

Stop GameObject hierarchy injection recursion if a descendant LifetimeScope is found #819

@epaezrubio

Description

@epaezrubio

Hello VContainer maintainers. I'm opening this issue to suggest a change in the InjectGameObjectRecursive function.

The problem

Currently this function traverses the entire tree of the GameObjects' hierarchy without any mechanism of stopping it. However, in my opinion this breaks the parent/child nature of scopes. It makes the prefabs unusable in nested contexts.

Let's look at an example, starting with a simple MonoBehaviour:

public class MyBehaviour : MonoBehaviour
{
    [Inject]
    private IFoo _foo;

    [Inject]
    private Bar _bar;
}

Now let's imagine that I wish to use this component in the following two hierarchies:

Standalone prefab:

PrefabScope
└── MyBehaviour

Prefab as a descendant of a different scope:

ProjectScope
├─┬ PrefabScope
│ └── MyBehaviour
└── OtherBehaviour

In this scenario, both ProjectScope and PrefabScope are LifetimeScopes that provide the following:

  • ProjectScope: Foo : IFoo
  • PrefabScope: Bar and optionally DummyFoo : IFoo

What this allows is to use PrefabScope as a standalone scope without parent (and provide a dummy IFoo to MyBehaviour), as well as using an object provided by an upper scope.

With the current InjectGameObjectRecursive implementation, ProjectScope will traverse all the way down to MyBehaviour and won't be able to provide an instance of Bar.

We could then configure the PrefabScope container as following:

public class PrefabScope : LifetimeScope
{
    protected override void Configure(IContainerBuilder builder)
    {
        if (Parent == null)
        {
            builder.Register<DummyFoo>(Lifetime.Singleton).As<IFoo>();
        }

        builder.Register<Bar>(Lifetime.Singleton);
    }
}

Or even a more flexible approach using other APIs such as Container.TryResolve.

Suggested solution

While traversing the hierarchy, stop if one LifetimeScope is found, asuming that it will take care of the injection of its descendants in the tree itself.

            void InjectGameObjectRecursive(GameObject current)
            {
                if (current == null)
                    return;

                using (ListPool<MonoBehaviour>.Get(out var buffer))
                {
                    buffer.Clear();
                    current.GetComponents(buffer);

+                    if (current != gameObject)
+                    {
+                        foreach (var monoBehaviour in buffer)
+                        {
+                             if (monoBehaviour is LifetimeScope)
+                                return;
+                        }
+                    }

                    foreach (var monoBehaviour in buffer)
                    {
                        if (monoBehaviour != null)
                        { // Can be null if the MonoBehaviour's type wasn't found (e.g. if it was stripped)
                            resolver.Inject(monoBehaviour);
                        }
                    }
                }

                var transform = current.transform;
                for (var i = 0; i < transform.childCount; i++)
                {
                    var child = transform.GetChild(i);
                    InjectGameObjectRecursive(child.gameObject);
                }
            }

This might come with backwards compatibility issues, so please let me know if you can think of a more elegant way of implementing it.

Please let me know if this could be a good addition and I'll prepare a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions