-
-
Notifications
You must be signed in to change notification settings - Fork 218
Description
Hi,
Thanks for building and maintaining VContainer - so far we're really happy to use it, finding it a good improvement over Zenject.
However, we have run into a problem that we suspect is a bug in the framework. It seems that MonoBehaviour
s that implement "entry point interfaces" (e.g. IInitializable
, IStartable
, IPostStartable
, etc.) get called multiple times - once per child scope loaded. [Inject]
decorated methods also appear to suffer from the issue of being called once per child scope loaded.
So binding a MonoBehaviour
that implements an entry point interface will have its method called multiple times, despite its scope never being unloaded or reloaded. You don't even need separate scenes to replicate it, as its only tied to loading more VContainer child scopes it seems. Thus MonoBehaviour
s bound in a "root" / "grand parent" scope will have their entry points called 3 times (one for itself, one for the parent and one for the child).
At a glance it seems that this issue is a duplicate of this issue, but since that has been closed and we are still replicating the bug in v1.6.3 it seems that either the issue was not fully resolved, or that it has reappeared. Also that issue appears to have been caused by bindings in the child scope, whereas here the issue seems to stem from bindings in 'parent' scopes.
Here's a minimal test project where I've set up a case to replicate the behaviour:
VContainerTestProject.zip
Steps to reproduce (already set up in the above project which can be downloaded and played out of the box):
-
Install latest version of VContainer from UPM in a new, empty Unity project
-
Set up three game objects which will have each their own
LifetimeScope
implementation in a scene, and a fourth game object for the behaviours to bind.
-
Implement abstract base behaviour and child implementations, one for each scope, then attach all three behaviours to the
BehaviourTest
game object:
using System;
using UnityEngine;
using VContainer;
using VContainer.Unity;
public abstract class BaseBehaviourTest : MonoBehaviour,
IInitializable,
IPostInitializable,
IStartable,
IPostStartable,
IDisposable
{
private int _injectedCounter;
private int _initializeCounter;
private int _postInitializeCounter;
private int _startCounter;
private int _postStartCounter;
private int _disposeCounter;
[Inject]
public void Inject()
{
Debug.Log($"{this}(id: {GetInstanceID()}) {nameof(Inject)} called {++_injectedCounter} times");
}
void IInitializable.Initialize()
{
Debug.Log(
$"{this}(id: {GetInstanceID()}) {nameof(IInitializable.Initialize)} called {++_initializeCounter} times");
}
void IPostInitializable.PostInitialize()
{
Debug.Log(
$"{this}(id: {GetInstanceID()}) {nameof(IPostInitializable.PostInitialize)} called {++_postInitializeCounter} times");
}
void IStartable.Start()
{
Debug.Log($"{this}(id: {GetInstanceID()}) {nameof(IStartable.Start)} called {++_startCounter} times");
}
void IPostStartable.PostStart()
{
Debug.Log(
$"{this}(id: {GetInstanceID()}) {nameof(IPostStartable.PostStart)} called {++_postStartCounter} times");
}
void IDisposable.Dispose()
{
Debug.Log($"{this}(id: {GetInstanceID()}) {nameof(IDisposable.Dispose)} called {++_disposeCounter} times");
}
}
public class RootBehaviourTest : BaseBehaviourTest
{
}
public class ParentBehaviourTest : BaseBehaviourTest
{
}
public class ChildBehaviourTest : BaseBehaviourTest
{
}
- Then implement the lifetime scopes, each binding their behaviour implementation:
using VContainer;
using VContainer.Unity;
public class RootLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterComponentInHierarchy<RootBehaviourTest>().AsImplementedInterfaces();
}
}
public class ParentLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterComponentInHierarchy<ParentBehaviourTest>().AsImplementedInterfaces();
}
}
public class ChildLifetimeScope : LifetimeScope
{
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterComponentInHierarchy<ChildBehaviourTest>().AsImplementedInterfaces();
}
}
-
Set up root -> parent -> child lifetime scope relationship setting
-
Press play and you should see how many times each behaviour has its entry points and inject methods called in the console log, e.g.:
So, as can be seen from the above - the behaviour bound in the root / grand parent scope has its entry points and inject methods called thrice. The behaviour bound in the parent scope has its entry points and inject methods called twice, and the child behaviour methods in the child scope are invoked (correctly) once.
This bug is a serious pain for us unfortunately and means that we've had to make workarounds. POCO/Non-MonoBehaviour
implementations do not appear to suffer from this issue.
Let me know if there's any more information I can provide to help. Thanks in advance!