Open AntonPetrov83 opened 4 years ago
I have investigated the issue a bit and can confirm that the feature is bugged. Here is a summary:
WithKernel works as Binding a new Kernel
to subcontainer and binding the interfaces of that Kernel to parent subcontainer. In theory when the parent container starts InitializableManager will pick up IInitializable binding of the subcontainer kernel and resolve it. Causing the internal kernel to start.
However this WithKernel registration happens during subcontainer creation. Which only happens when Foo
is resolved. When Foo
is not IInitializable
even if it is NonLazy
there is a chance that the InitializableManager
resolves before Foo
. If this happens the List
Workaround: Making your Facade class (Foo
) IInitializable and NonLazy at the same time seems to fix the issue.
Proper Fix: At first I though adding subcontainer kernel to parent kernel before Foo is initialized may be the solution. However that means that Kernel would start running regardless if Subcontainer is created lazy or not. This would particularly be problem for transient subcontainers. Often used with factories or pools.
So I think the proper fix is to immediately bind a SubcontainerKernelProxy to parent context if none exists. This would be a proxy kernel that reserves a spot on InitializationManager, TickableManager etc. But won't do anything until actual kernel is created. When the subcontainer actually initializes, subcontainer kernel would hook to proxy kernel to receive callbacks.
I think another possible solution is to use InitializableManager.Add()
(and similar methods for other managers) on a parent container later when a sub-container is instantiated.
Proper Fix: At first I though adding subcontainer kernel to parent kernel before Foo is initialized may be the solution. However that means that Kernel would start running regardless if Subcontainer is created lazy or not. This would particularly be problem for transient subcontainers. Often used with factories or pools.
So I think the proper fix is to immediately bind a SubcontainerKernelProxy to parent context if none exists. This would be a proxy kernel that reserves a spot on InitializationManager, TickableManager etc. But won't do anything until actual kernel is created. When the subcontainer actually initializes, subcontainer kernel would hook to proxy kernel to receive callbacks.
If I understood this correctly, this is the workaround I ended up implementing also. We have this concept of "persistent object installers", which can be used to create objects in a subcontainer that live across scene loads. We bind an PersistentObjectContext
at the ProjectContext
level (this is essentially what I believe you were referring to as SubcontainerKernelProxy
), and then bind a Kernel
and one of these in each subcontainer:
private class KernelRegisterer
{
public KernelRegisterer(PersistentObjectContext context, Kernel kernel)
{
context.DisposableManager.Add(kernel);
context.DisposableManager.AddLate(kernel);
context.TickableManager.Add(kernel);
context.TickableManager.AddLate(kernel);
context.TickableManager.AddFixed(kernel);
kernel.Initialize();
}
}
For transient objects, you should implement removal here (as IDisposable
) also, but in our use case, these live as long as the PersistentObjectContext
, so it's not a problem to never unregister.
Few years later, it's still a problem. Yet another workaround that I found, if someone is interested, is actually from examples. You just need to make your facade class to extend Kernel
public class Greeter : Kernel
{
public Greeter()
{
Debug.Log("Created Greeter");
}
}
And then you bind self and interfaces to
public override void InstallBindings()
{
Container.BindAllInterfacesAndSelf<Greeter>()
.To<Greeter>().FromSubContainerResolve().ByMethod(InstallGreeter).AsSingle().NonLazy();
}
This simple example does not work as expected. While
Bar
's constructor is called theTick()
method is never called.UPDATE: For now I can see that
SubContainerCreatorUtil
bindsKernel
interfaces during Resolve-phase whenInitializableManager
already exists. That is whyInitializableManager
knows nothing aboutKernel
.UPDATE 2: Changing code to this fixes the issue:
UPDATE 3: This test fails because of early
Resolve<InitializableManager>();
like the real application.UPDATE 4: Duplicate and related issues: https://github.com/modesttree/Zenject/issues/626 https://github.com/modesttree/Zenject/issues/574 https://github.com/svermeulen/Extenject/issues/13
UPDATE 5: The method of inheriting the Kernel described here works. https://github.com/svermeulen/Extenject/blob/master/Documentation/SubContainers.md#using-byinstaller--bymethod-with-kernel
So the interesting part is the difference between a manually bound Kernel-based class and the automatic Kernel setup in
SubContainerCreatorUtil
.