Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unity3D, how to process events in the correct thread

I'm writing a Unity3D script and using a networking library. The library emits events (calls delegates) when data is ready. My library reads that data and emits events which try to access GameObject but I get these errors

CompareBaseObjectsInternal can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread 
when loading a scene. Don't use this function in the constructor or field 
initializers, instead move initialization code to the Awake or Start function.

ArgumentException: CompareBaseObjectsInternal can only be called from the main 
thread. Constructors and field initializers will be executed from the loading 
thread when loading a scene. Don't use this function in the constructor or field 
initializers, instead move initialization code to the Awake or Start function.

That's fine. I think I understand that the network messages are being handled on a separate thread and Unity3D is trying to point that out and how it's not thread safe to go manipulating stuff from another thread.

My question is: What's the recommended way to deal with this?

What I'm thinking is instead of calling the delegate directly in my handler I'll make some kind of object that references all the parameters delivered in the event and adds them to a List<MyEvent> of events.

I'll have to make a thread safe wrapper class to contain that list that lets me add and remove events with a lock because List<> is not thread safe.

I'll then somehow need to process those events on the main thread. I'm not sure what's the best way to do that. The class that's dealing with the network is a MonoBehavior itself so I guess I can have its Update method remove events from the list and send them.

Does this sound like a good idea or is there some other, better, or simpler way to achieve this?

like image 675
gman Avatar asked Mar 19 '14 17:03

gman


2 Answers

I created a simple class for this reason that you can call with a one-liner.

You can use it like this:

public IEnumerator ThisWillBeExecutedOnTheMainThread() {
    Debug.Log ("This is executed from the main thread");
    yield return null;
}
public void ExampleMainThreadCall() {
    UnityMainThreadDispatcher.Instance().Enqueue(ThisWillBeExecutedOnTheMainThread());
}

Simply head over to https://github.com/PimDeWitte/UnityMainThreadDispatcher and start using it if you'd like.

Cheers

like image 149
Pim de Witte Avatar answered Sep 29 '22 18:09

Pim de Witte


Here's what I ended up with.

public class EventProcessor : MonoBehaviour {

    public void QueueEvent(Action action) {
        lock(m_queueLock) {
            m_queuedEvents.Add(action);
        }
    }

    void Update() {
        MoveQueuedEventsToExecuting();

        while (m_executingEvents.Count > 0) {
            Action e = m_executingEvents[0];
            m_executingEvents.RemoveAt(0);
            e();
        }
    }

    private void MoveQueuedEventsToExecuting() {
        lock(m_queueLock) {
            while (m_queuedEvents.Count > 0) {
                Action e = m_queuedEvents[0];
                m_executingEvents.Add(e);
                m_queuedEvents.RemoveAt(0);
            }
        }
    }

    private System.Object m_queueLock = new System.Object();
    private List<Action> m_queuedEvents = new List<Action>();
    private List<Action> m_executingEvents = new List<Action>();
}

The startup code in some other class adds one of these to the GameObject that started it. The networking thread adds Action by calling eventProcessor.queueEvent and the Update function above ends up executing those actions on the main thread. I pull all the actions to another queue to keep things locked as little as possible.

I guess this wasn't that hard. I was just wondering if there was some other recommended solution.

like image 32
gman Avatar answered Sep 29 '22 16:09

gman