Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Using ref or Action with a IEnumerator function

Discussion in 'Scripting' started by Deleted User, Sep 4, 2015.

  1. Deleted User

    Deleted User

    Guest

    Hey folks,

    This is what i'd like to do

    I'd like to use this function for multiple booleans/animations. However, since this is a IEnumerator I won't be able to use a ref parameter.. Now I've looked into the Action method, but that doesn't seem entirely right for my problem either. Anybody got any advice on how to solve this problem?
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I believe you can pass in a delegate (including lambda functions). That might help.
     
  3. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
    As @BoredMormon mentioned, you can pass in lambda functions as paramters which can effectionally pass in references. Here is an example of how you can "hack" references into Enumerators:

    Code (CSharp):
    1.  
    2.     private bool memberValue;
    3.  
    4.     private IEnumerator AssignBoolean(Action<bool> assigner)
    5.     {
    6.         bool boolToAssign = true;
    7.  
    8.         assigner.Invoke(boolToAssign);
    9.  
    10.         yield return null;
    11.     }
    12.  
    13.     public void Start ()
    14.     {
    15.         StartCoroutine (AssignBoolean (value => memberValue = value));
    16.     }
    In the above example, "memberValue" is assigned to the value of "boolToAssign" from the Enumerator.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    You can simplify that to just be

    Code (csharp):
    1. assigner(boolToAssign);
    I prefer that because it just looks like any other function.
     
    Kiwasi likes this.
  5. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,579
    I like to distinguish between the use cases of a throwaway lambda method and a concrete method. But all to their own! Thanks for pointing it out. :)
     
  6. Deleted User

    Deleted User

    Guest

    Thanks guys, this was exactly what I was looking for!
     
  7. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    Hi, I hope it's not too late to add to another question to this.
    With the above example you are setting "memberValue" from the "boolToAssign" value. Is there a way to go the opposite way and have "memberValue" update something within the coroutine?
    Basically I want to have an IEnumerator with a while loop that watches the current value of a variable.
    I'm a bit stuck with this, but this seems really close to what I'm trying to do.
    Thanks!
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    You are very close.

    A
    System.Action<bool>
    is a pointer to a function that looks like this:

    void MyFunction(bool foo);


    so it is for "sending out" bools to things.

    What you want (I believe) is the opposite: you want to GET a bool from outside the Coroutine.

    That would be a
    System.Func<bool>
    , which would be a pointer to a function like:

    bool MyResult()
    {
    return booleanResultHere;
    }


    so your coroutine would look like:

    Code (csharp):
    1. IEnumerator Foobar( System.Func<bool> KeepGoing)
    2. {
    3.  while( KeepGoing())
    4.  {
    5.    yield return null;
    6.  }
    7. }
    and you would invoke it with

    StartCoroutine( Foobar( MyResult));
     
  9. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    Hey thanks Kurt, that sounds right but there's something I'm not getting :/ Sorry!
    How can you point to different bools for "Foobar" to reference?
    If I do something like this -
    Code (CSharp):
    1.     public bool testVal2;
    2.     private void Start()
    3.     {
    4.         StartCoroutine( Foobar( testVal2 ));
    5.     }
    I get this -
    Argument type 'bool' is not assignable to parameter type 'System.Func<bool>'
     
  10. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    ah, you need to pass a function in that RETURNS a bool... not a bool.

    But luckily you can trivially make an on-the-fly function, called a lambda, like so:

    Code (csharp):
    1. StartCoroutine( Foobar(
    2.  
    3. // this is the lambda portion:
    4.  () => {
    5.    return testVal2;
    6.  }
    7.  
    8. // and now close out the rest of the Foobar and StartCoro...
    9. ));
    You can also strip out those comments and put it all on one line ... I just did that for illustration.
     
  11. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    Ahh okay great! Thanks for that.
    Just wondering though is there a way to assign the bool from another function like this?
    Code (CSharp):
    1.     public void WaitForTest(ref bool SomeBool)
    2.     {
    3.         StartCoroutine(Foobar(() => { return SomeBool; }) );
    4.     }
     
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I don't think you can bridge through like that with a ref or out argument... you could pass the function in multiple layers though... perhaps there is a C# language weenie who can suggest a better construct?

    EDIT: just realized you could make a little BoolBox class that contains the bool and then pass that around... other things would reference that bool via the class reference. Just tested and it works nicely.

    Code (CSharp):
    1. using System.Collections;
    2. using UnityEngine;
    3.  
    4. public class RoolTheBools : MonoBehaviour
    5. {
    6.     public class BoolBox
    7.     {
    8.         public bool TheBool;
    9.         public BoolBox() { }
    10.         public BoolBox(bool x) { TheBool = x; }
    11.     }
    12.  
    13.     IEnumerator Foobar(System.Func<BoolBox> GetSomeBoolBox)
    14.     {
    15.         while (true)
    16.         {
    17.             Debug.Log("Bool is " + GetSomeBoolBox().TheBool);
    18.             yield return null;
    19.         }
    20.     }
    21.  
    22.     public void WaitForTest(BoolBox SomeBoolBox)
    23.     {
    24.         StartCoroutine(Foobar(() => { return SomeBoolBox; }));
    25.     }
    26.  
    27.     BoolBox box;
    28.  
    29.     private void Start()
    30.     {
    31.         box = new BoolBox();
    32.         WaitForTest(box);
    33.     }
    34.     private void Update()
    35.     {
    36.         box.TheBool = !box.TheBool;
    37.     }
    38. }
    39.  
    Screen Shot 2021-04-29 at 5.46.12 PM.png
     
    Last edited: Apr 30, 2021
  13. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    No, there's no way around that. Ref parameters are already a hack that uses actual pointers to the memory location of the variable. Due to this fact such pointers can not be stored in any variable since such pointers could even point to a variable on the stack of the calling method. So storing it for later use is completely impossible.

    It's a different story when you use a lambda expression. In this case the compiler will create a closure object that wraps the variable into an internal class. That class can now be referenced by any code that wants to access this variable. Though closures are just ordinary C# classes which use ordinary C# references and not memory pointers.

    ps: You can simplify your lambda expression to:

    Code (CSharp):
    1. StartCoroutine(Foobar(() => SomeBool) );
    This of course is only possible if the lambda represents a single statement / expression. Using a method body explicitly allows more complex code, though it also requires the use of the return statement.
     
    mopthrow, Kurt-Dekker and petey like this.
  14. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    Thanks Bunny... I just like big blocks of vertical code with lotsa whitespace... maybe it's because back in the Z80 / 6502 assembly days we did one thing per line and we LIKED it. :)

    Either way, seems I always gotta stick a breakpoint or a Debug.Log in there so... "why not have it all aired out?" is my reasoning.

    EDIT: vertical spacing also makes version control diff logs SO much more meaningful, revertable, cherry-pickable, etc.
     
    Lurking-Ninja likes this.
  15. You mean...
    Code (CSharp):
    1.            CLC         clear the carry
    2.            LDA $20     get the low byte of the first number
    3.            ADC $22     add to it the low byte of the second
    4.            STA $24     store in the low byte of the result
    5.            LDA $21     get the high byte of the first number
    6.            ADC $23     add to it the high byte of the second, plus carry
    7.            STA $25     store in high byte of the result
     
    Kurt-Dekker likes this.
  16. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    If 6502 assembly is good enough to power a Cyberdyne Systems Model 101, it's good enough for me.

    https://www.theterminatorfans.com/the-terminator-vision-hud-source-code-explained/

    It will not stop until you are dead.

    EDIT: but seriously, so many folks have problems early on packing everything into one line and getting a nullref, it's just ... why not rise above it? :)
     
    Lurking-Ninja likes this.
  17. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,817
    I just find scanning through stuff kinda hard but that’s a really good point, and you can fold things up easy enough.
    Hey I was a little confused by the box code, but does that work similarly to how you can reference an object in an IEnumerator and get updated values (Such as transform values) from that?