Search Unity

  1. We've closed the job boards. If you're looking for work, or looking to hire check out Unity Connect. You can see more information here.
    Dismiss Notice
  2. We're running great holiday deals on subscriptions, swag and Asset Store packages! Take a peek at this blog for more information!
    Dismiss Notice
  3. Check out our Unite Austin 2017 YouTube playlist to catch up on what you missed. More videos coming soon.
    Dismiss Notice
  4. Unity 2017.2 is now released.
    Dismiss Notice
  5. The Unity Gear Store is here to help you look great at your next meetup, user group or conference. With all new Unity apparel, stickers and more!
    Dismiss Notice
  6. Introducing the Unity Essentials Packs! Find out more.
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice
  8. Unity 2017.3 beta is now available for download.
    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:
    89
    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,025
    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:
    89
    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:
    89
    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,025
    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:
    89
    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,025
    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:
    89
    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,025
    Thanks!
     
  13. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,025
    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,025
    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:
    3
    Hello man,
    How can i inject a base type like float?
     
  16. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,025
    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,025
    How do I use "BindLabeled"? I can find any examples.
     
  18. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,025
    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,025
    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:
    89
    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,025
    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:
    89
    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. jk314

    jk314

    Joined:
    Jun 23, 2011
    Posts:
    35
    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. jk314

    jk314

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

    Can you nest a context loaded from another scene?

    Thanks.
     
  25. jk314

    jk314

    Joined:
    Jun 23, 2011
    Posts:
    35
    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:
    89
    This tutorial on the IoC+ website explains this in detail!
     
  27. softlion

    softlion

    Joined:
    Sep 6, 2013
    Posts:
    89
    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:
    89
    The view should be added to the context first.
     
  29. jk314

    jk314

    Joined:
    Jun 23, 2011
    Posts:
    35
    @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. jk314

    jk314

    Joined:
    Jun 23, 2011
    Posts:
    35
    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:
    89
    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. jk314

    jk314

    Joined:
    Jun 23, 2011
    Posts:
    35
    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:
    89
    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!
     
    jk314 likes this.
  34. TokyoDan

    TokyoDan

    Joined:
    Jun 16, 2012
    Posts:
    1,025
    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:
    89
    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,025
    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,025
    See post #72
     
  39. mrcoffee666

    mrcoffee666

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