Search Unity

[RELEASED] Zenject Dependency Injection Framework

Discussion in 'Assets and Asset Store' started by eventropy, Jun 3, 2014.

  1. FREEZX

    FREEZX

    Joined:
    Apr 2, 2013
    Posts:
    64
    @makeshiftwings That's how i do things. This has an advantage over singletons in that it cleans instance memory when changing scenes, and feels cleaner overall.
    That said, i'd be very interested in learning better ways to use Zenject, as in, a tutorial on how to structure things with many dependencies, best practices, when to use and when to avoid DI, etc.
    I feel like the lack of resources about using this in practice is keeping developers away, and even those that do try it are a bit confused (as was i, i think i'm getting a hang of the basics).
     
  2. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    That is one of the reasons I prefer constructor injection - it makes the dependencies more explicit. Field injection isn't necessarily bad though, and is actually necessary if you have circular dependencies (although circular dependencies are usually a sign of bad design anyway)

    There is an upcoming feature listed in the github issues which may help here as well. I want to add support for defining parameters in methods marked with [PostInject], which would be filled in the same way they are for constructors. This would allow you to define a method on MonoBehaviour's which act as the constructor and takes all dependencies.

    In my opinion using [Inject] is still much better than using a globally accessible singleton. Declaring dependencies as fields is still better than being able to reference the singleton from any method. It still forces you to think about the dependencies between classes (unlike with singleton) which is good.

    Not mention the other issues with singleton (makes testing harder, have to depend on a concrete class rather than an interface, can introduce issues with initialization and destruction order, etc.)
     
  3. llde_chris

    llde_chris

    Joined:
    Aug 13, 2009
    Posts:
    205
    Hi guys,

    We've been profiling, and we've noticed that Zenject (1.17) can have 0 GC per frame with the following changes:

    - Comment out PROFILIING_ENABLED in ProfileBlock.cs
    - Comment out ProfileBlock.Start calls in TickableManager
    - Comment out OnGUI from UnityEventManager
    - Replace foreach calls in TaskUpdater with node walk versions for the LinkedLists (ClearRemovedTasks already does this) and the Lists replace them with normal for loops

    After these changes it looks much cleaner in the profiler (Unity 5 b18).

    Hope this helps anyone trying to minimize GC calls.
     
  4. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    I've merged in your suggestions to the main Zenject branch - thanks :)
     
    cannon likes this.
  5. Ellandar

    Ellandar

    Joined:
    Jan 5, 2013
    Posts:
    207
    G'day Eventropy,

    I may be doing something wrong with .ToSingleFromPrefab, or there's a regression of this problem:
    https://github.com/modesttree/Zenject/commit/d427c4476af25d6261ff634d319ba48f5c12c7d0.
    I'm using zenject version 1.17 from the asset store and unity 4.61 (but problem also reproduce-able in 5.0.0b18)

    I have an installer with the following info in it:
    Code (CSharp):
    1. public override void InstallBindings()
    2.     {
    3.         Container.Bind<UIHelper>().ToSingleFromPrefab<UIHelper>(myData.MenuGameObject);
    4.         Container.Bind<IInitializable>().ToSingleFromPrefab<UIHelper>(myData.MenuGameObject);
    5.     }
    When the app starts I get two prefabs of myData.MenuGameObject spawned due to the IInitializable spawning one, and then a component that Injects UIHelper spawning another. The component that requires UIHelper is also a component that is part of the prefab that UIHelper is in, so I get circular warnings as well due to the tosingle not working.
    I was expecting to only get one.

    The other point I thought I'd mention is when dealing with unity's new UI objects with Rect transforms there are two problems:
    1. the use of SetParent(transform) is preferred over transform.parent = transform. so on line 90 of GameObjectInstantiator I changed it to
    Code (CSharp):
    1. gameObj.transform.SetParent(_rootTransform);
    This seemed to make Unity happy.

    2. If a UI canvas element is not in the root of the hierarchy it isn't visible. This might just be something strange with my project, but I'm having to SetParent(null) in my awake on UI elements instantiated with zenject.

    Cheers!
    - Ell
     
  6. cannon

    cannon

    Joined:
    Jun 5, 2009
    Posts:
    751
    Regarding #2, UI canvas shows fine for me no matter how I parent or prefab it. This is with 5.0b18.

    Might want to check your camera, and also if the parent is a RectTransform instead of just a plain Transform.
     
  7. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Regarding #1, Zenject doesn't currently have very good support for circular dependencies. I think in your example, the best solution is just to use public MonoBehaviour references to satisfy dependencies between scripts on the same prefab. Would that address your problem?

    By the way, not strictly related to your question but worth noting: ToSingleFromPrefab will create unique instances based on the pair of (concrete type and the prefab). So if you provide a different prefab in your second call there, then it will create multiple instances of Test1 (this is by design). But if you use the same concrete type (Test1) and the same prefab, it should bind to the same instance, provided it doesn't hit a circular dependency error or something. I just double checked that it does work this way (on the latest version)

    I will update GameObjectInstantiator to use SetParent, thanks for letting me know.

    I can't much comment on #2 however, as I only have a bit of experience with the new UI stuff.
     
  8. Ellandar

    Ellandar

    Joined:
    Jan 5, 2013
    Posts:
    207
    For #1; yes, it would be fine. I was buzzing on the DI juice and used it everywhere :) I'll dial it back a bit.

    In the example, the 'myData' object is a sub class of the installer that the InstallBindings is attached to, so in this case the concrete class (UIHelper) and myData.MenuGameObject prefab is the same in both calls.

    I'm at work, but I might sneak in a quick check in unity. I'll remove the circular dependency and then try it again, as it might be the problem.

    #2, I'll try @cannon suggestion there, as I know the parent gameobject is a standard Transform; I didn't realise it needed to be a rect all the way to root.

    Thanks for your help dude. Liking the framework so far, i'm very new to DI and am really enjoying it. It feels freeing.
    - Ell
     
  9. Ellandar

    Ellandar

    Joined:
    Jan 5, 2013
    Posts:
    207
    Got home and tried these solutions.

    #1 setting the link as a mono-behaviour reference worked, and yes it turned out it was a circular reference that was instantiating the prefab twice.

    #2 it must have been related to the circular reference (and related errors) above, because once this first problem was fixed, number two fixed itself too.

    Thanks both of you for your help, I now have a Dep. Injected menu system \o/

    - Ell
     
  10. cannon

    cannon

    Joined:
    Jun 5, 2009
    Posts:
    751
    RectTransform isn't required, I prefer plain Transforms myself. However IIRC an empty gameobject with a RectTransform that's used as a parent could break the canvas.

    Anyway, glad you have it all sorted out!
     
  11. Illusive-S

    Illusive-S

    Joined:
    Sep 19, 2013
    Posts:
    8
    Hello there
    I have recently downloaded zenjet and i am trying to understand the best way to use DI in unity and in general
    And im having a lot of problems with it :<

    So from what i understand now
    Zenject is used to inject stuff from IOC container that is there somewhere in the CompositionRoot
    So can there be multiple containers? Or CompositionRoots? Or is that just the same thing?

    ToSingle binds allow to create singletons
    So a good way to use it is to have a "Battle" object that deals with ending the battle and units having Injected "Battle" singleton to know whats going on...?
    Is there a way to delete a singleton? And create another one? Or do i have to get a Reset() method?
    ToTransient binds always create new object
    But i can pass references to the created objects to other objects? They wont be created again?


    So from what i have gathered so far its easier to use dependency injection when i would implement something in my code by either singleton or prototype?
     
  12. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    With Zenject you add bindings to the Container, so that it "knows" how to create a bunch of different objects. Then when the container goes to create a new object it can find its dependencies automatically.

    The CompositionRoot is just the place where all the bindings are declared. It is what calls all the 'installers'. Yes, there is a bit of jargon to pick up on :) But it is mostly standard jargon from dependency injection in general

    There can be multiple Containers and multiple CompositionRoot's but that is getting into more advanced usage of Zenject so I would recommend against it for beginners. See above for the difference between the two.

    Yes, that sounds reasonable

    Singletons are assumed to exist for the lifetime of the container. When the CompositionRoot is destroyed, it will call Dispose() on all bindings that are bound to the IDisposeable interface, which can be good for cleanup when changing scenes for example.

    In general though, it's bad practice to change the contents of the Container after the application has started.

    Not completely sure what you mean. If you want to create a new instance and pass in parameters at runtime, then you should use a Factory instead of ToTransient or ToSingle. See the github page for details on Factories.

    Not sure what you mean.
     
  13. Illusive-S

    Illusive-S

    Joined:
    Sep 19, 2013
    Posts:
    8
    Then whats the point of ToTransient?

    And what i meant was every time i do
    Code (CSharp):
    1. ClassBindedWithTransient object;
    It will be instantiated when exactly? When i have it in the class that will be created at runtime?

    and if ill do

    Code (CSharp):
    1. [InjectOptional]
    2. ClassBindedWithTransient object;
    it wont be instantiated?

    Well from what i understand so far, if i feel the need to have only one object of class or if i will make many objects of class that are identical, its the moment when i should probably use DI.
     
  14. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Hey sorry for the late response

    ToTransient is still useful for cases where you want a new object to be created every time that dependency is injected somewhere, and the object doesn't require runtime parameters.

    Assuming that you have marked 'object' with the [Inject] attribute, and that ClassBindedWithTransient is marked as ToTransient() then it will be created at the same time that the class that it's inside is created.

    If it's ToSingle() it will be created the first time Zenject creates something that has it as a dependency.

    I think I understand what you're getting at. The other cases you're talking about are those objects that require runtime parameters, is that right?

    Again, in that case you can use factories, and then create those objects by passing in the runtime parameters to the factory Create() method.

    I was wondering the same thing when I started learning about dependency injection. Seemed great for singletons or (as you say) identical objects that you are creating many of, but in the real world it doesn't take long before they require some kind of parameter that you won't have until after the program starts. But there is a way to handle that (factories). The sample project has some examples
     
  15. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I may be overthinking things, but is there a way to basically bind to a GetComponent call? What I'm imagining is that you would have a MonoBehaviour with a field like "[Inject] IFoo foo" and you could have one Installer for the actual game that binds IFoo to its game object's FooComponent by calling GetComponent<FooComponent>(), and a different Installer for unit testing that binds IFoo to a non-MonoBehavior mock class.
     
  16. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    That's interesting. You can almost do what you describe currently. I ran a quick test and got the following working with just a few minor tweaks to how Zenject works internally. This is what it would look like:

    Code (csharp):
    1.  
    2. public override void InstallBindings()
    3. {
    4.   ...
    5.   Container.Bind<TestMonoBehaviour>().ToMethod(CreateTest);
    6.   ...
    7. }
    8. TestMonoBehaviour CreateTest(DiContainer container, InjectContext context)
    9. {
    10.     var gameObj = ((MonoBehaviour)context.EnclosingInstance).gameObject;
    11.  
    12.     // Any of the following could work here
    13.     // return gameObj.AddComponent<TestMonoBehaviour>();
    14.     // This would be better for cases where TestMonoBehaviour has its own dependencies:
    15.     //return container.Resolve<GameObjectInstantiator>().AddMonobehaviour<TestMonoBehaviour>();
    16.  
    17.     return gameObj.GetComponent<TestMonoBehaviour>();
    18. }
    19.  
     
    Last edited: Feb 7, 2015
    makeshiftwings likes this.
  17. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Cool, yeah something like that would work. In Unity 5, you can now call GetComponent<> with an interface as the type argument as well which might make things easier.
     
  18. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Ok I added the few minor tweaks necessary to make my sample code work to the main Zenject repo. I also changed the sample code above to use AddMonoBehaviour instead of AddComponent in case the object its creating has its own dependencies.
     
    makeshiftwings likes this.
  19. WPCJack

    WPCJack

    Joined:
    Mar 15, 2013
    Posts:
    26
    Hey all,
    Getting some broken behaviour trying to do a 64-bit IL2CPP build with zenject. I was only on 4.6.2.p2 so I'm jumping up to 4.6.3. Has anyone else had any luck with a 64-bit build?

    Cheers
     
  20. WPCJack

    WPCJack

    Joined:
    Mar 15, 2013
    Posts:
    26
    4.6.3f1 Fixed it.
     
  21. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    I have another question: If I have multiple Composition Roots in a scene, and they each have an installer binding some class with ToSingle<>, are they both using the same instance of that class, or does it actually create two instances?

    Alternatively, is there a way to make a Composition Root install to other game objects that are not its children? I have several assets that depend on them being at the root level of the hierarchy, so I can't put them underneath a single Composition Root object.
     
  22. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Each composition root has its own container, so any binding with ToSingle<> will have a new instance created for each composition root. In most cases one composition root is what you want - I can't think of many cases where you really benefit from having multiple. Possibly when loading an entire other scene using LoadLevelAdditive, not sure.

    Anyway, if you want your composition root to inject on everything in the scene and not just the objects underneath it, you can change CompositionRoot to do that pretty easily.

    If you open up CompositionRoot.cs and go to the Init() function, then delete the line with "InjectionHelper.InjectChildGameObjects" in it, and replace it with the following, then it should do what you want.

    Code (csharp):
    1.  
    2.   var rootGameObjects = GameObject.FindObjectsOfType<Transform>().Where(x => x.parent == null).Select(x => x.gameObject).ToList();
    3.  
    4.   foreach (var rootObj in rootGameObjects)
    5.   {
    6.   InjectionHelper.InjectChildGameObjects(_container, rootObj, !OnlyInjectWhenActive);
    7.   }
    8.  
    I haven't tested this so let me know if that works for you. If you think it would be useful I can also add a flag to CompositionRoot to use this behaviour. You're not the first person to ask about that.
     
  23. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Ah, ok thanks! Yeah, I think that would definitely be a useful flag to add. There are a lot of Assets in the asset store (UFPS, RTP, TerrainComposer, MasterAudio, and Unistorm being some examples) that have functionality that relies on some game object being in the root. UFPS for instance assumes the player is a root object and uses "transform.root" all over the place in the code, and it would be a huge pain to try and change all that code.
     
  24. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    FYI, this sort of works but has some bugs. It seems that the Composition Root later calls "CompositionRootHelper.InstallStandardInstaller(container, this.gameObject);" so there is one standard installer on the composition root object that the other objects are not children of. This seems to be causing some weirdness, like DisposableManager is now throwing an assert in OnApplicationQuit that it's getting disposed multiple times.
     
  25. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Hey, so I had a chance today to look at this issue in more detail. I was able to reproduce it, then found that it was because it was also injecting into the GlobalCompositionRoot in addition to all the objects that were pre-built into your scene. So I just changed it to skip any root game objects that are either themselves a CompositionRoot or a GlobalCompositionRoot then it seems to work now.

    I've add the flag to the github repo and have submitted it to the asset store (which takes a few days). If you just want the code here's what it looks like now:

    Code (csharp):
    1.  
    2.   var rootGameObjects = GameObject.FindObjectsOfType<Transform>()
    3.   .Where(x => x.parent == null && x.GetComponent<GlobalCompositionRoot>() == null && (x.GetComponent<CompositionRoot>() == null || x == this.transform))
    4.   .Select(x => x.gameObject).ToList();
    5.  
    6.   foreach (var rootObj in rootGameObjects)
    7.   {
    8.   InjectionHelper.InjectChildGameObjects(_container, rootObj, !OnlyInjectWhenActive);
    9.   }
    10.  
    11.  
     
    makeshiftwings likes this.
  26. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    Awesome, thank you!
     
  27. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Updated Zenject to v2.2.

    Lots of improvements / minor bug fixes. Added support for using Zenject outside of Unity 3d, some optimizations (now has zero per-frame allocations), better support for identifiers / conditional bindings, etc. Also, since it is a major version upgrade there was some API-breaking changes (sorry). See release notes for full list.
     
  28. denlee0710

    denlee0710

    Joined:
    Mar 9, 2015
    Posts:
    36
    Hi,
    I'm new to DI and I think I'm having trouble understanding the ToSingle binding:
    Code (CSharp):
    1. Container.Bind<IFoo>().ToSingle<Foo>();
    In the doc it says that the above binds class Foo to every class that asks for IFoo. But in the example asteroid project,
    multiple classes are bound to ITickable and IInitializable (I suppose so that the Initialize() and Tick() methods in those classes will be called). So what does Bind<Interface>().ToSingle<Class>() really mean?

    Thanks,
    Dennis
     
  29. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Hi Dennis, yes I can see how that would be confusing :)

    The way Zenject interprets what to do when you bind multiple things to the same interface is to create a list dependency. See this section of the docs: https://github.com/modesttree/Zenject#list-bindings. There is no special case handling of ITickable or IInitializable or anything.
     
  30. denlee0710

    denlee0710

    Joined:
    Mar 9, 2015
    Posts:
    36
    I came back to say that I found the answer after looking more carefully in the doc. But you already replied! That was fast. Thanks!
     
  31. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Updated Zenject to v2.5 which includes:

    - Better support for circular dependencies in the PostInject method or as fields (just not constructor parameters)
    - Removed BindValue in favour of just using Bind for both reference and value types for simplicity
    - Added "ParentContexts" property to InjectContext, to allow very complex conditional bindings that involve potentially several ancestor identifiers, etc.
    - New fluent syntax for binding to factories
    - Added ability to build dlls for use in outside unity from the assembly build solution
    - Many simplifications to the API
     
  32. Maeslezo

    Maeslezo

    Joined:
    Jun 16, 2015
    Posts:
    330
    Hello!

    How could I use decorator pattern with Zenject (not scene decorator)

    I mean,

    Imagine I have IInputService. InputService and DecoratorInputService inherit from the interface.

    How do I bind the Interface with an implementation and a possible decorator of this implementation?

    Thx
     
  33. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Assuming I understand you correctly, maybe something like this?

    Code (CSharp):
    1.  
    2.   Container.Bind<IInputService>().ToTransient<InputService>().WhenInjectedInto<DecoratorInputService>();
    3.   Container.Bind<IInputService>().ToSingle<DecoratorInputService>();
    4.  
     
    Maeslezo likes this.
  34. mecharius

    mecharius

    Joined:
    Jan 11, 2014
    Posts:
    20
    Hello, I've just come across Zenject and so far I'm enjoying using it and the detailed docs and example.

    I'm using a GameObjectFactory and to allow a single BaseEnemyFactory to create multiple enemy types (i.e. different attributes and sprites but the same behaviour) I'm looking at injecting a settings object in a similar way to in the Asteroids example. I also want to construct the object with a few ints / floats.

    The documentation shows how to do this with standard factories and constructor injection, e.g.

    Code (CSharp):
    1. public class Factory : Factory<float, Enemy> { }
    I can see that the GameObjectFactory has a similar method signature but as MonoBehaviours have no constructor, how are basic value types handled? e.g. is there a way to do something like:
    Code (CSharp):
    1. public class Factory : GameObjectFactory<float, float, Enemy> { }
    A second question is whether there is a simple Zenject replacement for [RequireComponent] >> GetComponent to populate MonoBehaviour references? I.e. it would be very simple to be able to do

    Code (CSharp):
    1. Container.Bind<MyMonoBehaviour>().ToTransient()
     
  35. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Hey, glad you're enjoying it so far :)

    For GameObjectFactory's with parameters, you can inject those using any of the other types of injection other than constructor injection, which includes field, property or postinject. In my opinion the best option is to use a [PostInject] method that acts like a constructor

    I'm not sure I understand what you're suggesting re: [RequireComponent]. Can you elaborate?
     
  36. mecharius

    mecharius

    Joined:
    Jan 11, 2014
    Posts:
    20
    Ah ok, I get it. Works perfectly thanks :)

    Yeah after sleeping on it this isn't nearly as sensible as I was thinking last night :) What I'm talking about is the kind of behaviour you mentioned in this post:

    It uses Zenject to connect up components (i.e. like you would do in an Awake method using GetComponent), however this doesn't seem to compile. I can do this with e.g. a "Health" MonoBehaviour

    Code (CSharp):
    1. Container.Bind<Health>().ToMethod(ctx => ((GameObject)ctx.ObjectInstance).GetComponent<Health>());
    but then I don't think we get any dependency injection on the Health MonoBehaviour.

    Then again I'm not sure that being able to do this gives any advantages other than slightly neater MonoBehaviour code (i.e. no need to do a load of GetComponent calls in an Awake method)
     
  37. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    As I mentioned in the comment within that other post, you should be able to get dependency injection on the health MonoBehaviour as well by doing something like this:

    Code (CSharp):
    1.  
    2.   public override void InstallBindings()
    3.   {
    4.   ...
    5.   Container.Bind<TestMonoBehaviour>().ToMethod(CreateTest);
    6.   ...
    7.   }
    8.  
    9.   TestMonoBehaviour CreateTest(DiContainer container, InjectContext context)
    10.   {
    11.   var gameObj = ((MonoBehaviour)context.ObjectInstance).gameObject;
    12.  
    13.   // Notice that this is Instantiate and not Resolve
    14.   // If we used Resolve here we'd get an infinite loop
    15.   return container.InstantiateComponent<TestMonoBehaviour>(gameObj);
    16.   }
    17.  
    I'm not sure whether it would be a good idea to do this or not - it would depend a lot on the specific problem you're solving. I personally haven't encountered a need for this yet but it is possible to do if that's what you want
     
  38. mecharius

    mecharius

    Joined:
    Jan 11, 2014
    Posts:
    20
    Ah ok, I see - thanks.

    The main benefit I suppose its a simple shorthand for hooking up required components on a game object and it lets you debug using the unity inspector. .

    I think I'll convert these MonoBehaviours to standard C# classes where possible and inject transient instances, and only GetComponent<> where it isn't possible to remove the MonoBehaviour dependency.

    Thanks for all your help :)
     
  39. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    No problem :)
     
  40. mecharius

    mecharius

    Joined:
    Jan 11, 2014
    Posts:
    20
    Do you have any thoughts about how it would be possible to make Zenject compatible with libraries such as Photon Networking that require their own Instantiate methods? In the case of Photon, the instantiate method creates objects on connected clients and sets them up to be synchronised across the network so we can't use a GameObjectFactory.
     
  41. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    I'm not familiar with the library, but you might be able to just have Zenject inject all dependencies after receiving the instance from Photon. Something like:

    Code (csharp):
    1.  
    2. _container.InjectGameObject(yourphotonobject)
    3.  
     
  42. mecharius

    mecharius

    Joined:
    Jan 11, 2014
    Posts:
    20
    Hmmm.. ok thanks. Will probably need a bit of thought to sort out the best way :)
     
  43. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Zenject has been updated to version 3.0

    The major change this release (and why it's 3 instead of 2.9) is that nested containers are a lot more sane and useful now. So the previous support for this in the form of fallback provider / bind scope have been removed. I also added a new concept "Facades" to make use of this feature more easily, and also added better support for WebGL.

    Full Release Notes:

    3.0
    • Nested containers now works more closely to what you might expect: Any parent dependencies are always inherited in sub-containers, even for optional injectables. Also removed BindScope and FallbackContainer since these were really just workarounds for this feature missing. Also added [InjectLocal] attribute for cases where you want to inject dependencies only from the local container.
    • Changed the way execution order is specified in the installers. Now the order for Initialize / Tick / Dispose are all given by one property similar to how unity does it, using ExecutionOrderInstaller
    • Added ability to pass arguments to Container.Install<>
    • Added support for using Facade pattern in combination with nested containers to allow easily created distinct 'islands' of dependencies. See documentation for details
    • Changed validation to be executed on DiContainer instead of through BindingValidator for ease of use
    • Added automatic support for WebGL by marking constructors as [Inject]
    2.8

    • Fixed to properly use explicit default parameter values in Constructor/PostInject methods. For eg: public Foo(int bar = 5) should consider bar to be optional and use 5 if not resolved.
    2.7

    • Bug fix to ensure global composition root always gets initialized before the scene composition root
    • Changed scene decorators to use LoadLevelAdditive instead of LoadLevel to allow more complex setups involving potentially several decorators within decorators
    2.6
    • Added new bind methods: ToResource, ToTransientPrefabResource, ToSinglePrefabResource
    • Added ability to have multiple sets of global installers
    • Fixed support for using zenject with .NET 4.5
    • Created abstract base class CompositionRoot for both SceneCompositionRoot and GlobalCompositionRoot
    • Better support for using the same DiContainer from multiple threads
    • Added back custom list inspector handler to make it easier to re-arrange etc.
    • Removed the extension methods on DiContainer to avoid a gotcha that occurs when not including 'using Zenject
    • Changed to allow having a null root transform given to DiContainer
    • Changed to assume any parameters with hard coded default values (eg: int x = 5) are InjectOptional
    • Fixed bug with asteroids project which was causing exceptions to be thrown on the second run due to the use of tags
     
  44. phucvin

    phucvin

    Joined:
    Nov 12, 2015
    Posts:
    5
    Hi, I'm new to Zenject.

    But after some simple tests, I think that rebinding inside sub-container affects the parent container. While binding inside sub-container does not make binding become a list, and also does not affect parent container.

    And if you can use "simple for" instead of "foreach" then performance may increase while keeping garbage small.

    Hope to see Zenject becomes more popular.
     
  45. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Can you provide the code for your tests of behaviour that doesn't behave as you expect? Also, I assume you're using the latest version 3.0, is that right?

    Yes I should probably do a pass and remove foreach and also all the uses of LINQ
     
  46. phucvin

    phucvin

    Joined:
    Nov 12, 2015
    Posts:
    5
    It's weird, last night I made a simple scene to test sub-container, and saw that after rebinding A to C with injected sub-container, the parent container resolve A also return C but it is binded with B.
    But today, I made a unit test to demo it clearly then everything is good, A return B in parent container as expected. I try to run the scene last nigh and everything also run correctly just like the unit test.
    May be I was not in full possession, sorry about that.

    Below is the unit test, both test cases run successfully.
    Code (CSharp):
    1. using NUnit.Framework;
    2. using Zenject;
    3.  
    4. [TestFixture]
    5. public class TestSubContainer
    6. {
    7.     public interface IHaveMessage
    8.     {
    9.         string GetMessage();
    10.     }
    11.  
    12.     public class Welcome : IHaveMessage
    13.     {
    14.         public string GetMessage()
    15.         {
    16.             return "Welcome";
    17.         }
    18.     }
    19.  
    20.     public class Bye : IHaveMessage
    21.     {
    22.         public string GetMessage()
    23.         {
    24.             return "Bye";
    25.         }
    26.     }
    27.  
    28.     public class User
    29.     {
    30.         [Inject]
    31.         private IHaveMessage _iHaveMessage;
    32.  
    33.         [Inject]
    34.         private DiContainer _container;
    35.  
    36.         public string SayIt()
    37.         {
    38.             return _iHaveMessage.GetMessage();
    39.         }
    40.  
    41.         public void Rebind()
    42.         {
    43.             _container.Rebind<IHaveMessage>().ToSingle<Bye>();
    44.             _container.Inject(this);
    45.         }
    46.     }
    47.  
    48.     [Test]
    49.     public void RebindingInSubContainer()
    50.     {
    51.         DiContainer parentContainer = new DiContainer();
    52.         parentContainer.Bind<IHaveMessage>().ToSingle<Welcome>();
    53.  
    54.         Assert.AreEqual("Welcome", parentContainer.Resolve<IHaveMessage>().GetMessage());
    55.  
    56.         DiContainer childContainer = parentContainer.CreateSubContainer();
    57.         childContainer.Rebind<IHaveMessage>().ToSingle<Bye>();
    58.  
    59.         Assert.AreEqual("Bye", childContainer.Resolve<IHaveMessage>().GetMessage());
    60.  
    61.         Assert.AreEqual("Welcome", parentContainer.Resolve<IHaveMessage>().GetMessage());
    62.     }
    63.  
    64.     [Test]
    65.     public void RebindingInSubContainer2()
    66.     {
    67.         DiContainer parentContainer = new DiContainer();
    68.         parentContainer.Bind<IHaveMessage>().ToSingle<Welcome>();
    69.      
    70.         Assert.AreEqual("Welcome", parentContainer.Resolve<IHaveMessage>().GetMessage());
    71.  
    72.         DiContainer childContainer = parentContainer.CreateSubContainer();
    73.         User user = new User();
    74.         childContainer.Inject(user);
    75.  
    76.         Assert.AreEqual("Welcome", user.SayIt());
    77.         user.Rebind();
    78.         Assert.AreEqual("Bye", user.SayIt());
    79.  
    80.         parentContainer.Inject(user);
    81.         Assert.AreEqual("Welcome", user.SayIt());
    82.     }
    83. }
    And one more question please, does the sub-container and the whole Zenject are good at performance and memory optimized? I really care about that, I know about caching reflection of Zenject but if you have any benchmark then that will be good.

    I also tried to roll my own IoC game-specific-framework that does not use reflection, instead it resolves/injects for each object with a specific method that know about that object. I think it is quite good, fast and memory optimized. How do you think about this?
     
  47. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    251
    Glad that it does work as you expect after all.

    Performance-wise, I'm going to publish some detailed benchmarks with source code soon. See here for a discussion of these benchmarks and some preliminary data: https://groups.google.com/forum/#!topic/zenject/H4IbH_lrBpA

    Memory-wise, Zenject *should* be making zero per-frame allocations but its been awhile since I've confirmed that so I'll have to double check that.

    Can you elaborate on your approach that doesn't use reflection? That sounds interesting.
     
  48. phucvin

    phucvin

    Joined:
    Nov 12, 2015
    Posts:
    5
    The benchmark is good, but I think it is not enough for me. I did my benchmark with my requirements to my manual way vs. using Zenject. And I think I will use Zenject for another situation :)
    More on memory, I need to create lots of sub-container at runtime (maybe once per seconds), and I believe Zenject sub-container is taking more memory than my way.

    My approach is setting dependency by-hand, it is a lot of repeated code. But I can not find a better way, other DI frameworks use Reflection.Emit which is not available in IL2CPP.
    Here is my simple demo, if there is any problem with it, please help me know.
    Code (CSharp):
    1. using System;
    2.  
    3. namespace manual_di_ioc
    4. {
    5.     public delegate T Provider<T>();
    6.  
    7.     public delegate T Provider<T, S>(S settings);
    8.  
    9.     public class Browser
    10.     {
    11.         public User User { get; set; }
    12.         public string DefaultUrl { get; set; }
    13.         public Provider<Tab, Tab.Settings> TabCreator { get; set; }
    14.  
    15.         public Tab NewTab(string url = null)
    16.         {
    17.             var settings = new Tab.Settings
    18.             {
    19.                 Url = url ?? DefaultUrl,
    20.                 Color = new Random().Next()
    21.             };
    22.             return TabCreator(settings);
    23.         }
    24.     }
    25.  
    26.     public class User
    27.     {
    28.         public string DefaultUrl { get; set; }
    29.     }
    30.  
    31.     public class Tab
    32.     {
    33.         public class Settings
    34.         {
    35.             public string Url;
    36.             public int Color;
    37.         }
    38.  
    39.         public User User { get; set; }
    40.         public AddressBar AddressBar { get; set; }
    41.         public WebPage WebPage { get; set; }
    42.  
    43.         public void GotoHomePage()
    44.         {
    45.             AddressBar.Url = User.DefaultUrl;
    46.         }
    47.     }
    48.  
    49.     public class AddressBar
    50.     {
    51.         public string Url { get; set; }
    52.         public int Color { get; set; }
    53.     }
    54.  
    55.     public class WebPage
    56.     {
    57.         public AddressBar AddressBar { get; set; }
    58.     }
    59.  
    60.     public class Simple
    61.     {
    62.         public static void Main(string[] args)
    63.         {
    64.             GlobalContainer.BrowserContainer = new BrowserContainer();
    65.             GlobalContainer.BrowserContainer.SetUserInstance(new User
    66.             {
    67.                 DefaultUrl = "https://wwww.google.com"
    68.             });
    69.             var browser1 = GlobalContainer.BrowserContainer.ResolveBrowser();
    70.             var browser1_tab1 = browser1.NewTab();
    71.             Console.WriteLine(browser1_tab1.AddressBar.Url);
    72.  
    73.             GlobalContainer.BrowserContainer = new BrowserContainer();
    74.             GlobalContainer.BrowserContainer.SetUserInstance(new User
    75.             {
    76.                 DefaultUrl = "chrome://newtab"
    77.             });
    78.             var browser2 = GlobalContainer.BrowserContainer.ResolveBrowser();
    79.             var browser2_tab1 = browser2.NewTab("https://www.facebook.com");
    80.             Console.WriteLine(browser2_tab1.AddressBar.Url);
    81.             browser2_tab1.GotoHomePage();
    82.             Console.WriteLine(browser2_tab1.AddressBar.Url);
    83.         }
    84.     }
    85.  
    86.     public static class GlobalContainer
    87.     {
    88.         public static BrowserContainer BrowserContainer;
    89.     }
    90.  
    91.     public class BrowserContainer
    92.     {
    93.         private User _userInstance;
    94.  
    95.         public BrowserContainer SetUserInstance(User user)
    96.         {
    97.             _userInstance = user;
    98.             return this;
    99.         }
    100.  
    101.         public User ResolveUser()
    102.         {
    103.             return _userInstance;
    104.         }
    105.  
    106.         public Browser ResolveBrowser()
    107.         {
    108.             var browser = new Browser();
    109.             inject(browser);
    110.             return browser;
    111.         }
    112.  
    113.         public TabContainer ResolveTabContainer()
    114.         {
    115.             return new TabContainer(this);
    116.         }
    117.  
    118.         public Provider<Tab, Tab.Settings> ResolveTabCreator()
    119.         {
    120.             return settings =>
    121.             {
    122.                 return ResolveTabContainer().SetSettingsInstance(settings)
    123.                     .ResolveTab();
    124.             };
    125.         }
    126.  
    127.         private void inject(Browser browser)
    128.         {
    129.             browser.User = _userInstance;
    130.             browser.DefaultUrl = _userInstance.DefaultUrl;
    131.             browser.TabCreator = ResolveTabCreator();
    132.         }
    133.     }
    134.  
    135.     public class TabContainer
    136.     {
    137.         private BrowserContainer _browserContainer;
    138.  
    139.         private Tab.Settings _settingsInstance;
    140.         private Tab _tabSingle;
    141.         private AddressBar _addressBarSingle;
    142.         private WebPage _webPageSingle;
    143.  
    144.         public TabContainer(BrowserContainer browserContainer)
    145.         {
    146.             _browserContainer = browserContainer;
    147.         }
    148.  
    149.         public TabContainer SetSettingsInstance(Tab.Settings settings)
    150.         {
    151.             _settingsInstance = settings;
    152.             return this;
    153.         }
    154.  
    155.         public Tab ResolveTab()
    156.         {
    157.             if (_tabSingle == null)
    158.             {
    159.                 _tabSingle = new Tab();
    160.                 inject(_tabSingle);
    161.             }
    162.             return _tabSingle;
    163.         }
    164.  
    165.         public AddressBar ResolveAddressBar()
    166.         {
    167.             if (_addressBarSingle == null)
    168.             {
    169.                 _addressBarSingle = new AddressBar();
    170.                 inject(_addressBarSingle);
    171.             }
    172.             return _addressBarSingle;
    173.         }
    174.  
    175.         public WebPage ResolveWebPage()
    176.         {
    177.             if (_webPageSingle == null)
    178.             {
    179.                 _webPageSingle = new WebPage();
    180.                 inject(_webPageSingle);
    181.             }
    182.             return _webPageSingle;
    183.         }
    184.  
    185.         private void inject(Tab tab)
    186.         {
    187.             tab.User = _browserContainer.ResolveUser();
    188.             tab.AddressBar = ResolveAddressBar();
    189.             tab.WebPage = ResolveWebPage();
    190.         }
    191.  
    192.         private void inject(AddressBar addressBar)
    193.         {
    194.             addressBar.Url = _settingsInstance.Url;
    195.             addressBar.Color = _settingsInstance.Color;
    196.         }
    197.  
    198.         private void inject(WebPage webPage)
    199.         {
    200.             webPage.AddressBar = ResolveAddressBar();
    201.         }
    202.     }
    203. }
     
  49. ForSpareParts

    ForSpareParts

    Joined:
    Dec 14, 2015
    Posts:
    1
    Hey, Zenject guys!

    I'm curious about using Zenject, and this seemed like the best place to go to ask questions. In particular, I'm trying to figure out whether it'd fit into my project, and the documentation doesn't quite cover my use case. Here's what I'm trying to do:

    My game's a top-down shmup. Its levels are a relatively simple nested data structure; it's composed mostly of position and timing values. My game's not a rhythm game, but its needs are similar: a long series of timed behaviors: fire this cannon at times [x, y, x], fire this cannon at times [a, b, c], and so forth.

    Right now, I'm doing this with a hierarchy of GameObjects that have components on them. Unfortunately, that means that the level is a component hierarchy at runtime, and I don't really want that. I'm thinking I'd prefer something like an MVVM strategy: where the level is a plain C# object that updates itself, and pushes changes to a view model (which would map the changes onto real Unity objects. That'd let me test using mock view models.

    So, I was attracted to Zenject because it seems to favor the "keep behavior in pure C# classes" strategy. What I can't quite wrap my head around, though, is how I can use the Unity editor to build that pure C# data structure. I gather from the documentation that the solution has something to do with Installers, but I don't get much more than that: my understanding is that installers create DI bindings between a class/interface and a concrete instance, but my hypothetical MonoInstallers really exist (from my perspective) to define *values* on an instance.

    Is the solution to create a MonoInstaller that binds its own properties from the editor to properties on an instance of a pure C# class, then binds that class to a new context? And more broadly: does this feel like a good use case for Zenject to you? Or am I trying to make it do something it wasn't designed for?
     
  50. Ben-BearFish

    Ben-BearFish

    Joined:
    Sep 6, 2011
    Posts:
    1,204
    @eventropy I was just about to dive in and start using Zenject for my project, but I had a question for you. Since Zenject is more purely DI and not a MVC framework, do you have any recommendations on how to setup my code & MonoBehaviours to use Zenject with the MVC pattern?