Question

So I would like to make an object that has a bunch of static methods to it. The methods are an API to a remote server. I was reading and thought I could use the unity StartCoroutine method but thats not available in this situation, so now I don't know where to go.

The general idea is I want to be able to call one of my object's methods, pass it a delegate and have id go off an do its work. When its done, call the delegate with the results. I can't use threading as Unity3D is not thread safe.

I know c# has this yield thing and I've read about it several places and it still confuses me. How can I refactor my code below so I can accomplish what I'm attempting to do?

public class Server
{
        private static string baseURL = "http://localhost/game.php";
        private static Hashtable session_ident = new Hashtable();

        //--- Public API
        public delegate void DeviceSeenCallback(bool seen);
        public static void DeviceSeen(DeviceSeenCallback callBack) {
                StartCoroutine(DoDeviceSeen(callBack));
        }

        public delegate void AuthenticateCallback(bool authenticated, string errorMessage);
        public static void Authenticate(string username, string passwordHash, AuthenticateCallback callBack) {
                StartCoroutine(DoAuthenticate(username, passwordHash, callBack));
        }


        //--- Private API
        private static IEnumerator DoDeviceSeen(DeviceSeenCallback callBack)
        {
                WWWForm form = new WWWForm();
                form.AddField("deviceID", SystemInfo.deviceUniqueIdentifier);

                WWW www = new WWW(baseURL + "?cms=seen", form.data, session_ident);
                        yield return www;

                // Check for errors
                callBack(ResultIsOk(www.text));
        }

        private static IEnumerator DoAuthenticate(string username, string passwordHash, AuthenticateCallback callBack)
        {
                WWWForm form = new WWWForm();
                form.AddField("deviceID", SystemInfo.deviceUniqueIdentifier);
                form.AddField("deviceType", SystemInfo.deviceType.ToString() + "||" + SystemInfo.deviceModel);
                form.AddField("user", username);
                form.AddField("pass", passwordHash);

                WWW www = new WWW(baseURL + "?cms=auth", form.data, session_ident);
                        yield return www;

                if (ResultIsOk(www.text)) {
                        callBack(true, "");
                } else {
                        int code;
                        string message;
                        ResultGetError(www.text, code, message);
                        callBack(false, message);
                }
        }

        private static bool ResultIsOk(string resultText) {
                return false;
        }

        private static void ResultGetError(string resultText, out int code, out string message) {
                code = -1;
                message = "Some Error Message";
        }
}
Was it helpful?

Solution

The trick you always run into with Unity, is that the only context under which your code runs is during script callbacks.

This basically means that eventually somewhere, you must have a GameObject in a scene with a MonoBehaviour attached to said object that calls the code you want to run repeatedly.

Aside from the fact that there is no StartCoroutine available other than in the instance of a class inheriting from MonoBehaviour, you have the problem that you need to have code execute every frame or so to check on something. There's no real way around this other than by making a MonoBehaviour, just embrace it.

A common pattern is to create a singleton-esque GameObject that either has DontDestroyOnLoad called on it, or is recreated as needed for each scene. You could even designate one object as a "coroutine bucket" that isn't tied to a specific class or anything. Literally an empty MonoBehaviour whose sole purpose in life is to start coroutines.

You can quickly solve this problem by making your Server class a MonoBehaviour:

And then by adding a singleton property:

    private static Server ServerObject {
        get { 
            if (_serverObject == null) {
                var gameObj = new GameObject("Server Object");
                _serverObject = gameObj.AddComponent<Server>();
                GameObject.DontDestroyOnLoad(_serverObject); // Optional
            }
            return _serverObject;
        }
    }
    private static Server _serverObject;

(Singletons are gross, but it's a necessary evil in this circumstance)

And then by changing all your StartCoroutine calls to be called on the singleton MonoBehaviour instance:

    public static void DeviceSeen(DeviceSeenCallback callBack) {
            this.ServerObject.StartCoroutine(DoDeviceSeen(callBack));
    }

GoKit uses this pattern and makes a single GameObject named "Go" that processes all the logic for tweens. Since Unity doesn't provide a way to specify scripts that run regularly without being attached to a game object, this is the best we can do.

If your Server class would maintain a lot of state, or it's not essential that it remain part of th escene, it may be advisable that you don't call DontDestroyOnLoad on it. If the scene changes and the ServerObject game object is deleted, subsequent checks to _serverObject will come up as null and thus be reinstantiated (since Unity overloads the null conversion operator).

OTHER TIPS

For unity you usually have to do manual threading (unless your threads don't make any calls to the Unity API). That is, have e.g. Authenticate push a function which polls for its result (no blocking calls!) onto a private static list. Then provide a static Update method which goes through the list and calls each polling method, removing it if it indicates that it has finished. When each polling method finishes, it should call the associated callback. Arrange to have your application call this static Update method regularly (e.g. in the usual unity Update pass).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top