Search Unity

[Mirror] Setting initial values on client connect for SyncDictionary / SyncList

Discussion in 'Scripting' started by veggiesama, Apr 7, 2019.

  1. veggiesama

    veggiesama

    Joined:
    Jul 11, 2017
    Posts:
    7
    As I understand it, SyncDictionaries (Mirror-only) are basically fancy SyncLists (present in UNET). So I hope this general forum can still provide an answer.

    In the Mirror documentation, it says: "SyncDictionaries work much like SyncLists: when you make a change on the server the change is propagated to all clients and the Callback is called."

    Under SyncLists, the documentation says, "You can also detect when a synclist changes in the client or server. This is useful for refreshing your character when you add equipment or determining when you need to update your database. Subscribe to the Callback event typically during Start, OnClientStart or OnServerStart for that. Note that by the time you subscribe, the list will already be initialized, so you will not get a call for the initial data, only updates."

    OK--the SyncList will sync for future updates, but for the initial data I'm on my own. Right?

    My question: What is the right way to initialize SyncList/SyncDictionary values for newly connecting clients?

    My situation: I have a SyncDictionary<int slot, GameObject player>. When the server starts, a game controller object builds 4 unassigned player objects and puts them into the dictionary. Players are assigned one of these objects. Your slot determines where your UI healthbar appears on screen. Anyway, when a new client connects, this SyncDictionary has 0 items in it. I want it to have the server's 4. I'm not sure how to force the server to update a new client with those initial values.
     
    Last edited: Apr 7, 2019
  2. veggiesama

    veggiesama

    Joined:
    Jul 11, 2017
    Posts:
    7
    So, I switched from using SyncDictionary to a SyncList, but I'm having the same issue where initial values aren't syncing on client join.

    I tried writing an RPC call to iterate through the server's list and add each entry to the client's list, but I got an error. SyncList can only be modified on the server side.

    Next things I want to try:
    1. Switch from GameObject to int. Even though the documentation indicates GameObjects are sync-able, maybe that's the issue. I could set a Player ID SyncVar and try the SyncList again to order it.
    2. I saw this suggestion somewhere else: use a "server list" and a "local list." Whenever the server list is updated, RPC the update to the client's local list. This seems horribly redundant.
    3. Is there some way to force an update? I've tried using Dirty() to force the sync to trigger but couldn't figure it out.
     
  3. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    When the object is spawned the syncdictionaries will be populated with whatever the server has at the time.

    If you want to do something with the initial value, just do it in your OnStartClient(), for example:

    Code (CSharp):
    1. using UnityEngine;
    2. using Mirror;
    3.  
    4. public class ExamplePlayer : NetworkBehaviour
    5. {
    6.     public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}
    7.  
    8.     public struct Item
    9.     {
    10.         public string name;
    11.         public int hitPoints;
    12.         public int durability;
    13.     }
    14.  
    15.     public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();
    16.  
    17.     public void OnStartServer()
    18.     {
    19.         Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
    20.         Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
    21.         Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
    22.         Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
    23.     }
    24.  
    25.     private void OnStartClient()
    26.     {
    27.         // Equipment is already populated with anything the server set up
    28.         // but we can subscribe to the callback in case it is updated later on
    29.         Equipment.Callback += OnEquipmentChange;
    30.  
    31.         InitEquipment();
    32.     }
    33.  
    34.     private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
    35.     {
    36.         // equipment changed,  perhaps update the gameobject
    37.         Debug.Log(op + " - " + key);
    38.     }
    39.  
    40.    private void InitEquipment()
    41.    {
    42.        // do whatever you want here with the initial equipment,  the syncdictionary already
    43.        // contains the initial values.
    44.        // for example,  build your character model and attach all equipment mesh
    45.  
    46.        // I typically call a "RefreshEquipment()"  function,  
    47.        // and I also call RefreshEquipment() in the callback
    48.    }
    49. }
    50.  
    See the InitEquipment() function? do whatever you want there with the initial values.
     
    Last edited: Apr 10, 2019
    brainwipe and MentalGames like this.
  4. HajiyevEl

    HajiyevEl

    Joined:
    Feb 19, 2020
    Posts:
    47
    Hi goldbug! Can you please tell me, if i have SyncDictionary (PlayersTurnDictionary for example), and inside that dictionary is list of bools(simple list, not SyncList). Will clients get updates when something in list of booleans changes value?
    Found vis2k comment about Mirror not supporting SyncDictionaries inside of SyncDictionaries, but what about simple list inside SyncDictionary?
    https://forum.unity.com/threads/mir...-unet-replacement.425437/page-26#post-4652305
     
  5. goldbug

    goldbug

    Joined:
    Oct 12, 2011
    Posts:
    767
    No, Mirror does not track changes inside these lists, even if these were SyncLists it would not work.
    You can however tell the SyncDictionary to resync an entry. For example:

    Code (CSharp):
    1. class Pepe : NetworkBehaviour
    2. {
    3.      // note starting with version 20 you can use them directly like this
    4.      public SyncDictionary<string, List<bool>> mydictionary;
    5.  
    6.      [Server]
    7.      public void AddToEntry() {
    8.            // assume mydictionary already has
    9.            // "hello" => []
    10.            // let's add "true" to it
    11.            // "hello" => [true]
    12.            mydictionary["hello"].Add(true);
    13.  
    14.            // mirror does not know there is a change yet
    15.            // so it does not know it needs to resync that entry.
    16.  
    17.            // you can trick mirror into resyncing that entry like this:
    18.            mydictionary["hello"] = mydictionary["hello"];
    19.      }
    20. }
    Note that the entire list in an entry is sent when the entry is resync, not just the diff in the list.

    Another alternative is to create your own synchronizing datastructure, you just need to implement SyncObject interface. SyncSet, SyncDictionary and SyncList are 3 examples you can use. This way you could avoid resending the entire list of a player.
     
    Last edited: Oct 11, 2020
  6. HajiyevEl

    HajiyevEl

    Joined:
    Feb 19, 2020
    Posts:
    47
    Alright, i think i understood. Thanks!
     
  7. AdityaPanangat

    AdityaPanangat

    Joined:
    Jul 28, 2020
    Posts:
    3
    I think that there are two areas where your problem may be occurring. First, you have not initialized your SyncDictionary. I think you might have to do that. You can do that by doing something like this: "SyncDictionary<string, int> example = new SyncDictionary<string,int>(). Another problem you might be having is that you have made "AddToEntry" a server method. This means that it will not update on clients unless you use TargetRpc or ClientRpc.
     
  8. downwes

    downwes

    Joined:
    Jul 7, 2021
    Posts:
    1
    I am using SyncList<GameObject>, and it is supposed to be populated with whatever the server has as you state, but I only got empty game objects when the SyncList<GameObject>.gameObject is spawned.

    PS, the GameObject has attached the `NetworkIdentity` Component.

    Instead, I have to use SyncList<uint>. This works fine.

    Do you have any idea about that?