Search Unity

Any fans of Dependency Injection? New framework released.

Discussion in 'Scripting' started by eventropy, Jun 14, 2014.

  1. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Any other Unity devs familiar with the concept of a dependency injection framework? If not you should consider finding out! It is, in my opinion, invaluable when creating any kind of non-trivial software.

    Our company has been happily using a DI framework for Unity that we wrote internally and have recently decided to open-source it so others can benefit. You can get it for free in the Unity Asset store here:

    http://u3d.as/content/modest-tree-media/zenject-dependency-injection/7ER

    We are all "dependency injection" junkies at my company but when we moved to Unity we couldn't find anything that met our needs, so we rolled our own! It is similar to Strange IoC if you've heard of that except that it's more of a pure dependency injection framework (and also has a fairly different feature set)

    We give a basic introduction to the idea on the documentation page (which you can find here: https://github.com/modesttree/Zenject)

    What's everyone's experience/understanding of DI?
     
    Dameon_ likes this.
  2. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I just gave the package a quick look, and I'm sorry, but I don't understand what it's supposed to bring to my own projects. Maybe you could explain the advantages?
     
  3. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    These articles explain the motivation better than I could:
    http://blog.sebaslab.com/ioc-container-for-unity3d-part-1/
    http://blog.sebaslab.com/ioc-container-for-unity3d-part-2/

    In a nutshell, the default way to Unity encourages us to manage dependencies between different game classes/components (that is, using singletons or public members in MonoBehaviours, or GameObject.find) are all flawed in their own way. Dependency injection frameworks offer a very nice solution to this problem.

    The tricky thing is, it's hard to appreciate the benefits at first because it really only becomes obvious once you start getting into larger projects.
     
    Last edited: Jun 14, 2014
  4. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    I should also mention that there are many other benefits that result from using DI which include
    - Testability of code
    - Seperation of concerns
    - Better adherence to the Single Responsibility Principle
    - More flexible code that is much easier to both refactor and re-use

    I personally find that DI encourages better code design in general. I think this is because it forces you to think more in terms of the interfaces between classes. The result is a lot more loosely-coupled code which is a very good thing.
     
  5. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I worked on VERY large project - multi-millions line of codes - and I never needed such level of indirection. I read both blog post and it doesn't give a single example of an implementation that would becomes easier with this kind of DI. DI is great when you have two code-base that need to talk to each other, like server and a client.

    Seriously? That's the best reason to avoid Singleton?

    I never use GameObject.Find or GameObject.FindObjectOfType. I use singleton, but I never have to "manage" them. All my "manager" are prefabs, and I could easily implement their behaviour as abstract base class. (Because we know how Unity love to serialize interface) My code never became a "big pile of crap" because of them.

    I also have a registrable system that keep track of all the instance of specific types/subtypes I want.

    I prefer subcomposition; breaking down my behaviour to its smallest bit and building it like legos. States of a StateMachine that are independent of the object owning the machine.

    But above all, I prefer behaviours that can be changes in data. That I can change the whole game's behaviour with one click in Unity, without changing any code. Abstraction trough data; composition by the designer.

    The point of dependency-injection, from my point of view, is to change a service with ease - from the data, and right now, your example package doesn't give me that possibility. What I'm trying to say is, your package should give an example of where DI is useful, and right now it isn't.
     
    Last edited: Jun 14, 2014
  6. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Sounds like you're using the Service Locator Pattern. Which is fine, and in my view the best alternative besides using a DI framework. There are downsides however. With SL, you end up having to deal with a lot more run time errors, since any method could ask the SL for an instance of a type that isn't found. With DI, you can validate your object graph easily even before you run your application.

    It's also much more clear where the dependencies are using DI (you can just look at the constructor of your class to see them)

    Also, if you write your code using SL, it is coupled to the implementation of SL. You cannot re-use that class without also using service locator. If you are using DI correctly then you actually don't even need to be coupled to the dependency injection framework. You can re-use your classes by manually passing in all the dependencies into their constructor if you want.

    There's many other reasons as well to avoid SL in favour of DI (you can find a few more here: http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/)

    Then it sounds like you would love DI! Using DI makes it incredibly easy to split up your code base into lots of tiny classes. This is because you don't have to worry about passing dependencies around. Each class can just declare the services that it requires in its constructor and then focus specifically on the single thing that it is responsibile for.

    I agree, data driven designs are great. So is data-oriented design (which is slightly different but related). I also admit that if you really embrace data-oriented design, that you can avoid relying on object oriented code (which in many cases conflicts with DOD). However, if you're like me and have completely embraced the OOP philosophy then DI will make your life 1000x easier.

    I actually think this is a common misunderstanding of dependency injection. The ability to swap one service for another is only one of the many benefits of DI.

    I agree that the example project isn't the best example, but that's because it's a small project. I literally threw together the sample in a weekend. If you were to take the sample application and try to build a several-month long project around it, THEN it would become much clearer how helpful it was to start out using a DI framework.

    I'm happy to answer any more questions
     
  7. toan9fury

    toan9fury

    Joined:
    Nov 13, 2013
    Posts:
    2
    How is performance when using DI over just doing it ?

    I target mobile primarily so I would like to avoid any performance issues !
     
  8. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    DI mainly affects start-up time when it builds the initial object graph. Though in some cases you also create object graphs at run time so may affect that as well. But even then it is usually sporadic (nothing per-frame).

    It does use heavy C# reflection which is typically slow, but in Zenject a lot of this work is cached (using a C# reflection library called fasterflect) so it's not bad. So it should only incur reflection costs once per class rather than once per instance.
     
  9. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    ... what?
     
  10. JamesLeeNZ

    JamesLeeNZ

    Joined:
    Nov 15, 2011
    Posts:
    5,616
    IMO, frameworks like this should generally be avoided in game development.

    They are often over architected and un-nessesarily, especially when you want lean fast code.

    Ive seen streamlined apps become sluggish crud... but hey, youre giving it away free, so I cant fault you there.


    I would like to hear more about the project(s) you are putting this into...
     
    JonPQ, xVergilx and toan9fury like this.
  11. Bigepidemic

    Bigepidemic

    Joined:
    Jun 16, 2014
    Posts:
    7
    Thanks for sharing it! Looks great, and solves a lot of problems for my design. I'll be using it on my project.
     
  12. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Zenject avoids costly reflection operations by making a trade-off between performance and memory. Zenject needs to process a class to find all the dependencies that it needs (which include its constructor parameters and any fields/properties marked with [Inject] attribute). But it only needs to do this once when it instantiates the first instance of said class (and thereafter uses the data from the cache). Make sense?

    DI frameworks are a fantastic way to manage complexity in software and allow your software to scale well over time. It becomes less useful the simpler your problem domain is. Game development tends to be among some of the most complex software there is and so if anything, actually needs DI more than other kinds of software. Also, games tends to have requirements that are constantly changing (as you discover what works and what doesn't) which means you need to have an extremely flexible codebase (which DI also helps with)

    Can you elaborate on why you think DI should be avoided in game development? You mentioned speed considerations, but as I said above the performance hit is neglible in most cases.

    I do agree that most DI frameworks are overly complicated, which is also something we tried to avoid with Zenject. I personally think it's much simpler than most others you'll find but as the author I'm biased

    No problem :) If you end up trying it and have any questions be sure to let me know (you can also submit requests/bugs on the github page)
     
  13. Dszordan

    Dszordan

    Joined:
    Jan 13, 2014
    Posts:
    7
    I use dependency injection at work all the time, truly a paradigm shift that more developers should get into for large projects to at the very least separate concern.

    I don't have a use for this framework in Unity just yet, but I'll keep it on my radar.
     
  14. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    This would also need to be heavily tested in iOS as I'm betting some dependencies will trigger AOT errors.
     
  15. toan9fury

    toan9fury

    Joined:
    Nov 13, 2013
    Posts:
    2
    Is there any game released with DI framework Zenject or StrangeIoC ?
     
  16. paranoidx

    paranoidx

    Joined:
    Apr 17, 2014
    Posts:
    43
    Firstly thanks for framework. However looking at your 'hello world', first thing I notice is that it is far more difficult to read, so not really sure how feasible this would be.
     
  17. Caiuse

    Caiuse

    Joined:
    Jan 6, 2010
    Posts:
    30
    @eventropy Thanks a lot for the framework, I've been testing it out. I've got the hang on setting up a RootContext and injecting into normal C# classes.

    I now wanting to create some GameObjects within my scene with MonoBehaviours attached and inject into those MonoBehaviours, how does Zenject support this without me having to re-instantiate my GameObjects, as having GameObject placed within a scene prior to the application start is one of the core features of Unity.

    Brilliant work on this framework -
     
  18. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    How about:

    Code (csharp):
    1.  
    2. container.Bind<YourGameObjectType>.To(yourGameObjectInstance)
     
  19. Texaggie96

    Texaggie96

    Joined:
    Jan 23, 2009
    Posts:
    81

    I just opened the Asteroids sample scene and the first object under "CompositionRoot" is "Gui". It has a MonoBehavior attached to it called "GuiHandler." (So it qualifies as a monobehavior that "lives" in the scene.) This GuiHandler has field injection that looks like this (can't do constructor injection on a Monobehavior):
    Code (csharp):
    1.  
    2. public class GuiHandler : MonoBehaviour
    3. {
    4.     [Inject]
    5.     public GameController _gameController;
    6. ...
    7. }
    8.  
    The binding in the installer looks like this:
    Code (csharp):
    1.  
    2. _container.Bind<GameController>().ToSingle();
    3.  
     
    newPublicVoid likes this.
  20. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Just wanted to resurrect this thread to mention that Zenject has been updated to version 1.08, which includes, most notably, several more performance improvements. I believe it will satisfy the concerns such as those mentioned in this thread.

    For anyone else interested I've answered this question in the google group here:
    https://groups.google.com/forum/#!topic/zenject/OjJgz9p_XtM
     
  21. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Hey @eventropy first thanks for the framework. And many thanks cause you introduced me to Fasterflect. Couldn't have been more happier about it, I really felt like a kid in a candy store. You mentioned that Fasterflect didn't work out of the box in Unity so you had to make some changes, and included the source for the version you changed. However, building that against anything other that .NET 4.0 didn't succeed (mostly compile errors about runtime generated code, and not being able to find Microsoft.CSharp assembly - just like when you try to use the 'dynamic' keyword) Am I missing something? I know you have a dll there that I could use but it's handy to have some source code that I could add/modify and build from.

    [EDIT] My bad I was building the Benchmarks project not the main project. I thought you modified all the projects to work so I thought if one didn't, then the others won't.

    Another question, I read your code, you still seem to be using regular reflection (like invoking a method info) and not cached Fasterflect API (the DelegateForXXX stuff) - is there any reason for that?

    I haven't really used your framework in depth yet - Will give thoughts when I do. It seems very promising. What you're trying to do is very noble. Unity doesn't seem to like but from my experience
     
    Last edited: Jul 21, 2014
  22. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Can Zenject inject UnityEngine.Objects dependencies other than binding to an instance? For ex

    Code (csharp):
    1.  
    2. public class Script : MonoBehaviour
    3. {
    4.      public BoxCollider boxCollider;
    5.      public Transform target;
    6. }
    7.  
    The Script uses those references to do some work. How can I inject those dependencies? (I guess it doesn't really make sense here? Cause the usual way is just to assign them from the inspector, or use GetComponent/InChildren/Parent)
     
  23. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @Dustin Horne: I'm not sure what you mean with your line

    Code (csharp):
    1.  
    2. container.Bind<YourGameObjectType>.To(yourGameObjectInstance)
    3.  
    What you mean "YourGameObjectType"? You can't inherit GameObject.if that's what you mean...
     
    Last edited: Jul 21, 2014
  24. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @eventropy I have some more questions if you don't mind

    1- From what I understood, if you bind a concrete type A to an abstract one B, then A will be used (injected) wherever there lies a B.

    Code (csharp):
    1.  
    2. container.Bind<IFoo>.ToSingle<Foo>();
    3.  
    That means (correct me if I'm wrong) that wherever there is an IFoo reference, it will be assigned to a single Foo instance (i.e. all IFoos will reference the same object)

    And

    Code (csharp):
    1.  
    2. container.Bind<Foo>().ToSingle();
    3.  
    Means that wherever there's a Foo, the same Foo instance is used.

    Now what does it mean to bind more than one concrete class to an interface? You mentioned in your doc this will create a list of dependencies, not really sure what that means.

    Code (csharp):
    1.  
    2. container.Bind<IFoo>().ToSingle<Bar1>();
    3. container.Bind<IFoo>().ToSingle<Bar2>();
    4.  
    How will an IFoo reference be resolved now? i.e. if I have an IFoo somewhere, will it be a Bar1 or Bar2?
    This is kind of like what you did in your example with ITickable, you bind to it many types like Ship, AsteroidManager and GameController.

    2- It says in Zenject's doc "I prefer to avoid MonoBehaviours when possible in favour of just normal C# classes."

    First, why?

    Second, doesn't that get rid of something powerful Unity is characterized by which is being Component-based? i.e. with MonoBehaviours (Components in general) I could just write classes that gives my entity unique behaviours and 'craft' it all the way through. Ex an Item GO for ex, I could attach it a Usable behaviour to make it Usable (used on triggers to unlock doors etc), a Consumable behaviour to make it consumable (food, health kits etc), Combinable behaviour to let it be combined with other items, etc etc. And at any time (during edit/runtime) I could add/remove behaviours to/from my entity. How will dealing with plain C# classes give me this flexibility?

    Third, what about coroutines? No MonoBehaviours == No Coroutines.

    Don't get me wrong, I'm not saying regular classes are not useful, System.Object classes are pretty good for things like Settings, etc (mainly things that don't need to be attached to GOs) and they also have a lighter memory footprint.

    3- Triggering an assembly reload while playing the asteroid example breaks a lot of links (references) thus I get too many NullReferenceExceptions - I always like to have my stuff play well with Unity's serialization system, so I could edit code while the game is running and still see the effect of that without having to restart my game. Seems like Zenject's code needs some modification to achieve that?
     
    Last edited: Jul 26, 2014
    amanpu likes this.
  25. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Sorry, I meant MonoBehavior
     
  26. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Hi vexe,
    Great questions. I'll attempt to answer them

    I originally integrated Fasterflect for the purposes of using its caching features but ended up doing the caching myself, since it was easy enough to do (which you can find in TypeAnalyzer.cs). But I still found Fasterflect useful for some of the extra functionality that it adds (such as the Flags.ExcludeBackingMembers setting, more readable names for generics, etc.). But honestly, given that I never really used it for its original purpose I don't think Zenject needs it anymore and the library would be much simpler without it, so I'm tempted to extract the little bit of functionality I'm using from it and get rid of it entirely.

    Thanks! I think Unity is really lacking in this area (code architecture support) and we found something with Zenject that works well for us internally so I wanted to spread it out to the community. What do you mean "Unity doesn't seem to like but from my experience" ?

    No, you can inject into Monobehaviour classes as well, you just need to add [Inject] attributes like the following:

    Code (csharp):
    1.  
    2. public class Script : MonoBehaviour
    3. {
    4.      [Inject]
    5.      public BoxCollider boxCollider;
    6.  
    7.      [Inject]
    8.      public Transform target;
    9. }
    10.  
    If the MonoBehaviour is attached to an object in the scene, and that object is a child of the composition root, then it will be injected during Unity's "Awake", which means that the dependencies will be available for use in the Start() method. However, if the monobehaviour is created dynamically such as through a factory or a binding that uses ToSingleFromPrefab, ToTransientFromPrefab, etc. then you must use a [PostInject] method instead of Awake() or Start() methods. You can use Awake() or Start() however you should be aware that the dependencies will not have been injected.

    In this case, if you have a class that requires a single IFoo reference, then Zenject will throw a "ZenjectResolveException" when you attempt to instantiate a new instance. Zenject follows a somewhat similar convention to what's used in Ninject here. If an interface is bound to multiple types, then its interpreted as a list.

    So, given this code:

    Code (csharp):
    1.  
    2. container.Bind<IFoo>().ToSingle<Bar1>();
    3. container.Bind<IFoo>().ToSingle<Bar2>();
    4.  
    The following will throw exceptions:

    Code (csharp):
    1.  
    2. class Qux
    3. {
    4.     public Qux(IFoo foo)
    5.     {
    6.     }
    7. }
    8.  
    However, the following will work, with the result being a list filled with an instance of Bar1 and an instance of Bar2.

    Code (csharp):
    1.  
    2. class Qux
    3. {
    4.     public Qux(List<IFoo> foos)
    5.     {
    6.     }
    7. }
    8.  
    That line in the doc might be a bit off topic and a little subjective - so thanks for pointing it out :)

    I think the better question is why use MonoBehaviours if you don't have to? If you're writing a class that needs access to the API provided by the MonoBehaviour class, then sure, make it a MonoBehaviour. For example, if you want to display a GUI using Unity's built in GUI, then you'll probably want to override the OnGUI() method. Or if you have a bunch of different behaviour all of which is associated with a transform or a mesh (like in your example) then it probably makes sense there as well.

    But I don't think when you start writing a new class you should start by making it a Monobehaviour - you should make it a MonoBehaviour only after you realize you need to

    In terms of coroutines, there's nothing stopping you from using them in normal C# classes. Unity isn't doing anything all that special to make that happen (as explained here: http://www.altdev.co/2011/07/07/unity3d-coroutines-in-detail/)

    This is a great point and I completely agree. I've worked on projects before that retained Unity's ability to change code and re-serialize data live and it's been wonderful. This didn't occur to me because it's been awhile since I've worked on a project that made that possible however. I'm definitely going to look into adding better support for this when I have time (or if you want to give it a shot it is open source remember ;))
     
  27. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Thanks for your quick reply.

    I'm sorry my bad, it seems I accidentally post without continuing my statement.

    What I was trying to say, is that what you're doing is noble, but I think it's kind of like fighting against Unity. It's kind of forcing it to work in a way that it's not really meant for it. I've fought with it a lot too! Usually it ends up with Unity winning and me bashing my head against the wall.

    Don't get me wrong, I would love to do TDD, I would love to program using IoC Container, but not in Unity, IMO all those techniques are for business apps, not Unity...

    In Unity you could still program against interfaces, If you want to switch implementations you could do it from the editor! Here's one way to do it:
    Code (csharp):
    1.  
    2. public class AI : BetterBehaviour
    3. {
    4.      [SerializeField, ShowType(typeof(AIStrategy))]
    5.      private SerializedType strategyType;
    6.  
    7.      private AIStrategy strategy;
    8.  
    9.      private Awake()
    10.      {
    11.          strategy = Activator.CreateInstance(strategyType.Value);
    12.      }
    13.  
    14.      public void Action()
    15.      {
    16.           strategy.Perform();
    17.      }
    18. }
    19.  
    20. public abstract class AIStrategy
    21. {
    22.     public abstract void Perform();
    23. }
    24. public class Fallback : AIStrategy
    25. {
    26.     public override void Perform() { Debug.Log("Falling back..."); }
    27. }
    28. public class Attack : AIStrategy
    29. {
    30.     public override void Perform() { Debug.Log("Attacking..."); }
    31. }
    32. etc...
    33.  
    SerializedType is a serialized representation of System.Type. Using ShowType you will get a popup of all the non-abstract implementers of AIStrategy. Just choose one, and then at run-time you create your strategy with whatever type you chose (we could benefit from Fastreflect here...)

    (BetterBehaviour, ShowType, etc are some of my home-cooked property attributes in ShowEmAll - which will be available on the asset store for free soon)

    What else... Unit testing? - I'm a huge fan of it. I always encourage it because it encourages very good design habits, like decoupling and writing highly focused/cohesive classes. In business apps it's a must. But again, come to Unity, it's also nice to do - but that's in Theory, in practice though is it really feasible? Is it really worth it to add all those layers of abstractions, write all those interfaces, add extra complexity just to get a test working?.... IMO no.

    I also saw StrangeIoC today, downloaded their examples... and oh boy.... it's just an over-overkill. The examples were very simple, yet the code was highly complex. That's for a small example, imagine how a large project would be?... I think great code is one that's easy to understand by everyone - More POO than OOP (Programming fOr Others)

    It's not our fault though that Unity's API is S***ty. But good news is that they admit they made a lot of design mistakes and are fixing it... Hopefully we see more support for Unit testing and interfaces.

    I'm never against learning something new - something with a high learning curve. I'm willing to spend time, money and effort if the thing would repay me back what I put through it. But IMO, the problems that comes with IoCC when applying it in Unity are as many as the good value it provides.

    I would love to be proven wrong. I would love to hear from you how far you went with this, how big of a project have you used IoCC in?

    Lastly,

    Code (csharp):
    1.  
    2. public class Script : MonoBehaviour
    3. {
    4.    [Inject] public BoxCollider boxCollider;
    5.    [Inject] public Transform target;
    6. }
    7.  
    I still don't get what will the values be for 'target' and 'boxCollider'?

    I hope I don't have any chopped statements this time :D

    Thank you!
     
    Last edited: Jul 22, 2014
  28. Texaggie96

    Texaggie96

    Joined:
    Jan 23, 2009
    Posts:
    81
    I have been doing some thinking about IoC and its OOP design. What I am trying to wrap my brain around (and I think it relates to what Vexe is saying) is that does IoC lead you to the problem - a concrete class for each kind of entity? Richard Fine refers to the problem here - http://www.gamasutra.com/view/news/124682/Opinion_The_Six_Misconceptions_I_Had_About_Unity.php - in misconception #2. I have been down the road of a terrible hierarchy of "types of objects" he refers to and it is not a good one.

    Unity is not "fighting" so much as it stresses "has-a" relationships. IoC seems, to me at least, to stress "is-a" relationships. (Now I am aware that Zenject can inject MonoBehaviors, but this behavior is much the same as linking objects in MBs. So it kinda devolves to the Service Locator pattern, which Zenject can do as well). IoC seems to shine when working with pure C# classes (because you don't have to pass int the dependencies).

    My thinking is still evolving on this so I would love some feedback. Vex did I accurately rephrase what you are trying to say?
     
  29. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Partially yes. When I first came to Unity, I came from a pure OOP environment. I didn't know anything about Unity's component-based approach so I initially designed all my classes in a pure hierarchical form (pure 'is a's). It ended up pretty bad, very inflexible - Things were very different after I started thinking in terms of components (has a).

    But I was talking more generally, I don't know about you, but I'm kind of a stubborn guy, if something doesn't work the way I want to I try to force the situation this is why I end up 'fighting' Unity. For ex trying to make property drawers work for arrays or properties, or when I first came in, I didn't know that Unity isn't so friendly with interfaces and generics, I would have all my designs depending on having to deal with interfaces and generic types, only then to find out that Unity doesn't serialize interfaces nor generic types, so I fight, I try to force it to be friendly with those, but it just won't... (there are solutions now to interfaces and generic types of course...)

    I'm just saying, a lot of Unity's API and design decisions are bad, if we try to force it to play well with good coding quality standards, things "might" back-fire against us (at least from my experience)

    I hope to see IoCC used more in large scale projects to see if it will make it back in shape.

    You mean drag-dropping objects via the inspector? Yeah that's the thing, if you have an abstract reference you could just drag/drop the implementation you want.
     
  30. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    I think if one would shy away from the idea of an IoCC in Unity it might be because of:

    1. It's natural to resist at first cause it's brand new to him. How did people first react to the idea that earth is not flat? They prosecuted the guy who claimed so... It's ok to feel some resistance but it would be foolish to be closed-minded, eyes and ears shut not to listen to what the other guy has to offer and say, cause it could be the truth!
    2. The fear of a back-fire due to the potential of the idea ending up in a fight with Unity (like mentioned ^)
    3. The lack of practical real examples.
    More on 3:
    Say someone's asking you how delegates work, you explain to him with the following code that you could have a delegate reference a method, and so when you invoke the delegate the method gets invoked too:
    Code (csharp):
    1.  
    2. void Method() { }
    3. void Main()
    4. {
    5.     Action a = Method;
    6.     a();
    7. }
    8.  
    Him looking at this, mostly likely his first reaction will be "I could just invoke "Method" immediately, why would I do it like this?"
    And he's right, because that's not how/where you'd use a delegate.

    Now let me ask, when would you use Poor man's DI or an IoCC to inject dependencies? The answer (correct me if I'm wrong) depends on the number of dependencies (hmm, the answer 'depends' sounds like we could inject the answer lol). If you had few, you'd go with Poor man's. If not, you'd use an IoCC cause that's where it's meant to be used. An IoCC doesn't do anything you can't do with Poor man's DI (by hand) - it just does it cleaner and more efficiently.

    Looking at the examples provided demonstrating the use of an IoCC in Unity, they're just pretty small. There's very few dependencies. So it's natural for people new to the concept to not be able to see the power of an IoCC. If you show them a big project with many dependencies with and without using IoCC they would clearly see the difference and appreciate the concept more, like would you rather do this:

    Code (csharp):
    1.  
    2. var svc =newShippingService(newProductLocator(),newPricingService(),newInventoryService(),newTrackingRepository(newConfigProvider()),newLogger(newEmailLogger(newConfigProvider())));
    3.  
    or this:
    Code (csharp):
    1.  
    2. var svc =IoC.Resolve<IShippingService>();
    3.  
    ?

    The answer is clear. I hope you got my point (the code snippet is taken from here btw)

    It's easy to say that something is an overkill/overdesigned/overengineered/overwhatever when it's divorced from the practical context it's meant for.
     
    Last edited: Jul 22, 2014
  31. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    I agree with a lot of what you're saying. Good practices such as IoC, unit testing, etc. can often result in too much overhead, to the point where it's just not worth it. Using an DI framework would be overkill for simple applications, and the maintenance costs of unit tests can quickly become painful. As you say, it's also harder to justify when writing games as opposed to business apps. And on top of that you have to consider the learning curve... which in the case of a DI framework can be a lot from what I've witnessed at my company.

    But our experience with Zenject has been a very positive one all things considered. At my company we've been working on a multi-year project and there's just no way we could have had it scale the way it has without using a DI framework.

    I'm not going to hold my breath that Unity is going to really fix things though - I think they are pretty limited in terms of making large changes to the current design. Not sure if you're aware but Unity did release a unit testing framework recently (unity test tools). I wasn't able to integrate it into our pipeline in a way that I was really satisfied with, but YMMV.

    Zenject definitely does 'fight' the way that Unity encourages developers to work, and I think that's a totally valid reason to be skeptical about it. But as you say Unity's API has a lot of problems so fighting that may actually be a good thing. We've iterated on Zenject quite a bit over the last year and I think we've landed on something that plays pretty nicely with it

    Sorry I totally misunderstood what you were saying. When dealing with things such as Colliders / Transforms / etc. it does seem easier to me to just inject via Unity's normal method (that is, as public fields in a monobehaviour)
     
  32. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I'm also having trouble seeing this in the domain of game development. A game development specific example would help.

    @eventropy, you mentioned two things together that contribute to this. The first is that DI becomes more useful as complexity increases, which I've seen for myself and agree with (I've been involved in one major project where it was used to great benefit - but that wasn't a game project). Then you mentioned that games can be amongst the most complex types of software... but I don't understand where that complexity arises in regards to dependency management, which simply hasn't ever been an issue to me in regards to game development in particular. Having said that, something doesn't have to be broken for a better way to exist.

    To be fair, I've never worked on something with the scope of, say, GTA or anything similarly large, so maybe it's just that I've never worked on a project of the scope where this becomes worthwhile. On the other hand, I could just be misunderstanding where the practical advantages are to me and/or my projects, and if that's the case... well, I'd love to be enlightened. :)
     
  33. Texaggie96

    Texaggie96

    Joined:
    Jan 23, 2009
    Posts:
    81
    The advantages are listed in the Sebastino's articles in post #3. Those 2 blog posts were combined into 1 article for GamaSutra. As with almost everything in life, there are pros and cons with this type of approach.
     
  34. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I have... If you assume Assassin's Creed 3 was of the same scope as GTA, and we never needed or used DI. The complexity of a video game is usually horizontal - number of features - instead of being vertical - depth of a feature. I always thought DI was useful mostly when you have complete sealed system that requires clear communication channel to another unknown - but defined - interchangeable system. To me, it is like a composition pattern but at the system level. Which frankly... I still wondering about the usefulness in video games. Every example I've seen just add complexity to the system without any direct gain.

    Yeah... My issue with that article is that I find his "Unity golden rules" to be... well... stupid, so it doesn't start too well. He has an horribly reductive view of what a component pattern can do or should do. On top, his 3 "Unity solutions"... Well, two I never use - GameObject.Find/Object.FindObjectOfType - and singleton is only for game-wide manager, so I should end up with big dependency issues, right? I guess he never used pool, registrable pattern, composition, anonymous broadcast or other pattern I must be forgetting about. "I don't use singleton because I don't like using singleton". Meh. Singleton are a tool like any other tool and it has a specific purpose.
     
    bourriquet likes this.
  35. Texaggie96

    Texaggie96

    Joined:
    Jan 23, 2009
    Posts:
    81
    What I am trying to wrap my brain around with Sebastino's golden rules is that it seems to push me toward the problem I mentioned above in the Richard Fine article. If your MBs are only for interacting with the attached GameObject, then all your "Manager" classes are pure C# classes. How then do you bolt (or remove) behaviors on to GameObject programmatically (because bolting/removing behaviors on to GameObjects is the whole reason you use a component based architecture) in pure C#?

    Lets take an extremely simple example. Lets say you have racing game. All the racers get one type of a certain number of cars, including the player. The player chooses to race with CarFrameA, say a Prius. The application also gives CarFrameA to one of the AI opponents. Now the game designers want the player's CarFrame to have an extra behavior, say nitro boost; but not give the extra behavior to the AI's CarFrameA. If you are creating these "Car objects" programmatically in a MonoBehavior, this is pretty easy. Instantiate(CarFrameA) twice. For the player's CarFrameA, addComponent<NitroBoost>().

    How do you accomplish this in pure C#?
    A) 1 way is to never create objects programmatically. You Prefab everything. But that is really not maintainable. It would be a nightmare to maintain prefabs for each type of car, both Player and AI controlled.
    B) I guess you could try to make a system to make pure C# act like components; some kind of IBehaviors[] in a base abstract class. But now switch from my simple example to a RPG with many, many types of behaviors. This quickly becomes a mess and any solution I can think of along this line ultimately leads me right back to Richard Fine’s hierarchy of types problem.

    @eventropy I have looked at your Asteroids example that comes with Zenject. How would you modify your “Ship” GameObject to potentially add/remove behaviors at run-time?
     
  36. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Yeah, that's more or less my exact impression too. Like I said, the fact that the way I currently do things isn't broken doesn't mean it can't be improved. But at this stage I don't understand where this might offer practical improvements for my use cases.

    I agree re: the 3 Unity solutions... I looked at that and immediately thought "what about all the other ways you can achieve those things?" Before we even get to the solution, I don't see that the problem he's describing actually exists. (I don't agree with his set of rules, though I do have my own set of rules that work for me.)

    Having said that, still, I mostly work on small projects. Perhaps if I was working on something with a large team of coders this stuff could be super handy. As I said, I've seen examples of that being the case outside of game development. I haven't seen examples within game development.

    I'm not trying to shoot the concept down. I'm genuinely interested in whether or not it can make my work easier.
     
    Last edited: Jul 25, 2014
  37. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @angrypenguin, @LightStriker don't wanna go too astray, but since the subject's been brought, could you guys elaborate (if you can) about your 'golden rules'?

    I'm still building my own so it'd be interesting to listen to others.

    I did agree on one thing though, where he said MBs should operate on the GO they're attached to - I did find it simpler to just do that, instead of having the MB operate on a parent object or something. Consistency is nice. It's not a must, but it is to be preferred IMO.

    But I agree that the solutions he mentioned were very trivial, stuff that you see at newbie youtube tutorials. (GO.Find, etc) - There's many good ways around not needing to do such things.

    And... could somebody tell me how to quote someone properly? I don't see a quote button in the new editor I just use the QUOTE tag but it doesn't show "XXX said". Do I have to click reply on somebody's post and leave only what I'm quoting him?
     
    Last edited: Jul 25, 2014
  38. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Reading the rest of his 'golden rules'

    1. MonoBehaviours are reusable, single responsibility classes.
    2. GameObjects should not be created without a view (that would be a mesh, collider or something that is really an entity in the game, the only exceptions are for empty GameObjects used as “folders” and other specific cases I will illustrate another time).
    3. MonoBehaviours can know other components on the same GameObject using GetComponent.
    4. The behaviour of a GameObject should be extended adding MonoBehaviours instead of adding methods to a single MonoBehaviour.
    Well:
    1. That's true, I think it's pretty good to design you components like that to achieve their max potential (S in SOLID)
    2. Not so sure about that, he did say he'll illustrate later.
    3. OK... that's just a fact. If you're in a MB and write GetComponent you can access other Components on the GO the MB is attached to.
    4. That's good too IMO. The O in SOLID. Open for extension and closed for modification. For ex if you have an AI class - and you wanted to have different AIs, you'd extend the base AI and write your custom AIs instead of writing/adding everything to one class.
    Maybe I'm missing something, but @LightStriker care to elaborate why you find these to be 'stupid'? Or maybe you just meant the first rule, mentioned in my previous post?
     
    Last edited: Jul 25, 2014
  39. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    As you wish;

    To me, it's an horrible idea. How about spawners? They role is not to operate on themselves, but to instantiate a prefab. How about spline? A spline does nothing by itself, but other GO will use it to navigate or place themselves in space. How about game-wide manager? They are usually a GameObject with a single script that does something related to the whole game, like saving a score, loading a scene, handling sound, pooling objects, managing inventory, loading resources...

    A MonoBehaviour can be a container of information, a manager, a creator of other GameObject. They can be many things.

    MonoBehaviour are exactly like any object-oriented class. Resuable? Maybe, maybe not. My Player script in a single player game is only used once. Same for my game-wide manager... Why would I need two instance of the same script to save my score? As for single responsibility, it's usually a good idea when structuring your classes, but it really depends on your definition of "responsibility".

    That's an horrible idea. Why would spawners need a collider or a mesh? Their uses is to have a position and orientation in space and to instantiate a prefab at those coordinate at run time. A spline has no mesh or collider, but is horribly useful to get coordinate for say... placing a camera, or a moving sport car, or a patrolling guard.

    A GameObject is a container that happen to have a transform component. Which means anything that needs a position or an orientation can very well be on a GameObject!

    Also, while I would prefer to place game-wide managers in their own loading scope, I end up placing them on prefab so I can change their parameters. So my managers are also on GameObject, even if I don't care about their transforms.

    I'm not sure how this is a "rules". However, since I'm a user of composition pattern, I don't always link my component by "GetComponent". I often create "sub-component" directly from the inspector.

    That's the idea that a component should almost have only a single method and you should build behaviours with countless MonoBehaviour, like lego blocks. For some cases, it's a good idea, for other, it's as dumb as a rock. It's a case-by-case issue, so making it a "golden rules" is simply blocking you from any other pattern or design that would not directly fit in this socket.

    Let's say I want a Particle System to self-destruct once it stops emitting. I made a SelfDestructFX script that does just that. It makes sense for this behaviour to be "one-job oriented".

    However, if I want to add a "jump" to my player, I will not create a "Jump" MonoBehaviour! The communication between script would quickly become insane! I will add methods to my Player class, or even better, if I'm using a state machine, I will create a new "PlayerJumpState".

    I'll create an independent MonoBehaviour if it makes sense to do so, if it makes sense to be reusable, and if its communication channel to other script are clearly defined. I'll not build a grenade by adding a "throwable" script, a "fuse" script, and a "exploding" script! I'll just write a "Grenade" script!

    MonoBehaviour does not prevent class inheritance and you should be using that like any other tool at your disposal.

    Well, that's what I do.

    As for my golden rules?

    - Use the proper tool for the proper job. Singleton, static, composition, DI, interfaces, inheritance, and all other code pattern are tools best suited for a specific job. Don't try to fit a square peg in a round hole. Sometimes there is no proper tool for the job. Make one from existing tool, and create a new one. They were not made for that? If it works and there was no better alternative, who care?
    - GameObject are spatial containers of components.They are a point in space or a prefab, nothing more.
    - MonoBehaviour are instances of a class that exist on a GameObject and have access to a collection of method and callback for handling said GameObject interaction. They also have access to the game update loop.They may or may not care about their GameObject.
    - ScriptableObject are loadable container of information.
    - Any of the above type can be created by data - in the editor - or by code at runtime. Choose the method of creation that best suit the specific problem.
     
    Last edited: Jul 25, 2014
    christh likes this.
  40. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    This is something we could literally write books on. Multiple, large books. The question kind of boils down to "how do you approach software architecture in Unity?"

    With that in mind, please don't take any of the following as me bashing the author's golden rules. The fact that I don't agree with them could very well come down to the fact that it's impossible to do the topic justice in 5 short dot points.

    That's why I prefer to use "roles" instead of "responsibilities". A role can involve multiple responsibilities, but they should generally be very closely related. If a component is handling unrelated responsibilities then it's time to re-consider what it's role is. On the other hand, if you find that a lot of different things have similar, overlapping responsibilities or that related responsibilities are spread over multiple classes, it might be time to see if the roles can be re-organized and consolidated.

    This is similar to one of my rules, but it's not complete in this form. Indeed, as LightStriker points out, what about roles that by definition are all about interacting with other GameObjects?

    So rather than having a golden rule that a MonoBehaviour won't operate on a 3rd party GameObject, I instead have rules about what strategies will be used when operating on different categories of GameObjects.


    Like LightStriker, I'm not sure why this is a rule or what it's trying to say about how things should work.

    I have a few policies about how different things communicate with and/or are aware of one another. There are far more ways to do this than are mentioned in the article, all of which can be excellent tools in different situations. For me, the most common (but not the only) three in no particular order are automated hookups* (used wherever possible to reduce manual labour from designers, typically within a single GameObject or between one GameObject and child GameObjects which it is considered to own), direct Inspector hookups (where a designer needs control... if they don't need control then why aren't you automating it?), and communication via events (wherever the communicating entities can't be or don't have to be directly aware of one another - a situation I do my best to maxmize).

    I don't mind this one, but that's because I don't interpret it how LightStriker has. I see it as more or less a re-statement of how component-oriented design works - don't make monolithic components that do lots of things and are specific to an entity, instead make components that define roles and use different combinations and configurations of these to define more complex aggregate behaviours.

    * GetComponent et. al. are common here, but are not the only available approach. Consider how you might achieve similar things if you weren't working within Unity. All of those "non-Unity" approaches work here, too, and should be considered in addition to the tools Unity provides via its own API. Don't let the fact that you're using a MonoBehaviour blinker you into thinking you have to use the Unity API for everything. Furthermore, don't let the fact that you're using Unity blinker you into thinking you have to use MonoBehaviours for everything.
     
  41. Texaggie96

    Texaggie96

    Joined:
    Jan 23, 2009
    Posts:
    81
    Your problem relates to what I was saying above. After giving this more thought, I think your only options are to create a prefab or to create the GameObject through deserializaiton from XML. All your Monobehaviors have to be subclassed like - http://answers.unity3d.com/questions/120991/gameobject-to-xml.html Then you would recreate the object using XML deserialization - http://forum.unity3d.com/threads/save-load-a-gameobject-to-an-external-file.179428/ (see Cahman's post).
     
  42. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    You could also include PlayerController in the prefab and then do this:

    Code (csharp):
    1.  
    2. PlayerController player = _instantiator.Instantiate<PlayerController>(settings.charTemplate);
    3.  
    But maybe you have some reason to add the monobehaviour dynamically after creating it.

    If you use the new version of Zenject you can also do the following: (which is my preferred method). This also assumes that the prefab has the PlayerController MonoBehaviour attached to it.

    Code (csharp):
    1.  
    2. public class PlayerController : MonoBehaviour
    3. {
    4.     ...
    5.  
    6.     public class Factory : GameObjectFactory<PlayerController>
    7.     {
    8.     }
    9. }
    10.  
    11. public class MyInstaller : MonoInstaller
    12. {
    13.     public GameObject PlayerControllerPrefab;
    14.  
    15.     public override void InstallBindings()
    16.     {
    17.         ...
    18.         Container.BindGameObjectFactory<PlayerController.Factory>(PlayerControllerPrefab);
    19.         ...
    20.     }
    21. }
    22.  
    23. public class Foo
    24. {
    25.     PlayerController.Factory _playerFactory;
    26.  
    27.     public Foo(PlayerController.Factory playerFactory)
    28.     {
    29.         _playerFactory = playerFactory;
    30.     }
    31.  
    32.     public void InitPlayer()
    33.     {
    34.         _player = _playerFactory.Create();
    35.         .... etc.
    36.     }
    37. }
    38.  
    If you use the Factory class you can also pass arguments into it quite easily as well. The documention on the github page now includes more explanation of this approach.
     
  43. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Zenject has been updated to version 1.12 which includes the following changes:
    • Added support for Global Composition Root to allow project-wide installers/bindings
    • Added Rebind<> method which can be used to override any previous bindings
    • Changed Factories to use strongly typed parameters by default. Also added ability to pass in null values as arguments as well as multiple instances of the same type
    • Renamed _container to Container in the installers
    • Added DiContainer.ToSingleMonoBehaviour method
    • Removed some boiler plate code that was previously required. (eg. you no longer need to include the StandardUnityInstaller, or declare a dependency root)
    • Added IFixedTickable class to support unity FixedUpdate method
    Note that this is a breaking update so will result in a few compiler errors if you have already started a project with a previous version.
     
  44. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Updated Zenject to v1.17.

    This update includes mostly minor tweaks / bug fixes but also one new feature: Scene Decorators! Which can be used to add functionality to a scene from another scene without actually changing the scene directly, which can be useful particularly for testing (see documentation for details).
     
  45. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    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, etc. Also, since it is a major version upgrade there was some API-breaking changes. See release notes for full list.
     
  46. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    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
     
  47. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Take a look at how AutoFac handles this. It builds an object graph which gives it the capability to instantiate a single instance and even resolve it for constructors.
     
  48. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    You can use contructor injection for circular dependencies as long as you aren't using it for both sides of the circular dependency. . At least one end of the circular chain of dependencies has to use a field or property or [PostInject] method

    I don't think it's possible to support constructor injection for both sides since that would require somehow creating the object without calling the constructor.
     
  49. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    You're correct, you can't. Unless you're Chuck Norris. :)
     
  50. eventropy

    eventropy

    Joined:
    Oct 4, 2012
    Posts:
    255
    Yes, in Chuck Norris's implementation he could support circular references via constructor injection :)