Coroutine chaining and using WaitUntil or WaitForSeconds

I would like to have a Pluggable Function to use in my Coroutines. Tried to find the implementation of something like WaitUntil. I am trying to show an animation for various algorithms (maze, spawning, etc.) and want to have flexibility in how it is controlled. For instance I would love to do this:

   internal class SpawnerJuice : InstanceTracker
    {
        [SerializeField] private JuiceAbstract _juiceSettings;
        [SerializeField] private float _spawnInterval = 0.3f;
        [SerializeField] private float _juiceTimeScale = 1f;
        public bool SpawnNext { get; set; } = false;
        public Func<int, IEnumerator> TimingControlFunc {  get; set; } = (_) => { return new WaitForSeconds(0.3f); };

        public IEnumerator PlayAll()
        {
            SpawnNext = true;
            foreach (var (gameObject, spawnOrder) in this.GetActiveInstances())
            {
                gameObject.SetActive(true);
                var juice = gameObject.GetComponent<JuiceAbstract>();
                juice?.Play();
                //SpawnNext = false;
                //yield return new WaitUntil(() => SpawnNext);
                yield return TimingControlFunc(spawnOrder);
            }
        }

I could then swap out the default a simple class using WaitUntil and have variable under my control.

_mySpawnerJuice.TimingControlFunc = (_) { return new WaitUntil( ...); // some key was pressed or ...

The error I get with the above is: “Cannot implicitly convert type ‘UnityEngine.WaitForSeconds’ to ‘System.Collections.IEnumerator’” for line 7.

I tried to see if the WaitForSeconds class had a GetEnumerator, but it really has no methods and seems like it is just an Object.

I will just spawn up a co-CoRoutine :slight_smile: that will run beside and use WaitUntil with a class bool that I can set whenever I want. But curious if there is an elegant solution.

You need to subclass CustomYieldInstruction.

Easy. Highlight the WaitUntil in your code, then choose “Inspect”. Usually that means Ctrl-clicking the word. If the IDE doesn’t show you at least the decompiled IL code, then that IDE sucks. :grin:

This is not that meaningful because you can just pass a custom predicate into the coroutine:

        public IEnumerator PlayAllUntil(Func<bool> predicate)
        {
            foreach (var (gameObject, spawnOrder) in this.GetActiveInstances())
            {
                yield return new WaitUntil(predicate);
            }
        }

StartCoroutine(PlayAllUntil( ()=> /* condition here */ )));

Edit: This was false: “They both just implement IEnumerator.”

YieldInstruction is a native code class that does not seem to implement IEnumerator.

        ---------------------------------------------

Thanks @CodeSmile. Didn’t think about Disassembly. Got me closer, but surprisingly, WaitUntil and WaitForSeconds do not inherent from a consistent base class. They both just implement IEnumerator. WaitUntil uses CustomYieldInstruction (thanks. I also found that shortly after posting) and WaitForSeconds inherits from YieldInstruction.

I can write CustomYieldInstruction classes, but I am trying to create a tool where the user could supply an easy yield method. I think my biggest hurdle is the fact that this really needs to be a Factory, calling new on the resulting class.

In any case, you gave me some good pointers on the CustomYieldInstruction. I will get it working for all of my use cases and then see if I can generalize it from there and make the framework as easy to use as possible without requiring the users to write custom yield classes. Would be nice if these were all IEnumerable and then I could just call GetEnumerator each time. That may be what I end up supporting.

Maybe not what you’re looking for but what about a child coroutine?

    IEnumerator MyCoroutine()
    {
        Debug.Log("Do some stuff...");
        yield return MyChildCoroutine(condition, 5, 12);
        Debug.Log(".. child ended");
        DoMoreStuff();
    }

    IEnumerator MyChildCoroutine(Func<bool> condition, int a, int b)
    {
          while (!condition.Invoke())
          {
               yield return null;
          }
         DoTheThing(a, b);
         // maybe you are looping thru several conditions / DoTheThing()'s
    }

Note that for quite some time you do not need and probably don’t want to call StartCoroutine when you want to yield a child coroutine. Whenever you yield a IEnumerator value Unity’s scheduler will actually iterate the child coroutine in place.

Of course keep in mind that a coroutine / generator method will in the end just be a compiler generated class anyways.

If you want to understand how coroutines work in Unity, I wrote a coroutine crash course some time ago.

I decided to go with an IEnumerable framework, as that is very familiar and pretty easy to wrap Unity’s existing classes. Was able to get several up and running quickly:

  • No-op
  • Wait for n seconds
  • Wait until some user-defined predicate function
  • Wait until some key-press
  • (todo) Wait for some event.

Here is the code:

    class NoWaitEnumerable : IEnumerable
    {
        public IEnumerator GetEnumerator()
        {
            yield break;
        }
    }
    class WaitUntilEnumerable : IEnumerable
    {
        private Func<bool> _predicate;
        public WaitUntilEnumerable(Func<bool> predicate)
        {
            _predicate = predicate;
        }
        public IEnumerator GetEnumerator()
        {
            yield return new WaitUntil(_predicate);
        }
    }
    class WaitForSecondsEnumerable : IEnumerable
    {
        private float _seconds;
        public WaitForSecondsEnumerable(float seconds)
        {
            _seconds = seconds;
        }
        public IEnumerator GetEnumerator()
        {
            yield return new WaitForSeconds(_seconds);
        }
    }
    class WaitForKeyPressEnumerable : MonoBehaviour, IEnumerable
    {
        public KeyCode Key { get; set; }
        private bool _predicate;
        public IEnumerator GetEnumerator()
        {
            _predicate = false;
            yield return new WaitUntil(() => { return _predicate; });
        }

        [ExecuteAlways]
        void Update()
        {
            if (Input.GetKeyDown(Key))
            {
                _predicate = true;
            }
        }
    }

Pretty easy to use.

        public IEnumerable TimingControlYield { get; set; }
...
            foreach (var target in _juiceList.Keys)
            {
                var (spawnOrder, juice) = _juiceList[target];
                target.SetActive(true);
                if (juice != null) StartCoroutine(juice.Play(this));

                yield return TimingControlEnumerable.GetEnumerator();
            }
            yield return null;

Where the TimingControlEnumerable is an IEnumerable property that can be set by the user to control how to proceed:

mySpawnerJuice.TimingControlEnumerable = new WaitForSecondsEnumerable(_spawnInterval);