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

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.

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.

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:

using UnityEngine;
using Mirror;

public class ExamplePlayer : NetworkBehaviour
{
    public class SyncDictionaryStringItem : SyncDictionary<string, Item> {}

    public struct Item
    {
        public string name;
        public int hitPoints;
        public int durability;
    }

    public SyncDictionaryStringItem Equipment = new SyncDictionaryStringItem();

    public void OnStartServer()
    {
        Equipment.Add("head", new Item { name = "Helmet", hitPoints = 10, durability = 20 });
        Equipment.Add("body", new Item { name = "Epic Armor", hitPoints = 50, durability = 50 });
        Equipment.Add("feet", new Item { name = "Sneakers", hitPoints = 3, durability = 40 });
        Equipment.Add("hands", new Item { name = "Sword", hitPoints = 30, durability = 15 });
    }

    private void OnStartClient()
    {
        // Equipment is already populated with anything the server set up
        // but we can subscribe to the callback in case it is updated later on
        Equipment.Callback += OnEquipmentChange;

        InitEquipment();
    }

    private void OnEquipmentChange(SyncDictionaryStringItem.Operation op, string key, Item item)
    {
        // equipment changed,  perhaps update the gameobject
        Debug.Log(op + " - " + key);
    }

   private void InitEquipment()
   {
       // do whatever you want here with the initial equipment,  the syncdictionary already
       // contains the initial values.
       // for example,  build your character model and attach all equipment mesh

       // I typically call a "RefreshEquipment()"  function,  
       // and I also call RefreshEquipment() in the callback
   }
}

See the InitEquipment() function? do whatever you want there with the initial values.

2 Likes

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://discussions.unity.com/t/636221 page-26#post-4652305

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:

class Pepe : NetworkBehaviour
{
     // note starting with version 20 you can use them directly like this
     public SyncDictionary<string, List<bool>> mydictionary;

     [Server]
     public void AddToEntry() {
           // assume mydictionary already has
           // "hello" => []
           // let's add "true" to it
           // "hello" => [true]
           mydictionary["hello"].Add(true);

           // mirror does not know there is a change yet
           // so it does not know it needs to resync that entry.

           // you can trick mirror into resyncing that entry like this:
           mydictionary["hello"] = mydictionary["hello"];
     }
}

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.

3 Likes

Alright, i think i understood. Thanks!

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.

I am using SyncList, 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 is spawned.

PS, the GameObject has attached the NetworkIdentity Component.

Instead, I have to use SyncList. This works fine.

Do you have any idea about that?