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

Passing ref variable to coroutine

Discussion in 'Scripting' started by Mindaugasw, Jan 14, 2016.

  1. Mindaugasw

    Mindaugasw

    Joined:
    Oct 25, 2014
    Posts:
    13
    Is there any way to pass a reference variable to coroutine?
    I would like to have something like this:
    Code (C#):
    1. IEnumerator myCoroutine(ref float myVariable)
    2. {
    3.     myVariable = getNewValue();
    4.     yield return null;
    5. }
    6. StartCoroutine(myCoroutine(ref myFloat));
    But it says "Iterators cannot have ref or out parameters".
     
  2. Schneider21

    Schneider21

    Joined:
    Feb 6, 2014
    Posts:
    3,512
    Maybe you don't need the variable to be passed by reference. Just do whatever adjustments you end up using the variable for inside the loop of your coroutine (which I'm assuming you left out of this example for brevity, since otherwise this coroutine is pretty useless).
     
    PlayCreatively likes this.
  3. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    Iterators can't have ref or out parameters, because the backing for an iterator in C# is a class that is generated by the compiler. Classes can't have ref or out fields.

    You can, however, create a Ref<T> class to pass value types by reference.
    Code (csharp):
    1.  
    2.     public class Ref<T>
    3.     {
    4.         private T backing;
    5.         public T Value {get{return backing;}}
    6.         public Ref(T reference)
    7.         {
    8.             backing = reference;
    9.         }
    10.     }
    Then use the Value of the reference anyplace you'd normally use the myVariable field.
     
    Dongmany, oAzuehT, Bunny83 and 3 others like this.
  4. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    Ya I'd just create a class/struct that contains the value and pass in that object into the coroutine. Kru's example is solid. :)
     
  5. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    A struct wouldn't get you any further than a primitive would ;)
     
    Nevermiss likes this.
  6. Mindaugasw

    Mindaugasw

    Joined:
    Oct 25, 2014
    Posts:
    13
    I want to use that coroutine from multiple other classes to set value over some time. So I don't think there is any way without using references. And yeah, the calculations would be done inside the loop, like this:
    Code (CSharp):
    1. public static IEnumerator myCoroutine(ref float myVariable)
    2. {
    3.     float time = 0;
    4.     float timeLimit = 1;
    5.     while (time < timeLimit)
    6.     {
    7.         // Do calculations
    8.         myVariable = newValue;
    9.         time += Time.deltaTime;
    10.         yield return null;
    11.     }
    12. }
    I want to be able to also set the variable, not only read, so that class doesn't work.
    Also, I will need to pass only single float, no any objects.
     
    alphdevcode likes this.
  7. Fajlworks

    Fajlworks

    Joined:
    Sep 8, 2014
    Posts:
    344
    You can also use closures to achieve same effect:
    Code (CSharp):
    1. IEnumerator myCoroutine( System.Action<float> callback)
    2. {
    3.       float myVariable = getNewValue();
    4.       yield return null;
    5.  
    6.       if (callback != null) callback(myVariable);
    7. }
    8.  
    9. void MyFunction()
    10. {
    11.      StartCoroutine( myCoroutine( (result) =>
    12.      {
    13.           this.myFloat = result;
    14.      }));
    15. }
     
    vlab22, Neil-Corre, oAzuehT and 6 others like this.
  8. kru

    kru

    Joined:
    Jan 19, 2013
    Posts:
    452
    Add a setter
    Code (csharp):
    1. public T Value { get { return backing; } set { backing = value } }
     
    tcz8 likes this.
  9. jtsmith1287

    jtsmith1287

    Joined:
    Aug 3, 2014
    Posts:
    787
    Lol I actually never use them. I gambled and lost!
     
    chadfranklin47 likes this.
  10. Mindaugasw

    Mindaugasw

    Joined:
    Oct 25, 2014
    Posts:
    13
    Doesn't work because of this line:
    this.myFloat = result;
    As I need to set value in other class.

    The Ref class still uses a new field instead of a reference.

    So I tried every solution I could find on the internet and I believe it may be impossible to get it working how I need. Guess I'll have to think of another approach.
     
  11. NgBobShoaun

    NgBobShoaun

    Joined:
    Feb 28, 2017
    Posts:
    1
    I found a solution for your problem :

    Code (CSharp):
    1. public static IEnumerator myCoroutine(ref float myVariable)
    2. {
    3.     float time = 0;
    4.     float timeLimit = 1;
    5.     while (time < timeLimit)
    6.     {
    7.         // Do calculations
    8.         myVariable = newValue;
    9.         time += Time.deltaTime;
    10.         yield return null;
    11.     }
    12. }
    instead of this ^, do this :

    Code (CSharp):
    1. public static IEnumerator myCoroutine(Action<float> myVariableResult)
    2. {
    3.     float time = 0;
    4.     float timeLimit = 1;
    5.     while (time < timeLimit)
    6.     {
    7.         // Do calculations
    8.         myVariableResult (newValue);
    9.         time += Time.deltaTime;
    10.         yield return null;
    11.     }
    12. }
    and to call the function, do this :

    Code (CSharp):
    1. float varToBeChangedInCoroutine;
    2. StartCoroutine (myCoroutine (result => varToBeChangedInCoroutine = result));
    cheers
     
  12. Snorch

    Snorch

    Joined:
    Oct 19, 2017
    Posts:
    3
    ABSOLUTELY GREAT!

    I could do a generic version that works great to change variables over time.

    Code (CSharp):
    1. public static IEnumerator DelayedVarChange<T>(Action<T> variable, float time, T initialValue, T finalValue)
    2.     {
    3.         Debug.Log(typeof(CoroutineVariableTest) + ": Start time: " + Time.time);
    4.         variable(initialValue);
    5.  
    6.         yield return new WaitForSeconds(time);
    7.  
    8.         variable(finalValue);
    9.         Debug.Log(typeof(CoroutineVariableTest) + ": End time: " + Time.time);
    10.     }
    And example of call would be:

    Code (CSharp):
    1. StartCoroutine(DelayedVarChange<bool>(result => var = result, time, true, false));
    or

    Code (CSharp):
    1. StartCoroutine(DelayedVarChange<int>(result => var = result, time, 1, 99));
     
    landstalker310 likes this.
  13. unity_rUXG6TRGKyfxuQ

    unity_rUXG6TRGKyfxuQ

    Joined:
    Nov 28, 2019
    Posts:
    5
    This is a great solution and it helps on many of my problems.
    But I got to a new problem: what if I want to change the value of the variable and read it in the same coroutine?

    Here is my problem:

    Code (CSharp):
    1. IEnumerator PlayDialogueAfterWaiting(System.Action<bool> myCondition, int waitTime) {
    2.             myCondition(true);
    3.             yield return new WaitForSeconds(waitTime);
    4.             if (/* I want to read the value of my bool here, because it might have been changed by other functions during the wait time */) {
    5.                 // Do something
    6.             }
    7. }
    8.  
    Thanks!
     
  14. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @unity_rUXG6TRGKyfxuQ

    "But I got to a new problem:"


    And that is when you should ask it in your own question perhaps? It has very little to do it original question.

    You could pass your custom delegate to your method or use premade one Func which also can return a value.

    And this thread was also necro'ed by someone yesterday.
     
  15. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    The whole thread is very silly anyway. The standard solution is to pass in a reference to a class. No one wrote that since it's so obvious and easy. It's like if someone asked how to add 2 numbers, no one would say to use +. They assume you're asking for other ways.
     
  16. tcz8

    tcz8

    Joined:
    Aug 20, 2015
    Posts:
    504
    Isn't that what kru suggested?
     
    Bunny83 likes this.
  17. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    I can see why it seems that way, but no. Basic C# says everything is _already_ in a class. No one would want to pass
    int score
    by reference. They would already have a class Score and would pass s1 to the coroutine, which would use member functions to change it.

    I assumed Kru's answer was a joke: "well, if you really want to do such a strange and silly thing, you could hack it with this oddball language feature which I won't even explain how to use".
     
    tcz8 likes this.
  18. tcz8

    tcz8

    Joined:
    Aug 20, 2015
    Posts:
    504
    I see what you mean. Thanks for taking the time to reply.
     
  19. Noxury

    Noxury

    Joined:
    Mar 8, 2015
    Posts:
    22
    For those who still look for the same problem as @unity_rUXG6TRGKyfxuQ

    Here is something I came up:

    Code (CSharp):
    1. public class WaitConditionRef<T> {
    2.     public bool abortCondition { get; set; }
    3.     private Coroutine coroutine;
    4.     public WaitConditionRef(MonoBehaviour caller, Action onUpdate, T yieldReturn, Action onFinish){
    5.         coroutine = caller.StartCoroutine (WaitUntilRef(onUpdate, yieldReturn, onFinish));
    6.     }
    7.     private IEnumerator WaitUntilRef<T>(Action onUpdate, T yieldReturn, Action onFinish){
    8.         while (!abortCondition) {
    9.             onUpdate ();
    10.             yield return yieldReturn;
    11.         }
    12.         onFinish ();
    13.     }
    14. }
    15.  
    example Usage:
    Code (CSharp):
    1.  
    2. public IEnumerator BlinkObjects(List<GameObject> visuals){
    3.     bool visible = false;
    4.     var blinkObjectsWait = new WaitConditionRef<int> (this, ()=> {
    5.             for (int i = 0; i < visuals.Count; i++) {
    6.                 visuals[i].SetActive (visible = !visible);
    7.             } }, 3, () => {
    8.             for (int i = 0; i < visuals.Count; i++) {
    9.                 visuals[i].SetActive (true);
    10.             } } );
    11.  
    12.     yield return ... //could be WaitUntil or fixxed time
    13.     blinkObjectsWait.abortCondition = true;
    14. }
    This example will blink the objects with the iterator step of 3 frames for as long as the abortCondition property is set to true. Afterwards enable these again when it stopped when they're not visible.

    You could also cache the WaitConditionRef instance and call the condition whenever / whereever you like.
     
    Last edited: Nov 17, 2020
  20. FullHeartGames

    FullHeartGames

    Joined:
    Jul 21, 2015
    Posts:
    18
    Another simple and possibly useful approach depending on the use case is to invoke a function from the coroutine to set a public variable. Something like this:


    Code (CSharp):
    1. private IEnumerator booWait;
    2. bool someBoo = false;
    3.  
    4. void Start()
    5. {
    6.         booWait = WaitAndCallFunction("enableBoo", 2.0f);
    7.         StartCoroutine(booWait);  
    8. }
    9.  
    10. private IEnumerator WaitAndCallFunction(string str, float waitTime, bool repeat=false)
    11. {  
    12.  
    13.     do
    14.     {
    15.         yield return new WaitForSeconds(waitTime);
    16.         Invoke(str, 0.0f);
    17.     }
    18.     while (repeat);
    19. }
    20.  
    21. public void enableBoo()
    22. {
    23.         someBoo = true;
    24. }
     
  21. bart42

    bart42

    Joined:
    Dec 29, 2014
    Posts:
    5
    I would not know how to use your Ref<T> example, so I came up with the following.

    Code (CSharp):
    1. public class ResultObject<T>
    2. {
    3.     public T result;
    4. }
    5.  
    6. IEnumerator StartConvert()
    7. {
    8.     ResultObject<string> resultObject = new ResultObject<string>();
    9.     yield return StartCoroutine( Process(resultObject));
    10.     Debug.Log(resultObject.result);
    11.  }
    12.  
    13. IEnumerator Process(ResultObject<string> resultObject)
    14. {
    15.     yield return new WaitForSeconds(1);
    16.     resultObject.result = "Convert process success";
    17. }