Search Unity

Assemblies and Looping Dependencies

Discussion in 'Scripting' started by Fedora_Dev, Apr 23, 2020.

  1. Fedora_Dev

    Fedora_Dev

    Joined:
    Oct 28, 2016
    Posts:
    35
    Hello!

    I am trying to elevate my programming and make my systems much more modular. In order to force myself to do this, I've started adding assemblies to each major system in my project. I already separate my code into system folders so it should be simple, right?

    Well, I have a few issues. In particular, my Game Manager needs to know about just about all my other systems, and all of my other systems need to know about my Game Manager. I've been structuring my code such that the game manager handles inter-dependency so my other systems don't really need to reference each other.

    So my question is this; how can I let my assemblies know about that looping style dependency, or how can I program such that I don't need to?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    Well you can't really get it...

    The best you can get is an interface to it. As long as that interface doesn't uncover members that also create looping dependencies. (like your GameManager has a property that allows access to the EnemyManager from the Enemy assembly).

    But basically you'll have some assembly that all other assemblies can reference. Lets call this the "SuperAssembly" for sake of discussion.

    In SuperAssembly you may define a "IGameManager" interface like so:
    Code (csharp):
    1. public interface IGameManager
    2. {
    3.     bool IsPaused {get;}
    4.  
    5.     void SaveGame();
    6.  
    7.     //I don't know what your IGameManager does... so put what you need here
    8. }
    Now your GameManager class will exist outside of this assembly, but implement this IGameManager interface.

    Then your other assemblies will consume the GameManager not as GameManager, but as IGameManager:
    Code (csharp):
    1. public class EnemyManager
    2. {
    3.     public void Init(IGameManager manager)
    4.     {}
    5. }
    Note, I also usually like to do a "ServiceManager" approach here as well. Where the 'SuperAssembly' defines a "Services" class where your managers register themselves with the service manager. Something like:

    Code (csharp):
    1. public interface IService
    2. {
    3.  
    4. }
    5.  
    6. public static class Services
    7. {
    8.  
    9.     public static T Get<T>() where T : IService
    10.     {
    11.         return ServiceEntry<T>.instance;
    12.     }
    13.  
    14.     public static void Register<T>(T service) where T : IService
    15.     {
    16.         ServiceEntry<T>.instance = service;
    17.     }
    18.  
    19.     public static void Unregister<T>(bool destroy = true)
    20.     {
    21.         var inst = ServiceEntry<T>.instance;
    22.         ServiceEntry<T>.instance = null;
    23.         if(inst != null && destroy && inst is UnityEngine.Object)
    24.         {
    25.             UnityEngine.Object.Destroy(inst and UnityEngine.Object);
    26.         }
    27.     }
    28.  
    29.     private static class ServiceEntry<T> where T : IService
    30.     {
    31.      
    32.         public static T instance;
    33.      
    34.     }
    35.  
    36. }
    37.  
    38. public interface IGameManager : IService
    39. {
    40.     //same as before
    41. }
    42.  
    43. //some code somewhere
    44. var game = Services.Get<IGameManager>();
    45. game.Pause();
    Note - that was a very very simple naive approach for demonstration purposes. Here's the version I use in my production scenarios that supports lazy instantiation, single instance management, and other things:
    https://github.com/lordofduct/space...b/master/SpacepuppyUnityFramework/IService.cs

    Then your "managers" would all have interfaces associated with them, and on startup you'd register them in 'Services', and you could then access them from anywhere.

    You may be thinking this looks sort of like a singleton, which it sort of kind of is. But the nice approach here is that since they implement an interface it means you can have multiple versions of each service and register the one needed. This way say you're doing a "Unit Test", you can have a "UnitTestGameManager : IGameManager" filled with dummy data specifically for testing purposes.
     
    Fedora_Dev likes this.
  3. Fedora_Dev

    Fedora_Dev

    Joined:
    Oct 28, 2016
    Posts:
    35
    Thanks for the detailed reply! I've started piecing this solution together. After poking through your code quite a bit, I have a few questions.

    1. When does a ServiceScriptableObject register itself? Awake is usually run when the instance is created which means something somewhere has to have a reference to one in order for it to register itself, correct?
    2. What are some resources I can use to read up more on this solution?
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    It registers during its awake method if it's not already registered, or you can register it when you create it. So like if it was an asset you created in the asset folder and referenced in scripts, then it'd be called when the game started. But if you created it with the Services.Create<T> method, it'd be registered when Create was called.

    Uhhh, I don't really have any resources on it. It's just what I wrote a long while back. That 'spacepuppyunityframework' on github is all the resource I have for my code. And I would argue it's not really intended for ease of use by other devs, but rather as a repository for me to keep a remote copy of my code wile also acting as something I can link to if I'm trying to explain how I do stuff.
     
  5. Fedora_Dev

    Fedora_Dev

    Joined:
    Oct 28, 2016
    Posts:
    35
    I went ahead and implemented my own version of what you showed me. It's been pretty great so far. It really made me think about what aspects of each system I was exposing and it helped me organize everything quite a bit better. After getting it all set up, I added the assemblies and the project feels a little bit snappier than it was prior. Thanks a bunch!
     
    lordofduct likes this.