Search Unity

[Solved] Unity 2017.1.0f1 SyncListStruct fails to sync lists

Discussion in 'Multiplayer' started by xVergilx, Jun 27, 2017.

  1. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Ok, so I've tried everything right now and ran out of ideas. SyncListStruct fails to syncronize struct from what it seems when there's more than one struct in the list and I cannot pin down why it does that. I've tried a clean, minimal setup for this, and it's the same behavior. Initialize is called from OnStartServer().

    Data:
    Code (CSharp):
    1.  
    2. using UnityEngine.Networking;
    3.  
    4.     public class PlayerDataSyncList : SyncListStruct<PlayerData> {
    5.         public void Initialize() {
    6.             PlayerData nullPlayer = new PlayerData(NetworkInstanceId.Invalid);
    7.             Add(nullPlayer);
    8.         }
    9.  
    10.         /// <summary>
    11.         /// Removes player with playerId from list
    12.         /// </summary>
    13.         public void RemoveById(NetworkInstanceId playerId) {
    14.             for (int i = 0; i < Count; i++) {
    15.                 if (this[i].Id == playerId) {
    16.                     RemoveAt(i);
    17.                 }
    18.             }
    19.         }
    20.  
    21.         protected override void SerializeItem(NetworkWriter writer, PlayerData item) {
    22.             writer.Write(item.Id);
    23.             writer.Write(item.PeerId);
    24.             writer.Write(item.Username);
    25.             writer.Write(item.VisibleUsername);
    26.             writer.Write((short) item.PlayerTeam);
    27.         }
    28.  
    29.         protected override PlayerData DeserializeItem(NetworkReader reader) {
    30.             NetworkInstanceId id = reader.ReadNetworkId();
    31.             int peerId = reader.ReadInt32();
    32.             string username = reader.ReadString();
    33.             string visibleUsername = reader.ReadString();
    34.             PlayerTeam playerTeam = (PlayerTeam) reader.ReadInt16();
    35.  
    36.             return new PlayerData {
    37.                                       Id = id,
    38.                                       PeerId = peerId,
    39.                                       Username = username,
    40.                                       VisibleUsername = visibleUsername,
    41.                                       PlayerTeam = playerTeam,
    42.                                   };
    43.         }
    44.     }
    45.  
    46.     [System.Serializable]
    47.     public struct PlayerData {
    48.         public NetworkInstanceId Id;
    49.         public string Username;
    50.         public string VisibleUsername;
    51.  
    52.         public PlayerTeam PlayerTeam;
    53.         public int PeerId;
    54.  
    55.         public PlayerData(NetworkInstanceId id,
    56.                           int peerId = -1,
    57.                           string username = null,
    58.                           string visibleUsername = null,
    59.                           PlayerTeam playerTeam = PlayerTeam.Undefined) {
    60.             Id = id;
    61.             Username = username;
    62.             VisibleUsername = visibleUsername;
    63.             PeerId = peerId;
    64.             PlayerTeam = playerTeam;
    65.         }
    66.     }
    67.  
    68.     public static class PlayerDataExt {
    69.         public const string NonamePrefix = "Noname player";
    70.     }
    71.  
    72.     [System.Serializable]
    73.     public enum PlayerTeam : short {
    74.         FFA,
    75.         TVT1,
    76.         TVT2,
    77.         Undefined
    78.     }
    79.  
    Note that when there's only one struct in the list it works perfectly fine, and fails in 100% cases when multiple structs are present. When player either added by script, or joins to the server this exception occurs:

    I've tried to use Reader.SeekZero(), in DeserializeItem, and as a result got an enourmous list that quickly led to Unity's editor crash when I tried to toggle list in the editor.

    What can be the cause of this? It's very very critical for our project. Core of the multiplayer is based around using those lists.
    @aabramychev Can you give me at least a direction in which I should dig? Any wild guess will be appreciated.

    In a meanwhile, maybe there's an alternative for SyncLists? Like passing messages and sync'ing lists manually? Idk.
     
  2. aabramychev

    aabramychev

    Unity Technologies

    Joined:
    Jul 17, 2012
    Posts:
    574
    wait please:) I readdressed your question to another guy who know hlapi much more better than me.
     
  3. aabramychev

    aabramychev

    Unity Technologies

    Joined:
    Jul 17, 2012
    Posts:
    574
    @VergilUa ok, just finished talking. It is definitely bug, please report them. Before this bug will be fixed, I guess you can do encoding/decoding job manually, directly working with Reader/Writer to encode/decode array of structures.

    If you want, just report this bug today and ping me with the bug number, we will try (cannot promise) to speed the fix up.

    again, I'm very sorry about this :(
     
    Deleted User likes this.
  4. larus

    larus

    Unity Technologies

    Joined:
    Oct 12, 2007
    Posts:
    280
    @VergilUa getting some repro steps in that report would be good, nothing really looks wrong in that synclist class, and just copying it into a project and using it actually works for me (multiple dummy PlayerData elements synched properly)
     
  5. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    It feels like (from the debugging) that there's something messing up the spawn sequence of the client scene and/or causing the packets to be messed up too. Unfortunately there's no way of telling what went wrong and where. It goes like this:
    1. Server starts (not a host, this is a server only instance). Switches the scene to online scene. Everything seems fine on this end, size of list is 1. (After initialization).
    2. Client attempts to connect.
    3. Client loads online scene.
    4. ClientScene fails to spawn an object with SyncList data from payload.
    5. Exception causes the whole client to desync.

    Other network objects is out of the question. I've tested connectivity of client and server in a clean scene, with only the actual multiplayer game manager that contains these lists and the player object itself.
    I've checked number of SyncVars present in both projects in those files and they're equal.

    My wild guess was that there was something with a custom network manager, but I was unable to track down what. My first thought that using extra network reader is the problem, but it's not. Even after removing network reader read message calls - it's the same behaviour. I might be wrong about it though. It could be the reason. Or not.

    Here's stripped parts of custom network manager from client and server:

    Server side (ignore peer id and player profile, this is master server related stuff):
    Code (CSharp):
    1.     public class CustomNetworkManager: NetworkManager {
    2.         #region Hosting handling
    3.  
    4.         public void ServerStart(int port) {
    5.             networkPort = port;
    6.  
    7.             StartServer();
    8.         }
    9.  
    10.         #endregion
    11.  
    12.         public override void OnServerAddPlayer(NetworkConnection conn,
    13.                                                short playerControllerId,
    14.                                                NetworkReader extraMessageReader) {
    15.             int peerdId;
    16.             try {
    17.                 peerdId = extraMessageReader.ReadMessage<IntegerMessage>().value;
    18.             } catch (Exception) {
    19.                 Debug.LogError("CustomNetworkManager:: OnServerAddPlayer() - Cannot read player peer Id properly."
    20.                                + " Dropping client.");
    21.                 conn.Disconnect();
    22.                 return;
    23.             }
    24.  
    25.             AddPlayerInternal(conn, playerControllerId, peerdId);
    26.         }
    27.  
    28.         private void AddPlayerInternal(NetworkConnection conn, short playerControllerId, int peerId) {
    29.             if (singleton.playerPrefab == null) {
    30.                 if (LogFilter.logError) {
    31.                     Debug.LogError("The PlayerPrefab is empty on the CustomNetworkManager. "
    32.                                    + "Please setup a PlayerPrefab object.");
    33.                 }
    34.                 return;
    35.             }
    36.  
    37.             if (singleton.playerPrefab.GetComponent<NetworkIdentity>() == null) {
    38.                 if (LogFilter.logError) {
    39.                     Debug.LogError("The PlayerPrefab does not have a NetworkIdentity. "
    40.                                    + "Please add a NetworkIdentity to the player prefab.");
    41.                 }
    42.                 return;
    43.             }
    44.  
    45.             if (playerControllerId < conn.playerControllers.Count &&
    46.                 conn.playerControllers[playerControllerId].IsValid &&
    47.                 conn.playerControllers[playerControllerId].gameObject != null) {
    48.                 if (LogFilter.logError) {
    49.                     Debug.LogError("There is already a player at that playerControllerId for this connections.");
    50.                 }
    51.                 return;
    52.             }
    53.  
    54.             GameObject player = Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
    55.  
    56.             NetworkServer.AddPlayerForConnection(unetConnection, player, playerControllerId);
    57.  
    58.             // ... Other player components setup and profile manipulation ...
    59.  
    60.             Player playerData = player.GetComponent<Player>();
    61.  
    62.             MultiplayerGameManager mgm = MultiplayerGameManager.Instance;
    63.  
    64.             // Here's where Player.Add is called,
    65.             mgm.RegisterPlayer(netId, playerData.PlayerName, peerId, profile);
    66.         }
    67.  

    Client side:

    Code (CSharp):
    1.     public class CustomNetworkManager: NetworkManager {
    2.         public override void OnClientConnect(NetworkConnection conn) {
    3.                 autoCreatePlayer = false;
    4.  
    5.                 /// Peer Id received from master server here (response). This is wrapped in a callback
    6.                 //  When the id is received -> add player is called. Which can be basicly trimmed down to
    7.                 //  ClientScene.AddPlayer(conn, 0, new IntegerMessage(%anyInt%));
    8.                 Msf.Connection.SendMessage((short) CustomOpCodes.RequestConnectedPeerId,
    9.                                            (status, response) => {
    10.                                                if (status != ResponseStatus.Success) {
    11.                                                    Debug.LogError("CustomNetworkManager:: OnClientConnect() - "
    12.                                                                   + "Unable to retrieve master peer id."
    13.                                                                   + " Shutting down connection.");
    14.                                                    conn.Disconnect();
    15.                                                    return;
    16.                                                }
    17.                              
    18.                                               Debug.Log("Sending gameserver peer id " + response.AsInt());
    19.                                               ClientScene.AddPlayer(conn, 0, new IntegerMessage(response.AsInt()));
    20.                                            });
    21.         }
    22.     }
    23.  
    Actual server MultiplayerGameManager Player.Add part:
    Code (CSharp):
    1.         [Server]
    2.         public void RegisterPlayer(NetworkInstanceId playerId,
    3.                                    string visibleUsername,
    4.                                    int peerId,
    5.                                    ObservableServerProfile userProfile) {
    6.            //... If contains already -> don't add checks, player team checks
    7.            //... profile and statistic updates ...
    8.  
    9.             // Params are - NetworkInstanceId, %any_int_received_from_client#, non-empty string, non-empty string,
    10.             // enum with int value
    11.             PlayerData player = new PlayerData(playerId, peerId, userProfile.Username, visibleUsername, playerTeam);
    12.  
    13.             Debug.Log(string.Format("<color=cyan>Adding player - {0} {1} {2} {3} {4}</color>",
    14.                                     playerId,
    15.                                     peerId,
    16.                                     userProfile.Username,
    17.                                     visibleUsername,
    18.                                     playerTeam));
    19.             Players.Add(player);
    20.  
    21.             Debug.Log("<color=cyan>Players count - " + Players.Count + "</color>");
    22.  
    23.             //....
    24.         }
    25.  
    Player is successfully added on the server by the way, and it's count is logged properly as 2.

    @aabramychev I've mentioned earlier that I've tried to serialize/deserialize those items with overrides and it resulted in an infinite amount of items in the list. Which is no go. Or do you mean there's another way to to serialization for those lists?

    Also, now I'm re-thinking project network architecture. If nothing comes across my mind tomorrow, I'll fill bug report and will try to rework everything without using SyncLists.
    (Which is pain, but I guess there's no other way around)

    P.s. Sorry for the delay, I'm having a day-off work today.
    P.p.s. Thanks for your attention
     
    Last edited: Jun 28, 2017
  6. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Okay, I've solved this issue. While I was rewriting my project to remove SyncList as a whole I've noticed the following. The cause of this behavior is two missmatching SyncVar structures that were declared in the same class in a different projects.

    TL;DR:
    On client it was:
    Code (CSharp):
    1. public struct VictoryData {
    2.     public NetworkInstanceId WinnerId;
    3.     public PlayerTeam WinnerTeam;
    4.     public string WinnerString;
    5. }
    And on the server it was:
    Code (CSharp):
    1. public struct VictoryData {
    2.         public string WinnerUsername;
    3.         public PlayerTeam WinnerTeam;
    4.         public string WinnerMessage;
    5. }
    For the future records, can we have some sorts of prevention measure for this to happen? Maybe there's a way to catch and isolate single a serialization failure instead of trashing whole client/sever interraction?

    Or at least a warning that X SyncVar cannot be serialized as Y?
    Because as far as I understand NetworkWriter/NetworkReader just went through those without hesitation and done their best to process corrupted data.