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

Resolved How to stop a function midway via event subscribers?

Discussion in 'Scripting' started by Dennooo, Sep 2, 2020.

  1. Dennooo

    Dennooo

    Joined:
    May 12, 2015
    Posts:
    78
    Hi there,

    I struggle to accomplish the following and hope you guys can help me out:

    I have a function (ReceiveDamage()) that calls an Event (onHit). Now I want one of the Subscribers of onHit to stop the execution of ReceiveDamage() midway. The solution should not incorporate a flag set in the ReceiveDamage() function nor in Start() of the following simplified example.

    Code (CSharp):
    1. public delegate void OnHit();
    2. public event OnHit onHit;
    3.  
    4. void Start()
    5. {
    6.      // the subscribers will be randomly assigned to onHit
    7.      // but I can not incorporate a flag
    8.      // in here to check for the subscribers in the non-simplified problem
    9.      if(Random.value < .5f) onHit += SubscriberOne;
    10.      else onHit += SubscriberTwo;
    11.      
    12.     onHit += SubscriberThree;
    13. }
    14.  
    15. public void ReceiveDamage()
    16. {
    17.     if(onHit!= null) onHit;
    18.  
    19.     // this is the part of the function that I don't want to be executed
    20.     // depending on the subscribers of onHit
    21. }
    22.  
    23. public void SubscriberOne()
    24. {
    25.     // Stop the onHit execution from within this function
    26. }
    27.  
    28. public void SubscriberTwo()
    29. {
    30.     // Do something but don't affect the execution of ReceiveDamage()
    31. }
    32.  
    33. public void SubscriberThree()
    34. {
    35.     // Do something but don't affect the execution of ReceiveDamage()
    36. }
    Now the question: How do I change SubscriberOne() so that ReceiveDamage() is stopped after calling SubscriberOne() while all other subscribers are still called by onHit?
    Many thanks in advance. Any advice on a feasible solution is very much appreciated :)
     
  2. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    You can create an event state object with a "used" boolean, passing it as a parameter to the event. Once one listener uses it, it sets "used" to true, and all other listeners check that value before doing their thing.

    However, you should know that event execution order is not guaranteed. It looks like it executes them in the order that they are registered, however this is not a part of the spec and therefore could change without warning. So SubcriberThree and SubscriberTwo might get called before SubscriberOne stops the execution. If you want to guarantee a certain order, use a List<OnHit> or something, and loop through them, rather than an event.
     
    Dennooo likes this.
  3. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,722
    You're putting some strange constraints on this problem that I fear make what you're asking impossible. There's only a few things in C# that can stop the execution of a function midway through:
    • A return statement.
    • A thrown exception.
    • a yield statement (Only works if the method is an iterator, like a coroutine)
    You've ruled out setting a flag, and event subscribers cannot return values (or at least, the invoker of an event cannot retrieve values from subscribers). So there's no way I can see that you could conditionally have a return statement. If you throw an exception from within the handler, that could stop ReceiveDamage, but you'd want to have a try/catch and... frankly it's a really ugly and slow way to control program flow.

    Why do you have these restrictions like not setting a flag? What's your end goal here? There's probably a cleaner way to accomplish what you want.

    In general events are designed to be "Fire and Forget", so doing some conditional execution based on the result of one or more event subscribers seems like an antipattern to me.
     
    Last edited: Sep 2, 2020
    Dennooo likes this.
  4. Dennooo

    Dennooo

    Joined:
    May 12, 2015
    Posts:
    78
    Thank you very much for your prompt assistance! :)

    It seems that such behavior is difficult (or impossible) to implement and subject to some major restrictions / work arounds. I was hoping that there is a simple solution with callbacks that I do not know of (I am not a trained programmer).

    The reason for my restrictions is the following: I am currently working on a grid-based tactical RPG where the traits of the characters are determined procedurally and assigned to a character in a modular way. Each trait reacts to an event such as suffering damage (e.g. by counter attacks, buffs etc.). When a trait is rolled, it subscribes to its corresponding event. This works well as long as the trait does not have to influence the function calling the event:

    For a better understanding of the process and the actual problem: If a character is attacked, ReceiveDamage(int damage) is called, the character suffers damage and executes the onHit event. Assuming there are traits that can influence the amount of damage on a hit, but not every character has this trait. These traits can be something like a shield or e.g. another character that takes damage instead of the hit character. I could add this as follows:

    Code (CSharp):
    1. public void ReceiveDamage(int damage)
    2. {
    3.     // onHit now only contains subscribers that don't affect the
    4.     // damage within this instance
    5.     if(onHit!= null) onHit;
    6.  
    7.     // When trait 1 is active we redirect the damage
    8.     if(ShieldActive())
    9.     {
    10.         DamageToShield(damage);
    11.         return;
    12.     }
    13.  
    14.     // When trait 2 is active we redirect the damage
    15.     if(OtherCharacterBlocks())
    16.     {
    17.         DamageToOtherCharacter(damage);
    18.         return;
    19.     }
    20.  
    21.     // We only substract hp in this instance when none of the two traits is active
    22.     hp -= damage;
    23. }
    24.  
    25. public bool ShieldActive()
    26. {
    27.    // returns true when there is an active shield - else false
    28. }
    29.  
    30. public bool OtherCharacterBlocks()
    31. {
    32.    // returns true when there is another character that blocks - else false
    33. }
    34.  
    35.  
    However, it would be rather unpleasant if I had to include such queries for each of these traits as it appears less modular and somewhat hackish if many of these traits exist. It would be nicer if the logic could be moved to the event subscribers, so that ReceiveDamage(int damage) could look like this:

    Code (CSharp):
    1. public void ReceiveDamage(int damage)
    2. {
    3.     // onHit now also contains subscribers that could
    4.     // end this function midway
    5.     if(onHit!= null) onHit;
    6.  
    7.     // We only substract hp in this instance when no onHit subscriber interrupted this method
    8.     hp -= damage;
    9. }
    10.  
    I hope this makes a reasonable sense. Maybe you have ideas how to do something like this better.
     
  5. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    Events are optimized for one-way communication (listeners are not expected to send feedback to the event source).

    But you can build your own event-like system based on different assumptions, if you want. E.g. replace your event with a private collection of delegates and some public functions for subscribing/unsubscribing, and then those delegates can have a return type, and you can control the order that the delegates are called.

    But if the reason you want to control the order is that you have a bunch of individual one-off game effects and the game designer needs explicit control over which effect gets priority over another, then you're probably still going to want an explicit ordered list of all effects somewhere in your game, and it's not immediately obvious why that list shouldn't be in the form of some source code that simply calls each of them one at a time.
     
    Dennooo and PraetorBlue like this.
  6. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,722
    Yep some custom delegate processing is the way to go here I think. You could have a delegate like
    Code (CSharp):
    1. public delegate int DamageProcessor(int damage);
    Then you have a
    List<DamageProcessor> damageProcessors


    Then receiving damage looks like:
    Code (CSharp):
    1. void ReceiveDamage(int damage) {
    2.   foreach (var processor in damageProcessors) {
    3.     damage = processor(damage);
    4.   }
    5.  
    6.   hp -= damage;
    7. }
    This way you could have processors that reduce the damage to 0, redirect it to the shield, etc..

    For example the one that reduces it to zero would simply be:

    Code (CSharp):
    1. int blockAllDamage(int damage) {
    2.   return 0;
    3. }
     
    Dennooo likes this.
  7. Dennooo

    Dennooo

    Joined:
    May 12, 2015
    Posts:
    78
    Ahhh this is a good suggestion. I will try that! :)

    Thanks a lot for your help.