Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Networking Best practice for serializing a large array of integers.

Discussion in '5.1 Beta' started by Shinyclef, Jun 7, 2015.

  1. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    Hello,

    I'm working with the custom serialization methods with my network objects.
    Here is my OnSerialize method, and the Encode method that is called within it.

    Code (csharp):
    1. public override bool OnSerialize(NetworkWriter writer, bool initialState)
    2. {
    3.     writer.Write(ClusterSet.gameObject);
    4.     writer.Write(GridPosition); // this is my own struct. I have written an extension method that writes 3 ints
    5.     writer.Write(Encode()); // a string created by the Encode method
    6.     return true;
    7. }
    8.  
    9. private string Encode()
    10. {
    11.     StringBuilder sb = new StringBuilder();
    12.     for (int i = 0; i < CLUSTER_RES_3; i++)
    13.     {
    14.         sb.Append(IntArr[i]).Append(",");
    15.     }
    16.     String s = sb.ToString();
    17.     Debug.Log(System.Text.ASCIIEncoding.Unicode.GetByteCount(s)); // log output: 16384
    18.     return s;
    19. }
    This works fine without trying to send the results of the Encode method.
    I'm trying to send 4096 integers. Not so surprisingly, I get an error "Failed to send big message".

    I will be trying to use things like run-length encoding later on, but wanted to ensure a worse case scenario works first, so I'm trying to get it work with no encoding for now. Each integer is separated with a ',' to split the string on the other side. I believe using a string like this is a horrible way to do it.

    I see a couple of candidates in the NetworkReader class:
    - NetworkWriter.WriteBytes()
    - NetworkWriter.WriteBytesAndSize()
    - NetworkReader.ReadMessage()

    My question is, what is best practice (or suggestions/ideas) for trying to serialize a large array of ints like this to send over the network via OnSerialize:
    - without any encoding
    - with something like run-length encoding (think of these ints representing blocks in a voxel-like scenario)

    Also, what is the maximum length of a string you can send?
    Network.Reader.ReadString states: Reads a string from the stream. (max of 32k bytes).
    But I believe I only tried to send about half that.

    Thank you :)
     
  2. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    This information log appears before the 'Failed to send big message' error:
    Wikipedia has given me some answers on this. http://en.wikipedia.org/wiki/Maximum_transmission_unit
    MTU is the maximum transmission unit. 1500 bytues seems to be a fairly common limitation across the internet, and I'm trying to send much more. So I need another strategy to split this data up into several smaller packets.

    I think therefore a good strategy might be to send several packets of shorts (as I doubt I'll need the capacity of ints for my needs) via RPCs, with a boolean flag indicating if it is the last packet.

    It's a bit of a shame that I can't send all the data about my objects across the OnSerialize method, but it is what it is.

    I'll post back how I go to let others fellow serializers know.
     
  3. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    You might want to consider the super fast lz4 compression algorithm, there was some mention that it might be integrated into Unity in relation to their 2D tile maps.

    I could also be a great utility for networking larger packets.
     
  4. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    Will definitely check it out! Fast compression algorithms are definitely on the ToDo list.

    I tried to get around the 1500 byte limit problem by calling an RPC a bunch of times with split up packets of data, with the last one flagged as last. Made some progress, and found some hurdles. Here's what I've learnt.
    • You cannot call RPCs from OnSerialize, so I'm assuming this means during OnSerialize, you are bound to one 'message' with a Max Transmission Unit (MTU) (max packet size) of 1500 bytes. Trying to call an RPC from OnSerialize will cause an error. Don't know if there's a way around this at the moment.
    • You can get around this somewhat by starting a coroutine that waits a frame.
    • The packets are received in reverse order on the LocalClient (the 'final' packet is received first). Think I read about that somewhere so think it's a known bug, but does kind of kill that strategy at least for now.
    • OnStartServer() is called only once for a spawned object. OnStartClient is called for each client that is connected at the time. Additional clients that connect will also call OnStartClient, but the server will not call OnStartServer again. You therefore cannot use OnStartServer to send additional data to the client if you hit the 1500 MTU limit in OnSerialize. You'll need to work something else out, like RPCs.
    Still looking for a smarter way to send this data.
     
  5. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    There is a Fragmented channel QoS type, this supports message lengths of up to 64k. Try adding a new channel of that type, and using it for your large messages.
     
  6. Shinyclef

    Shinyclef

    Joined:
    Nov 20, 2013
    Posts:
    505
    Thanks @seanr. I've been looking in that direction a little but is also not giving me the results I need.
    I'll be as clear as I can to show what I'm trying to do, and what I've tried.

    Problem:
    • The data want to send during OnSerialize is too large for the OnSerialize method, which throws an error if sending more than 1500 bytes (MTU).
    Requirements:
    • Send large amount of data (let's say 10k bytes) for object setup when clients have an object spawned.
    • To only send that data to the clients that are spawning that object.
    • Be able to take advantage of built in area of interest features like NetworkProximityChecker. I assume this allows clients to join and only spawn objects that are close by, while despawning things as they move out of range, so I want to take advantage of that.
    Avenues Explored:
    • Splitting the message up myself into sub-1500byte packets and calling an RPC multiple times, with a 'last packet' marker, from the OnSerialize method.
      Not suitable because the RPC cannot be accessed before the object is ready, which only occurs after OnSerialize is finished. Succeeded by waiting a frame via a coroutine and then sending, but RPCs ended up backwards on LocalClient. Also, this is super messy. More importantly, I don't want to send this to every player, only the player client spawning the object.
    • Creating a message by inheriting from MessageBase, and using that send to the target player, with a netID to identify the object to apply the serialized data to.
      I need a send method that allows me to select a single client to send to (the client spawning the object), and also select the channel (need fragmented type for large message). But, during OnSerialize, I have no way of knowing which player this data is being serialised for.
    • Use a method like NetworkServer.SendBytesToPlayer(), NetworkConnection.SendWriter() or similar, but call it from NetworkServer.OnServerReady(NetworkConnection conn). Here, I can use the connection object to determine which player to send the data to. But this way, I cannot determine which objects should send data. I only want to send data from objects in the player's area of interest, and this also does not work at all for spawning new objects in the game while the player is already connected.
    Feature Wish:
    • I still haven't found a good solution. I would love a way to easily send more than 1500 bytes of data on OnSerialize for object spawning setup. Maybe if we could choose the channel to use for OnSerialize or something like that...
    If anyone can I think of good way to send large amounts of data on object spawning you'd be saving me...


    • Update 1: I have succeeded in sending large messages via NetworkServer.SendByChannelToAll(...), however, there is no method available to send by channel to client (unless I'm utterly blind).
    • Update 2: I think Network.NetworkConnection might hold the key. It looks like it might be a connection object per player, and through it you might be able to send a message on a specific channel.
     
    Last edited: Jun 8, 2015
  7. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    Did you see the ObjectVisibility page of the Manual? It describes the observer system for doing this.

    TLDR objects are observed by clients (connections), so updates to that object are only sent to it's observers. NetworkProximityChecker is implemented using the public API of observer system.