Search Unity

Resolved Cant do a NetworkList<T> where T contains an Array inside

Discussion in 'Netcode for GameObjects' started by JavierVigorCrea, Apr 21, 2023.

  1. JavierVigorCrea

    JavierVigorCrea

    Joined:
    Jan 13, 2020
    Posts:
    3
    I'm trying to create a network array of a struct, where that struct has inside an struct witch has an struct Array.
    private NetworkList<PlayerData> playerDataNetworkList;

    Error CS8377: The type 'PlayerData' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'NativeList<T>'

    I've tried with both array and native list, like the official documentation says:
    https://docs-multiplayer.unity3d.com/netcode/current/advanced-topics/serialization/inetworkserializable#nested-serial-types

    but doesn´t work.
    If, instead of making a NetworkList, I do a NetworkVariable, it works fine:
    private NetworkVariable<PlayerData> playerData;

    NetworkList_error.png

    The code:

    PlayerData:

    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Netcode;
    4.  
    5. public struct PlayerData : IEquatable<PlayerData>, INetworkSerializable
    6. {
    7.     public ulong clientId; //for netcode
    8.     public FixedString64Bytes playerName;
    9.     public FixedString64Bytes playerId;  //for AuthenticationService (lobby (and relay?) system)
    10.  
    11.     public TheStruct theStruct; //need to be implemented the set
    12.  
    13.  
    14.     public bool Equals(PlayerData other)
    15.     {
    16.         return
    17.             clientId == other.clientId &&
    18.             playerName == other.playerName &&
    19.             playerId == other.playerId;
    20.     }
    21.  
    22.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    23.     {
    24.         serializer.SerializeValue(ref clientId);
    25.         serializer.SerializeValue(ref playerName);
    26.         serializer.SerializeValue(ref playerId);
    27.         theStruct.NetworkSerialize(serializer);
    28.     }
    29. }

    TheStruct (and the SecondStruct):

    Code (CSharp):
    1. using System;
    2. using Unity.Collections;
    3. using Unity.Netcode;
    4.  
    5. public struct TheStruct : IEquatable<TheStruct>, INetworkSerializable
    6. {
    7.     public SecondStruct[] structArray;
    8.  
    9.     public TheStruct(SecondStruct[] structArray)
    10.     {
    11.         this.structArray = structArray;
    12.     }
    13.  
    14.     public bool Equals(TheStruct other)
    15.     {
    16.         for(int i = 0; i < structArray.Length; i++)
    17.         {
    18.             if (!structArray[i].Equals(other.structArray[i])) return false;
    19.         }
    20.         return true;
    21.     }
    22.  
    23.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    24.     {
    25.         // Length
    26.         int unitsLength = 0;
    27.         if (!serializer.IsReader)
    28.         {
    29.             unitsLength = structArray.Length;
    30.         }
    31.        
    32.         serializer.SerializeValue(ref unitsLength);
    33.        
    34.         // Array
    35.         if (serializer.IsReader)
    36.         {
    37.             structArray = new SecondStruct[unitsLength];
    38.         }
    39.  
    40.         for (int n = 0; n < unitsLength; ++n)
    41.         {
    42.             structArray[n].NetworkSerialize(serializer);
    43.         }
    44.     }
    45. }
    46.  
    47. public struct SecondStruct : IEquatable<SecondStruct>, INetworkSerializable
    48. {
    49.     public int intA;
    50.     public int intB;
    51.  
    52.     public bool Equals(SecondStruct other)
    53.     {
    54.         return
    55.             intA == other.intA &&
    56.             intB == other.intB;
    57.     }
    58.  
    59.     public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    60.     {
    61.         serializer.SerializeValue(ref intA);
    62.         serializer.SerializeValue(ref intB);
    63.     }
    64. }


    How can I fix this?

    Thanks for the help
     
  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,892
    You can only fix this by not using an array, which is a managed object. You can also not nest native collection types either.

    You could provide a fixed number of SecondStruct, such as s1 to s15 fields. Since you want synchronized data it shouldn‘t be many bytes to begin with. If that won‘t work you could use a FixedString which encodes whatever array contents you have for example using json or csv. But again this will be limited to whatever the fixed string size is (I think 4096 length is max).

    Overall I would say this code looks like something that is overengineered for a network shared set of data. Consider the traffic for synchronizing this, especially if it happens per frame by multiple clients: every change could create possibly thousands of bytes to transfer. Check if you really have to synchronize everything together (caching? deterministic behaviour?) or whether other (simpler) data structures or splitting the dataset are more appropriate.
     
  3. JavierVigorCrea

    JavierVigorCrea

    Joined:
    Jan 13, 2020
    Posts:
    3
    What I want to do is an army game. The PlayerData needs to contain the clientId, playerName, playerId and the ArmyStruct.
    The ArmyStruct is an array of UnitStruct. Each UnitStruct contains two ints: "the enum index int i have created" and "the amount of soldiers in the unit".
    But the units amount can differ from one game to another, so I wanted to have an array.
    This data is only needed to be shared when one player connects to the lobby of the host player.

    One example of this is a card game: you can have 20 cards of the same type (array has a lenght of 1), or 3 of one type, 4 of another and 13 of another one (array has a lenght of 3).
    NetworkList_error2.png
    (Legends of Runeterra example, where the player needs to share the deck).

    I don't know if there is another way of sharing this data in a better way.
     
  4. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    5,892
    If you only need to send it once (or rarely) you should be passing it as a parameter to a ServerRpc method. You can convert the entire data set to json thus sending only a string.

    Network variables are for synchronizing frequently changing data.
     
  5. JavierVigorCrea

    JavierVigorCrea

    Joined:
    Jan 13, 2020
    Posts:
    3
    Ok, thanks!
     
  6. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    @JavierVigorCrea a much simpler way to sync these kind of thigns is to have a local database of cards (A list in a ScriptableObject), where each card has an ID. You then send "My deck has X copies of card with ID Y" and reconstruct the deck on the other side (server/client)
     
  7. JavigorGamedev

    JavigorGamedev

    Joined:
    Aug 4, 2022
    Posts:
    5
    Thank you!

    Anyways that was what I was trying with the structs:
    the PlayerData struct was supposed to have a Deck struct
    The Deck struct was supposed to have a "points" int and a CardIdAmount struct array
    Finally, the CardIdAmount was supposed to have 2 ints: the ID of the card and the amount.

    The problem was that the NetworkVariable struct can, but the NetworkList<struct> can't contain an struct that haves an struct array, because it detects as nullable type.
    I finally solved it setting first the PlayerData struct NetworkVariable and later send the CardIdAmount array via an ServerRpc parameter. It works this way, not as intended, but good enought.

    The official documentation says that the way I implemented is the correct way to do it since I can create an NetworkVariable that way, so I thought it also would let me create an NetworkList of the same struct (a struct that contains an struct array), but thats not the case, so reports that Error.

    @RikuTheFuffs-U do you know if this is a bug or is supposed to work this way? Just for knowing it for future projects.
     
  8. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    An even easier way is to send just the IDs as a NetworkList, without the quantity. Even easier to add/remove cards to the deck dynamically.

    I'm not sure I understood what the issue is, sorry ^^'
     
  9. JavigorGamedev

    JavigorGamedev

    Joined:
    Aug 4, 2022
    Posts:
    5

    Thanks for the idea.
    I'm sorry, my english is not the best, so i'll try explaining it in another way.

    As you can see in this image, it allows me to do an NetworkVariable of type PlayerData, but not an NetworkList:
    upload_2023-5-22_21-35-41.png

    It reports me this Error:
    Error CS8377: The type 'PlayerData' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter 'T' in the generic type or method 'NativeList<T>'
    That error is reported because of the code in the first post:
    1. public struct PlayerData : IEquatable<PlayerData>, INetworkSerializable
    2. { (...) ----------> Player data variables
    3. public TheStruct theStruct; -----------> Deck struct
    4. (...)}
    1. public struct TheStruct : IEquatable<TheStruct>, INetworkSerializable
    2. {
    3. public SecondStruct[] structArray; -----> Card Id and amount
    4. (...)}
    1. public struct SecondStruct : IEquatable<SecondStruct>, INetworkSerializable
    2. {
    3. public int intA; ---->Card Id
    4. public int intB; ----->Amount
    5. (..)}
    Since the NetworkSerialize<T> functions are programmed as in the official documentation says, and the NetworkVariable is working fine, is it a bug that the NetworkVariable does't accept those structs?

    Anyways, the problem was solved, I only have the question of if it is intended or a bug, so I know that for my future projects.
     
  10. RikuTheFuffs-U

    RikuTheFuffs-U

    Unity Technologies

    Joined:
    Feb 20, 2020
    Posts:
    440
    I don't know to be honest, it could be a limitation related to the different way NetworkList and NetworkVariables work.

    Great!