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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Events vs Method calling

Discussion in 'Scripting' started by internethip, Apr 7, 2016.

  1. internethip

    internethip

    Joined:
    Oct 21, 2013
    Posts:
    20
    Hi,
    I recently discovered events in Unity and i find them very natural to use.

    I developed an FSM where depending on what state the FSM is in, It subscribes or unsubscribes to certain events of other classes.

    The way i accomplished my goals before was that i had methods that i ran depending on which state the FSM is in. Which is what you mostly see in other peoples code.

    Now i am feeling a bit apprehensive to continue with my event idea because i have not seen this way of doing things among others people's code. I am afraid that there is something i am missing. So with this post i am hoping to get some input on this idea and also get some clarification on a few things.

    I imagine an event being like a callback in other languages, is this analogy close?

    If i subscribe a method to an event. And that event is being handled in a different class somewhere else, where there is a method running each frame checking for something to happen, and when it does it sends the event.

    Am i wrong in thinking that the above setup should be more performant than if i have the same "checking"-method store the value somewhere and then have another method dependent on that value check the value each frame instead of subscribing to an event?

    event example:
    Code (CSharp):
    1. Events.cs:
    2. public class Events : MonoBehaviour {
    3.  
    4.     public delegate void Event();
    5.     public event Event sendevent;
    6.  
    7.     void Update () {
    8.         if (Input.GetKeyDown("space")) {
    9.             sendevent();
    10.         }
    11.     }
    12. }
    13.  
    14. Jumper.cs:
    15. public class Jumper : MonoBehaviour {
    16.     private Events events;
    17.  
    18.     void Awake() {
    19.         events = GetComponent<Events>();
    20.     }
    21.  
    22.     void OnEnable() {
    23.         events.sendevent += Jump;
    24.     }
    25.  
    26.     void Jump() {
    27.         rigidbody.AddForce(transform.up * 100f, ForceMode.Impulse);
    28.     }
    29. }
    checking method example:
    Code (CSharp):
    1. GetInput.cs:
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class GetInput : MonoBehaviour {
    6.  
    7.     public bool spacePressed;
    8.  
    9.     void Update () {
    10.         if (Input.GetKey("space")) {
    11.             spacePressed = true;
    12.         } else {
    13.             spacePressed = false;
    14.         }
    15.     }
    16. }
    17.  
    18. Jumper.cs:
    19. using UnityEngine;
    20. using System.Collections;
    21.  
    22. public class Jumper : MonoBehaviour {
    23.     private GetInput getInput;
    24.  
    25.     void Awake() {
    26.         getInput = GetComponent<GetInput>();
    27.     }
    28.  
    29.     void Update() {
    30.         if (getInput.spacePressed) {
    31.             Jump();
    32.         }
    33.     }
    34.  
    35.     void Jump() {
    36.         rigidbody.AddForce(transform.up * 100f, ForceMode.Impulse);
    37.     }
    38. }
    So in summary:
    - Is using events, kind of like callbacks, performant enough to be done largely through a project?
    - Do you see any issues with an architecture where most of the method calls within classes will be done through events?
    - Has anyone implemented a system like this before, how did it go?
    - Is there a similar way of doing things to this that is better?

    PS.
    If you feel the post could have a better format let me know and ill update it.
     
  2. CrymX

    CrymX

    Joined:
    Feb 16, 2015
    Posts:
    179
    It's better to call one event in one frame and execute your script than check if you can execute your script at every frames.

    In a large project that can be more complex to deal with a lot of events.
    If you are going on the event way i suggest you tu use interfaces.
     
  3. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Event driven programming and functional programming are just methodologies; they are just ways of organizing your code and are equivalent regarding of performance. Though, programming using events gives a bit more abstraction. But don't fool yourself, in any event driven implementation there is a polling loop that runs and will check if an event has occurs and eventually call a call back function.

    I don't fully grasp what an event-based FSM would be. FSM is just a set of states interconnected with condition transitions. How does events fit in that model? (rhetorical question)

    If you are interested in an alternative to FSM, I highly suggest to have a look at Behaviour Tree, is a technique far more flexible than FSM or HFSM.

    I'm the author of Panda BT, it's a script based Behaviour Tree engine. More information about this tool here:

    http://www.pandabehaviour.com/
     
    Last edited: Apr 7, 2016
    Plains likes this.
  4. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    If you only have a small project, then this wouldn't even be noticeable.

    It can be useful but it can also cause some unexpected behaviour as well. The following may not be complete, but it illustrates some general thoughts (which are rather subjective) and some troubles you may run into.

    First of all, you won't need a delegate type for every event. You can conveniently use System.Action or any generic version as well as System.Func. Latter also allows a return type.

    Some personal thoughts in general:
    a) If conditions are lightweight, it would probably be not noticeable unless a decent amount of subscribers exist.

    b) If your conditions are heavy and would need to be done more than once in different places, it's also a reason to manage it in one place and fire an event (or something that works similar, e.g. keep track of instances which have been registered via interfaces, it's just another way to achieve a similar thing).

    c)If you have multiple places in which you'd want to check the same conditions and adjustments would have to be done everywhere to be consistent, it's worth to think about having the check in one place to ensure that conditions do not get messed up.

    d) helps to decouple classes, however this can also be solved in other ways


    But you also have to think a bit further:
    1) you should always check whether the event has subscribers before raising it

    2) you have to unsubscribe properly, if you don't do that, you can run into some problems:
    2.1) you'll be having memory leaks for normal classes (System.Object and derived types) when you think you've lost all references and don't keep in mind that you've subscribed somewhere
    2.2) subclasses of UnityEngine.Object (i.e. your behaviours and all other components) will be destroyed, or engine-wise, properly unregistered (components would be detached from gameobjects, the messages like Update etc won't be called anymore so you may think it's gone completely) but the object is still accessible and you'll be having memory leaks just like in 2.1
    2.3) one step worse: consider 2.2) but with the addition that the subscribing method in a MonoBehaviour also accesses the gameobject it was attached to, or the transform or anything else that involves various internal dependancies: You'd get exceptions and may not immediately figure out where they come from, afaik the stack trace won't help you much in this case.

    3) be careful with anonymous subscribers, you should keep a reference in order to be able to unsubscribe, otherwise one of the above cases applies

    4) execution order related
    Scripts are usually run depending on the execution order. If you have one event that fires in the beginning of a frame, all subscribers will execute their code as well.
    You may not always want that, as states could change several times in between which could cause unexpected behaviour, especially if you have set up a custom execution order.

    5) maybe some more
     
    Mehrdad995 and Plains like this.
  5. internethip

    internethip

    Joined:
    Oct 21, 2013
    Posts:
    20
    Ah nice, i had no idea this was a developed methodology. Thank you so much for showing me that.

    And yes of course there is a polling loop, i was just wondering if running functions to check a condition somewhere would be equivalent/less/more performant in the case where you do it event-driven.

    The way i do my FSM's event-driven is that when i enter an event i subscribe to x amount of events and when i leave it i unsubscribe from them. So i there will be some events that are sent but i will not be subscribing to them when they are. Do you think this is a problem?

    I have not looked into Behaviour Tree's but i will definitely check it out. But for the sake of this thread i'd like to keep it about events in unity. Thanks.
     
  6. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    Generally, events are a way to reverse the direction of communication between objects. Instead of A knowing about B and calling B.Method at a certain moment, B knows about A and subscribes to have it's Method called at that moment. From this perspective, it is clear that an event uses a callback, so that's a safe analogy to use.

    Events are a great way to decouple an object by either inverting a dependency, or pushing it up to a higher component. Like I said earlier, A will no longer need to know about B if A can provide an event for B to subscribe to. This means either B knows about A and subscribes on its own, or a new "manager" C knows about A and B and does the subscription on B;s behalf, allowing both A and B to remain blissfully ignorant of each other.

    I think some of the weaknesses of events have already been mentioned by others, but let me reiterate some and add a few more:

    1. Events change the order in which your code is read. Think of a big paper you've written, one large enough to warrant an "appendix". The appendix was probably moved out of the main flow of the document for a purpose; maybe it was tangential to the topic at hand, or it was useful to be referenced in more than one spot in your paper. However, the appendix comes with a cost: when you want to read the related information, you need to flip to the back or refer to another source, which is a bit more clumsy for the reader.
    2. When you call a method sequentially (without events) the compiler is able to do static checking to make sure your method call will succeed. This gives you some guarantees about your code and allows for certain optimizations. Events use delegates to jump into methods and delegates can be either null, singular, or compound (aka multicast). A null delegate cannot be invoked. You would receive a NullReferenceException, so you'll need to wrap your delegate calls with a null check. A singular delegate is optimized by the runtime to be super fast, though it still can't compete with the optimizations the compiler would make for a direct method call. A compound delegate is created for you when you add two delegates together, as in when more than one callback is added to an event. Compound delegates aren't as safe to optimize, so they are just straight up slower. However, the added overhead for these kinds of things are usually greatly exaggerated and overestimated. I suspect using multicast delegates are not going to significantly slow down your code.
    3. The order of multicast delegates isn't dictated by the C# specification, so your code needs to be okay with an arbitrary call order. This typically isn't a problem because: a) the order is pretty stable despite not being explicitly specified and b) as good coders, we avoid temporal coupling such as requiring methods to called in a certain order, right?
    4. Once you start calling an event, there is no built-in way to "uncall", or stop processing the delegates subscribed to that event. This has some weird implications, particularly if you try to add or remove delegates from an event while it is firing. The rule of thumb is that you need to write your callback methods to be okay with getting invoked even after they have unregistered themselves. This might add some significant complexity to an otherwise simple method.
    5. When you add a delegate to an event, you create a reference to that delegate from the owner of the event. This is essentially tying the lifetime of the delegate to the lifetime of the event owner. In other words, if you register object A to an event on object B and then exit object A's scope, the garbage collector won't be able to clean up object A's memory because object B still holds a reference to it. These references are pretty easy to forget about and are a form of "memory leak", though that's not quite the right term.
    I would say events make your code a little more complicated to read because of their inversion effect on source code and additional complexity around managing their pitfalls. However, they do allow you to do some interesting things from a design/architecture perspective. Your opinion on whether an event based system is good or not will rely heavily on your skill and experience dealing with events.
     
  7. internethip

    internethip

    Joined:
    Oct 21, 2013
    Posts:
    20
    Could you give an example of that?

    Yes, i have had these thoughts as well and i use static events and such as well.
    For the sake of keeping the code short i did not put these parts in, i am aware though. Do you think i should add it in the code if other people will refer to it?

    This i handle with states in my FSM's
     
  8. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    If I understand this correctly, each state of your finite state machine is associated to a set of events. Only the current state responds/receives its events, while events assigned to the other states are simply ignored. If that is what you mean, I don't see any problem with this approach. How do you manage the condition transitions, are they also connected to events?

    Edit:
    @plindgren btw, thanks to have a look to Panda BT. If Behaviour Tree is new to you, get prepared to open a door to a world of possibilities and simplicity, where FSM is considered as torture machine.
     
    Last edited: Apr 7, 2016
    Plains and internethip like this.
  9. internethip

    internethip

    Joined:
    Oct 21, 2013
    Posts:
    20
    Hmm, could you give an example of exiting an object's scope?
    And if i understood correctly, wouldnt this be fine if i make sure to always unsubscribe from events?
     
  10. internethip

    internethip

    Joined:
    Oct 21, 2013
    Posts:
    20
    Yes.
     
  11. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Code (CSharp):
    1.  
    2. // takes no arguments
    3. public event System.Action Action1;
    4. // int argument
    5. public event System.Action<int> Action2;
    6. // int and string argument
    7. public event System.Action<int, string> Action3;
    8. // and so on
    9.  
    10. // last type parameter is always the return type
    11. // no argument, return type int
    12. public event System.Func<int> Func1;
    13. // int argument, returns int
    14. public event System.Func<int, int> Func2;
    15. // ...
    16. public event System.Func<int, string, object> Func3;
    You'll get the idea.
    Note that using the return type only works reliably for one subscriber. If you have more than one subscriber you'll. If you need all you need to loop through the subscribers manually.

    That's up to you. As long as you know how it works it may be okay as it is right now.
     
    internethip likes this.
  12. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    OffTopic:

    Actually true. I wonder if there was a term that could be used instead?
     
  13. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    Code (csharp):
    1. var a = new A();
    2. {
    3.   var b = new B();
    4.   a.StateChanged += b.Method();
    5. }
    The code block that b was scoped in has ended (it might be an if statement, a switch statement, or even a method), so we no longer have a reference to b. However, a still knows about b, so the garbage collector can't clean up the object, even though we have no way to reference it anymore. The garbage collector won't be able to clean up b until a is also cleaned up, this is what I mean by
    And yes, it would be fine if you always unsubscribe. My point was that it is easy to forget this step because, like the subscribing, it typically happens somewhere disconnected from the rest of your code.

    Sorry I didn't mean to sound correcting. It's a term I've heard others use for this problem before too, so I think it's suitable. Not really sure what else I would use to describe it either, just that, supposedly, there are no memory leaks in managed code.
     
    Last edited: Apr 7, 2016
    Suddoha and internethip like this.
  14. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    If one of the delegates threw an exception, that'd stop the execution of any other delegates that haven't yet been called. I'm not sure that code base which relied on throwing exceptions to handle flow control is a wise decision, however.
     
    eisenpony likes this.
  15. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    A good point and another weakness of events. An unhandled exception in an unrelated subscriber could prevent another subscriber from executing at all. Because of the arbitrary order, this could introduce be a tricky bug to track down.
     
  16. Dave-Carlile

    Dave-Carlile

    Joined:
    Sep 16, 2012
    Posts:
    967
    While you can't use C#'s built-in event syntax with them (i.e. you'd have to roll your own delegate list and such), using a WeakReference in such a system seems like it would be a viable way of dealing with this issue.
     
  17. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    I don't think so. A WeakReference is a way to tell the GC "I'm probably done with this, so you can clean it up if necessary, but just in case I change my mind, please keep track of it while it's still alive." If the garbage collector decides to run before I ask for the object back, I won't be able to restore it.

    It would be reasonable to do if the code knew I was done with the delegate and had forgotten to clean up, but if it knew that, it would just remove the reference and be done with it. Since it doesn't know if I'm done or not, it's not really acceptable to tell the garbage collector it can clean this object up if it needs to clear some memory.
     
    Last edited: Apr 8, 2016
  18. Dave-Carlile

    Dave-Carlile

    Joined:
    Sep 16, 2012
    Posts:
    967
    Admittedly I haven't put much thought into it yet, but my understanding of the weak reference is that if it is the only reference to the object the garbage collector is allowed to discard it. The phrase in italics is the key piece here. If something still has a normal reference to the object it won't be collected - only if the weak reference is the last thing.

    So if I have an event manager class that handles all of my events, and the event manager handles all of the registering of listeners and senders, and maintains the list of all of these delegates itself through weak references, as soon as the last listener on an event gets collected there is no longer a strong reference to the event delegate.

    Once that happens the garbage collector can collect it. Next time that event is posted the the event manager can detect the invalid reference via the weak reference class and remove that listener.

    This doesn't seem to be a typical use of the weak reference (most things I've read about it deal with caching of large objects), and I've not tried it in practice, but it's on my list to try for my event manager (which already does all of the other pieces - just requires listeners to unregister themselves when finalized). But, it wouldn't surprise me at all if there are huge holes in my assessment.

    This wouldn't be an issue here. Once the listener done listening it will never need to come back. If we get a new instance of the same listener class it will re-register itself as a listener for that event. Once the weak reference is the only remaining reference there would never be a need to ask for the object back in this scenario.
     
    Plains and eisenpony like this.
  19. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    It sounds like Dave is suggesting the use of a Weak Event to protect against event publishers from keeping their subscribers alive. Weak Events are useful, although a bit more verbose on the publisher side. MS Info page on weak events: https://msdn.microsoft.com/en-us/library/aa970850(v=vs.85).aspx

    I think that this is misleading. It would indicate that having two weakrefs to the same object would prevent that object from being collected. I think instead it is better to say that the GC ignores weak refs when it is deciding whether or not to collect the object.
     
    eisenpony likes this.
  20. Dave-Carlile

    Dave-Carlile

    Joined:
    Sep 16, 2012
    Posts:
    967
    Yes, this pattern. Not sure I'd want to use their implementation in a Unity app (System.Windows namespace - does Mono even support that?), but that pattern definitely.

    Yes, yours is better phrased.
     
    Plains likes this.
  21. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    971
    Right, we are on the same page in regards to WeakReference. Honestly, I hadn't considered using it like this before. It's a really clever idea!

    One comment, you probably can't use multicast delegates since the WeakReference would be the only reference, so it might get prematurely collected. I guess you had probably considered that when you said "you can't use C#'s built-in event syntax with them (i.e. you'd have to roll your own delegate list and such)"

    So that could potentially remove another of the weaknesses of event driven programming. Still, I think the biggest weakness is the disconnecting effect it has on your written code. I've heard someone else describe it as "turning your code inside out" which is about how I feel when I try to read event driven code. I think this is why C# 5's async keyword is so powerful for creating asynchronous code. It allows you to write essentially event driven code with a top-down flow.