Search Unity

NetworkDictionary: Difficulty with Generics and FastBufferReader/Writer

Discussion in 'Netcode for GameObjects' started by Salmonman, Oct 29, 2022.

  1. Salmonman

    Salmonman

    Joined:
    Jul 14, 2016
    Posts:
    23
    I'm trying to implement a NetworkDictionary, extending the NetworkVariableBase class. Obviously a dictionary should take in two generic types for the Keys and Values, but FastBufferReader.ReadValueSafe complains unless I constrain the types to unmanaged AND implementing INetworkSerializable. Interestingly, FastBufferWriter.WriteValueSafe only demands that the type implement INetworkSerializable, and doesn't require it to be unmanaged.

    Code (CSharp):
    1. public class NetworkDictionary<TKey,TValue> : NetworkVariableBase,
    2.     where TKey:unmanaged,INetworkSerializable
    3.     where TValue:unmanaged,INetworkSerializable
    4. {
    5. private Dictionary<TKey, TValue> dictionary;
    6.  
    7. public override void WriteField(FastBufferWriter writer)
    8.     {
    9.         writer.WriteValueSafe(Count);
    10.         foreach(var keyVal in dictionary)
    11.         {
    12.             writer.WriteValueSafe(keyVal.Key);
    13.             writer.WriteValueSafe(keyVal.Value);
    14.         }
    15.     }
    16.     public override void ReadField(FastBufferReader reader)
    17.     {
    18.         int count;
    19.         TKey key;
    20.         TValue value;
    21.         reader.ReadValueSafe(out count);
    22.         for(int i=0;i<count;i++)
    23.         {
    24.             reader.ReadValueSafe(out key);
    25.             reader.ReadValueSafe(out value);
    26.             dictionary.Add(key, value);
    27.         }
    28.     }
    29. }
    So not only am I limited to using structs, but I can't even use primitive types (because the types must implement INetworkSerializable). The only workaround I thought of was to write a wrapper class for a primitive type:

    Code (CSharp):
    1. public struct NetInt : INetworkSerializable
    2. {
    3.     private int value;
    4.     public NetInt(int value) => this.value = value;
    5.     public static implicit operator int(NetInt i) => i.value;
    6.     public static implicit operator NetInt(int i) => new NetInt(i);
    7.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    8.     {
    9.         serializer.SerializeValue(ref value);
    10.     }
    11.     public override string ToString() => value.ToString();
    12. }
    So yeah, is there something I'm missing that would help make this class way more user-friendly?
     
  2. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    660
    I had a look into this, I haven't messed with NetworkVariableBase but maybe will in the future. I was able to get something working although it's limited to having an int as the key and a INetworkSerializable value. It's a bit hard-coded as well to keep it concise but hopefully still useful.
    Code (CSharp):
    1. public class SerialDictionary<TKey, TValue> : NetworkVariableBase where TKey : unmanaged where TValue : unmanaged, INetworkSerializable
    2. {
    3.     Dictionary<int, TValue> networkDictionary = new Dictionary<int, TValue>();
    4.  
    5.     public override void WriteField(FastBufferWriter writer)
    6.     {
    7.         writer.WriteValueSafe(networkDictionary.Count);
    8.         writer.WriteValueSafe(System.Runtime.InteropServices.Marshal.SizeOf<TKey>());
    9.  
    10.         foreach (KeyValuePair<int, TValue> keyValue in networkDictionary)
    11.         {
    12.             writer.WriteBytesSafe(BitConverter.GetBytes(keyValue.Key));
    13.  
    14.             writer.WriteValueSafe(keyValue.Value);
    15.         }
    16.     }
    17.  
    18.     public override void ReadField(FastBufferReader reader)
    19.     {
    20.         reader.ReadValueSafe(out int entryCount);
    21.         reader.ReadValueSafe(out int keySize);
    22.  
    23.         Debug.Log("SerialDictionary ReadField entryCount: " + entryCount);
    24.         Debug.Log("SerialDictionary ReadField keySize: " + keySize);
    25.  
    26.         byte[] keyByteArray = new byte[keySize];
    27.  
    28.         for (int n = 0; n < entryCount; n++)
    29.         {
    30.             for (int i = 0; i < keySize; i++)
    31.             {
    32.                 reader.ReadByteSafe(out keyByteArray[i]);
    33.             }
    34.  
    35.             int key = BitConverter.ToInt32(keyByteArray, 0);
    36.             reader.ReadValueSafe(out TValue value);
    37.  
    38.             networkDictionary.Add(key, value);
    39.         }
    40.     }
    41. }
    I didn't look into the delta's too much as they require a bit more thought.
     
  3. Salmonman

    Salmonman

    Joined:
    Jul 14, 2016
    Posts:
    23
    I see you went with a slightly different approach, trying to convert as much into byte arrays as possible, which could circumvent some of the limitations. I think ultimately I'm just going to have to make a bespoke implementation for my inventory system (which is what Im trying to make) rather than trying to make a generic implementation.