Ejemplo n.º 1
0
        ///<summary>Adjust the remote tick offset if necessary</summary>
        private void AdjustRemoteTickOffset()
        {
            LastTickDuplicated = false;
            if (CurrentRemoteTickOffsetTarget != CurrentRemoteTickOffset)
            {
                // Ensure that we don't modify the offset two consecutive frames in a row
                if (LastRemoteTickOffset != CurrentRemoteTickOffset)
                {
                    LastRemoteTickOffset = CurrentRemoteTickOffset;
                    return;
                }

                if (CurrentRemoteTickOffsetTarget > CurrentRemoteTickOffset)
                {
                    MDLog.Debug(LOG_CAT,
                                $"Increasing remote tick offset from {CurrentRemoteTickOffset} to {CurrentRemoteTickOffset + 1}");
                    // Notify that we are skipping this tick
                    OnRemoteSkippedTick(GetRemoteTick());
                    CurrentRemoteTickOffset++;
                }
                else
                {
                    MDLog.Debug(LOG_CAT,
                                $"Decreasing remote tick offset from {CurrentRemoteTickOffset} to {CurrentRemoteTickOffset - 1}");
                    CurrentRemoteTickOffset--;
                    // We need to mark that last tick was a duplicate
                    LastTickDuplicated = true;
                }
            }
        }
Ejemplo n.º 2
0
        private void UpdateLabel()
        {
            DisplayLabel.Clear();
            DisplayLabel.PushTable(2);
            AddText("Name", Colors.White);
            AddText("Value", Colors.White);

            foreach (string key in DebugInfoList.Keys)
            {
                string text = "";
                try
                {
                    text = DebugInfoList[key].InfoFunction.Invoke();
                    AddText(key, DebugInfoList[key].Color);
                    AddText(text, DebugInfoList[key].Color);
                }
                catch (Exception ex)
                {
                    // Something went wrong
                    MDLog.Debug(LOG_CAT, ex.ToString());
                }
            }

            DisplayLabel.Pop();
        }
Ejemplo n.º 3
0
        private void CheckAllClientsSynched(Timer timer)
        {
            if (PeerSynchInfo.Values.Where(peerInfo => peerInfo.CompletedSynch == false).ToList().Count > 0)
            {
                MDLog.Debug(LOG_CAT, "All clients are not synched yet");
                return;
            }

            // Check if we still need to wait for a better confidence on the TickMsec value
            if (PeerSynchInfo.Values.Where(peerInfo => peerInfo.IsClientMSecConfident() == false).ToList().Count > 0)
            {
                MDLog.Debug(LOG_CAT, "Still waiting for a more secure msec value");
                return;
            }

            MDLog.Debug(LOG_CAT, "All clients synched, sending unpause signal");
            // Alright tell all clients to unpause in a bit
            foreach (int peerid in GameSession.GetAllPeerIds())
            {
                // Get our current game tick
                uint tickToUnpause = GameClock?.GetTick() != null?GameClock.GetTick() : 0;

                if (peerid != MDStatics.GetServerId())
                {
                    RpcId(peerid, nameof(UnpauseAtTickMsec),
                          GetPlayerTicksMsec(peerid) + GetUnpauseCountdownDurationMSec(), tickToUnpause);
                }
            }

            UnpauseAtTickMsec(OS.GetTicksMsec() + GetUnpauseCountdownDurationMSec(), 0);
            PeerSynchInfo.Values.ToList().ForEach(value => value.CompletedSynch = false);
            timer.RemoveAndFree();
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Unregisters the given instance's fields marked with [MDReplicated()]
        /// </summary>
        /// <param name="Instance">The node to unregister</param>
        public void UnregisterReplication(Node Instance)
        {
            if (!IsInstanceValid(Instance))
            {
                return;
            }

            foreach (ReplicatedNode repNode in NodeList)
            {
                if (repNode.Instance.GetRef() != Instance)
                {
                    continue;
                }

                MDLog.Debug(LOG_CAT, $"Unregistering node {Instance.Name}");
                NetworkIdKeyMap.RemoveMembers(repNode.Members);
                foreach (MDReplicatedMember member in repNode.Members)
                {
                    GroupManager.RemoveReplicatedMember(member);
                    KeyToMemberMap.Remove(member.GetUniqueKey());
                }
            }

            NodeList.RemoveAll(RepNode => RepNode.Instance.GetRef() == Instance);
        }
Ejemplo n.º 5
0
 /// <summary>
 /// Sets the current tick
 /// </summary>
 /// <param name="Tick">The new tick</param>
 public void SetCurrentTick(uint Tick)
 {
     MDLog.Debug(LOG_CAT, $"Tick changed by code from {CurrentTick} to {Tick}");
     CurrentTick         = Tick;
     TickSynchAdjustment = 0;
     OnGameTickChanged(CurrentTick);
 }
Ejemplo n.º 6
0
 protected virtual void OnClientSentPlayerName(string ClientName)
 {
     MDLog.Debug(LOG_CAT, $"Server received initialization for PeerId [{PeerId}] from owner");
     if (HasInitialized == false)
     {
         PlayerName = ClientName;
         MarkPlayerInitializationCompleted();
     }
 }
Ejemplo n.º 7
0
        ///<summary>Calculate the player estimated offset for OS.GetTicksMSec</summary>
        private void CalculatePlayerEstimatedTicksMSecOffset()
        {
            int estimate = TicksList.Sum();

            estimate /= TicksList.Count;

            TickMSecOffset = estimate;
            MDLog.Debug(LOG_CAT,
                        $"Estimated OS.GetTicksMsec offset for peer [{PeerId}] is {estimate} based on {TicksList.Count} measurements");
        }
Ejemplo n.º 8
0
        private void OnPlayerInitialized(int PeerId)
        {
            MDPlayerInfo PlayerInfo = GetPlayerInfo(PeerId);

            if (PlayerInfo != null)
            {
                PlayerInfo.HasInitialized = true;
                NotifyPlayerInitializedEvent(PeerId);
                MDLog.Debug(LOG_CAT, $"Player {PeerId} Initialized");
            }
        }
Ejemplo n.º 9
0
        private void ClientSynchStatus(int SynchedNodes)
        {
            // Synch in progress
            MDLog.Debug(LOG_CAT,
                        $"Peer [{Multiplayer.GetRpcSenderId()}] has synched {SynchedNodes} out of {NodeList.Count} nodes");

            // Send status update to all players so they can update UI
            UpdateSynchStatusOnAllClients(Multiplayer.GetRpcSenderId(), (float)SynchedNodes / NodeList.Count);

            // Update our own UI
            OnPlayerSynchStatusUpdateEvent(Multiplayer.GetRpcSenderId(), (float)SynchedNodes / NodeList.Count);
        }
Ejemplo n.º 10
0
 internal void BeginInitialization()
 {
     MDLog.Debug(LOG_CAT, $"Starting initialization for PeerId [{PeerId}]");
     if (PeerId == MDStatics.GetPeerId())
     {
         OnServerRequestedInitialization();
     }
     else
     {
         // Send message to owning client from server to request initialization data
         RpcId(PeerId, nameof(OnServerRequestedInitialization));
     }
 }
Ejemplo n.º 11
0
        /// <summary>
        /// Notifies all clients (except the new one) that a new player has initialized
        /// </summary>
        /// <param name="Joiner">The PeerID of the joining client</param>
        protected void BroadcastNewPlayerInitialized(int Joiner)
        {
            foreach (int PeerId in Players.Keys)
            {
                if (PeerId == Joiner || PeerId == SERVER_ID)
                {
                    continue;
                }

                MDLog.Debug(LOG_CAT, $"Notifying Peer [{PeerId}] that Peer [{Joiner}] has initialized");
                RpcId(PeerId, nameof(OnPlayerInitialized), Joiner);
            }
        }
Ejemplo n.º 12
0
        /// <summary>
        /// Notifies all clients that a player has left
        /// </summary>
        /// <param name="Leaver">The PeerID of the leaving client</param>
        protected virtual void BroadcastPlayerLeft(int Leaver)
        {
            foreach (int PeerId in Players.Keys)
            {
                if (PeerId == Leaver || PeerId == SERVER_ID)
                {
                    continue;
                }

                MDLog.Debug(LOG_CAT, $"Notifying Peer [{PeerId}] that Peer [{Leaver}] left");
                RpcId(PeerId, nameof(ClientOnPlayerLeft), Leaver);
            }
        }
Ejemplo n.º 13
0
        public void ReplicateClockedValues(uint ID, uint Tick, params object[] Parameters)
        {
            string key = NetworkIdKeyMap.GetValue(ID);

            if (key == null || !KeyToMemberMap.ContainsKey(key))
            {
                MDLog.Debug(LOG_CAT, $"Received replication for id {ID} and tick {Tick} not in map");
                // We got no key so add it to our buffer
                NetworkIdKeyMap.AddToBuffer(ID, Tick, Parameters);
                return;
            }

            KeyToMemberMap[key].SetValues(Tick, Parameters);
        }
Ejemplo n.º 14
0
        // Peeks the JIPPlayer queue and returns the first peerid if enough time has passed
        private int CheckForNewPlayer()
        {
            if (JIPPlayers.Count > 0)
            {
                NewPlayer JIPPlayer = JIPPlayers.Peek();
                if (JIPPlayer.IsReadyForReplication())
                {
                    MDLog.Debug(LOG_CAT, $"JIP Peer Id {JIPPlayer.PeerId} ready for MDReplicated");
                    return(JIPPlayers.Dequeue().PeerId);
                }
            }

            return(-1);
        }
Ejemplo n.º 15
0
 /// <summary>
 /// Replicate this value to the given peer
 /// </summary>
 /// <param name="Value">The value to replicate</param>
 /// <param name="PeerId">The peer to replicate to</param>
 protected virtual void ReplicateToPeer(object Value, int PeerId)
 {
     MDLog.Debug(LOG_CAT, $"Replicating to JIP Peer {PeerId} for member {Member.Name} with value {Value}");
     if (IsReliable())
     {
         Replicator.RpcId(PeerId, MDReplicator.REPLICATE_METHOD_NAME, Replicator.GetReplicationIdForKey(GetUniqueKey()),
                          GetGameTick(), ConvertToObject(Value, true));
     }
     else
     {
         Replicator.RpcUnreliableId(PeerId, MDReplicator.REPLICATE_METHOD_NAME, Replicator.GetReplicationIdForKey(GetUniqueKey()),
                                    GetGameTick(), ConvertToObject(Value, true));
     }
 }
Ejemplo n.º 16
0
        protected virtual void OnServerRequestedInitialization()
        {
            MDLog.Debug(LOG_CAT, $"Initializing PeerId [{PeerId}] from owner");
            string PlaceholderName = "Player_" + PeerId;

            if (MDStatics.IsServer())
            {
                OnClientSentPlayerName(PlaceholderName);
            }
            else
            {
                RpcId(MDStatics.GetServerId(), nameof(OnClientSentPlayerName), PlaceholderName);
            }
        }
Ejemplo n.º 17
0
        private void ClientSynchDone()
        {
            // Great this client is done
            MDLog.Debug(LOG_CAT, $"Peer [{Multiplayer.GetRpcSenderId()}] completed synch");
            if (PeerSynchInfo.ContainsKey(Multiplayer.GetRpcSenderId()))
            {
                PeerSynchInfo[Multiplayer.GetRpcSenderId()].CompletedSynch = true;
            }

            // Send status update to all players so they can update UI
            UpdateSynchStatusOnAllClients(Multiplayer.GetRpcSenderId(), 1f);

            // Update our own UI
            OnPlayerSynchStatusUpdateEvent(Multiplayer.GetRpcSenderId(), 1f);
        }
Ejemplo n.º 18
0
        /// <summary>
        /// Replicate this value to all clients
        /// </summary>
        /// <param name="Value">The value to replicate</param>
        protected virtual void ReplicateToAll(object Value)
        {
            MDLog.Debug(LOG_CAT, $"Replicating {Member.Name} with value {Value} from {LastValue}");
            if (IsReliable())
            {
                Replicator.Rpc(MDReplicator.REPLICATE_METHOD_NAME, Replicator.GetReplicationIdForKey(GetUniqueKey()),
                               GetGameTick(), ConvertToObject(Value, false));
            }
            else
            {
                Replicator.RpcUnreliable(MDReplicator.REPLICATE_METHOD_NAME, Replicator.GetReplicationIdForKey(GetUniqueKey()),
                                         GetGameTick(), ConvertToObject(Value, false));
            }

            LastValue = Value;
        }
Ejemplo n.º 19
0
 private void UpdateNetworkIdMap(params object[] updates)
 {
     for (int i = 0; i < updates.Length; i += 2)
     {
         string key = (string)updates[i + 1];
         uint   id  = (uint)long.Parse(updates[i].ToString());
         MDLog.Debug(LOG_CAT, $"Received Network Map Update with id {id} and key [{key}]");
         NetworkIdKeyMap.AddNetworkKeyIdPair(id, key);
         if (KeyToMemberMap.ContainsKey(key))
         {
             NetworkIdKeyMap.CheckBuffer(id, KeyToMemberMap[key]);
         }
         else
         {
             MDLog.Trace(LOG_CAT, $"KeyToMemberMap does not contain key {key}");
         }
     }
 }
Ejemplo n.º 20
0
        private void RpcReceiveNodeCount(int NodeCount)
        {
            MDLog.Debug(LOG_CAT, $"Total nodes that need synch are {NodeCount}");
            this.NodeCount     = NodeCount;
            NodeSynchCompleted = false;
            // Start synch timer
            Timer timer = this.CreateUnpausableTimer("SynchTimer", false, SYNCH_TIMER_CHECK_INTERVAL, true, this,
                                                     nameof(CheckSynchStatus));

            timer.Start();

            // Send out synch started event
            OnSynchStartedEvent(IsPauseOnJoin());
            // Set all peers to 0% except server
            foreach (int peerid in GameSession.GetAllPeerIds().Where(peerid => peerid != MDStatics.GetServerId()))
            {
                OnPlayerSynchStatusUpdateEvent(peerid, 0f);
            }
        }
Ejemplo n.º 21
0
        private void OnPlayerJoined(int PeerId)
        {
            // Skip local player
            if (PeerId == MDStatics.GetPeerId())
            {
                return;
            }

            MDLog.Debug(LOG_CAT, $"Registered JIPPlayer with Id: {PeerId}");
            JIPPlayers.Enqueue(new NewPlayer(PeerId, OS.GetTicksMsec()));
            if (MDStatics.IsServer())
            {
                List <object> networkIdUpdates = new List <object>();
                foreach (uint key in NetworkIdKeyMap.GetKeys())
                {
                    networkIdUpdates.Add(key);
                    networkIdUpdates.Add(NetworkIdKeyMap.GetValue(key));
                }

                RpcId(PeerId, nameof(UpdateNetworkIdMap), networkIdUpdates);
            }
        }
Ejemplo n.º 22
0
        private void UpdateLabel()
        {
            DisplayLabel.Clear();
            DisplayLabel.PushTable(2);

            foreach (string Category in MDOnScreenDebug.DebugInfoCategoryMap.Keys)
            {
                if (MDOnScreenDebug.HiddenCategories.Contains(Category))
                {
                    continue;
                }

                AddCateogryTitle(Category);

                Dictionary <string, OnScreenDebugInfo> DebugInfoList = MDOnScreenDebug.DebugInfoCategoryMap[Category];

                foreach (string key in DebugInfoList.Keys)
                {
                    try
                    {
                        string text = DebugInfoList[key].InfoFunction.Invoke();
                        AddTextCell(key, DebugInfoList[key].Color);
                        AddTextCell(text, DebugInfoList[key].Color);
                    }
                    catch (Exception ex)
                    {
                        // Something went wrong
                        MDLog.Debug(LOG_CAT, ex.ToString());
                    }
                }

                AddEmptyLine();
            }

            DisplayLabel.Pop();
        }
Ejemplo n.º 23
0
 private void ResponseTicksMsec(uint ClientTicksMsec, uint ServerTimeOfRequest, int RequestNumber)
 {
     MDLog.Debug(LOG_CAT,
                 $"Msec response number {RequestNumber} from peer [{Multiplayer.GetRpcSenderId()}] is {ClientTicksMsec} local Msec is {OS.GetTicksMsec()}");
     PeerSynchInfo[Multiplayer.GetRpcSenderId()].ProcessMSecResponse(ClientTicksMsec, ServerTimeOfRequest, RequestNumber);
 }
Ejemplo n.º 24
0
        private void CheckSynchStatus(Timer timer)
        {
            // Only do this on clients
            if (!this.IsClient())
            {
                timer.RemoveAndFree();
                return;
            }

            if (NodeCount < 0)
            {
                MDLog.Debug(LOG_CAT, "We got no node count");
                // We don't know how many nodes we got yet
                return;
            }

            int SynchedNodes = NodeCount;

            if (NodeList.Count < NodeCount)
            {
                MDLog.Debug(LOG_CAT, "We still don't have all nodes");
                // We still don't have all nodes
                SynchedNodes = NodeList.Count;
            }
            else if (!NodeSynchCompleted)
            {
                // This is the first time we synched all nodes, notify the server
                MDLog.Debug(LOG_CAT, "Node synch complete, notifying server");
                NodeSynchCompleted = true;
                RpcId(GameSession.GetNetworkMaster(), nameof(NotifyAllNodesSynched));
            }

            int NotSynchedNodes = 0;

            // Check node custom logic to see if synch is done
            foreach (Node node in NodeList)
            {
                if (!(node is IMDSynchronizedNode) || ((IMDSynchronizedNode)node).IsSynchronizationComplete())
                {
                    continue;
                }

                // We are not synched
                MDLog.Trace(LOG_CAT, $"A node is still synching: {node.GetPath()}");
                SynchedNodes--;
                NotSynchedNodes++;
            }

            if (SynchedNodes == NodeCount)
            {
                // We are done synching
                SynchronizationState = SynchronizationStates.SYNCRHONIZED;
                RpcId(MDStatics.GetServerId(), nameof(ClientSynchDone));
                MDLog.Debug(LOG_CAT, "We are done synching notifying server");
                // Set ourselves to done
                OnPlayerSynchStatusUpdateEvent(MDStatics.GetPeerId(), 1f);
                timer.RemoveAndFree();
            }
            else
            {
                float percentage = (float)SynchedNodes / NodeCount;
                MDLog.Debug(LOG_CAT,
                            $"We have {NotSynchedNodes} nodes that are still synching. Current status: {percentage * 100}%");
                // Notify the server of how many nodes we got synched
                RpcId(GameSession.GetNetworkMaster(), nameof(ClientSynchStatus), SynchedNodes);

                // Update our own UI
                OnPlayerSynchStatusUpdateEvent(MDStatics.GetPeerId(), percentage);
            }
        }
Ejemplo n.º 25
0
 /// <summary>
 /// Call this when a Player Info is done initializing
 /// </summary>
 /// <param name="PeerId">The PeerId of the player that completed initialization</param>
 public void OnPlayerInfoInitializationCompleted(int PeerId)
 {
     MDLog.Debug(LOG_CAT, $"Player {PeerId} initialization completed");
     OnPlayerInitialized(PeerId);
     BroadcastNewPlayerInitialized(PeerId);
 }
Ejemplo n.º 26
0
        /// <summary>
        /// Looks up the MethodInfo in our cache, if it does not exist it is resolved
        /// </summary>
        /// <param name="Node">The node to look this up for</param>
        /// <param name="Method">The name of the method</param>
        /// <param name="Parameters">The parameters you intend to send to the method</param>
        /// <returns>The MethodInfo or null if it does not exist</returns>
        public static MethodInfo GetMethodInfo(Node Node, string Method, params object[] Parameters)
        {
            Type          nodeType        = Node.GetType();
            List <Type>   Signature       = MDStatics.GetSignatureFromParameters(Parameters);
            StringBuilder SignatureString = new StringBuilder();

            Signature.ForEach(type => SignatureString.Append(type.ToString()));

            String key = $"{nodeType.ToString()}#{Method}#{SignatureString.ToString()}";

            if (!MethodInfoCache.ContainsKey(key))
            {
                MethodInfo newInfo = nodeType.GetMethod(Method, MDStatics.BindFlagsAllMembers, null, Signature.ToArray(), null);
                if (newInfo != null)
                {
                    MethodInfoCache.Add(key, newInfo);
                }
                else
                {
                    // Couldn't find anything with direct compare so we will search for the method manually and cache it if found
                    MethodInfo[] Methods = nodeType.GetAllMethods();
                    foreach (MethodInfo CandidateMethod in Methods)
                    {
                        if (CandidateMethod.Name != Method)
                        {
                            continue;
                        }

                        ParameterInfo[] CandidateParams = CandidateMethod.GetParameters();
                        if (CandidateParams.Count() != Signature.Count)
                        {
                            continue;
                        }

                        List <Type> CandidateSignature = new List <Type>();
                        CandidateParams.ToList().ForEach(param => CandidateSignature.Add(param.ParameterType));

                        MDLog.Debug(LOG_CAT, $"Evaluating {CandidateMethod.Name} ({GetParametersAsString(CandidateSignature.ToArray())})");

                        bool IsCompatible = true;

                        for (int i = 0; i < Signature.Count; ++i)
                        {
                            Type SignatureType = Signature[i];
                            Type CandidateType = CandidateSignature[i];
                            bool isNullable    = CandidateType.IsNullable();
                            if ((CandidateType.IsNullable() && (Parameters == null || Parameters[i] == null)) == false &&
                                (SignatureType.IsCastableTo(CandidateType) == false))
                            {
                                MDLog.Debug(LOG_CAT, $"CandidateMethod.Name: {CandidateMethod.Name} SignatureType: {SignatureType.ToString()} does not cast to {CandidateType.ToString()}");
                                IsCompatible = false;
                                break;
                            }
                        }

                        if (IsCompatible)
                        {
                            MDLog.Debug(LOG_CAT, $"Adding compatible method key {key}");
                            MethodInfoCache.Add(key, CandidateMethod);
                        }
                    }
                }
            }

            if (MethodInfoCache.ContainsKey(key))
            {
                return(MethodInfoCache[key]);
            }
            return(null);
        }
Ejemplo n.º 27
0
 private void ClientOnPlayerJoined(int PeerId)
 {
     MDLog.Debug(LOG_CAT, $"Player {PeerId} Joined");
     OnPlayerJoined_Internal(PeerId);
 }
Ejemplo n.º 28
0
        /// <summary>
        /// Registers the given instance's fields marked with [MDReplicated()]
        /// </summary>
        /// <param name="Instance">The node to register</param>
        public void RegisterReplication(Node Instance)
        {
            List <MemberInfo>         Members     = MDStatics.GetTypeMemberInfos(Instance);
            List <MDReplicatedMember> NodeMembers = new List <MDReplicatedMember>();

            foreach (MemberInfo Member in Members)
            {
                MDReplicated RepAttribute = Member.GetCustomAttribute(typeof(MDReplicated)) as MDReplicated;
                if (RepAttribute == null)
                {
                    continue;
                }

                MDReplicatedSetting[] Settings   = GetSettings(Member);
                MDReplicatedMember    NodeMember = CreateReplicatedMember(Member, RepAttribute, Instance, Settings);

                NodeMembers.Add(NodeMember);

                ProcessSettingsForMember(NodeMember, ParseParameters(typeof(Settings), Settings));

                GroupManager.AddReplicatedMember(NodeMember);

                MDLog.Debug(LOG_CAT, $"Adding Replicated Node {Instance.Name} Member {Member.Name}");

                if (HasRPCModeSet(Member) == false)
                {
                    Instance.RsetConfig(Member.Name, MultiplayerAPI.RPCMode.Puppet);
                }
            }

            if (NodeMembers.Count > 0)
            {
                NodeList.Add(new ReplicatedNode(Instance, NodeMembers));
                List <object> networkIdUpdates = new List <object>();
                foreach (MDReplicatedMember member in NodeMembers)
                {
                    string MemberUniqueKey = member.GetUniqueKey();
                    KeyToMemberMap.Add(MemberUniqueKey, member);

                    // Check if we have a buffer waiting for this member
                    if (NetworkIdKeyMap.ContainsKey(MemberUniqueKey))
                    {
                        NetworkIdKeyMap.CheckBuffer(NetworkIdKeyMap.GetValue(MemberUniqueKey), member);
                    }

                    if (MDStatics.IsServer())
                    {
                        if (!NetworkIdKeyMap.ContainsKey(member.GetUniqueKey()))
                        {
                            uint networkid = GetReplicationId();
                            MDLog.Trace(LOG_CAT, $"Adding NetworkIdKeyMap key [{member.GetUniqueKey()}] with id [{networkid}]");
                            NetworkIdKeyMap.AddNetworkKeyIdPair(networkid, member.GetUniqueKey());
                            NetworkIdKeyMap.CheckBuffer(networkid, member);
                            networkIdUpdates.Add(networkid);
                            networkIdUpdates.Add(member.GetUniqueKey());
                        }
                    }
                }

                if (MDStatics.IsNetworkActive() && networkIdUpdates.Count > 0)
                {
                    Rpc(nameof(UpdateNetworkIdMap), networkIdUpdates);
                }
            }
        }