Passing ref variable to coroutine

Is there any way to pass a reference variable to coroutine?
I would like to have something like this:

IEnumerator myCoroutine(ref float myVariable)
{
    myVariable = getNewValue();
    yield return null;
}
StartCoroutine(myCoroutine(ref myFloat));

But it says “Iterators cannot have ref or out parameters”.

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).

1 Like

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 class to pass value types by reference.

    public class Ref<T>
    {
        private T backing;
        public T Value {get{return backing;}}
        public Ref(T reference)
        {
            backing = reference;
        }
    }

Then use the Value of the reference anyplace you’d normally use the myVariable field.

6 Likes

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. :slight_smile:

A struct wouldn’t get you any further than a primitive would :wink:

1 Like

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:

public static IEnumerator myCoroutine(ref float myVariable)
{
    float time = 0;
    float timeLimit = 1;
    while (time < timeLimit)
    {
        // Do calculations
        myVariable = newValue;
        time += Time.deltaTime;
        yield return null;
    }
}

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.

1 Like

You can also use closures to achieve same effect:

IEnumerator myCoroutine( System.Action<float> callback)
{
      float myVariable = getNewValue();
      yield return null;

      if (callback != null) callback(myVariable);
}

void MyFunction()
{
     StartCoroutine( myCoroutine( (result) =>
     {
          this.myFloat = result;
     }));
}
9 Likes

Add a setterpublic T Value { get { return backing; } set { backing = value } }

1 Like

Lol I actually never use them. I gambled and lost!

1 Like

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.

I found a solution for your problem :

public static IEnumerator myCoroutine(ref float myVariable)
{
    float time = 0;
    float timeLimit = 1;
    while (time < timeLimit)
    {
        // Do calculations
        myVariable = newValue;
        time += Time.deltaTime;
        yield return null;
    }
}

instead of this ^, do this :

public static IEnumerator myCoroutine(Action<float> myVariableResult)
{
    float time = 0;
    float timeLimit = 1;
    while (time < timeLimit)
    {
        // Do calculations
        myVariableResult (newValue);
        time += Time.deltaTime;
        yield return null;
    }
}

and to call the function, do this :

float varToBeChangedInCoroutine;
StartCoroutine (myCoroutine (result => varToBeChangedInCoroutine = result));

cheers

14 Likes

ABSOLUTELY GREAT!

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

public static IEnumerator DelayedVarChange<T>(Action<T> variable, float time, T initialValue, T finalValue)
    {
        Debug.Log(typeof(CoroutineVariableTest) + ": Start time: " + Time.time);
        variable(initialValue);

        yield return new WaitForSeconds(time);

        variable(finalValue);
        Debug.Log(typeof(CoroutineVariableTest) + ": End time: " + Time.time);
    }

And example of call would be:

StartCoroutine(DelayedVarChange<bool>(result => var = result, time, true, false));

or

StartCoroutine(DelayedVarChange<int>(result => var = result, time, 1, 99));
1 Like

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:

IEnumerator PlayDialogueAfterWaiting(System.Action<bool> myCondition, int waitTime) {
            myCondition(true);
            yield return new WaitForSeconds(waitTime);
            if (/* I want to read the value of my bool here, because it might have been changed by other functions during the wait time */) {
                // Do something
            }
}

Thanks!

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.

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.

Isn’t that what kru suggested?

1 Like

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”.

1 Like

I see what you mean. Thanks for taking the time to reply.

For those who still look for the same problem as * @unity_rUXG6TRGKyfxuQ *

Here is something I came up:

public class WaitConditionRef<T> {
    public bool abortCondition { get; set; }
    private Coroutine coroutine;
    public WaitConditionRef(MonoBehaviour caller, Action onUpdate, T yieldReturn, Action onFinish){
        coroutine = caller.StartCoroutine (WaitUntilRef(onUpdate, yieldReturn, onFinish));
    }
    private IEnumerator WaitUntilRef<T>(Action onUpdate, T yieldReturn, Action onFinish){
        while (!abortCondition) {
            onUpdate ();
            yield return yieldReturn;
        }
        onFinish ();
    }
}

example Usage:

public IEnumerator BlinkObjects(List<GameObject> visuals){
    bool visible = false;
    var blinkObjectsWait = new WaitConditionRef<int> (this, ()=> {
            for (int i = 0; i < visuals.Count; i++) {
                visuals[i].SetActive (visible = !visible);
            } }, 3, () => {
            for (int i = 0; i < visuals.Count; i++) {
                visuals[i].SetActive (true);
            } } );

    yield return ... //could be WaitUntil or fixxed time
    blinkObjectsWait.abortCondition = true;
}

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.

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:

private IEnumerator booWait;
bool someBoo = false;

void Start()
{
        booWait = WaitAndCallFunction("enableBoo", 2.0f);
        StartCoroutine(booWait);   
}

private IEnumerator WaitAndCallFunction(string str, float waitTime, bool repeat=false)
{   

    do
    {
        yield return new WaitForSeconds(waitTime);
        Invoke(str, 0.0f);
    }
    while (repeat);
}

public void enableBoo()
{ 
        someBoo = true;
}