Search Unity

Netcode. Is optimizing input as bitmasks worth it?

Discussion in 'NetCode for ECS' started by illinar, Nov 28, 2020.

  1. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Is it worth it to implement the input as bitmasks? The scenario is a lot of players (100 + if possible) and each player controlling multiple space vehicles. (players themselves and client-side AI vehicles) The control is not predictive. The physics sim results or transforms are sent back to players.

    Code (CSharp):
    1.  
    2. public struct VehicleMovementControl : ICommandData
    3. {
    4.     public uint Tick { get; set; }
    5.     public DirectionMask Translation;
    6.     public DirectionMask Rotation;
    7. }
    8.  
    9. [System.Serializable]
    10. public struct DirectionMask
    11. {
    12.     public const byte XPosMask = 1;
    13.     public const byte XNegMask = 2;
    14.     public const byte YPosMask = 4;
    15.     public const byte YNegMask = 8;
    16.     public const byte ZPosMask = 16;
    17.     public const byte ZNegMask = 32;
    18.  
    19.     public bool XPos => (XPosMask & Value) == XPosMask;
    20.     public bool XNeg => (XNegMask & Value) == XNegMask;
    21.     public bool YPos => (YPosMask & Value) == YPosMask;
    22.     public bool YNeg => (YNegMask & Value) == YNegMask;
    23.     public bool ZPos => (ZPosMask & Value) == ZPosMask;
    24.     public bool ZNeg => (ZNegMask & Value) == ZNegMask;
    25.  
    26.     public byte Value;
    27. }
    28.  
    Also, is it safe/reliable? As I'm reading up on this it seems very reliable for currently existing hardware.

    Or maybe Netcode would compress bools into a bitmask on its own?
     
    Last edited: Nov 28, 2020
  2. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Actually the generated serialization code is converting my bytes into uints... even though it is perfectly capable to write bytes... That is so strange. Are uints better optimized or sth?

    Code (CSharp):
    1.  
    2.         public void Serialize(ref DataStreamWriter writer, in VehicleMovementControl data)
    3.         {
    4.             writer.WriteUInt(data.translation.value);
    5.         }
    6.  
    7.         public void Deserialize(ref DataStreamReader reader, ref VehicleMovementControl data)
    8.         {
    9.             data.translation.value = (byte) reader.ReadUInt();
    10.         }
     
    Last edited: Nov 28, 2020
    manpower13 likes this.
  3. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Well for starters you have a single multi value flag. So you just need a single byte not a bit mask. Bit masks are something different.

    Netcode I'm sure uses variable length integer encoding, which encodes integers using the least amount of bytes necessary. So integers <= 127 for instance are already being sent as a single byte. That's just happening at a lower level in their code somewhere.

    So just use an int/uint/enum and get rid of all that byte stuff, it's not adding any value.

    And optimizing what the client sends the server is almost inconsequential. Client is sending data for 1 player. Server is sending data for 100 players to every client. You focus on optimizing what the server sends.
     
    apkdev and Lukas_Kastern like this.
  4. StickyTommie

    StickyTommie

    Joined:
    Oct 23, 2019
    Posts:
    13
    Do you have anything to back this claim up? Or do you just assume that they use variable length int encoding "somewhere"?
    I am also trying to pack player IDs into a single byte for network traffic, as it would only take up 1 byte. but the auto generated serializer turns it into a uint anyway.
     
  5. timjohansson

    timjohansson

    Unity Technologies

    Joined:
    Jul 13, 2016
    Posts:
    473
    The templates we use to generate serialization are optimizing for snapshots sent from server -> client since they are generally much bigger than commands. You can open `Multiplayer > Open NetDbg` and connect when in play mode to see the size of snapshots vs commands in your game.
    For snapshots we use uints because we write them using WritePackedUIntDelta - which will use variable length encoding based on huffman tables. We do not have a WritePackedByteDelta because it does not have any benefit over uint.

    Command packets writes the most recent command using WriteUInt - which is not using variable length - then it writes 3 previous commands using WritePackedUIntDelta. We could potentially use WriteByte for the first command we send and save a few bytes, but the savings from doing so would be very small since it would only change the size for 1/4 commands in the packet so it is not something we have prioritized optimizing yet.
     
    manpower13 likes this.