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

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 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'

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;

8965647--1232097--NetworkList_error.png

The code:

PlayerData:

using System;
using Unity.Collections;
using Unity.Netcode;

public struct PlayerData : IEquatable<PlayerData>, INetworkSerializable
{
    public ulong clientId; //for netcode
    public FixedString64Bytes playerName;
    public FixedString64Bytes playerId;  //for AuthenticationService (lobby (and relay?) system)

    public TheStruct theStruct; //need to be implemented the set


    public bool Equals(PlayerData other)
    {
        return
            clientId == other.clientId &&
            playerName == other.playerName &&
            playerId == other.playerId;
    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref clientId);
        serializer.SerializeValue(ref playerName);
        serializer.SerializeValue(ref playerId);
        theStruct.NetworkSerialize(serializer);
    }
}

TheStruct (and the SecondStruct):

using System;
using Unity.Collections;
using Unity.Netcode;

public struct TheStruct : IEquatable<TheStruct>, INetworkSerializable
{
    public SecondStruct[] structArray;

    public TheStruct(SecondStruct[] structArray)
    {
        this.structArray = structArray;
    }

    public bool Equals(TheStruct other)
    {
        for(int i = 0; i < structArray.Length; i++)
        {
            if (!structArray[i].Equals(other.structArray[i])) return false;
        }
        return true;
    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        // Length
        int unitsLength = 0;
        if (!serializer.IsReader)
        {
            unitsLength = structArray.Length;
        }

        serializer.SerializeValue(ref unitsLength);

        // Array
        if (serializer.IsReader)
        {
            structArray = new SecondStruct[unitsLength];
        }

        for (int n = 0; n < unitsLength; ++n)
        {
            structArray[n].NetworkSerialize(serializer);
        }
    }
}

public struct SecondStruct : IEquatable<SecondStruct>, INetworkSerializable
{
    public int intA;
    public int intB;

    public bool Equals(SecondStruct other)
    {
        return
            intA == other.intA &&
            intB == other.intB;
    }

    public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    {
        serializer.SerializeValue(ref intA);
        serializer.SerializeValue(ref intB);
    }
}

How can I fix this?

Thanks for the help

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.

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).
8965755--1232136--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.

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.

Ok, thanks!

@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)

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 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.

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

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.

[quote]
do you know if this is a bug or is supposed to work this way?
[/quote]

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

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:
9029860--1246306--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'
That error is reported because of the code in the first post:

  • public struct PlayerData : IEquatable, INetworkSerializable
  • { (...) ----------> Player data variables
  • public TheStruct theStruct; -----------> Deck struct
  • (...)}

  • public struct TheStruct : IEquatable, INetworkSerializable

  • {

  • public SecondStruct[ ] structArray; -----> Card Id and amount

  • (...)}

  • public struct SecondStruct : IEquatable, INetworkSerializable

  • {

  • public int intA; ---->Card Id

  • public int intB; ----->Amount

  • (..)}

Since the NetworkSerialize 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.

I don't know to be honest, it could be a limitation related to the different way NetworkList and NetworkVariables work.

Great!