🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Coroutine help

Started by
4 comments, last by Shaarigan 4 years, 2 months ago

I'm trying to make operational turn signals/hazards for a vehicle I'm making. I'd like to use coroutines for this, but I don't seem to have a very good understanding of how coroutines work. I'm hoping someone can 1. help me get these turn signals working and 2 (and more importantly) help me gain a better understanding of coroutines. I've posted my code below. These need to be fully operational just like a real vehicle. I hit a button, they stay on until either I hit the button again, or until the steering goes far enough to the side and then back to center, only one can be on at a time, and hazards would override them as well. Thank you in advance for any help!

private void OperateTurnSignals()
    {
        if (GetControlValueAsBool("LeftTurnSignal") && !isCoroutineActive)
        {
            StopAllCoroutines();
            TurnOffTurnSignals(RightTurnSignals);
            isCoroutineActive = true;
            StartCoroutine(TurnSignalTiming(LeftTurnSignals));
        }
        else if(GetControlValueAsBool("RightTurnSignal") && !isCoroutineActive)
        {
            StopAllCoroutines();
            TurnOffTurnSignals(LeftTurnSignals);
            isCoroutineActive = true;
            StartCoroutine(TurnSignalTiming(RightTurnSignals));
        }
        else if(GetControlValueAsBool("Hazards") && !isCoroutineActive)
        {
            StopAllCoroutines();
            isCoroutineActive = true;
            StartCoroutine(Hazards(LeftTurnSignals, RightTurnSignals));
        }
        else
        {
            StopAllCoroutines();
            isCoroutineActive = false;
            TurnOffTurnSignals(LeftTurnSignals);
            TurnOffTurnSignals(RightTurnSignals);
        }
    }

    private IEnumerator TurnSignalTiming(List<Light> turnSignals)
    {
        TurnOnTurnSignals(turnSignals);
        yield return new WaitForSeconds(1.0f);
        TurnOffTurnSignals(turnSignals);
        yield return new WaitForSeconds(1.0f);
        isCoroutineActive = false;
        Debug.Log("end of coroutine");
    }

    private void TurnOnTurnSignals(List<Light> turnSignals)
    {
        foreach(Light light in turnSignals)
        {
            light.gameObject.SetActive(true);
        }
    }

    private void TurnOffTurnSignals(List<Light> turnSignals)
    {
        foreach(Light light in turnSignals)
        {
            light.gameObject.SetActive(false);
        }
    }

    private IEnumerator Hazards(List<Light> leftTurnSignals, List<Light> rightTurnSignals)
    {
        TurnOnTurnSignals(leftTurnSignals);
        TurnOnTurnSignals(rightTurnSignals);
        yield return new WaitForSeconds(1.0f);
        TurnOffTurnSignals(leftTurnSignals);
        TurnOffTurnSignals(rightTurnSignals);
        yield return new WaitForSeconds(1.0f);
    }
Advertisement

Coroutines are Unity's ‘abuse’ of the yield keyword introduced in C# 2.0. It is an order to the C# compiler to decompose a function into a class which contains a state maschine for every time you yield return something.

Your function will become a class that implements IEnumerator and defines the MoveNext() function for example as follows

private bool MoveNext()
{
	/*foreach(int i in new int[] {1, 2, 3, 5, 8})
	{
    		yield return i;
	}*/
    try
    {
        switch (state)
        {
            case 0: //initial state
                state = -1;
                state = 1;
                this.values = new int[] { 1, 2, 3, 5, 8 };
                this.currentPositionInValues = 0;
                while (this.currentPositionInValues < this.values.Length)
                {
                    current_i = this.values[this.currentPositionInValues];
                    current = current_i;
                    state = 2; //yield return i;
                    return true;
                Label_007F:
                    state = 1;
                    this.currentPositionInValues++;
                }
                this.Finally2();
                break;

            case 2:
                goto Label_007F;
        }
        return false;
    }
    fault
    {
        this.System.IDisposable.Dispose();
    }
}

Unity uses those generated iterators to schedule them as Coroutines into the main thread. Each Coroutine is executed after Unity leaves the Update loop by calling MoveNext() on the iterator and processing of the returned result until the Coroutine returns false, which is an indicator that the IEnumerator reached the end of the function or has been ended via yield break.

I implemented my own Coroutines the same way in C# 4.0 and scheduled them a thread pool, so my coroutines are more like C# Tasks


        public bool Evaluate()
        {
            result = active.Current; //get the result last returned from the iterator
            state = ExecutionState.Create(ExecutionFlags.Active);

            if (result is ExecutionFlags)
                result = ExecutionState.Create((ExecutionFlags)result);

            if (result is IEnumerable)
                result = (result as IEnumerable).GetEnumerator();

            if (result is IEnumerator)
            {
                stack.Push(active);
                active = result as IEnumerator;
                result = null;
            }
            else if (result is ExecutionState)
            {
                state = (ExecutionState)result;
                result = null;

                switch (state.Flag)
                {
                    case ExecutionFlags.Completed:
                        if (stack.Count > 0)
                        {
                            active = stack.Pop();
                            state = ExecutionState.Create(ExecutionFlags.Active);
                        }
                        else return false;
                        break;
                    case ExecutionFlags.Cancel:
                    case ExecutionFlags.Failed: return false;
                    case ExecutionFlags.Reset:
                        active.Reset();
                        break;
                }
            }
            else if (stack.Count > 0) active = stack.Pop();
            else
            {
                state = ExecutionState.Create(ExecutionFlags.Completed);
                return false;
            }
            return true;
        }

        public bool SwitchContext()
        {
            Current = this;
            using (ThreadContext.WriteLock(stateLock))
                try
                {
                    if (state != null && !state.Signaled)
                        throw new InvalidOperationException();

                    if (active.MoveNext()) return Evaluate(); //test if the iterator can proceed and evaluate the result
                    else if (stack.Count > 0)
                    {
                        result = active.Current;
                        state = ExecutionState.Create(ExecutionFlags.Active);
                        active = stack.Pop();

                        return true;
                    }
                    else
                    {
                        result = active.Current;
                        state = ExecutionState.Create(ExecutionFlags.Completed);
                        return false;
                    }
                }
                catch (Exception er)
                {
                    lastError = er;
                    state = ExecutionState.Create(ExecutionFlags.Failed);
                    return false;
                }
                finally
                {
                    Current = null;

                    switch (state.Flag)
                    {
                        case ExecutionFlags.Cancel:
                        case ExecutionFlags.Completed:
                            {
                                if (parent != null)
                                    parent.SetResult(this, result);
                            }
                            break;
                        case ExecutionFlags.Failed:
                            {
                                if (parent != null)
                                    parent.SetError(this, lastError);
                            }
                            break;
                    }
                }
        }

(Source can be found on GitHub)

In my scheduler, each Thread gets a context object from a list of pending jobs and call SwitchContext, which leads to a call to MoveNext of the underlaying IEnumerator, which executes the Coroutine. My context can handle slightly more return values than Unity does but the behavior is quite similar. If I return true back to the calling Thread, the state of my Coroutine is tested and for example put back into the list of pending jobs if it is in “Pending” state awaiting a signal to be set to be executed again.

This is the closest we can get to cooperative multitasking in C# if not using Tasks

@undefined I appreciate your answer… but I'm looking to use Unity's api for coroutines… I don't want to implement your methods of making your own coroutines when unity already has this functionality built in…

Ok, so I've revamped my code quite a bit, and I think I'm pretty close to getting this. I think the only thing preventing it from working is that once a turn signal is turned on, it's going into the if statement every frame and I can't seem to figure out a way to let it know that the coroutine has already been started and not go into the if statement again. I thought I could do something like IEnumerator coroutine and then assign the coroutine to that variable… then check if it's already running? But I'm not sure how that would work exactly… Any help is much appreciated!

private void OperateTurnSignals()
    {
        if (GetControlValueAsBool("LeftTurnSignal"))
        {
            StopAllCoroutines();

            StartCoroutine(TurnOnTurnSignals(LeftTurnSignals));
        }
        else if(GetControlValueAsBool("RightTurnSignal"))
        {
            StopAllCoroutines();

            StartCoroutine(TurnOnTurnSignals(RightTurnSignals));
        }
        else if(GetControlValueAsBool("Hazards"))
        {
            StopAllCoroutines();
            StartCoroutine(TurnOnHazards(LeftTurnSignals, RightTurnSignals));

        }
        else
        {
            StopAllCoroutines();
            TurnOffTurnSignals(LeftTurnSignals);
            TurnOffTurnSignals(RightTurnSignals);
        }
    }

    private IEnumerator TurnOnTurnSignals(List<Light> turnSignals)
    {
        foreach(Light light in turnSignals)
        {
            light.gameObject.SetActive(true);
        }
        yield return new WaitForSeconds(1.0f);
        StartCoroutine(TurnOffTurnSignals(turnSignals));
    }

    private IEnumerator TurnOffTurnSignals(List<Light> turnSignals)
    {
        foreach(Light light in turnSignals)
        {
            light.gameObject.SetActive(false);
        }
        yield return new WaitForSeconds(1.0f);
        StartCoroutine(TurnOnTurnSignals(turnSignals));
    }

    private IEnumerator TurnOnHazards(List<Light> leftTurnSignals, List<Light> rightTurnSignals)
    {
        TurnOnTurnSignals(leftTurnSignals);
        TurnOnTurnSignals(rightTurnSignals);
        yield return new WaitForSeconds(1.0f);
        StartCoroutine(TurnOffHazards(leftTurnSignals, rightTurnSignals));
    }

    private IEnumerator TurnOffHazards(List<Light> leftTurnSignals, List<Light> rightTurnSignals)
    {
        TurnOffTurnSignals(leftTurnSignals);
        TurnOffTurnSignals(rightTurnSignals);
        yield return new WaitForSeconds(1.0f);
        StartCoroutine(TurnOnHazards(leftTurnSignals, rightTurnSignals));
    }

ethancodes said:
but I'm looking to use Unity's api for coroutines… I don't want to implement your methods of making your own coroutines when unity already has this functionality built in…

I don't start to discuss about Unity's coroutines or their threading model at this point at this point (spoiler, I don't like it)

But the code I shared here as I said

Shaarigan said:
My context can handle slightly more return values than Unity does but the behavior is quite similar.

was just an intention to illustrate how coroutines work

This topic is closed to new replies.

Advertisement