Search Unity

[Tutorial] Simple co-routine handling and WWW client wrapper

Discussion in 'Community Learning & Teaching' started by jordan-anderson, Aug 26, 2015.

  1. jordan-anderson

    jordan-anderson

    Joined:
    Aug 5, 2013
    Posts:
    2
    Problem
    Handling Co-routines may get messy if you don't have a good method of handling them, especially if you're not familiar with the concept. Below I have POC'd a simple way of handling methods which require you to execute using the StartCoroutine function. for this post we will be creating a simple WWW client which will have its request queued and processed via StartCoroutine.

    Solution - CommandQueue / WWWClient
    Firstly we will use the Command pattern to encapsulate our requests into an object. These objects will be stored and then processed by the CommandQueue which we will create down below. The definition of the command pattern:

    Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

    More information about the Command Pattern can be found here - Click me!
    Code (CSharp):
    1. public class CommandQueue : MonoBehaviour
    2.     {
    3.         static readonly Queue<ICommand> Commands = new Queue<ICommand>();
    4.         public void Update()
    5.         {
    6.             if (Commands.Count != 0)
    7.             {
    8.                 ICommand command = Commands.Dequeue();
    9.        
    10.                 StartCoroutine(command.Request());
    11.             }
    12.         }
    13.         public static void Queue(ICommand command)
    14.         {
    15.             Commands.Enqueue(command);
    16.         }
    17.     }
    The Queue<ICommand> will be responsible for storing all of our command requests in one place ready for execution. Each command in the queue is dequeued and passed to the StartCoroutine method at the rate of once per-frame. If you require the the commands to be processed faster you could change the if statement to a loop although this may have an impact on performance.

    Now that we have a class to process our co-routine requests we need to encapsulate a request into a command object. For this example we will be encapsulating web request. It is best practice to use the StartCoroutine function when waiting for a response back from a request made via the WWW class in unity. The following code helps encapsulate this request and then adds it to the queue where it will eventually be processed automatically.


    WWW Client
    Code (CSharp):
    1. public class WWWClient
    2.     {
    3.         private readonly Uri _baseAddress;
    4.         public WWWClient(Uri baseAddress)
    5.         {
    6.             _baseAddress = baseAddress;
    7.         }
    8.         public void GetAsync(string path, Action<WWW> requestCallback)
    9.         {
    10.             Uri requestPath = _baseAddress.AddPath(path);
    11.             ICommand getRequest = new WWWCommand
    12.             {
    13.                 Path = requestPath,
    14.                 RequestCompleted = requestCallback
    15.             };
    16.             CommandQueue.Queue(getRequest);
    17.         }
    18.         public void PostAsync(string path, byte[] data, Action<WWW> requestCallback)
    19.         {
    20.             Uri requestPath = _baseAddress.AddPath(path);
    21.             ICommand postRequest = new WWWCommand
    22.             {
    23.                 Path = requestPath,
    24.                 Data = data,
    25.                 RequestCompleted = requestCallback
    26.             };
    27.             CommandQueue.Queue(postRequest);
    28.         }
    29.     }
    For the moment this class has been kept simple with two method a Get and a Post. Each method constructs its request into a WWWCommand object and passes it to the commandQueue where it will get queued until it is processed. For usages see below

    Constructor -
    • Parameters
      • baseAddress - the base addess is the domain in which you are creating the client for e.g www.BaseAddress.com/[Not Including This Part] / [Or This]
    GetAsync -
    • Parameters
      • Path - The path to the required resource not including the base address e,g www.[Not this part].com/[Just Anything From This Point On-wards]
      • RequestCallback - when the request is completed it will execute our call back action passing it the response for us to process however we like
    Get request in action
    Code (CSharp):
    1. WWWClient client = new WWWClient(new Uri("http://www.FooBar.com/"));
    2. client.GetAsync("Home/", (www) => FooBar(www.text));
    PostAsync -
    • Parameters
      • Path - Same as above
      • Data - this contains our data we wish to post to the server converted to byte array
      • RequestCallback - Same as above
    Post request in action
    Code (CSharp):
    1. const string postdata = "CreateAccount";
    2. byte[] byteData = Encoding.UTF8.GetBytes(postdata);
    3. WWWClient client = new WWWClient(new Uri("http://www.FooBar.com/"));
    4. client.PostAsync("Account/", byteData, (www) => FooBar(www.text));
    Finally we need to create our Comand objects.The WWWCommand class below allows us to encapsulate our WWW request. This class inherits from ICommand which the CommandQueue requires in order for it to be queued and processed. The Benefit of this is that we can have any object inherit from ICommand where want our request to be process in a co-routine.

    ICommand
    Code (CSharp):
    1. public interface ICommand
    2.     {
    3.         IEnumerator Request();
    4.     }
    WWW Command
    Code (CSharp):
    1. public class WWWCommand : ICommand
    2. {
    3.         public Uri Path { get; set; }
    4.    
    5.         public  byte[] Data { get; set; }
    6.    
    7.         public  Dictionary<string, string> Headers { get; set; }
    8.    
    9.         public Action<WWW> RequestCompleted { get; set; }
    10.    
    11.         public IEnumerator Request()
    12.         {
    13.             WWW client = new WWW(Path.ToString(), Data, Headers);
    14.             yield return client;
    15.             if (RequestCompleted != null)
    16.                 RequestCompleted(client);
    17.         }
    18. }
    Putting it all together
    To use, all you have to do is Attach the CommandQueue to an empty gameobject within your scene then you are free to new up the WWWClient at any point to make requests. Any questions or improvements don't hesitate to comment! :)
     
    Last edited: Aug 27, 2015
    SubZeroGaming and Paulohmm like this.
  2. SubZeroGaming

    SubZeroGaming

    Joined:
    Mar 4, 2013
    Posts:
    1,008
    Very well done!