Search Unity

How to properly use delegates

Discussion in 'Scripting' started by IpRay, Nov 16, 2019.

  1. IpRay

    IpRay

    Joined:
    Apr 28, 2018
    Posts:
    7
    H everyone,
    So first, what I'm trying to achieve:
    In my game, a grand-strategy inspired by Civ and CK2, time passes as days go buy (appr. 1 sec = 1 day) and each day, certain things can happen, for example people age and characters are doing things.
    So in a gameObject called GameManager I handle the general time passing (relevant snippet from the whole script):

    Code (CSharp):
    1.     public int daysPassed;
    2.     int currentDay;
    3.     int currentMonth;
    4.     int currentYear;
    5.     int currentYearDay; // this goes from 1 to 360
    6.     float gameSpeed; // This shows the number of secs between days, so the higher, the slower!
    7.  
    8.     public delegate void myDelegate();
    9.     public Dictionary<int, myDelegate> yearDayEvents = new Dictionary<int, myDelegate> (); // for yearly recurring events
    10.     public Dictionary<int, myDelegate> totalDaysEvents = new Dictionary<int, myDelegate> (); // for only happening once events
    11.  
    12.     IEnumerator TimePassing ()
    13.     {
    14.         while(true)
    15.         {
    16.             daysPassed++;
    17.             currentDay++;
    18.             currentYearDay++;
    19.             if (currentDay > 30)
    20.             {
    21.                 currentMonth++;
    22.                 if (currentMonth > 12)
    23.                 {
    24.                     currentYear++;
    25.                     currentMonth = 1;
    26.                     currentYearDay = 1;
    27.                 }
    28.                 currentDay = 1;
    29.             }
    30.             yearDayEvents[currentYearDay]();  // here fire events that trigger once per year
    31.             if (totalDaysEvents.ContainsKey(daysPassed))
    32.             {
    33.                 totalDaysEvents[daysPassed]();  // all added events for that day fire here
    34.                 totalDaysEvents.Remove(daysPassed);
    35.             }
    36.             timeLabel.text = currentDay.ToString("00") + "." + currentMonth.ToString("00") + "." + currentYear.ToString("0000");
    37.             yield return new WaitForSeconds(gameSpeed);
    38.         }
    39.     }
    40.  
    41.     public void StartTime()
    42.     {
    43.         gameSpeed = 1f;
    44.         daysPassed = 0;
    45.         currentYear = startYear;
    46.         currentMonth = 1;
    47.         currentDay = 1;
    48.         currentYearDay = 1;
    49.  
    50.         for (int i = 1; i <= 360; i++)
    51.         {
    52.             if(!yearDayEvents.ContainsKey(i))
    53.             {
    54.                 yearDayEvents[i] = new myDelegate(FillerFunction);
    55.             }
    56.         }
    57.  
    58.         StartCoroutine(TimePassing());
    59.     }
    the relevant object here is the totalDaysEvents which is a dictionary having as a key an integer that is the number of the day since the game started and as value a delegate which SHOULD contain all events that will trigger on that day.
    Here is the function from the character.cs script, in which I try to add functions to the delegates:

    Code (CSharp):
    1.     public void QueueDoSomething ()
    2.     {
    3.         int daysToNextDoing = 5;  // will be replaced by a function
    4.         int nextDay = gameManager.daysPassed + daysToNextDoing;
    5.         if (gameManager.totalDaysEvents.ContainsKey(nextDay))
    6.         {
    7.             var myDelegate = gameManager.totalDaysEvents[nextDay];
    8.             myDelegate += this.DoSomething;
    9.         }
    10.         else
    11.         {
    12.             var myDelegate = new GameManager.myDelegate(this.DoSomething);
    13.             gameManager.totalDaysEvents.Add(nextDay, myDelegate);
    14.         }
    15.     }
    16.  
    17.     public void DoSomething ()
    18.     {
    19.         if (verbose)
    20.         {
    21.             Debug.Log("I'm thinking about doing something :-)");
    22.         }
    23.  
    24.         QueueDoSomething();
    25.     }
    So, now to the thing that won't work: My delegates seem always only to hold one function and never multiple. So I guess I must be doing something wrong when initializing the delegate?

    Maybe I do this completely wrong and there is a much easier way to do this? (I'm new to Unity so feel free to give me advice :))

    Thanks in advance, I'm really lost here,
    Cheers, Ray
     
  2. IpRay

    IpRay

    Joined:
    Apr 28, 2018
    Posts:
    7
    Well, I was stupid. Delegates aren't mutable, so I had to add the line
    Code (CSharp):
    1. gameManager.totalDaysEvents[nextDay] = myDelegate;
    Hope this helps if anyone else stumbles upon this problem at some point.
     
  3. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    Now I'm imagining how they could be mutable. Maybe things like: f1.addStatement("if(n<0) n=0;", 0);
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    Well technically a delegate is a reference to method/s, not a method itself (delegates are technically multicast, so it's a collection of method references).

    So to mutate a delegate would be to mutate what it is referencing, rather than mutate the methods that are referenced.

    It'd be like mutating an array... mutating the array doesn't mutate the element. It mutates what the array has in it.
     
  5. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    I'm pretty sure delegates don't have to be multicast. They will simply create a multicast delegate if you use the + operator on two regular delegates.

    And yes, delegates are immutable, so when you "add" a regular delegate to an existing multicast delegate, it creates a new object which is the multicast delegate that contains all the old calls plus the new one. In the OP's case, if you don't reassign the new object back into the array, the changes will just get thrown away.

    As for mutable methods .. you can use Expression<Action> and related types to build code programmatically. The Expression can then be compiled into a delegate. If you have the original Expression, then you can "mutate" the code and then recompile. Of course, mutate is probably the wrong word to use here since Expression is also immutable. Adding expressions to an expression tree creates a new tree.
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    All concrete delegate types inherit from System.MulticastDelegate.

    You can investigate the inheritance chain of any delegate you create yourself, or a delegate that is in the framework (like System.Action) and see that it'll directly inherit from MultcastDelegate, followed by Delegate, followed by Object.

    The language has no way of inheriting directly from Delegate, and I'm not sure exactly why they broke its logic into 2 classes like that either. I read somewhere that Microsoft originally had some idea to use it for, but then never did it.
     
    Last edited: Nov 20, 2019
    eisenpony likes this.