Search Unity

"Verfices", a well-written, clean documented and optimized global access system!

Discussion in 'Scripting' started by vexe, Nov 11, 2013.

  1. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    EDIT: I've re-written the whole thing here, thanks to Smooth P for his hint.

    ORIGINAL: Hello guys,

    I want to share something pretty cool with you - A global access system that I wrote yesterday.
    This is going to be an extension of my answer here where we had a discussion on what's the best global access system to use, we talked about singletons, we talked about other alternatives like Mr Fattie's Grid system, the monostate pattern, etc.

    At the end we agreed upon that Mr Fattie's Grid system is best, at least better than the singleton pattern in that it provides very easy access, and you could have as many managers as you want as opposed to the singleton where one instance is only allowed.

    What I don't like about the Grid system, is the fact that it violates the O in SOLID - the open-closed principle which states that classes should be open for extension and closed for modification - In the Grid system, each time you wanted to add something, you'd have to go to the Grid class and add it to it - thus modifying the class - something not very cute.

    What if for example, I used the Grid system in one of my published assets, where I only had lets say an InventoryManager and AtlasManager, but the user of my asset, kinda liked the Grid idea, so he decided to add some more Managers - Now, it's time for me to update my asset - I'm still using the Grid, but I didn't add the managers that my user added, cause I don't know about them. Now when he upgrades, my Grid system will overwrite his! - This is just an example.

    The system I came up with, avoids this problem - it doesn't violate the O principle - it also allows for multiple instances of a manager/service to be available (as opposed to the singleton - one instance only)

    My system's logic goes like this: Dear XXXManager If you wanna be accessed globally, then register yourself, let me know.

    (More on the sucker's name "Verfices" later Mr Fattie, don't worry ;))

    Usage:

    1- First, there's an IService interface (which is currently just a "marker" interface) that your manager have to implement.
    Code (csharp):
    1.  
    2. public interface IService
    3. {
    4.  
    5. }
    6.  
    7. public class InventoryMan : MonoBehaviour, IService { /* Stuff */ }
    8.  
    2- Next, your manager/service have to register itself, like this: "Verfices.Register(this);"
    Code (csharp):
    1.  
    2. public class InventoryMan : MonoBehaviour, IService
    3. {
    4.     void Awake()
    5.     {
    6.         Verfices.Register(this);
    7.     }
    8.  
    9.     public void Ping() // just a way of telling that the service is alive
    10.     {
    11.         Debug.Log(this + ": providing service!");
    12.     }
    13. }
    14.  
    3- Now, anywhere else in code, you could request the service by simply:
    Code (csharp):
    1.  
    2. InventoryMan inventory = Verfices.Request<InventoryMan>();
    3. inventory.Ping();
    4.  
    4- If you had more than one instance of a service registered, you could request them all, or many:
    Code (csharp):
    1.  
    2. // lets assume that we have 3 inventory services registered
    3. var many = Verfices.RequestMany<InventoryMan>(10);
    4. var all = Verfices.RequestAll<InventoryMan>();
    5. foreach (var inventory in all.Concat(many)) // Concat's in System.Linq
    6.    inventory.Ping();
    7.  
    Total prints will be 6 - Cause if you request more than the total, you'll get the total (that's an implementation difference, more on that later)

    5- You could unregister an instance of a service:
    Code (csharp):
    1.  
    2. Verfices.Unregister(Verfices.RequestAt<InventoryMan>(2));
    3.  
    Here, I use another version of Request, to request a service at a specific index.

    6- You could unregister a service type, now all instances of that type will be gone:
    Code (csharp):
    1.  
    2. Verfices.RemoveServiceType(typeof(InventoryMan));
    3.  
    7- You could also create a new instance of a service, and then register it:
    Code (csharp):
    1.  
    2. var atlas = Verfices.CreateService<AtlasMan>();
    3. Verfices.Register(atlas);
    4.  
    Some notes:
    1- This system isn't restricted to MonoBehaviours only, it works well with any type of class :)
    2- If a MonoBehaviour service registers, DontDestroyOnLoad will be called on it - so it stays persistent between scenes.
    3- The implementation you will see, is a "Base" implementation, it's designed to be extended by you however you like. As we'll see later, there are a lot of things that I didn't add, that would be nice to have - but you might not need them - Again, think of this as LFS (Linux from scratch) - Or more ArchLinux - where you have a base system and you build on top of it. Another reason I didn't add extra functionality, is that I wanted it to be light and clean to make it less overwhelming for the first-time readers - and the non-elite.

    Please note that this system is inspired by Mr Fattie's Grid system, and the Service Locator from gameprogrammingpatterns.com

    Anxious for the implementation? Here we go.... :cool: \m/

    Code (csharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using UnityEngine;
    6. using Object = UnityEngine.Object;
    7.  
    8. public static class Verfices
    9. {
    10.     /// <summary>
    11.     /// A dictionary of (Key = Type, Value = List of IService)
    12.     /// This is what makes this system superb over the singleton, in that there could be more than one instance to one Type.
    13.     /// </summary>
    14.     private static Dictionary<Type, List<IService>> dic = new Dictionary<Type, List<IService>>();
    15.  
    16.     /// <summary>
    17.     /// Registers a service.
    18.     /// More than one instance of a service type is allowed.
    19.     /// Registration will fail if the same instance has already registered itself.
    20.     /// </summary>
    21.     public static void Register(IService service)
    22.     {
    23.         Type type = service.GetType();
    24.         List<IService> list = GetServiceList(type, out list);
    25.  
    26.         /*
    27.         // Get rid of the next two lines if you'd chosen to create a new list in GetServiceList if the type is not found
    28.         // by uncommenting the line: dic[type] = list = new List<IService>(); in there (get back to this later if you're first reading).
    29.         */
    30.         if (list.IsEmpty())
    31.             dic[type] = list;
    32.         else if (list.Contains(service))
    33.             return;
    34.  
    35.         list.Add(service);
    36.  
    37.         if (service is MonoBehaviour)
    38.             Object.DontDestroyOnLoad(service as MonoBehaviour);
    39.     }
    40.  
    41.     /// <summary>
    42.     /// Unregisters a service - That is, if it was found in the services dictionary.
    43.     /// If there's no more services remaining of a certain type, that type entry will be removed from the dictionary.
    44.     /// </summary>
    45.     public static void Unregister(IService service)
    46.     {
    47.         Type type = service.GetType();
    48.         List<IService> list = GetServiceList(type, out list);
    49.         int index = list.IndexOf(service);
    50.         if (index != -1) {
    51.             list.RemoveAt(index);
    52.             if (list.Count == 0) // Just some house keeping...
    53.                 RemoveServiceType(type);
    54.         }
    55.     }
    56.  
    57.     /// <summary>
    58.     /// Removes a certain type of services from the services dictionary.
    59.     /// Note that there's no need to remove the individiual services, and then the type because once we
    60.     /// remove the reference list, the services will get garbage collected since we lost all reference to them
    61.     /// unless there's a temporary reference from the outside.
    62.     /// </summary>
    63.     public static void RemoveServiceType(Type type)
    64.     {
    65.         dic.Remove(type);
    66.     }
    67.  
    68.     /// <summary>
    69.     /// Requests the 'index'th service of type T.
    70.     /// 'index' will be clampped between 0 and the total number of services available.
    71.     /// <para>
    72.     /// Returns: The requested service if found, null otherwise</para>
    73.     /// </summary>
    74.     public static T RequestAt<T>(int index) where T : class, IService
    75.     {
    76.         Type type = typeof(T);
    77.         List<IService> list;
    78.         if (!dic.TryGetValue(type, out list)) {
    79.             return null;
    80.             // Un-comment the next line of code if you want to create a service and return it in case it wasn't found (promotes laziness).
    81.             // Then you could request services without having to worry about whether they registered or not.
    82.             // Doing so, you'd have to add the 'new()' constraint in the declarations of:
    83.             // Request<T>(int), Request<T>() and RequestMany<T>()
    84.             // Like: public static T Request<T>(int index) where T : class, IService, new()
    85.             /*
    86.             dic[type] = new List<IService> { CreateService<T>() };
    87.             */
    88.         }
    89.  
    90.         // This is how I chose to handle out-of-range indices, don't like it? change it...
    91.         index = Mathf.Clamp(index, 0, list.Count-1);
    92.         return list[index] as T; // this 'class' constraint above is needed for this cast to be valid
    93.     }
    94.  
    95.     /// <summary>
    96.     /// Requests the first service instance of type T.
    97.     /// <para>
    98.     /// Returns: The requested service if found, null otherwise.</para>
    99.     /// </summary>
    100.     public static T Request<T>() where T : class, IService
    101.     {
    102.         return RequestAt<T>(0);
    103.     }
    104.  
    105.     /// <summary>
    106.     /// Requests all services of type T.
    107.     /// <para>
    108.     /// Returns: The service list of type T if found, empty list otherwise.</para>
    109.     /// </summary>
    110.     public static List<T> RequestAll<T>() where T : IService
    111.     {
    112.         return GetServiceList<T>().Cast<T>().ToList();
    113.     }
    114.  
    115.     /// <summary>
    116.     /// Requests 'n' number of services of type T.
    117.     /// <para>
    118.     /// Returns:The required number of services required if the number of total services are more than what's required,
    119.     ///       else if the number of services required is higher than what we have, all the available services (this includes having 0 services).</para>
    120.     /// </summary>
    121.     public static List<T> RequestMany<T>(int n) where T : class, IService
    122.     {
    123.         List<IService> list = GetServiceList<T>(out list);
    124.  
    125.         int dif = n - list.Count;
    126.         if (dif > 0) {
    127.             // The number of required services is higher than what we have, return what we have;
    128.             // This is an implementation difference, for me I'm just wanna return all the services I have, if I get a number higher than what I have
    129.             return RequestAll<T>();
    130.  
    131.             /*
    132.             // Un comment the next piece of code if you want to create the missing services, and return them (promotes laziness)
    133.             // But please note that if you do so, you will need to create counter-measures for when
    134.             // there 'already' is, some unregistered services in the scene which will make creating
    135.             // new ones unnecessary - This is where FindServices come into play.
    136.             // An example: There are 2 'unregistered' instances of InventoryManager in your scene,
    137.             // if you come and do: RequestMany<InventoryManager>(2) - You'll create 2 'new' instances,
    138.             // while you already had two, you just had to look for them, find them and register them.
    139.             // This is what you have to keep in mind if you want this type of implementation.
    140.             */
    141.             /*
    142.             for (int i = 0; i < dif; i++) {
    143.                 list.Add(CreateService<T>());
    144.             }
    145.             return list.Cast<T>().ToList();
    146.             */
    147.         }
    148.  
    149.         // return what's required;
    150.         return list.Take(n).Cast<T>().ToList();
    151.     }
    152.  
    153.     /// <summary>
    154.     /// Tries to get the value list of type 'type' from the services dictionary.
    155.     /// <para>Returns: The list if found, empty list of services otherwise</para>
    156.     /// </summary>
    157.     private static List<IService> GetServiceList(Type type, out List<IService> list)
    158.     {
    159.         if (!dic.TryGetValue(type, out list)) {
    160.             // This is an implementation difference, I chose to return an empty list instead of null, easier.
    161.             // This saves me the null checking before looping for example.
    162.             return Enumerable.Empty<IService>().ToList();
    163.             /*
    164.             // Uncomment the next line of code, if you want to create a new list for the passed type,
    165.             // if it didn't exist instead of returning an empty list (promotes laziness)
    166.             */
    167.             /*
    168.             dic[type] = list = new List<IService>();
    169.             */
    170.         }
    171.         return list;
    172.     }
    173.  
    174.     /// <summary>
    175.     /// Gets the value list of type 'T' from the services dictionary.
    176.     /// <para>Returns: The list if found, an empty list otherwise</para>
    177.     /// </summary>
    178.     private static List<IService> GetServiceList<T>(out List<IService> list) where T : IService
    179.     {
    180.         return GetServiceList(typeof(T), out list);
    181.     }
    182.  
    183.     /// <summary>
    184.     /// Gets the value list of type 'T' from the services dictionary.
    185.     /// <para>Returns: The list if found, an empty list otherwise.</para>
    186.     /// </summary>
    187.     private static List<IService> GetServiceList<T>() where T : IService
    188.     {
    189.         List<IService> list = GetServiceList<T>(out list);
    190.         return list;
    191.     }
    192.  
    193.     /* <<< FOR PURE CONVENIENCE >>> */
    194.     #region
    195.     /// <summary>
    196.     /// Creates a service of type T.
    197.     /// If the service was a MonoBehaviour, a new game object with the type's name is created in the scene,
    198.     /// with the services added as a component. Otherwise a new service is created and returned.
    199.     /// Note that this won't register the service for you, do it yourself!
    200.     /// <para>
    201.     /// Returns: A reference to the created service - A reference to a component on a game object,
    202.     ///          if the type of service required was a MonoBehaviour,
    203.     ///          or a reference to a newly created object on the heap otherwise</para>
    204.     /// </summary>
    205.     public static T CreateService<T>() where T : class, IService, new()
    206.     {
    207.         Type type = typeof(T);
    208.         if (type.IsSubclassOf(typeof(MonoBehaviour))) {
    209.             var go = new GameObject(type.ToString());
    210.             return go.AddComponent(type) as T;
    211.         }
    212.         return new T();
    213.     }
    214.  
    215.     /// <summary>
    216.     /// Returns how many service instances available of type T.
    217.     /// </summary>
    218.     public static int GetServiceCount<T>() where T : IService
    219.     {
    220.         return GetServiceList<T>().Count;
    221.     }
    222.     #endregion
    223. }
    224.  
    Couple of notes:

    1- You might be wondering, why did I write the GetServiceList like this:
    Code (csharp):
    1.  
    2. private static List<IService> GetServiceList(Type type, out List<IService> list)
    3. {
    4.     if (!dic.TryGetValue(type, out list)) {
    5.         return Enumerable.Empty<IService>().ToList();
    6.     }
    7.     return list;
    8. }
    9.  
    10. // call it like:
    11. List<IService> list = GetServiceList(type, out list);
    12.  
    13.  
    And not directly like this:
    Code (csharp):
    1.  
    2. private static List<IService> GetServiceList(Type type)
    3. {
    4.     List<IService> list;
    5.     if (!dic.TryGetValue(type, out list)) {
    6.         return Enumerable.Empty<IService>().ToList();
    7.     }
    8.     return list;
    9. }
    10.  
    11. // call it like:
    12. List<IService> list = GetServiceList(type);
    13.  
    This is a trick that @Jamora taught me :D - The point being is that if we used the 2nd version, we're using 2 references - One when we're declaring the list when we're calling the method, another inside the method. - Using 'out' means we're using the same storage location i.e. same reference - so there's only one reference is created - You might think of this as a pre-mature optimization, but it's nice to have - @Jamora: The key addition I added that I thought would be nice to have, is that I'm returning the list at the end, whereas if you remember your version would be something like this:

    Code (csharp):
    1.  
    2. private static void GetServiceList(Type type, out List<IService> list)
    3. {
    4.     if (!dic.TryGetValue(type, out list)) {
    5.         list = Enumerable.Empty<IService>().ToList();
    6.     }
    7. }
    8.  
    9. // call it like:
    10. List<IService> list;
    11. GetServiceList(type, out list);
    12.  
    13.  
    I just like one-liners :)

    2- You might have noticed that I left a lot of "implementation difference" comments - what does this mean? It means do it how you like! - Want lazy initialization? want service availability 24/7? want extra functionality? do it yourself! ;)

    3- Here's a couple of things, you might wanna consider adding (again, this is a base system, add what you want):
    1. Lazy initialization and 24/7 service availability:
      This means that a service is always available to you, even if there was 0 instances registered.
      To add this functionality, uncomment the lines of code in places where I mentioned "promotes laziness"
      (but please don't tolerate with my note in RequestMany, read it and take measures carefully - accordingly)
      Now you can: Verfices.Request<InventoryMan>(); without having to do Verfices.Register(this); in InventoryMan.Awake(); as if there wasn't any inventory service, available a new one will be created for you - Another thing to note is that if you do this you might wanna remove the Verfices.Register(this); in Awake all together, Not that you might end up with an infinite circle, (cause I have countermeasures for when an instance is already registered, it can't register again) but it's just redundant to have it in this case.

      But I wouldn't go for laziness, the thing I don't like about it:
      • You don't know at any point of time, when will things register, when will things get initialized etc.
      • Lazy means you'll only get the thing when you need it, what if you needed it in a moment of intense computing and calculation?
        It might slow things down, if getting this lazy thing requires some overhead...
      • It makes it more singleton-like, and breaks the whole point of Registering a service.

    2. Know if a service has registered or not:
      You could add a bool HasRegistered; in the IService interface - The benefits of this:
      • Now, you don't need "if (list.Contains(service))" when you Register a service, to know if it already has been registered or not.
        Instead, you just "if (service.HasRegistered)" - (Remember, list.Contains is O(n). I'd give this a serious thought, if I had a big list)
      • It helps in debugging - what if at some point in your debugging, you wanted to know if whether a service has registered or not? Using this boolean this is now available to you. One thing to keep in mind, is access security. When and who will set this variable to true? - It should be only set inside the Verfices class, more accurately in Register and Unregister - Since it needs to be set from an outsider, it needs to be public in a way, but if it's public, this means that everybody could come in and do: atlas.HasRegistered = false; !!! - One way to go about this, is to search for the service instance in our dictionary, if it was found return true otherwise false.
    3. Add a unique identifier to each service:
      Currently, there's no way you could identify and request/remove specific instances of a service type. Yes, you could request by index, but what if for example you had 2 InventoryManagers: (same class) one you'd like to identify as PrimaryInventoryMan the other as SecondaryInventoryMan - What you could do, is add a string Identifier; in the ISerivce interface:
      Code (csharp):
      1.  
      2. public interface IService
      3. {
      4.     string Identifier { get; }
      5. }
      6. public class InventoryMan : MonoBehaviour, IService
      7. {
      8.     [SerializeField] protected string identifier; // assign via inspector
      9.     public string Identifier { get { return identifier; } }
      10. }
      11.  
      You could add some counter-measures to make sure that you have unique identifiers, but that's up to you.

      Another way to handle this situation, let's say you have a game of N players, and you wanted global access to them, instead of registering all the players as separate services and giving them unique IDs, why not make a PlayerManager, and register that, as a service instead ;)

    4. Crucial service:
      What if you had a service, that's it's very important for your to have ONLY one instance of it, and if you had more, your game will crash and everything will go to ruin! (I can hardly think of an example to this case... in a game... but let's assume there is) - You could mark a service as crucial when you register it: (an overload) Verfices.Register(this, true); - But then you'd have to maybe create a separate class for your service to know which is marked as crucial and which is not. Or maybe have another dictionary for the crucial services, or maybe the same dictionary but with a custom data type as the value instead of a list of services, etc A lot of ways, just think of something....

    There might be more stuff of course, but that's just what's on the surface of my head right now...

    Oh and, you might be wondering if it's easy to test, well I'm no testing expert but I don't think it's hard to test, here's a small test I wrote:

    User.cs
    Code (csharp):
    1.  
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class User : MonoBehaviour
    6. {
    7.     void Awake()
    8.     {
    9.         DontDestroyOnLoad(this);
    10.     }
    11.  
    12.     void OnGUI()
    13.     {
    14.         if (GUI.Button(new Rect(Screen.width - 100, 0, 100, 50), "test scene 1"))
    15.             Application.LoadLevel("test1");
    16.  
    17.         if (GUI.Button(new Rect(Screen.width - 100, 50, 100, 50), "test scene 2"))
    18.             Application.LoadLevel("test2");
    19.  
    20.         if (GUI.Button(new Rect(10, 0, 100, 50), "inventory count"))
    21.             Debug.Log(Verfices.GetServiceCount<InventoryMan>());
    22.  
    23.         if (GUI.Button(new Rect(10, 50, 100, 50), "atlas count"))
    24.             Debug.Log(Verfices.GetServiceCount<AtlasMan>());
    25.  
    26.         if (GUI.Button(new Rect(10, 100, 200, 50), "request inventory"))
    27.             Verfices.Request<InventoryMan>().Ping();
    28.  
    29.         if (GUI.Button(new Rect(10, 150, 200, 50), "request atlas"))
    30.             Verfices.Request<AtlasMan>().Ping();
    31.  
    32.         if (GUI.Button(new Rect(10, 200, 250, 50), "unregister an inventory service"))
    33.             Verfices.Unregister(Verfices.Request<InventoryMan>());
    34.  
    35.         if (GUI.Button(new Rect(10, 250, 250, 50), "unregister an atlas service"))
    36.             Verfices.Unregister(Verfices.Request<AtlasMan>());
    37.  
    38.         if (GUI.Button(new Rect(10, 300, 250, 50), "request all inventory")) {
    39.             foreach (var inventory in Verfices.RequestAll<InventoryMan>())
    40.                 inventory.Ping();
    41.         }
    42.  
    43.         if (GUI.Button(new Rect(10, 350, 250, 50), "request all atlas")) {
    44.             foreach (var atlas in Verfices.RequestAll<AtlasMan>())
    45.                 atlas.Ping();
    46.         }
    47.  
    48.         if (GUI.Button(new Rect(10, 400, 250, 50), "request 2 atlases")) {
    49.             foreach (var atlas in Verfices.RequestMany<AtlasMan>(2))
    50.                 atlas.Ping();
    51.         }
    52.  
    53.         if (GUI.Button(new Rect(10, 450, 250, 50), "request 2 inventories")) {
    54.             foreach (var inventory in Verfices.RequestMany<InventoryMan>(2))
    55.                 inventory.Ping();
    56.         }
    57.  
    58.         if (GUI.Button(new Rect(10, 500, 300, 50), "create a new inventory service, and register it")) {
    59.             Verfices.Register(Verfices.CreateService<InventoryMan>());
    60.         }
    61.  
    62.         if (GUI.Button(new Rect(10, 550, 300, 50), "create a new atlas service, and register it")) {
    63.             Verfices.Register(Verfices.CreateService<AtlasMan>());
    64.         }
    65.     }
    66.  
    67.     void Start()
    68.     {
    69.         // Comment this out if you wanna test the OnGUI stuff
    70.         // Test();
    71.     }
    72.  
    73.     void Test()
    74.     {
    75.         var inventory = Verfices.Request<InventoryMan>();
    76.         var atlas = Verfices.Request<AtlasMan>();
    77.         Debug.Log("Ping test");
    78.         atlas.Ping();
    79.         inventory.Ping();
    80.  
    81.         Debug.Log("Requesting an atlas man, at index 100");
    82.         Verfices.RequestAt<AtlasMan>(100).Ping();
    83.  
    84.         Debug.Log("altas manager RequestMany test: requesting 5 out of " + Verfices.GetServiceCount<AtlasMan>());
    85.         List<AtlasMan> many = Verfices.RequestMany<AtlasMan>(5);
    86.         foreach (var atlasMan in many) {
    87.             atlasMan.Ping();
    88.         }
    89.  
    90.         Debug.Log(string.Format("altas manager RequestMany test: requesting -1 out of {0} (should not get output)", Verfices.GetServiceCount<AtlasMan>()));
    91.         many = Verfices.RequestMany<AtlasMan>(-1);
    92.         foreach (var atlasMan in many) {
    93.             atlasMan.Ping();
    94.         }
    95.  
    96.         Debug.Log("inventory manager RequestAll test, total: " + Verfices.GetServiceCount<InventoryMan>());
    97.         var all = Verfices.RequestAll<InventoryMan>();
    98.         foreach (var inventoryMan in all) {
    99.             inventoryMan.Ping();
    100.         }
    101.  
    102.         Debug.Log("unregistering an instance of the atlas manager, then printing all, current total: " + Verfices.GetServiceCount<AtlasMan>());
    103.         Verfices.Unregister(Verfices.Request<AtlasMan>());
    104.         foreach (var atlasMan in Verfices.RequestAll<AtlasMan>())
    105.             atlasMan.Ping();
    106.  
    107.         Debug.Log("removing the inventory manager service type, and then printing all, shouldn't get anything");
    108.         Verfices.RemoveServiceType(typeof(InventoryMan));
    109.         foreach (var inventoryMan in Verfices.RequestAll<InventoryMan>())
    110.             inventoryMan.Ping();
    111.  
    112.         Debug.Log("removing the atlas manager service type, and then trying to request many, shouldn't get any output");
    113.         Verfices.RemoveServiceType(typeof(AtlasMan));
    114.         foreach (var atlasMan in Verfices.RequestMany<AtlasMan>(10))
    115.             atlasMan.Ping();
    116.     }
    117. }
    118.  
    InventoryMan.cs
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. public class InventoryMan : MonoBehaviour, IService
    4. {
    5.     void Awake()
    6.     {
    7.         Verfices.Register(this);
    8.     }
    9.     public void Ping()
    10.     {
    11.         Debug.Log(this + ": providing service!");
    12.     }
    13. }
    14.  
    AtlasMan.cs
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. public class AtlasMan : MonoBehaviour, IService
    4. {
    5.     void Awake()
    6.     {
    7.         Verfices.Register(this);
    8.     }
    9.     public void Ping()
    10.     {
    11.         Debug.Log(this + ": providing service!");
    12.     }
    13. }
    14.  
    In your scene, create a User game object with the User.cs script attached to it, then create 3 gameObjects with AtlasMan attached, and 2 with InventoryMan attached - Then, create 2 test scenes, named "test1" and "test2" - For some feedback, let each of those have a GUITexture to know which scene you're on - Done? You're set to go.

    Now, on to the important stuff, what's up with the Verfices name?! :D - (Skip reading if you're not interested, as this will be mainly between me and Mr Fattie)

    Mr @Fattie:
    If you remember from our discussion on what to name those suckers, we had trouble balancing the name - we wanted something to tell who contributed/made the thing - like FAS (Fattie Awesome System) - but we also wanted something self explanatory - something that I don't have to go and read the docs to know why it's named like that. So I came up with the name Verfices.

    1- The "V" is vexe, and the 'f' is Fattie - because, as I said before this was inspired by your system and the one in the service locator gameprogrammingpatterns - This solves the problem of needing a name that references the makers.
    2- The word in itself, it feels like a deformed way of saying "Services" - so it's pretty easy to tell that the name is adjusted to include the creators.
    3- The methods, and the contents - All are "Services" (GetServiceList, IService, etc) - all these tell that by Verfices I mean Services. if somebody didn't like the word, he could just rename it without having to rename the method ;)
    4- Please note that I didn't not mean, for the "V" to precede the 'f' - It's just that if it was "Fervices" - It's not clear that I have contributed in this, since the "v" is actually a letter in the word "Services" - This way, it appears as if it was purely made by you.

    Thanks if you read this far. I hope you all found this useful, and I really hope that you use it in your own games, and tell me what you think about it - what was your experience, Please criticize where criticism is due.

    If you thought this was helpful, you could always (at any given point of time 't') upvote my answer in the link I posted at the beginning :D

    "Whether you think you can do it or not, you're right in both cases"
    -V
     
    Last edited: May 1, 2014
  2. -JohnMore-

    -JohnMore-

    Joined:
    Jun 16, 2013
    Posts:
    64
    Hello,

    Great class! It awesome to have something that easy to implement that covers one of the worst headache many of us have: global access and flexible extension.

    I am having a few issues I would like to discuss if you don't mind because I want to mimic the exact functionality you have implemented.

    First, when I pasted the code, it found a few errors like these:

    Code (csharp):
    1. public static void Register (IService service)
    2.         {
    3.  
    4.                 Type type = service.GetType ();
    5.  
    6.                 List<IService> list = GetServiceList (type, out list);
    This one repeats a few times, it's just that the list does not exists when you call GetServiceList. I fixed it like this:

    Code (csharp):
    1.         public static void Register (IService service)
    2.         {
    3.  
    4.                 Type type = service.GetType ();
    5.  
    6.                 List<IService> list;
    7.                 list = GetServiceList (type, out list);
    8.  
    Then I checked the GetServiceList, it was weird to pass a list as an "out" parameter and at the same time overwrite this value with the function return value. There I found that the out was needed mostly for the TryGetValue function. If I understood well, this function checks for a type in a dictionary to return the associated list so I got rid of the out parameter and changed it to:

    Code (csharp):
    1. private static List<IService> GetServiceList (Type type)
    2.         {
    3.                 if (!dic.ContainsKey (type)) {
    4.                         dic [type] = Enumerable.Empty<IService> ().ToList ();
    5.                 }
    6.  
    7.                 return dic [type];
    8.         }
    Then the first code ends up like this:

    Code (csharp):
    1.         public static void Register (IService service)
    2.         {
    3.                 Type type = service.GetType ();
    4.                 List<IService> list = GetServiceList (type);
    5.  
    I had to modify its overrides too, and one override dissapeared (GetServiceList<T>) as the others already supply this functionality.

    Also, I was having errors like:

    Code (csharp):
    1.                 if (list.IsEmpty ())
    2.                         dic [type] = list;
    3.                 else if (list.Contains (service))
    4.                         return;
    5.  
    In wich the "IsEmpty" function was failing because in my case it does not exists. Maybe I am using a diferent mono version? As I am creating the list when checking for it in the GetServiceList function, this check had no sense for me so I removed it.

    I hope I did not break anything :). This class not only allows me to access any service globally but it also integrates with my own UI panel management system so easily it seems they were made for each other.

    Thank you,
    John
     
  3. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @JohnMore:

    First, I'd like to apologize for the very late respond as I didn't get notified at all! (although I subbed)
    Second, thanks for the feedback! I'm so glad someone found this useful!

    The IsEmpty is an extension method for IList - I thought I included that somewhere in my post but it seems that I didn't.

    Your adjustment to GetSerivceList makes more sense, no need for fancy 'out' stuff!

    This was a nice system but to be honest, I found out that most of the times, we don't really need "Managers" like that - there's always workarounds for not having to depend upon any static references.

    One problem I remember with this system however, is when you Unregister a service - if you notice, when we register a service we mark it as DontDestroyOnLoad... but when we Unregister it, we just remove its reference from the dictionary, so the object is still there with DontDestroyOnLoad (Leak) - possible solutions are to remove the unregister feature all together, or Destroy the object when it's unregistered.

    Regards.
     
  4. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    I would advice that you "unlearn" that "trick" as quickly as possible.

    Do

    Code (csharp):
    1.  
    2. List<Stuff> list = GetMyStuff();
    3.  
    or

    Code (csharp):
    1.  
    2. List<Stuff> list; //no initialization
    3. list = GetMyStuff();
    4.  
     
  5. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @LightStriker:

    why is that?
     
    Last edited: Mar 17, 2014
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Well, what's the benefit of the "trick"? It's not really a "trick", but it's typically used to share a struct* rather than to share a reference... that points to the same object anyway? I don't see what it's trying to achieve.

    Actually, to be honest, I'm not sure of the benefit of this whole thing. I'm not convinced of the benefits of global access on the whole. And Unity already gives us plenty of that, for better or worse, through the scene structure in any case.

    * For instance, to allow an external function to operate directly on a bunch of local structs.
     
    Last edited: Mar 18, 2014
  7. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @angrypenguin: as I mentioned before the only benefit is that you don't have to declare a reference twice (kind of premature, I know)

    Code (csharp):
    1.  
    2. Ref meRef = GetSomething();
    3.  
    4. GetSomething()
    5. {
    6.    Ref anotherRef;
    7.    if (....) {  }
    8.    return anotherRef;
    9. }
    10.  
    ^ using 2 references.

    as apposed to:

    Code (csharp):
    1.  
    2. Ref meRef = GetSomething(out meRef);
    3.  
    4. GetSomething(out Ref sameRef)
    5. {
    6.    if (...) { }
    7.    return sameRef;
    8. }
    9.  
    It is useful, especially for structs as you said, but I guess not very much here since @JohnMore found a cleaner way.

    And yes, you're right about global access. One might argue, what's the difference between:

    Code (csharp):
    1.  
    2. var inventory = Verfices.GetService<Inventory>();
    3.  
    and...

    Code (csharp):
    1.  
    2. var inventory = Inventory.Instance;
    3.  
    ?

    well one benefit might be that now you have everything in one central place and you don't have to create singletons all over the place... it's kind of like an advanced singleton, it still does the bad things that singletons do... provide static access to 'anybody' (including parts of the code that shouldn't have access)

    I wrote this a while ago but I didn't really use it that much, cause I always managed to find a way around having to need "Managers" and global access at all :)

    I thought it would solve some problems, maybe it did, maybe it didn't. If it did and some people found it useful, then great! - if it didn't, well, then it's another failed experiment, and what's better than learning from your fails? ;)

    Cheers.
     
    Last edited: Mar 18, 2014
  8. npsf3000

    npsf3000

    Joined:
    Sep 19, 2010
    Posts:
    3,830
    And the reason you don't want to 'declare a reference twice' is?
     
  9. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Jeeeezz..... I told you guys that was old code and I know it was a premature micro-optimization....
     
  10. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Code (csharp):
    1.  
    2.     Ref meRef = GetSomething(out meRef);
    3.      
    4.     GetSomething(out Ref sameRef)
    5.     {
    6.        if (...) { }
    7.        return sameRef;
    8.     }
    9.  
    I see two ref... One outside, and one as param of a method.

    Just so you know, the keyword "out" is exactly the same as the return value of a method.

    Code (csharp):
    1.  
    2. public void Method(out variable)
    3.  
    is exactly the same as

    Code (csharp):
    1.  
    2. public variable Method()
    3.  
    It is a way for a method to return more than one output.
     
  11. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    Do you mean they're different references pointing to the same thing? - if so, no. out/ref indicates that you're using the same storage location in memory, it doesn't declare a new ref and assign it from the parameter passed in. See Skeet's (it's like using the '' in C++)

    In terms of 'functionality', yes - having a return value for a method with no params VS having an input param with no return is the same.

    Declaring is fast; creating new objects isn't. But the two snippets I posted don't really have any functional difference - it was premature to have the out there. It would be different if it were

    Code (csharp):
    1.  
    2. Ref meRef = new Ref();
    3. meRef  = GetSomething();
    4.      
    5. GetSomething()
    6. {
    7.    Ref anotherRef = new Ref();
    8.    if (....) {  }
    9.    return anotherRef;
    10. }
    11.  
    where we new Ref twice, creating garbage - now imagine that creating Ref is expensive... so that's a reason not
    to 'declare a reference twice'
     
    Last edited: Mar 19, 2014
  12. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Ah! I see your point. But the issue here is that your first line is useless and should instead be;

    Code (csharp):
    1.  
    2. Ref meRef;
    3.  
    That way, you don't create garbage. Variables don't have to be initialized on the spot.

    Coming back on the main topic, I think the code is fine. We use something fairly similar to track multiple instance instead of FindObjectsOfType - which is horribly slow -, but with a hierarchical tree instead.
     
  13. exitsimulation

    exitsimulation

    Joined:
    Feb 10, 2014
    Posts:
    82
    Hey vexe,

    first of all thanks for your cool global access system. I want to use your system in my project and have a question regarding the .Request method. Btw I changed the name to vaccess, because I found it handier. ;) Hope you don't mind.

    I have a HelperClass set up like this which I instantiate during runtime via a prefab:

    Code (csharp):
    1. using UnityEngine;
    2.     using System.Collections;
    3.    
    4.     public interface IService
    5.        
    6.     {
    7.        
    8.        
    9.        
    10.     }
    11.    
    12.     public class HelperClass : MonoBehaviour, IService {
    13.    
    14.         void Awake() {
    15.    
    16.             Vaccess.Register (this);
    17.    
    18.         }
    19.    
    20.         void OnDestroy() {
    21.    
    22.             Vaccess.Unregister (this);
    23.    
    24.         }
    25.    
    26.         public void Ping() // just a way of telling that the service is alive
    27.            
    28.         {
    29.            
    30.             Debug.Log(this + ": providing service!");
    31.            
    32.         }
    33.     }
    Now I want to access this class from another script with

    Code (csharp):
    1.  
    2. HelperClass helper = Vaccess.Request<HelperClass>();
    3.  
    Now it seems that .Request returns the instance on the prefab if HelperClass is attached to it, correct?
    Right now to get the instance in my scene I have to use:

    Code (csharp):
    1. HelperClass helper = Vaccess.RequestAt<HelperClass>(1);
    Which works fine so far, but i'm wondering if there is a nicer way to exclude prefabs for .Request? I'm working with prefabs all the time and I'm planning to use your system a lot. Now everytime I call .request I will have to evaluate if the instances I want to get via .request are based on a prefab or not. Because if they are then .request will get me only the prefab instance and I would have to use .RequestAt<>(1) which is kind of nasty I think... Is there a way around this?


    By the way, is it possible that List.IsEmpty() is deprecated somehow? I'm asking because line 57 gave me an error ("System.Collections.Generic.List does not contain a definition for IsEmpty()").
    I then changed the line to

    Code (csharp):
    1. if (list.Count < 1)
    . Can you approve this change? So far I'm not running into any problems.
    Or is my change even related to the problem above?

    Thanks in advance!
     
  14. Smooth-P

    Smooth-P

    Joined:
    Sep 15, 2012
    Posts:
    214
    You aren't saving an instance of a reference type by doing the ref thing, all you're saving is a reference (ie: a "pointer") on the stack, which is insignificant and does not create garbage. In fact, by both returning *and* having an out reference, you're not really saving anything, though this is also insignificant. :)

    What is the point of the IService interface? It reduces flexibility and doesn't seem to add anything to the design.

    And why pass types, it would be simpler and cleaner just to use the generic parameter, like:

    Code (csharp):
    1.  
    2. public static void Register<T>(T service) {
    3.      var type = typeof(T);
    4.      // do whatever
    5. }
    6.  
    Edit: Actually, you could avoid the casting / messy Type usage and remove the main dictionary by making Verfices a generic type. Then you could also define a generic key type.

    Edit 2: Rereading the thread, I guess you already realize you aren't saving anything with the ref, and, ugg, this was a semi-necro? :p
     
    Last edited: Apr 30, 2014
  15. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    @monogon: First of all you really should ask your self, do I need this Helper thing to be globally accessed? do I need to bother my head with this this? what does your helper do? - if it's a utility class, it's better off being a static classes - accessed like "Utils.GiveMeSome();"

    I will have to test what you're saying first - But if I understood you correctly, you are saying that if you have the helper script attached to a prefab that you haven't instantiated any instances of it yet, you still get the helper - This shouldn't happen, because a prefab is not a real object, it's just a prototype/blueprint for an object. If you have a prefab with "Test" attached in your project hierarchy and an empty scene, then "Object.FindObjectsOfType<Test>().Lenght" should be zero - because in your scene there are no Test instances loaded yet.

    A MonoBehaviour's Awake shouldn't get executed the moment you attach it to a prefab (even if the MB is mocked with ExecuteInEditMode) because you're still not creating an actual instance till you instantiate the prefab... so I wonder...

    Here's something that might help you anyway:

    Code (csharp):
    1.  
    2. public static bool IsPrefab(GameObject gameObject)
    3. {
    4.     return PrefabUtility.GetPrefabType(gameObject) == PrefabType.Prefab;
    5. }
    6.  
    So maybe now you could create a GetFirstNonPrefabService or something:

    Code (csharp):
    1.  
    2. return services.FirstOrDefault(s => s is MonoBehaviour  !IsPrefab((s as MonoBehaviour).gameObject));
    3.  
    Sorry about the IsEmpty - I mentioned in previous comments ^ (not in my main post) that it's an extension method that does: "return list.Count == 0;"

    @Smooth P:

    It's currently just a marker interface, only usage for it now is to identify services "if (something is IService)" I left it like that for implementers to add to it as and when they need (read point 2 and 3 in blue n the main post)

    Agreed. I may re-write this whole thing at some point - although I'm no big fan of global access anymore, but it might be helpful to some people.

    I'll say it again, using ref/out in the context I used it in was premature optimization.
     
    Last edited: Apr 30, 2014
  16. exitsimulation

    exitsimulation

    Joined:
    Feb 10, 2014
    Posts:
    82
    Yes, exactly. I have an instances of my helper class component on a prefab. Now Verfices.Request<HelperClass>() returns te prefab.

    Ok, I see. Thanks! :)

    To be honest I don't understand how to implement this. So you are suggesting that I create a method within the Verfices class that checks if the service is on a prefab and ignores it then? Sounds perfect, but I really do not know where to start right now.

    Yes, I think so. I created a HelperClass from which I am inheriting further (small) classes that are components of different unique menu objects that I don't want to search with GameObject.Find or GetComponentByType because I think with Vertices I am much faster.

    Right now everything is working ok, though like I said it bothers me that I have to access a certain menu component (e.g., someMenuComponent : HelperClass) with Verfices.RequestAt<someMenuComponent>(1) if the menu component is created from a prefab.

    If this little problem is solved then your verfices system is the perfect solution for me right now.

    Thanks for your help!
     
  17. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    I gave a sample code earlier right after I mentioned GetFirstNonPrefabService.

    Sounds to me you should really rethink your whole design - doesn't seem like a good idea to me to mix global access with inheritance. Why can't you just inject your dependencies? If class X needs Y, why not have a Y reference in X?

    If that's all that's bothering you, then for the meantime while I investigate this further, just add a method that returns the second service:

    Code (csharp):
    1.  
    2. public static T RequestSecond<T>() where T : class, IService
    3. {
    4.    return RequestAt<T>(1);
    5. }
    6.  
     
  18. exitsimulation

    exitsimulation

    Joined:
    Feb 10, 2014
    Posts:
    82
    The problem is, it's very unstable right now because if it happens that I somehow duplicate a prefab, then I would have to access the service with RequestAt<>(2) ... or (3) if I duplicate it another time or (2) again if I delete a prefab and so on. It's prone to failure unfortunately.

    So I'd really like to implement your code that you provided:
    Code (csharp):
    1. return services.FirstOrDefault(s => s is MonoBehaviour  !IsPrefab((s as MonoBehaviour).gameObject));
    But right now I am kind of clueless. Sorry if I am lacking knowledge here. I'm still relatively new to programming, so I am missing these concepts I think.
    "services" does not exist in this domain if I try to insert this line into a method called "GetFirstNonPrefabService" (for example) ... And FirstOrDefault cannot be resolved...
    If you could provide me with this method or explain it to me further, this would be super awesome.

    Ok, if you are interested, I will explain to you what exactly I am planning to do:
    When a gameobject is selected during runtime there are dynamic option menus that are created based on the gameobjects type and properties. I pre-built those menus for every gameobject type and stored them into prefabs. Now based on the type of the selected gameobject a menu is created which the user can interact with.
    Now your verfices come into play. I want to easily identify the sub-elements of each menu type to coordinate the interaction from the user. All menus are created from a Manager class, but I thought it would be really slow (performance-wise) to store a reference for each sub-element when it is built because each menu type consists of 10 - 30 subelements. So if I wanted to store references to each sub-element in the manager class I would have to call GetComponentByType or similar functions relatively often. I thought it would be faster and easier to access those sub-elements globally... Probably it's not a good practice, I don't know. Do you think it would be better to store references in the menu manager class with GetComponentByType or similar?
     
  19. vexe

    vexe

    Joined:
    May 18, 2013
    Posts:
    644
    I've re-written the whole thing, here. Thanks to Smooth P for his advice - it's much simpler, shorter and kind of smoother :p

    @monogon: I haven't been able to replicate the issue you're facing. What I did:
    1- I had a service "InventoryMan" on a prefab "GO1" which I don't have any instances of, I print the number of inventory services, it's 0.
    2- I instantiate "GO1", now I print, the number is 1.
    3- I manually delegate "GO1" from the scene, back to zero (that's cause in OnDestroy, InventoryMan unregisters himself)

    From your description of what you're trying to do, it sounds like you could have a Menu class, with different menus deriving from it - your gameObjects will have the right menu references attached to them. Not sure what you mean with sub-elements, but it sounds to me it's something that could live as an aggregate (list/array) inside Menu, so maybe you could create a SubElement class, and let Menu have a List<SubElement>?

    That is not true - storing a reference to a component doesn't have to be only via gameObject.GetComponent<T> - you could just store the references while creating the sub-elements, no need for GetComponent. I'm still not very certain of what to advise you, it's still not clear to me. But definitely it doesn't sound like a situation where global access could help. It sounds more a design thing.

    It's located in System.Linq;
     
    Last edited: May 1, 2014
  20. exitsimulation

    exitsimulation

    Joined:
    Feb 10, 2014
    Posts:
    82
    I rethought my menu design and you are right, I was totally on the wrong track with the global access for the menu elements ...
    Thanks for your advice!
    I now have a menu class for each menu type derived from a base menu class. In this menu class all references to the sub-menus are stored and easily accessable.
    Now I'm using Verfices only for my manager classes as a replacement for Singletons!

    Perhaps I messed up the inheritances from the base HelperClass. I too cannot replicate this problem anymore... Though I was sure it worked this way, lol. I even wrote small helper methods to bypass this issue ... :confused:

    Anyway, thanks! :)
     
    Last edited: May 1, 2014