Search Unity

Multi-Threading solutions? How to use Unity exclusive functions?

Discussion in 'Multiplayer' started by Christian-Tucker, Oct 29, 2014.

  1. Christian-Tucker

    Christian-Tucker

    Joined:
    Aug 18, 2013
    Posts:
    376
    I've worked on numerous projects with Unity3D which involve my own networking solution ( Stand alone server, written without the use of Unity, usually in Java or C# ) While the way you handle networking on the Unity Client is the same, how you have to go about calling Unity related functions has been more than annoying.

    Around two years ago I was doing research and it seemed like the renowned method to solve this problem was to call a function that sets some variables, then check those variables in the Update() function and do something with them, and example of what this looks like is below:

    Code (CSharp):
    1. private bool UpdateRequested = false;
    2.  
    3. // This would be called from the network thread
    4. public void ApplyUpdateRequest() {
    5.     UpdateRequested = true;
    6. }
    7.  
    8. // This would be called from the Unity thread (from update())
    9. private void HandleUpdateRequest() {
    10.     // Do Unity Related code, such as GameObject.GetComponent<Script>
    11.     UpdateRequested = false;
    12. }
    13.  
    14. void Update() {
    15.     if(UpdateRequested) {
    16.         HandleUpdateRequest();
    17.     }
    18. }
    As you can see, this can quickly cause a ridiculous amount of clutter (Example from a tutorial video I wrote):
    ApplyCharacterLocationUpdate(int, Vector3, Vector3) is what's called from the network thread
    UpdateCharacterLocationUpdate() is called from Update() when ApplyCharacterLocationUpdateRequired is true

    Code (CSharp):
    1.           bool ApplyCharacterLocationUpdateRequired = false;
    2.           int ApplyCharacterLocationUpdateID;
    3.           Vector3 ApplyCharacterLocationUpdatePosition;
    4.           Quaternion ApplyCharacterLocationUpdateRotation;
    5.           public void ApplyCharacterLocationUpdate(int networkID, Vector3 position, Quaternion rotation)
    6.           {
    7.                     ApplyCharacterLocationUpdateID = networkID;
    8.                     ApplyCharacterLocationUpdatePosition = position;
    9.                     ApplyCharacterLocationUpdateRotation = rotation;
    10.                     ApplyCharacterLocationUpdateRequired = true;
    11.  
    12.           }
    13.  
    14.           void UpdateCharacterLocationUpdate()
    15.           {
    16.                     NPlayer updatedPlayer = MasterClient.GetPlayerForID(ApplyCharacterLocationUpdateID);
    17.                     updatedPlayer.gameObject.transform.position = ApplyCharacterLocationUpdatePosition;
    18.                     updatedPlayer.gameObject.transform.rotation = ApplyCharacterLocationUpdateRotation;
    19.                     ApplyCharacterLocationUpdateRequired = false;
    20.           }

    As you can see, this is an absolute... clusterfuck. This doesn't even include the lines of code that read the data from the packet sent from the server.


    The more I think about it and realize solutions like SmartFoxServer and Photon can call Unity3D functions with ease, there's only two immediate answers.

    1: They run on the same thread, without blocking it in any way.
    2: There's something I'm missing which allows Unity to function properly while multithreading.

    Either way, both of the solutions I can come up with for the issue, I can't come up with a way to implement them. I use TcpClient on the C# side for my network client, which by default is a Blocking-IO client. There's ways to make the TcpClient Asynchronous (NIO) but I don't have any experience with it and the information I've found online is fairly limited. I don't really like C#... But, You gotta use what you gotta use.
     
  2. iNoMore

    iNoMore

    Joined:
    Oct 13, 2013
    Posts:
    64
    I am watching this post, I was thinking the same yesterday (for AI usage as well as networking)
     
  3. Christian-Tucker

    Christian-Tucker

    Joined:
    Aug 18, 2013
    Posts:
    376
    I go over my solution here --
     
  4. Glader

    Glader

    Joined:
    Aug 19, 2013
    Posts:
    456
    While the above solution outlined in the video can be interesting for some given scenarios it doesn't promise that you'll avoid race conditions (I'm assuming you're threading since you're locking, I don't have time to watch the entire video to find out). You may have a delegate pointing to a game object that for some reason is no longer technically in existence by the time the queue is serviced. This may be because a message higher up on the Queue asked it to destroy a component or GameObject in the scene. You end up with a race condition. Deciding what to do on a separate thread, unless being very careful, like this will lead you to issues you may not forsee.

    If I understand the OP's problem then you seem to have your networking related code on a separate thread other than Unity's main thread. If that is correct then I'm also assuming you're having an issue moving data from that thread to Unity's main thread so to actually use the information coming down from your server.

    If all of this is true let me describe the general way to go about solving this issue.On your networking thread you should queue up received data on either a ConcurrentQueue (I think you can access these in Unity if you add in the Parallel Library dlls although I think Webplayer builds barf in this case) or write your own locking queue and poll the queue on the main thread in some fashion. In this way you can essentially transfer information from one thread context to another in a thread safe manner.

    This is how Photon Server works (assuming since it's closed source), a library you mentioned. When you call the Poll/Service method Photon's under the hood handles it. Its queue of operations that need servicing on the networking thread, when you call the service method it calls the proper method to service those that you implement via an interface, if I remember correctly. Calling your OnEvent and OnOperationResponse and OnStatusChange etc methods.

    You can easily implement this too with the way I have described.

    Edit: I've researched more on how Photon handles this issue. They do have a Queue<Action> essentially but all those Actions point to an instance of an object that implements an IListener-like interface and once the main thread services these are dispatched, or invoked, one by one. The Listener shouldn't reasonably become null before the actual network thread stops so it works. But you have to becareful that you're not enqueueing actions with your implementation that may not be around once they're serviced.
     
    Last edited: Nov 8, 2014