1

I have an event function which is called by some library.

// Called when an event is received
void OnEvent(EventData data) {
    // something here
}

I need to make coroutine wait until the OnEvent() function is called and receive the EventData or timeout.

I could use a custom WaitFor... function like that:

IEnumerable WaitForEventOrTimeout(int timeout, outc EventData eventData) {
    // Something
}

IEnumerable coroutine() {
    EventData eventData;

    yield return WaitForEventOrTimeout(4 /*timeout in 4 seconds if nothing received*/, eventData);

    if (evnt == null) {
        // Didn't receive event because timeout happened, handle that
    }

    // Do stuff with received event
}

But I don't know how to implement it.

macroni
  • 11
  • 2
  • https://stackoverflow.com/questions/30056471/how-to-make-the-script-wait-sleep-in-a-simple-way-in-unity – Simonster Mar 26 '21 at 15:59

1 Answers1

3

First of all, I wouldn't use a coroutine to achieve that, I'll simply use an EventHandler or a direct delegate, but if your requeriment is to "make a coroutine" I'll aproach it like this:

using System.Collections;
using UnityEngine;

public class SOExample: MonoBehaviour
{
    public bool OnEventCalled = false;
    
    private void Start()
    {
        //Starts coroutine to wait until OnEvent
        StartCoroutine(MyOwnCoroutine());
    }

    public void OnLibraryEvent(string data)
    {
        Debug.Log("OnLibraryEvent");
        OnEventCalled = true;
    }

    private IEnumerator MyOwnCoroutine()
    {
        Debug.Log("Waiting");
        yield return new WaitUntil(() => OnEventCalled);
        Debug.Log("Do stuff with received event");
    }
}

The key is to use the WaitUntil yield instruction, and use a custom flag, in this example I've used a simple boolean, but you can use whatever you want as a delegate.

Remember to set to false again the flag if you want to reuse it!

If you want to go further and create your own custom yield instruction try with CustomYieldInstruction class, but is similar under the hood.

EDIT: Can probably be better implemented but this aproximation with CustomYieldInstruction currently works:

public class WaitForEventOrTimeout : CustomYieldInstruction
{
    //used to call StartCoroutine inside this class
    private MonoBehaviour mono = null;                  

    private Func<bool> checkIfEventIsTriggered = null;  
    private float waitingTime = 0;
    private bool timeoutTriggered = false;

    //this will be checked continously while returns true
    public override bool keepWaiting
    {
        get
        {                
            return !timeoutTriggered && !checkIfEventIsTriggered();
        }
    }

    //Constructor called when "new"
    public WaitForEventOrTimeout(MonoBehaviour mono, Func<bool> checkIfEventIsTriggered, float waitingTime)
    {
        this.mono = mono;
        this.waitingTime = waitingTime;
        this.checkIfEventIsTriggered = checkIfEventIsTriggered;

        //Starts countdown to timeout
        mono.StartCoroutine(WaitForTimeout());
    }

    private IEnumerator WaitForTimeout()
    {
        yield return new WaitForSeconds(waitingTime);
        timeoutTriggered = true;
    }        
}

Use it like:

using System;
using System.Collections;
using UnityEngine;

public class SOO : MonoBehaviour
{
    private bool _onEvent = false;
    public bool OnEvent() => _onEvent;

    private void Start()
    {
        //Starts coroutine to wait until OnWaitForEventOrTimeout
        StartCoroutine(MyOwnCoroutine());
    }

    public void OnLibraryEvent()
    {
        //When LibraryEvent is called, set the flag to true
        _onEvent = true;        
    }

    private IEnumerator MyOwnCoroutine()
    {
        Debug.Log("Waiting");
        yield return new WaitForEventOrTimeout(this, OnEvent, 4);
        Debug.Log("Do stuff with received event");
        //Reset flag
        _onEvent = false;
    }
}
Lotan
  • 4,078
  • 1
  • 12
  • 30