Awake is not called before Initialize when using Container.InstantiatePrefab
When Instantiating prefab using Container.InstantiatePrefab Extenject disables the prefab, then Instantiates it, performs Inject which eventually results in GameObjectContext calling Initialize on all it's classes implementing IInitializable and then enables the instantiated prefab after that. If some class in the instantiated container expects some other MonoBehaviour class in the same container to be initialized via Awake() this doesn't happen because the gameobject is disabled at that moment.
This can be seen in DiContainer ln.2035.
I am including test project that illustrates this problem. The class
TestMBWithAwake
is preparing some data in Awake and class
TestPrefabScriptWithInitialize
is relying on this data to be ready in Initialize however that doesn't happen.
Note that this is barebone simplified structure that I'm using in project for different purpose. In my project I need to be able to instantiate arbitrary GameObjectContext prefab within arbitrary subcontainer, that's why there is the custom factory. I am well aware that what I'm doing in the test project can be solved by some different means, this project is meant to illustrate this problem and raise two questions:
- Why is it that DiContainer.InstantiatePrefab has to disable the prefab before instantiation ,then Inject on the instance and only after that enables it? Seems like that is very purposefully done so that Awake is not called at that time.
- Is it really correct that GameObjectContext Construct method calls Initialize? In other cases afaik you can rely on [Inject] methods being called first and then Initialize being called after. This seems like it's breaking that rule.
Hello there,
first of all, I'm not a developer of Extenject, so at the moment I can only give some educated guesses about why this is done.
While initializing newly created Monobehaviours (be it through Prefab instantiation or by adding them to an existing GameObject), Unity automatically calls the Awake, Start, and probably the OnEnable messages on all affected components. By definition, the Awake is supposed do all initialization of the component independent of other components state (e. g. calls to GetComponent, and Start is supposed to do all initialization which rely on other components (e. g. setting initial values to UI elements).
Extenject is just some other C# code added to the project, so it's not able to do some black magic to influence this initialization process. This also means, that it's not able to intercept the message calls, so it can't execute its own code inbetween, and I don't think there will be any good solution offered by Unity in the future.
Extenject is able to execute its own calls (IInitializable, [Inject] fields, [Inject] methods) in whatever order it wants, because the execution order of these is defined by Zenjects code, and not by Unity. Also, the [Inject] fields and methods are supposed to store the injected objects, but not to do any other initialization. Keeping this in mind, it is fine that all [Inject]ion is done first before the IInitializable methods are called, since the initialization probably requires all injection to be done by then.
I hope this answered the questions you had so far.
Since IInitializable is as far as I can tell supposed to be on the same level as Awake regarding its responsibilities, and due to the reason detailed above (it's not easily possible to execute code in between Awake and Start), a solution could be to add another interface like ILateInitializable that declares a method InitializeLate, or ILateStartable with StartLate, or something similar (no Start, since this could misbehave on a component), that's called after reactivating the GameObject (so after Start and possibly after OnEnable).
This sounds quite nice so far, until you notice that there are prefabs that might be inactive and activated later on by the programmer who instantiates it. If this is the case, the late initialization/late start can't be called automatically, but must be called from a component once the GameObject gets activated. Implementing a component for this and adding it if there is an implementation of that interface shouldn't be to difficult, but it's technically not reliable, since this additional component could be removed at any moment. Other options would cause the initialization to be delayed to the next frame/the end of the current one after activation, possibly causing other unexpected behavior.
This is just a guess from my side, but due to this technically not reliable, I don't think Extenject will include code to handle this case, since a user would expect this to "just work". But I'm not the one making decisions about Extenject. On the other hand, you can use an approach similar to what I've outlined above to write your own delayed initialization.
Or maybe I've still missed something enabling a simple solution within Extenject to solve your issue...
Good luck with your projects anyway. ;)