Search Unity

Bandwidth of ClientRpc vs sending messages

Discussion in 'Multiplayer' started by emrys90, Sep 2, 2016.

  1. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    Is it more bandwidth friendly to send a message to all clients (including a netId to reference who sent it to get the same functionality that a ClientRpc would have) than it is to do a ClientRpc? I'm sending player input to all clients usually around 20-30 times per second and I keep running into issues. I've increased buffer limits, etc, so now I'm trying to just optimize everything as much as possible. Each message I am sending about 22 bytes of data I believe as parameters to the ClientRpc.

    I would just profile it and find out... but I have yet to discover a way to profile the amount of bytes being sent/received in UNET.
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    I don't know the bandwidth, but note that your message doesn't need to include an explicit field for the sender; you should be able to identify the sender of a message based on the ID of the connection it arrived from. e.g.
    Code (CSharp):
    1.         protected void OnClientJoined(NetworkMessage msg)
    2.         {
    3.             Debug.Log("Connection Id: " + msg.conn.connectionId);
    4.         }
    (Even ignoring bandwidth, this is a better way to do things because it makes it harder for one client to impersonate another.)
     
  3. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    These messages are being sent from the server, to other clients, to receive a players input. On the client, you have to use ClientScene.FindLocalObject, and so I would need to pass the netId to the client.

    You can't send a message from one client to another, so msg.conn wouldn't work for this I don't think.

    I send player input from the local client to the server, and then out to all clients. The clients then run that input to move their player on the remote clients machine. I'm trying to optimize that process.
     
  4. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    It's horribly complex and convoluted but I *think* this is probably the best way to do things.
    https://bitbucket.org/Unity-Technol...nsform.cs?at=5.4&fileviewer=file-view-default

    Initialization (the same for both server-controlled and client-controlled objects)
    Step 1: Instantiate object locally on server.
    Step 2: Set objects position/rotation/velocity/etc. locally on server
    Step 3: Spawn object on clients. This calls OnSerialize/Deserialize
    Step 4: Read OnDeserialize and set initial position/rotation/velocity/etc. on clients
    Step 5: Client's first frame happens with up-to-date information.

    Updates
    For server controlled objects
    Step 1: Set the position/rotation/velocity/etc. locally on server every Update/FixedUpdate
    Step 2: Periodically (at some interval) send this data to clients (2 options: either OnSerialize or messages)

    For client controlled objects
    Step 1: Set the position/rotation/velocity/etc. locally on client every Update/FixedUpdate
    Step 2: Periodically (at some interval) send this data to server by (2 Options: message or [Command]... OnSerialize cannot be used for client --> server)
    Step 3: Read data on server and update position/rotation/velocity/etc. Then replicate this data to other clients (3 options, by message, by OnSerialize, or by [ClientRPC])
    Step 4: Read message, OnDeserialize, or clientRPC on other clients. If you also send this data back to the client owner then ignore it.

    Regardless of what you do you must use a mix both messages/Commands and OnSerialize. Messages/[Command]'s are needed to send client --> server data. And OnSerialize is needed to get the most up-to-date information for new spawned objects and also for late connecting players (otherwise the object will be at the wrong rotation/velocity/etc. until the first message is received)

    If you want to throw up I understand...
     
    Last edited: Sep 2, 2016
  5. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    Thanks for the reply, but I think most of that won't apply to my scenario. It's good to know that a message is less bandwidth though, that's what I was thinking. Do you happen to know a way that I can find out the amount of bytes a message is (including header/anything else UNET sends). I'd like to be able to see a before and after value to know how many bytes I have saved by changing it.

    As to why that doesn't apply to my scenario: I am setting the objects current position/rotation in SyncVars that have a sync interval of basically infinity, so it only will send the data for newly spawned objects. The input that I am sending from client->server->all clients is actually the players input of left, right, back, forward, etc and applying that to their copy of their player object on that players client.
     
  6. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    I think messages are
    16bits of Ushort Msg.Type + payload. There may be other header stuff but I'm not sure.

    Be a little careful with the SyncVars and hooks.
    If you move right (but the message isn't received by server yet) and then you change scenes or another client connects then the server will replicate the old position back to all clients (including the owner) and set the position to an old value. A workaround is to use SyncVar hooks and ignore the value if you are the owner.
     
  7. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    I'm not using hooks for these, and the client does not receive future updates for these sync vars because I set the interval to basically infinity. That means all sync vars on this script will only be sent for spawn messages.
     
  8. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    The SyncVar will be sent when it's spawned like you say, but it is also sent whenever a new client connects or a scene is changed.

    I think the part about a persistent object being OnSerialized during a new scene load (and treated as if it was a new scene object being spawned) is a bug.
     
  9. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    Yes, but it is only sent to the client that just connected, not to all clients. That's another spawn message. Don't worry, my player movement code itself seems to be working fine. It's been pretty good, my only issues are the bandwidth with the server getting overloaded and producing the common bandwidth errors UNET gives.
     
  10. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Ah maybe you will be OK. I did have intermittent issues if SyncVars are changed right around a scene change (if the timing was just right) so maybe look out for that. Hopefully you don't have problems.

    Maybe another reason for your bandwidth usage could be OnSerialize. If you have a script with SyncVars even with NetworkSendInterval of ~inf the script will still send 32bits every OnSerialize. The script itself will never call OnSerialize (except on spawn, other client connect, or scene change)... but if you have other scripts on the same object then it will call OnSerialize on the unused script with SyncVars and write 32 zero bits. So if you have NetworkAnimator or anything else attached then your SyncVar script will chew up bandwidth even if send interval is infinity. Not sure if that makes sense. But if you have a bunch of scripts all forcing OnSeriailze to be called a lot then they will consume a lot of bandwidth (approx. proportional to numScripts ^ 2).

    How many NetworkBehaviour scripts are on your player object?
     
  11. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    Yeah, that could very well be an issue. I have 12 at the moment, with the highest sync interval as instant but it rarely changes, and the next highest is 10 per second.
     
  12. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Hmm maybe that's it. 10x per second on 12 scripts will be 10x12x32 = 3840 bits/sec per object. This doesn't include payload or header overhead.
     
  13. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    My issues usually start once I get around 5-10 players online, so that would be around 15-30KB per second just for that, and that's per connection I would think, so actually server uploading for 10 players could be around 300KB per second for this. That could be it.
     
  14. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Another thing I found is that chewing up bandwidth for me was repeatedly setting a SyncVar to the same value on a script with NetworkSendInterval = 0. It flagged the dirty bit even though the value didn't change and was eating up bandwidth. I think I saw Unity was going to address that, but unsure if it's fixed.
     
  15. emrys90

    emrys90

    Joined:
    Oct 14, 2013
    Posts:
    755
    Hmm okay. I'll keep an eye out for that. Thanks.
     
  16. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    Ah yep you are probably right. I forgot about the fact that the server has to send information to each client.

    Luckily Unity gives options for custom serialization and messages if the HLAPI doesn't work out. That's what I'm doing (For 2 reasons. 1: for bandwidth 2: because Unity does not give a way for client to send information to other clients.
     
  17. Zullar

    Zullar

    Joined:
    May 21, 2013
    Posts:
    651
    What I'm going to do is this.

    1: Making a custom reader/writer than can write bits instead of full bytes.
    2: Creating CustomSyncVars and they will be class type objects and all in 1 script. They can be referenced externally.
    3: Each SyncVar will have a min/max NetworkSendInterval and an internal dirtBit. It's silly if an SyncVar sends every 100ms and a packet is being sent but only 99ms have passed since last send that it doesn't piggy pack onto the packet. So sendInterval could be set to 50msMin and 100msMax.

    I will use OnSerialize for Server --> Client and Messages (or Commands?) for Client --> Server

    If OnSerialize is called it will write zero byte if not updating anything
    If it is writing then it will write 1 bit. Then...
    ForEach CustomSyncVar in list
    if CustomSyncVar isDirty && timer expired
    write 1bit + payload
    else
    write 0bit
    then roundup the package to the nearest whole byte and write

    For customSyncVarBools it doesn't write dirtyBit, it just always writes value.

    For icon colors that have 8 choices I will write 3 bits (2^3) instead of a full byte.

    SendInterval will be increased for off-screen objects.

    I'm still working out the details. But it *should* be much better (maybe like 1/5th the bandwidth) of the default serialization.

    You could try something similar. Customize your serialized data based on the way your game works.

    The real ugly part is dealing with client controlled objects.