Working with Asynchronous Operations in Unity3d

| Mar 13, 2014 min read

We’re building a fairly large framework of components for our new game Bedlam. As part of these web services contracts we need to be able to very rapidly fire off commands to the server to persist data, fetch status updates for user collision detection and a bunch of other stuff. We are using the excellent (RestSharp)[http://restsharp.org] library for all of our state-ful, non-socketed network communication. This library provides a really handy template method for firing Asynchronous requests and we ultimately want to use that for every single call that goes out to the server. Ordinarily we’d just throw these calls in an IEnumerator block and forget about it but for this particular product we wanted to make sure that we were locking down all the pieces as we go. We needed a more thoughtful solution.

The basics of what we’ve done to work around the fact that Unity only allows you to modify the state of a GameObject on the main thread is by implementing a message queue. This centralized singleton provides us with a common broker for all network response handling and there’s really no limit to what you can do once you’ve implemented it for dealing with asynchronous operations.

The basic MessageQueue class:

// In case we need to work with unity native types.
using UnityEngine;
// For the messages list.
using System.Collections.Generic;

public class MessageQueue
{
    private static MessageQueue _instance;
    public static MessageQueue Instance
    {
        get
        {
            if (_instance == null) {
                _instance = new MessageQueue();
            }
        }
    }

    private List<string> serverMessages;

    public int MessageCount 
    {
        get {
            return this.serverMessages.Count;
        }
    }

    public MessageQueue ()
    {
        this.serverMessages = new List<string>();
    }

    public string ShiftMessage ()
    {
        string result = "";
        if (this.serverMessages.Count > 0) {
            result = this.serverMessages[0];
            this.serverMessages.RemoveAt(0);
        }
        return result;
    }

    // Here you could wrap the insertion of a new message to take some other action
    // when new data comes in, fire an event, etc.
    public string PushMessage (string msg)
    {
        this.serverMessages.Add(msg);
    }
}

This class contains all of what we need to handle simple text messages from the server (like Operation Successful, Operation Failed) along with any metadata we might want to include. The serverMessages member could just as easily be a dictionary that contains the response from your web service.

In our main network broker class we have an IEnumerator:

private IEnumerator ManageQueue ()
{
    // This enumerator should run for the lifetime of our app.
    while (true) {
        float delay = 3f;
        if (MessageQueue.Instance.MessageCount > 0) {
            string msg = MessageQueue.Instance.ShiftMessage();
            Camera.main.GetComponent<GUIWrapper>().statusString = msg;
            if (msg.IndexOf("Error") > -1) {
                delay = 6f;
            }
        } else {
            Camera.main.GetComponent<GUIWrapper>().statusString = "";
        }
        yield return new WaitForSeconds(delay);
    }
}

This enumerator is invoked as a coroutine in the Awake method of the network broker and runs continuously checking the queue for new server messages. Once it finds one it assigns it to the GUI class that can display server messages. We even extend the delay until we recycle the message if there is a reported error. (I would agree our approach here is ham-fisted but hey…)

In order to push new content into the queue our network broker invokes following RestSharp method:

var client = new RestClient(url);
var request = new RestRequest(endpoint);
client.ExecuteAsync(request, response => {
    if ((int)response.StatusCode != 200) {
        MessageQueue.PushMessage(string.Format("Error: {0} - something bad happened.", response.StatusCode);
    } else {
        MessageQueue.PushMessage("YAY!");
    }
});

This allows you to pretty much manage any kind of state you want without blocking on numerous network requests that are bound to occur in your game. Please feel free to use the Ask link on my blog if you run into any problems; I’m happy to help out. I hope you find this little snippet of code useful. I highly recommend switching from Unity:WWW to RestSharp as soon as you can manage it.