Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

How often do you use delegates and interfaces in your games?

Discussion in 'Scripting' started by ptgodz, Dec 8, 2016.

  1. Dameon_

    Dameon_

    Joined:
    Apr 11, 2014
    Posts:
    542
    Also, as far as the OnEventHappened pattern, I use that exclusively for event subscribers, and find that tends to work pretty well for me...I definitely remember some confusion a while back stemming from not being exclusive about that. Subscribers have the "On" prefix, handlers have the "Handler" suffix, and I find that works for me.
     
  2. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    A simple test project demonstrated that removing then re-adding a subscriber to a delegate invocation list does not generate another invocation, as we'd probably all expect from the discussion following my earlier claim. The real project where I thought I saw this was already too complicated to easily unwind to re-test, but knowing the invocation list is immutable led me to add a different, simple test: on-demand (via keystroke) I simply called the delegate list with some Debug.Logs and a return so the sub/unsub functionality didn't happen. It turns out somewhere in the program I missed an unsubscribe, so the multiple calls were really queued in the delegate list all along. I would track it down if I had any intention of finishing the project, but I don't.

    I just didn't want to leave the earlier assertion out there unresolved.

    However... that particular case was easy to hack to work correctly since the subscriber was added or removed in response to another property being set or cleared. I just added a blanket unsubscribe first (inside a try/catch), then did the subscribe if the property was being set. I wouldn't ship code that way but it allowed testing of another aspect we've been discussing -- making a temporary reference to the delegate list to invoke the subscribers.

    The temporary assignment doesn't seem to be necessary. My usage is exactly the case where the reference to the delegate invocation list is being changed to a new list while the old list is still being processed. The old list gets processed without interruption, and the new list works properly thereafter.

    I assume a temporary reference is already being done somewhere internally to maintain the integrity of the original list until iteration is completed. Of course, this is in Mono, I didn't test any of this in MS .NET, so that temporary reference may still be a good idea as a rule of thumb.
     
  3. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    I think there's a bit of misunderstanding about how immutability actually comes into play with delegates. That temporary assignment does not actually create a copy of the delegate. Delegate is a class, so saying var tempDelegate = MyDelegate; is just like referencing any other class, it creates a pointer to that class, not a copy.

    Here's an example of a regular class that's immutable:

    Code (csharp):
    1.  
    2. public class ImmutableClass
    3. {
    4.       public float Speed {get; private set;}
    5.       public float Weight {get; private set;}
    6.  
    7.       public ImmutableClass(float speed, float weight)
    8.       {
    9.                Speed = speed;
    10.                Weight = weight;
    11.       }
    12. }
    13.  
    The class above is immutable. Speed and Weight never change and can only be read from outside the class. Here's another example where you can create a factory:

    Code (csharp):
    1.  
    2. public class ImmutableClass
    3. {
    4.       public float Speed {get; private set;}
    5.       public float Weight {get; private set;}
    6.  
    7.       private ImmutableClass()
    8.       {
    9.       }
    10.  
    11.       public static ImmutableClass CreateFrom(float speed, float weight)
    12.       {
    13.             return new ImmutableClass { Speed = speed, Weight = weight };
    14.       }
    15. }
    16.  
    In both cases creating an instance and assigning it to a second variable only creates a pointer to that variable. Delegates behave in the same way.
     
  4. MV10

    MV10

    Joined:
    Nov 6, 2015
    Posts:
    1,889
    I think everyone understands that. My point was that if you replace the invocation list while that list is being iterated (using the single "original" reference to the list), it doesn't stop, change, or otherwise interrupt the iteration over the original list -- there isn't any need to add the extra step of making a copy before you start invocation.

    Drop this on Main Camera in a new scene. Left-click directly fires the invocation list, right-click fires on a copy of the pointer, pressing R resets the whole thing. Three methods are added to the list, and they log 1,2,3.

    Also, 2 adds a new method to the list, and 3 removes itself then re-adds itself, so each time the invocation list is processed it actually gets replaced three times. If you click again you'll see those new additions to the list (and the same changes will be applied again).

    None of those changes interrupt the ongoing processing of the original list in either approach (using the original reference or a copy of the reference).

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class Test : MonoBehaviour {
    4.  
    5.     delegate void handler();
    6.     handler handlerList;
    7.  
    8.     void Start () {
    9.         Debug.Log("left click - direct invocation, right click - copyref invocation, R - reset list");
    10.         Reset();
    11.     }
    12.  
    13.     void Update () {
    14.         if(Input.GetMouseButtonDown(0))
    15.         {
    16.             Debug.Log("direct start");
    17.             handlerList();
    18.             Debug.Log("direct end");
    19.         }
    20.  
    21.         if(Input.GetMouseButtonDown(1))
    22.         {
    23.             Debug.Log("copyref start");
    24.             var copyref = handlerList;
    25.             copyref();
    26.             Debug.Log("copyref end");
    27.         }
    28.  
    29.         if(Input.GetKeyDown(KeyCode.R))
    30.         {
    31.             Reset();
    32.         }
    33.     }
    34.  
    35.     void Reset()
    36.     {
    37.         Debug.Log("list reset");
    38.         handlerList = null;
    39.         handlerList += h1;
    40.         handlerList += h2;
    41.         handlerList += h3;
    42.     }
    43.  
    44.     void h1()
    45.     {
    46.         Debug.Log("subscriber 1");
    47.     }
    48.  
    49.     void h2()
    50.     {
    51.         Debug.Log("subscriber 2 -- adding subscriber new");
    52.         handlerList += h_new;
    53.     }
    54.  
    55.     void h3()
    56.     {
    57.         Debug.Log("subscriber 3 - removing and re-adding self");
    58.         handlerList -= h3;
    59.         handlerList += h3;
    60.     }
    61.  
    62.     void h_new()
    63.     {
    64.         Debug.Log("subscriber new");
    65.     }
    66. }

    1.jpg
     
  5. cmedinasoriano

    cmedinasoriano

    Joined:
    Dec 29, 2016
    Posts:
    1
    Ok, I know I am a little late for the party here but I have a question about object composition here from a noob. Do you think it is a good idea to code game behaviours by component?

    Ok to better explain myself... instead of:

    Code (CSharp):
    1. public class Enemy : MonoBehavior, IDamageable, IPlatformAIController ... { ... }
    2. public class Player: MonoBehavior, IDamageable, IPlatformController, ... { ... }
    What about:

    Code (CSharp):
    1. public class Enemy : MonoBehavior { ... }
    2. public class Player : MonoBehavior { ... }
    And add a Component to Enemy and Player named Damageable.
    But then add the Components { PlatformController, ItemPicker, WeaponMaster, Inventory } to Player and add { PlatformAIController, WeaponMaster } to Enemy,

    Any GameObject with these Components/Scripts will now be able to have/perform these behaviors and I preset some of their properties with the inspector (health, Collider/Trigger references, etc.). But decoupling everything by using Pub/Sub or Observer pattern. Would this be advisable or not?

    I come from C++ with inheritance/multiple-inheritance mindset. But since I started using Components I really love the modular approach and the problems it solves but I am really struggling to switch my mindset to it yet and how the communication/interaction is managed between these components.
     
  6. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    It really just depends on intent. If you need to interact with components based on them having specific contracts, then using the interfaces on the components is the way to go. But, if what you want is to have various components that can have different permutations that you define by attaching behaviors to them, then the latter approach is better. Personally, I like the latter approach because it lets you do very common tasks in the game objects and customize them through behaviors (components), but there's certainly nothing wrong with mixing and matching those approaches either. There's no real silver bullet, it's just what fits your game/architecture/workflow/needs the best.