Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

IOC Container Question

Discussion in 'Scripting' started by Sttifer, Nov 11, 2021.

  1. Sttifer

    Sttifer

    Joined:
    Dec 5, 2017
    Posts:
    6
    I have a problem... I'm making a library of roleplaying games, and I have many "entities" (they are objects in the game created dynamically) that have their own data, like status, health, position... And so what? the problem... I'm using the following layers:

    Entity (they are objects in the game created dynamically) Component (they are like controllers that are linked to an entity) Service (has service logic, how to use item, add item to inventory...) Context (has a context per Entity with its own data) What happened, in the game I configured the Container IOC as:

    Code (CSharp):
    1. container.Register<ILifeComponent, LifeComponent>();
    2. container.Register<ILifeService, LifeService>();
    So what's the problem? On the service layer, I need the context, but the game doesn't have a "global" context like many web services do, so when I call:

    Code (CSharp):
    1. container.Resolve<ILifeComponent>();
    LifeService needs the context, but I need the context of "that" entity... How could I do that?
     
    Last edited: Nov 11, 2021
  2. Lekret

    Lekret

    Joined:
    Sep 10, 2020
    Posts:
    274
    I don't quite understand your problem. I read and just don't understand what you are trying to do.
    Do you want LifeService to know about single specific context? What IOC framework are you using? Maybe some pieces of code would make things more clear, but currently I don't understand the problem.
     
    Last edited: Nov 11, 2021
    VolodymyrBS likes this.
  3. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Unity doesn't have any built-in or official package for an IoC system.
    If this is an issue with a third-party package, you may want to contact the developer of that package or any support method they might have.
     
  4. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I think you should say which library you're using, or how
    container
    works in case it's something you made, if you want some concrete advice. More importantly, there's crucial info missing, like how are services created and where do you access them. Why can't you pass the context as a parameter to the services' methods? Are service instances reused over multiple entities? Why can't you assign the context in the service's constructor?

    Without more information, I wonder if you're asking for a concrete how-to or if you want more of a subjective, best-practices answer. If it's the later, I've come to prefer logical structures that can be assembled in the Editor over those that can only be defined in code: It empowers non-coding people in a team, and it reduces the amount of issues that can't be fixed without changing code. This is made more enticing by recent Editor features like generics serialization, SerializeReference, UIToolkit, QuickSearch and the dependencies viewer package. So my advice in that case would probably be: "Don't use IoC that way"; which is probably not what you'd want, so maybe I would have said nothing.

    But, hey, those are my two cents. I hope you can solve your problem. If you post more information maybe I can help you more.
     
    Last edited: Nov 11, 2021
  5. Sttifer

    Sttifer

    Joined:
    Dec 5, 2017
    Posts:
    6
    First, I'm trying to implement less coupling in my game and avoid using Singletons.
    IOC Container is good because it's a good way to implement SOLID in my game. But implementing DIP (Dependence Injection Principle) is a difficulty I'm having.

    I don't know if using SOLID is a good way to make games. Anyway, if Unity uses ServiceLocator, which breaks the SOLID IOC, besides being an anti-pattern, I don't know if there is a good way to use SOLID in games.

    But I'm trying to use SOLID... And DIP is complicated without IOC Container because I don't always want to pass a service like: SoundService in Constructor for every component that uses it. And using Singleton is not a good way, because if I ever change this SoundService I will make a big mistake within the code.
     
    Last edited: Nov 11, 2021
  6. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I think that if you're using OOP, paying attention to SOLID is not a bad idea; but SOLID means a lot of different things to different people, so YMMV. Also, a programming principle's usefulness varies on each case; e.g, sometimes creating an abstraction for everything just adds unneeded complexity, specially if you can easily add interfaces through refactoring when needed.

    Now, IoC is more related to the D in SOLID than to the I; I stands for "Interface Segregation" while D stands for "Dependency Inversion". You can do SOLID without IoC. Dependency Inversion is about accessing objects through abstractions (i.e. interfaces) instead of depending on concrete classes. IoC is about filling abstract functionality slots in a module instead of the module deciding which functionality to use. IoC implies using DI because it needs the abstractions, but you can have DI without IoC. You can assign interface dependencies without a IoC container.

    One straight forward way you can do it in Unity is by satisfying those dependencies in the Editor. You can do
    GetComponent<IService>()
    and it'll return the first component that implements that interface. Sometimes you can use empty abstract classes instead of interfaces so you can drag and drop Objects to inspector fields; that's still SOLID. You can use attributes like RequireComponent or DisallowMultipleComponent to make some things easier. I'm curious about why you think Unity uses ServiceLocator, I've never heard that and I've never seen it that way.

    That said, most IoC libraries allow you to inject dependencies in the constructor, so if you have one container per entity, or you use the container's lifetime manager, maybe you could register the context in the container and have it injected to the services that need it. Or you could add an extra parameter to all Services' methods, so you can pass the context to them, this way you can save memory by only using one Service instance per type. If I had to use these kind of libraries, those are the two things I would try, but it depends on a lot of missing information from your use case.
     
  7. Lekret

    Lekret

    Joined:
    Sep 10, 2020
    Posts:
    274
    It works very similar to ServiceLocator. By saying gameObject.GetComponent<T> it's the same as saying ServiceLocator.Resolve<T>, your object is fulfilling dependencies himself, he knows from where to get them, it's kind of bad practice and give another responsibility to your object. It's even more clear when you are using FindObjectOfType, pure analogue of service locator.

    Unity is simply inconvenient in terms of game architecture and doesn't really lean you towards right design.
    No support for normal interface usage, you can use it only in GetComponent, and abstract class for everything is kind of bad and rigid solution.
    No easy way of making shared dependencies.
    Plus MonoBehaviour based approach is very hard to test.
    If you try to follow SRP, ISP or any other principles you will see how inconvenient it's. Where to put logic? In component? Then on what GameObject should I put this component then? What if I want whole game using single service? How to write unit-tests in world of MonoBehaviours?

    With power of DI I can easily make abstraction layers, make orderable game initialization, separate concerns, make classes as small as I want, make them communicate all through interfaces, and without constantly switching to inspector to add something new.
     
  8. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    10,004
    And time of the realization that you aren't architecting a software in Unity but purely scripting a game.

    If you want to fight against what comes natural in Unity, by all means, some people have done that in the past, sometimes successfully. But I wouldn't recommend that.
    Also don't forget that every layer of abstraction you put up chips away a bit of a performance from a pile which isn't that big at the begin with.

    My recommendation is:
    - forget the rigid patterns and principles
    - do what comes naturally in Unity (it is completely normal that the dependencies are stated in Awake method in the current class or even just dragged into slot in editor so they get serialized)
    - keep DRY
    - prefer composition over inheritance
    - make your game and not your abstractions better
    - feel free to ignore my advice any time :)
     
    Bunny83 and oscarAbraham like this.
  9. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I don't think the object is fulfilling the dependencies itself. The dependencies were added in the inspector by the user, the Script calling GetComponent knows nothing about their specific implementations. There's also GetComponentInChildren, so the dependency can be fulfilled from multiple different places. If the abstraction has to be bigger for some reason, an IDependencyFulfiller component can be created to do it while still being editable in the inspector.

    I would probably find that level of abstraction to be unnecessary most of the time, though. SOLID is still being respected with GetComponent. The script doesn't have the responsibility of fulfilling its dependencies, no script does; it's just calling GetComponent<T> instead of container.Resolve<T>. The responsibility is fulfilled by the user in the editor, without any code; I love when the responsibility of something doesn't fall on any runtime code at all.

    On the abstract classes being too rigid thing: In practice, I rarely find myself limited by them, and when I do, it's usually not hard to add an interface. I find lots of classes implementing only one abstraction, due to SRP. That said, if you really need it, you can use interfaces on inspector fields with a bit of editor magic. I use a generic struct Reference<T> where T is an interface. I have plans to publish my implementation as a package, but I haven't had time yet. Here's some code example; it needs a little Editor work that's outside the scope of this thread, but it can work very well:
    Code (CSharp):
    1. [System.Serializable]
    2. public struct Reference<T> where T : class
    3. {
    4.     [SerializeField] Object m_Object;
    5.     T m_CachedCastedObject;
    6.  
    7.     public T GetValue()
    8.     {
    9.         if (m_CachedCastedObject != m_Object)
    10.             m_CachedCastedObject = m_Object as T;
    11.         return m_CachedCastedObject;
    12.     }
    13.  
    14.     public void SetValue<TObject>(TObject newValue) where TObject : Object, T
    15.     {
    16.         m_Object = newValue;
    17.         m_CachedCastedObject = newValue;
    18.     }
    19. }
    I also haven't found MonoBehaviours hard to test. I do it all the time. Admittedly, sometimes I prefer integration tests over unit tests, and I tend to follow Martin Fowler's definition of UnitTest. My tests in Unity look a little different to tests for other frameworks that use a lot of mock ups, but they work. And the main reason I started to go that way was not that the common style of Tests was too troublesome with MonoBehaviours, but that mocking libraries don't work with AOT, so I started writing tests using mostly only real implementations and I found I actually prefer it.

    Having said all that, I do think DI is very important. But by DI I mean Dependency Inversion, which is different to Dependency Injection. I strive to use that principle a lot when it doesn't get in the way of performance too much; I just don't think IoC and Injection libraries are often the best way to achieve it in Unity. In other words, for many reasons, my favorite Dependency Injection tool in Unity is the Editor. But hey, for what is worth, I think there's a lot of good in those kinds of libraries, and I believe they fit like a glove to some projects/teams, so I wouldn't choose this hill to die on.
     
    Last edited: Nov 12, 2021
    chunky_octopus and Bunny83 like this.
  10. Lekret

    Lekret

    Joined:
    Sep 10, 2020
    Posts:
    274
    By your logic I can do some anti-patternal moves and inject DiContainer itself into any class. And call container.Resolve when I need something. Because dependencies are fulfilled by container, right?
    Practically yes, but your object is knowing from where the dependencies are coming, that's why DiContainer is forbidden to Inject unless it's factory class. And in Unity you are tied to this way, you can reference anything unless you are working with components or SerializeField.
    That's what pushes me from Unity common design, even though I can't escape it, because I need some kind of View objects to exist on scene.

    And my opinion that DI-Container is a great tool for any project, unless you are time limited on game jam or stuff like that. I don't see any reason why not to use them. They make your code and architecture super flexible, you have single place in your program when everything is built. It's very convenient, it's very powerful. From what I heard (From Raid Shadow Legends devs btw) and with what I agree, it's worth using even in small mobile hyper-casual games, because after some game success you will need to integrate ads, achievements, IAP and stuff like that, and container will absolutely help with that.
     
  11. Sttifer

    Sttifer

    Joined:
    Dec 5, 2017
    Posts:
    6
    From what I understand about IOC Container is that, unlike the ServiceLocator that you need to keep giving Resolve (GetComponent<T>()) in various parts of the code, unlike the IOC Container that you only Resolve 1 time (in theory), at the beginning of an instance, and it will instantiate everything with its dependencies recursively.

    IOC Container approach is very good for DI, so that I don't spend all the time in the constructor with each dependency.

    I think that's why ServiceLocator is considered Anti Pattern. Although Unity clearly uses it.
     
    Lekret likes this.
  12. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Well, not really. The idea of embedding container-like logic was more of a rhetorical one. If you need an instance's dependencies to come from many multiple places, and those dependencies can't be assigned by designers in the inspector, maybe because they are decided at runtime, you could add an extra component that has that responsibility while allowing designers to define its details in the inspector. The object would know that dependencies are coming from this dependency solver, but it wouldn't know where the solver gets the dependencies from.

    In practice, I don't think it is a common need, but I think it's nice to have in a tool belt. And if one wants to use factory patterns, a way to do it is to have factories create prefab instances, and factories themselves can be ScriptableObjects so they are editor friendly. I do think that is often useful.

    I think a good example of projects that can do better when dependencies are able to be filled in the editor, instead of through injection defined in code, is those where game designers are not programmers. Other example is when one wants to be able to add DLCs with new logic configurations, or even some bug fixes, without requiring users to download and update a new compiled executable, and without requiring an app store approval. A lot of the things I like have to do with editor tooling, both for debugging and designing, but I guess those are more subjective.
     
    Last edited: Nov 12, 2021
  13. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I guess ideally in your example you'd call Resolve only once, at least per entity; if you did that, it should be straight-forward to have the container inject the context dependency to all the services that need it, and your problem wouldn't exist. It'd be useful to know which library you're using in order to help you, in case, for example, this doesn't work because of lifetime configurations.

    On the GetComponent thing: Usually, it's only called in Awake when filling dependencies; it is called once per type and per script. Assigning dependencies through inspector fields doesn't need any call to anything, though. A nice thing about this approach is that members of an entity are not necessarily dependencies: A physics object needs a Rigidbody, but it can have any number of colliders. A robot can have any number of guns that all fire when a button is pressed, even non at all. Elements can be added and removed at anytime without thinking of a dependency graph. All this is easy when elements use Unity structures to get their dependencies, instead of parameters in their constructors or code injection.

    There is no static database of concrete types like there usually is in a ServiceLocator; no one type has hidden dependencies on other concrete types because those are the only ones that the Locator can provide. The caller of GetComponent is not creating any of its dependencies, it's just getting a reference to them; even if it did, that alone is not synonymous with SL. Yes, there is no single composition point for instantiation, but that's not a defining characteristic of SL.

    I don't think IoC container's main selling point is saving time making constructors. You still need to create dependency graphs, and it can be a lot of code that increases exponentially the more modular variations are desired for entities. I think the nice thing about IoC containers is having only one point of failure for all kinds of possible issues, that is on top of all the benefits that Dependency Inversion brings. I like to move many of those points of failure to a place with no code in the Editor, but I get the beauty of a single place for instantiation logic and at least half of the lifetime management code. I wouldn't try to convince you away from IoC containers without knowing more about you or your project; I'm just sharing my perspective in case it helps, because I don't know the details behind your issue.
     
    Last edited: Nov 12, 2021
  14. Sttifer

    Sttifer

    Joined:
    Dec 5, 2017
    Posts:
    6
    I don't understand one thing yet about SOLID in games, as GetComponent directly hurts the D (Dependecy Inversion) because every dependency you need, you're taking it directly instead of asking for an interface you really need.

    If we research it on the Service Locator Pattern, we will find that it hurts the D of SOLID.

    Where am I going? Is it possible to use SOLID to the letter in games as we use it in software?
     
  15. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    Hi, you can use GetComponent with an interface. Like I said in my first post in this thread, you can do
    GetComponent<IService>()
    ; GetComponent is not a Service Locator Antipattern. Also, this might be a bit more controversial, but an empty abstract MonoBehaviour / ScriptableObject can be another way of respecting the Dependency Inversion principle; that makes it easy to also use inspector fields to inject dependencies in the editor.

    It is possible to follow SOLID in games; it's the same SOLID. Maybe the thing is that game code tends to look different than the code you're used to. Also, like I said, it means different things to different people. I.e, Single Responsibility can be a very subjective thing. Some very strict views of SOLID might be harder to follow than others. I think this is one of those cases where it's best to interpret a dogma in the way that's more convenient for you now.

    Maybe a more important question is: Should you use SOLID to the letter? That D can make some code a lot more complex and take a lot more time to write. It can also affect games' performance a lot in some cases. If your project isn't too big, usually you can keep a good amount of modules tightly coupled and then easily add abstraction through refactoring if needed. Remember there's also YAGNI. Even if your project is big or it's going to be a public package, some stuff can still be easy to refactor if needed, and some abstractions may be more trouble than what they are worth for the scope of your project.

    Look, I think code is rarely perfect. There's always something you wish you had done differently, and even more that could be better but you'll never notice. Programming principles are supposed to make life easier in the long run, but maybe there's a point where they are costing you more than what they give you. I can't tell you where that point is for you. I can only suggest that it's not a black and white thing: You're already using Unity instead of going from scratch; maybe you can take more advantage of it, even if it's not in the shape you consider ideal. And you don't have to go all or nothing; like if you think an IoC-Container is going to be very useful for ads or IAP, it doesn't mean you have to use it everywhere.

    Finally, you keep commenting without adding information about your problem. I'd like to help you with whatever you explicitly need if I can. People like to help, but it's hard to do it without more information. I know I can't. Like, What container are you using? What have you tried? Why didn't it work? Or why didn't you like it?
     
    Last edited: Nov 15, 2021
  16. Lekret

    Lekret

    Joined:
    Sep 10, 2020
    Posts:
    274
    I completely agree with YAGNI and also like to design my code with KISS in mind. I often inject specific services without creating interfaces for them, because I know I wouldn't unit-test my pet-project and will never swap implementation of them. Even though I can do it easily later in 1 minute with

    But I don't agree that you shouldn't use IoC-Container everywhere, like why not. It's not some super hard work to maintain such architecture, for me it's easier to write one line "Container.Bind<MyService>()" and use service anywhere, than creating singleton, or going to inspector, and think of initialization order like who will be initialized first.
    I have small project which was created in like 10-15 hours for some youtube code review "competition".
    The initialization is super stable, super simple, there is no reasons of not using it in my opinion.
    Here is one of installers:
    https://github.com/Lekret/YT-Test-R...sets/Scripts/Init/Installers/GameInstaller.cs
     
    Last edited: Nov 15, 2021
  17. oscarAbraham

    oscarAbraham

    Joined:
    Jan 7, 2013
    Posts:
    431
    I don't think it's wrong. Using IoC-Containers everywhere isn't wrong. And using an injection library doesn't mean by itself making things harder. My point is that it goes around some of the Unity tools and ways completely, so you can't take advantage of them as much. You also have to wrap and bend some parts of Unity to fit. The farthest I'd go would be to say that maybe someone that doesn't know Unity that much, like the OP seems to be, shouldn't try to go against the grain. But they won't say more about their problem, so I can't be confident about what I should say.

    I love empowering game designers to create new game logic variations without having to go to a programmer for every little thing. Even though I like programming and architecture, I enjoy iterating game logic in the Editor; I love turning DomainReload off, going in and out of Play Mode so fast, while making real changes to logic and discovering new Ideas with easy emergent design. I love how easy it is to inspect references to a particular instance instead of just getting references to a class. I love that it makes it possible to take advantages of many new Unity features and packages. And I love the DLC and tooling possibilities.

    And if that kind of stuff is not something that matters much to you or your projects, I think it's perfectly fine. I don't think you're wrong. I've helped in a couple of projects that used Zenject/Extenject and I really missed some stuff, but I also got some of the beauty of it. I never wanted to dissuade anyone from using something they like because of ideas that are tangential to the topic. I'm not so devoted to my ways. I guess I tried to say a bit of some suggestions I might had make, had the OP added more information about their problem, like some kind of help menu. In retrospect, maybe I wish I hadn't, because the OP has said nothing more about their problem. But that's life.
     
    Last edited: Nov 15, 2021
    Lekret likes this.
  18. Sttifer

    Sttifer

    Joined:
    Dec 5, 2017
    Posts:
    6
    Firstly my game is an RPG and I'm starting to architect it, so I'm validating resources to use in architecture and design patterns. I see a lot about SOLID in software and was wanting to see points I can use in it. I know this is all abstract, I say because I still don't have a real problem to show, I'm just trying to see ways to use SOLID in games made in Unity.

    I think this discussion has gone a long way and was very valid, thanks for your opinions!