Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Use Parent Context option to ZenjectBinding #56

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

dustinlacewell
Copy link

Objective

Add a "Use Project Context" flag to ZenjectBinding to allow components to be bound in a parent container.

Approach

  • Add a Context ProjectContext property and bool UseProjectContext property to ZenjectBinding
  • During SceneContext.InstallBindings() but before SceneContext.InstallSceneBindings(), if ZenjectBinding.UseParentContext is true, set each ZenjectBinding.ParentContext to the nearest Context.
  • During Context.InstallSceneBindings() for each context, bind ZenjectBindings to the current context not only if it is equal to ZenjectBinding.Context but also ZenjectBinding.ParentContext.

Motivation

Sub-containers are a useful abstraction. The documentation states:

In some cases it can be very useful to use multiple containers in the same application. For example, if you are creating a word processor it might be useful to have a sub-container for each tab that represents a separate document. This way, you could bind a bunch of classes AsSingle() within the sub-container and they could all easily reference each other as if they were all singletons.

The documentation appreciates how sub-containers are essential for keeping large projects organized. It emphasizes how sub-containers work especially well with the Facade pattern:

Subcontainers can also be incredibly powerful as a way to organize big parts of your code base into distinct modules. You can manage dependencies at a higher level - as dependencies between modules instead of just between classes. Without subcontainers, as your code base grows larger and larger, having everything exist as singletons at the same level can get unwieldy over time, since every class can depend on every other class just by adding a constructor parameter for it. By instead grouping related classes into their own subcontainers and forcing any other interested classes to interact via a facade class, it can be much easier to manage and understand the overall dependencies between parts of the code.

Finally, the documentation acknowledges that many of the benefits of sub-containers are hard to enjoy when dealing with MonoBehaviours and scene-related matters:

One issue with the sub-container hello world example above is that it does not work very well for MonoBehaviour classes. There is nothing preventing us from adding MonoBehaviour bindings such as FromComponentInNewPrefab, FromNewComponentOnNewGameObject, etc. to our ByInstaller/ByMethod sub-container - however these will cause these new game objects to be added to the root of the scene heirarchy, so we'll have to manually track the lifetime of these objects ourselves by calling GameObject.Destroy on them when the Facade is destroyed. Also, there is no way to have GameObject's that exist in our scene at the start but also exist within our sub-container. Also, using ByInstaller and ByMethod like above does not support the use of interfaces such as IInitializable / ITickable / IDisposable inside the subcontainer. These problems can be solved by using Game Object Context.

The documentation goes through an example of building a Ship prefab, that contains a GameObjectContext, a facade, and a ZenjectBinding. The documentation explains that without the use of the "Use Scene Context" flag on the ZenjectBinding the Ship's facade would get bound to its own local GameObjectContext hiding it from dependants in the wider scene. By using "Use Scene Context" the facade will instead be bound in the Scene Context.

This is a great serendipitous way in which the natures of sub-containers and the scene-hierarchy align to bring additional power of Zenject to Unity. However, if one tries to copy this exact pattern, for all the same reasons, and for all the same benefits even one single level deeper, then you run into troubles. If the ship had an internal prefab which warranted its own encapsulation via it's own GameObjectContext, facade and ZenjectBinding, there is currently no way to get the facade to bind into the parent Ship's GameObjectContext even though the GOCs will already work correctly in this nested fashion. There is simply no way to use ZenjectBinding to do the binding.

This PR addresses that. I understand if this specific approach is not the best one and is just meant to start a conversation. It is simple and works though.

@dustinlacewell
Copy link
Author

I've uploaded a small .unityasset that contains a prefab and two scripts to demonstrate how the facade of an internal component, with it's own GOC, can use Use Parent Context to get bound into a parent's GOC.

ParentContextExample.zip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant