Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[RELEASED] IoC+, Inversion of Control for Unity

Discussion in 'Assets and Asset Store' started by softlion, Oct 2, 2016.

  1. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi again mrcoffee666!

    IoC+ does not work with constructor/method injection. IoC+ injects into fields of Commands and Mediators with the [Inject] attribute directly using Reflection. Only Commands and Mediators have injectable fields to prevent cross dependency in services and models. Your example could work like this:
    Code (CSharp):
    1. public class GameView : View { }
    2. public class GameMediator : Mediator<GameView> {
    3.  
    4.     [Inject] private IUserService userSettings;
    5.  
    6.     public override void Initialize() {
    7.         Debug.Log(userSettings.GetName());
    8.     }
    9.  
    10. }
    11.  
    12. public interface IUserService {
    13.     string GetName();
    14. }
    15.  
    16. public class LocalUserService : IUserService {
    17.     public string GetName() {
    18.         return "xzy";
    19.     }
    20. }
    21.  
    22. public class DatabaseUserService : IUserService {
    23.     public string GetName() {
    24.         return "sql";
    25.     }
    26. }
    27.  
    28. public class GameContext {
    29.  
    30.     protected override void SetBindings(){
    31.         base.SetBindings();
    32.      
    33.         Bind<IUserService, LocalUserService>();
    34.      
    35.         BindMediator<GameMediator, GameView>();
    36.      
    37.         On<EnterContextSignal>()
    38.             .InstantiateView<GameView>();
    39.      
    40.     }
    41.  
    42. }
    Let me know if this helps. The get a more basic understanding of IoC+, don't forget to go through the tutorials on the IoC+ website!
     
  2. mrcoffee666

    mrcoffee666

    Joined:
    Jun 21, 2013
    Posts:
    6
    Ok now I understand the reason of this limitation much better thanks. Before evaluating your framework I tested another IOC container and there it was also possible to inject constructor parameters of any class. I have to think a little different now to use your framework correctly.
     
  3. B4blue

    B4blue

    Joined:
    Oct 2, 2013
    Posts:
    1
    Any other way of buying this? I can't pay with paypal as I currently don't have any credit card, just paypal money. Stupid paypal...
     
  4. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    Hi softlion. How are things going?

    I've been away from Unity and game dev for a while but now I am about to start work on a new game idea and was just reviewing IoC+. And even my simplified example that we previously discussed (without Mediators) was hard to wrap my mind around after my months away.

    But I guess being away gave me more clarity and I found a way to simplify it even more:

    I got rid of Commands completely by changing the injected JumpCommandSignal and MoveCommandSignal in the PlayerInputView (that Dispatch the JumpSignal and the MoveSignal to the PlayerView) to injected JumpSignal and MoveSignal. And then in the Update() method of PlayerInputView where it reads the keyboard arrow keys to perform jumps and moves, I changed the jumpCommandSignal.Dispatch() and moveCommandSignal.Dispatch() calls respectively to corresponding jumpSignal.Dispatch() and moveSignal.Dispatch() calls. This works perfectly and made things 100% more clear.

    Why are intermediary Commands necessary?
     
  5. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi again!

    Commands are there so the functionality of your game can be configured via the Contexts. When mediator/view signals are decoupled correctly, then the Contexts allow you to wire the events (signals) with actions (commands).
     
  6. shaunmitchell84

    shaunmitchell84

    Joined:
    Dec 17, 2013
    Posts:
    3
    Hi,

    What would be the best way to handle signals that more then one object could listen to? For example a switch that sends a signal to a door? Every door will probably be listening for a signal but I need to only do something if it's the correct one.

    I thought about ids that could be sent along with the signal but that might lead to performance issues instead of a direct reference.

    Any thoughts would be great.

    Great tool btw

    Thanks,
    Shaun
     
  7. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi Shaun!

    That would depend on how a door should know that the switch is connected to that specific door. In some of the projects that use IoC+ at work we also use a trigger/actor system. We also use strings to connect an actor to a trigger. So for example a SwitchTriggerView, that has the "FrontDoorButton" id and a DoorActorView with the same id. The ActorView can then figure out whether it should respond to the TriggerSignal. You'd need a ridiculous amount of actors before this becomes a performance issue.

    I hope that helps!
     
  8. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    Hi softlion,

    How did this not using mediators work out for you? If it worked well are you going to update the starting tutorials to not include use of mediators?
     
  9. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi TokyoDan!

    Skipping mediators and going straight to the views worked out great for our projects. Currently I'm way to deep into unrelated projects to update the tutorials.

    If you have any questions related to not using mediators, please feel free to ask here and I will answer them to the best of my abilities!
     
  10. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    Do you have a text file (non-PDF) version of the documentation.pdf. For my own personal use, I'd like to update it with the new signal naming conventions that have been updated on http://iocplus.com/documentation/, and also I'd like to change it to reflect using IoC+ without mediators. This is just for my use in oder to keep things straight in my mind.
     
  11. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Here is a link to the Google Drive document. You'll have access to add comments and you can export to PDF!
    Offline IoC+ documentation link
     
  12. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    Thanks!
     
  13. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    A while ago in this direct conversation halfway down the page I asked about bindings in a Context:

    "One thing that surprised me was that I didn't need to do Bind<JumpCommandSignal>(); because it seems that On<JumpCommandSignal>().Do<JumpCommand>(); is enough. (Does that also cause a Bind/Inject ?)"

    You answered:

    "That's true. It injects the signal in the context if not already injected. It is not conventional however, as you'll soon lose track of bindings when having multiple contexts."

    I have a follow-up question:

    If, so as not to lose track of bindings when having multiple contexts, I wanted to use the Bind<>() statement to bind JumpCommand to JumpCommandSignal, how would I do it?

    Wouldn't a simple Bind<JumpCommandSignal>(); bind an instance of itself (JumpCommandSignal) instead of binding JumpCommand to JumpCommandSignal? as in:

    Bind<JumpCommandSignal>(JumpCommand); (which gives me an error.)
     
  14. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    Is Dispose() called even when Mediators are not used?
    Since I am not using Mediators I moved the Dispose method to the corresponding View.

    If Dispose method isn't called what should I use instead to cleanup when the view is removed?
     
  15. Lanslot

    Lanslot

    Joined:
    Nov 18, 2016
    Posts:
    37
    Hello man,
    How can i inject a base type like float?
     
  16. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    I got a 'best practices' question about IoC+:

    Say I have a tank unit in the game that both receives input (from maybe AI telling it where to move, or from player clicking on a map to move it) and also reacts what was input, by moving the tank to where the map was clicked or to where the AI sends it.

    Is it better to have two views: One view for getting the input and a second view that positions the unit? OR is it better to combine everything into one view?
     
  17. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    How do I use "BindLabeled"? I can find any examples.
     
  18. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    About example "05 - Signal Parameters" in the IoC+ Tutorials folder...

    When I run it a Player (clone) gameobject is created with the PlayerView component. Also created is a PlayerInputView gameobject with the PlayerInputView component.

    I want both views on one gameobject..
    How can I get both PlayerView and PlayerInputView components on the Player (clone) gameobject?

    OR

    Upon creation of the PlayerInputView gameobject how can I set it as a child of the Player (clone) gameobject?
    (I tried various ways: by trying to inject the Player (clone) gameobject into PlayerInputView.cs, and visa versa, trying to inject the PlayerInputView gameobject into PlayerView.cs but I was not successful.)
     
  19. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    I have a project where I have a mapView and a unitView both of which inject a signal. If I dispatch the injected signal in unitView it is OK, but if I try to dispatch the signal in mapView I get:

    NullReferenceException: Object reference not set to an instance of an object

    The signal in not being injected in to mapView. This is weird as mapView is pretty much the same as unitView: same signal, same [inject] statement.
     
  20. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    My apologies for the late responses. Unity seems to have forgotten to send me any notification of the updates on this thread.

    I'll try and answer each of your questions to the best of my ability!


    Binding a command to a signal is not how you use commands to trigger when a signal is dispatched, which I think is what you are trying to do here. To use a command as a response to a signal, use the following:
    Code (CSharp):
    1. On<Signal>()
    2.     .Do<Command>();

    Yes, Dispose() is called in views when it is removed from a context. However, this is only done when the view's gameobject has been active, as its unity does not call the OnDestroy method when gameobjects have never been active. Also, make sure you do not overwrite the view's OnDestroy method without calling base.OnDestroy().


    Yes, that's very much possible. It would look like the following.
    Code (CSharp):
    1. Bind<float>(1.0f);

    It would be better to have two views! This will keep your classes cleaner and will make refactoring more convenient. This is called "Separation of concerns".


    BindLabeled can be used to bind to same type with a different label. Normally, binding the same type would not go together as, when injecting, IoC+ would not know which value to inject (the first or the second binding). The following example shows how to bind and inject a labeled injection.
    Code (CSharp):
    1. public enum MyLabel {
    2.     BlueTeam,
    3.     RedTeam
    4. }
    5.  
    6. public class PlayerStatus {
    7.     public float Health;
    8. }
    9.  
    10. public class BluePlayerView : View {
    11.     [InjectLabeled(MyLabel.BlueTeam)] private PlayerStatus playerStatus;
    12. }
    13.  
    14. public class RedPlayerView : View {
    15.     [InjectLabeled(MyLabel.RedTeam)] private PlayerStatus playerStatus;
    16. }
    17.  
    18. public class MyContext {
    19.     protected override void SetBindings() {
    20.         BindLabeled<PlayerStatus>(MyLabel.RedTeam);
    21.         BindLabeled<PlayerStatus>(MyLabel.BlueTeam);
    22.     }
    23. }

    You can put two views on the same prefab when instantiating. This will automatically add all views in the instantiated gameobject to the context. The instantiation command would simply look like the following.
    Code (CSharp):
    1. public class InstantiatePlayerCommand : Command {
    2.  
    3.     [Inject] IContext context;
    4.  
    5.     protected override void Execute() {
    6.         View prefab = Resources.Load<View>("Player");
    7.         context.InstantiateView(prefab);
    8.     }
    9.  
    10. }
    This will instantiate the loaded view prefab to the context along with all other view component added to any gameobject in that prefab.


    There are multiple things that could trigger this behavior.
    * Is the view instantiated to the Context correctly?
    * Is the MapView derived from the View class?
    * Is the Signal dispatched before the Initialize() of the View is called?
    * Is the Signal dispatched after the Dispose() of the View is called?
    * Is the MapView instantiated to a different Context without a binding for that Signal?


    Sorry again guys for the late responses. I hope this answers your questions.
     
  21. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    To answer your questions...MapView is derived from the View class and it is a component of a gameObject that is already the scene when the game runs. Therefore MapView is not present in anyContext.

    The MapView gameObject is not instantiated because it is already in the scene. So I guess Initialize() and Dispose() are not called.

    Is this my problem?

    [UPDATE] Yep. That was my problem. I fixed it. Thanks!
     
    Last edited: Aug 19, 2017
  22. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Yes it should be added to the context to have the initialize method called. If the view is already in the scene you can find it in a command using Unity's FindObjectOfType<T> method and use the context.AddView to add it to the context, triggering its Initialize method.

    Good luck!
     
  23. jk15

    jk15

    Joined:
    Jun 23, 2011
    Posts:
    49
    Hi @softlion

    I'm having problems getting values from a signal into a command.

    I have a signal:

    Code (CSharp):
    1. public class MySignal : Signal<string, bool> {
    2.  
    3. }
    dispatches from a mediator:

    Code (CSharp):
    1. MySignal.Dispatch("myString", true);
    2.    
    the command:

    Code (CSharp):
    1. public class MyCommand :Command<string, bool>
    2.     {
    3.  
    4.         protected override void Execute(string myString, bool myBool)
    5.         {
    6.          
    7.         }
    8.     }
    Mapped in context

    Code (CSharp):
    1. On<MySignal>().Do<MyCommand>();
    error:

    Given amount of execution parameters for 'MyCommand' does not match the required amount. 2 parameter(s) are required but received 0.


    How do I make the command aware of the incoming values?

    Thanks.
     
  24. jk15

    jk15

    Joined:
    Jun 23, 2011
    Posts:
    49
    Hi @softlion

    Can you nest a context loaded from another scene?

    Thanks.
     
  25. jk15

    jk15

    Joined:
    Jun 23, 2011
    Posts:
    49
    Hi @softlion

    I have a button on a scene that I have attached a View script to, it has a Mediator and they have been Binded in the context - should this work as it appears the mediator does not get created.

    Thanks.
     
  26. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    This tutorial on the IoC+ website explains this in detail!
     
  27. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Currently not at my computer, but I believe you can do the following.
    Code (CSharp):
    1. public class MyCommand : Command {
    2.  
    3.     [Inject] private IContext context;
    4.  
    5.     public override void Execute() {
    6.         // First find other context in scene
    7.         context.AddContext(otherContext);
    8.     }
    9.  
    10. }
    This is not tested code though, as I currently have no access to any computer.
     
  28. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    The view should be added to the context first.
     
  29. jk15

    jk15

    Joined:
    Jun 23, 2011
    Posts:
    49
    @softlion

    Does this mean everything has to be instantiated from prefabs at runtime? Is it not possible to have objects already in the scene?

    Thanks.
     
  30. jk15

    jk15

    Joined:
    Jun 23, 2011
    Posts:
    49
    How?

    Also .AddContext does not seem to be a method of context.
     
    Last edited: Aug 26, 2017
  31. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Ok I'm back at work and can review the project where we did this.

    What we have is a Main scene with a GameRoot that constructs the GameContext. The GameContext loads the Level01 scene via a Command. The Level01 scene contains a LevelView. After loading the Level01 scene, the GameContext sets the LevelContext as current state.

    Code (CSharp):
    1. public class GameContext : Context {
    2.  
    3.     protected override void SetBindings() {
    4.         base.SetBindings();
    5.  
    6.             On<EnterContextSignal>()
    7.                 .Do<LoadLevelSceneCommand>("Level01") // Load scene of Level01
    8.                 .GotoState<LevelContext>(); // Set LevelContext as the GameContext's state
    9.     }
    10.  
    11. }
    12.  
    13. public class LevelContext : Context {
    14.  
    15.     protected override void SetBindings() {
    16.         base.SetBindings();
    17.    
    18.         BindMediator<LevelMediator, LevelView>();
    19.  
    20.         .On<EnterContextSignal>()
    21.             .Do<AddLevelViewToContextCommand>(); // Find LevelView instance in LevelScene and add it to the context
    22.     }
    23.  
    24. }
    25.  
    26. public class LoadLevelSceneCommand : Command<string> {
    27.  
    28.     public override void Execute(string levelName) {
    29.         SceneManager.LoadScene(levelName, LoadSceneMode.Additive);
    30.     }
    31.  
    32. }
    33.  
    34. public class AddLevelViewToContextCommand : Command {
    35.  
    36.     [Inject] private IContext context;
    37.  
    38.     public override void Execute() {
    39.         LevelView levelView = Object.FindObjectOfType<LevelView>();
    40.         context.AddView(levelView);
    41.     }
    42.  
    43. }
    Although I recommend only instantiating views via prefabs, we we're forced to use this method in our project as we are using Light Baking and async loading for the scene objects.

    Let me know if this answers your questions!
     
    Last edited: Aug 28, 2017
  32. jk15

    jk15

    Joined:
    Jun 23, 2011
    Posts:
    49
    Thanks for the response @softlion

    This does not seem to be a function for me, is this something that was added for your project?
     
  33. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi jk314!

    I've send you a link to the IoC+ 1.3.3 (beta) package in a private message. This version does contain the context.AddView method. Let me know if it works!
     
    jk15 likes this.
  34. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    Also in 1.3.3, you can simplify things because Mediators are not required? Is it recommended to not NOT use Mediators?

    I find not using Mediators makes a huge difference in making my code easy to understand.
     
  35. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    I'd say in most circumstances it is recommended to NOT use mediators. In my eyes there are only two reasons to go for a Mediator.
    1. When the same View needs to act differently in different Contexts. The Context can bind a different Mediator to the same View and you saved yourself some code.
    2. When you want your code base to be unit tested and the View becomes to complex. Views are MonoBehaviors, which cannot be properly unit tested. Thus, putting the complex code of the view into the Mediator allows the unit testing to be possible.

    I hope this answers your question!
     
  36. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    That is great answer. Thanks again as always!
     
  37. mrcoffee666

    mrcoffee666

    Joined:
    Jun 21, 2013
    Posts:
    6
    Is it possible somehow to inject views which are already placed in the editor? This would be super useful for a quick testing for our designer for example.
     
  38. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,080
    See post #72
     
  39. mrcoffee666

    mrcoffee666

    Joined:
    Jun 21, 2013
    Posts:
    6
    Thanks for the hint. Will try this.
     
  40. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi Liaonantian,

    This question has been answered before. If the view is already in the scene you can find it in a command using Unity's FindObjectOfType<T> method and use the context.AddView to add it to the context, triggering its Initialize method.
     
  41. Lanslot

    Lanslot

    Joined:
    Nov 18, 2016
    Posts:
    37
    upload_2018-3-10_14-59-26.png
    upload_2018-3-10_15-2-54.png
    T must be a reference type,how to do?
     
  42. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi guys!

    It seems there is a lot of interest in the newest development version of IoC+. The reason it takes so long for me to update on the asset is because most tutorials on the website should be rewritten as a result and I'm currently deep into other projects at work.

    I'm going to update the asset later this week nonetheless which might result in some tutorials being slightly outdated.

    Thank you all for your patience.
     
  43. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    I'm currently not able to access the IoC+ source code. What happens when you remove the "where T : class" part?
     
  44. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hi guys!

    I've just now submitted IoC+ 1.4 to the asset store, currently waiting for review. Here is a list of improvements since the last version:
    • References to views can now be injected via the Ref<T> and Refs<T> classes.
    • The example project is updated to use Ref<T> and Refs<T> classes.
    • Fixed issue where labeled inject type needed to be a class.
    • Fixed issue where views and mediators were not disposed correctly on destruction.
    • Views can now be instantiated without a mediator binding.
    • Views already in scene can now be added to the context using the AddView<T>() method.
    • Views can now use the [Inject] and [InjectLabeled] to inject fields just like commands and mediators.
    • Contexts can now use OnChild<T,U>() to define signal responses for signals dispatched in direct child contexts.
    • Creating scripts using Create/IoC+/... now instantly shows the new files in Unity.

    All tutorials on the website still work in IoC+. A big decision has been made however to allow users use views without having to set a mediator binding. Doing this will make it trickier to unit-test the view as this would normally be done with the mediator, but it could also speed up development by a lot. The tutorials have not been updated to demonstrate the bypassing of mediators, but the example project included in the IoC+ 1.4 does make use of it.

    With projects at work we've also experimented with the use of the Ref(s) class which is now also added to the package. Use this class to store references to instantiated views and call methods to those views directly instead of communicating to views via signals.

    Please look at the example project and online documenation for more information.

    I'm currently still in the middle of some crunching projects at work. I do my best to answer all your questions on this thread. Thank you for your patience.
     
    TokyoDan and Lanslot like this.
  45. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    IoC+ 1.4 is now approved and downloadable from the Asset Store!
     
    TokyoDan likes this.
  46. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    In my hurry I have not uploaded a version from below Unity 5.6 but I believe it should run fine. Let me know if you have any trouble.
     
  47. Lanslot

    Lanslot

    Joined:
    Nov 18, 2016
    Posts:
    37
    upload_2018-3-27_16-32-39.png
    hey man, It looks like a mistake.
     
  48. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    100
    Hey Lanslot, thank you for pointing that out! I think that should include the type that it is referencing. I'll be sure to include that in the next update.
     
  49. UnityZen

    UnityZen

    Joined:
    Jun 11, 2017
    Posts:
    2
    The website is down! Is this package worth buying?
     
  50. Nathan-Liu

    Nathan-Liu

    Joined:
    Apr 27, 2017
    Posts:
    8
    Please don't buy this plug-in unless you have too much money. I would suggest that you use strangeioc.