///<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; } } }
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(); }
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(); }
/// <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); }
/// <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); }
protected virtual void OnClientSentPlayerName(string ClientName) { MDLog.Debug(LOG_CAT, $"Server received initialization for PeerId [{PeerId}] from owner"); if (HasInitialized == false) { PlayerName = ClientName; MarkPlayerInitializationCompleted(); } }
///<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"); }
private void OnPlayerInitialized(int PeerId) { MDPlayerInfo PlayerInfo = GetPlayerInfo(PeerId); if (PlayerInfo != null) { PlayerInfo.HasInitialized = true; NotifyPlayerInitializedEvent(PeerId); MDLog.Debug(LOG_CAT, $"Player {PeerId} Initialized"); } }
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); }
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)); } }
/// <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); } }
/// <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); } }
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); }
// 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); }
/// <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)); } }
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); } }
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); }
/// <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; }
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}"); } } }
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); } }
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); } }
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(); }
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); }
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); } }
/// <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); }
/// <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); }
private void ClientOnPlayerJoined(int PeerId) { MDLog.Debug(LOG_CAT, $"Player {PeerId} Joined"); OnPlayerJoined_Internal(PeerId); }
/// <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); } } }