private void DoCall(Node Target) { switch (Type) { case TypeOfCall.RPC: MethodInfo methodInfo = MDStatics.GetMethodInfo(Target, Convert.ToInt32(Name)); if (methodInfo == null) { MDLog.Fatal(LOG_CAT, $"Could not find method {Target.GetType().ToString()}#{Name}"); return; } object[] convertedParams = MDStatics.ConvertParametersBackToObject(methodInfo, Parameters); MDStatics.GetReplicator().RpcSenderId = SenderPeerId; methodInfo.Invoke(Target, convertedParams); MDStatics.GetReplicator().RpcSenderId = -1; break; case TypeOfCall.RSET: MemberInfo memberInfo = MDStatics.GetMemberInfo(Target, Name); IMDDataConverter Converter = MDStatics.GetConverterForType(memberInfo.GetUnderlyingType()); object value = Converter.ConvertBackToObject(memberInfo.GetValue(Target), Parameters); MDLog.Trace(LOG_CAT, $"Setting {Name} on {Target.Name} to value {value}"); memberInfo.SetValue(Target, value); break; } }
/// <summary> /// Clients receive this from the server as a way to attempt to keep them in synch in case they freeze for any reason /// </summary> /// <param name="EstimateTime">The estimated time for the tick</param> /// <param name="EstimatedTick">The estimated tick for the time</param> public void CheckSynch(long EstimateTime, long EstimatedTick) { long currentTime = OS.GetTicksMsec(); if (EstimateTime < currentTime) { // This packet was delayed and is already in the past, ignore MDLog.Trace(LOG_CAT, $"[{currentTime}] Ignoring tick packet as it was in the past {EstimatedTick} at {EstimateTime}"); return; } // Figure out what tick we would be at when the estimated time is hit long localTickAtTime = GetTickAtTimeOffset(EstimateTime - currentTime); long tickOffset = EstimatedTick - localTickAtTime; if (Math.Abs(tickOffset) > MaxTickDesynch) { MDLog.Trace(LOG_CAT, $"[{currentTime}] We are out of synch, we should be at tick {EstimatedTick} at {EstimateTime}"); MDLog.Trace(LOG_CAT, $"[{currentTime}] We will be at tick {localTickAtTime} which is off by {tickOffset}"); // We are too far out of synch TickSynchAdjustment = (int)tickOffset; } }
private void SendClockedCall(int PeerId, ClockedRemoteCall.TypeOfCall Type, MDReliability Reliability, string NodePath, string Method, MDRemoteMode Mode, params object[] Parameters) { MDLog.Trace(LOG_CAT, $"Sending clocked call {Method} on {NodePath} with parameters ({MDStatics.GetParametersAsString(Parameters)})"); if (Reliability == MDReliability.Reliable) { if (PeerId != -1) { RpcId(PeerId, nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode, Parameters); } else { Rpc(nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode, Parameters); } } else { if (PeerId != -1) { RpcUnreliableId(PeerId, nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode, Parameters); } else { RpcUnreliable(nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode, Parameters); } } }
public object ConvertBackToObject(object CurrentObject, object[] Parameters) { MDLog.Trace(LOG_CAT, $"MDCommandReplicator converting back ({MDStatics.GetParametersAsString(Parameters)})"); if (Parameters.Length == 1 && Parameters[0] == null) { return(null); } T obj; if (CurrentObject != null) { // Replace values in existing object obj = (T)CurrentObject; } else { // Return a new object obj = (T)Activator.CreateInstance(typeof(T)); } for (int i = 0; i < Parameters.Length; i++) { // Get the length of the data int length = Convert.ToInt32(Parameters[i].ToString()); // Extract parameters and apply to the command replicator object[] converterParams = Parameters.SubArray(i + 1, i + length); obj.MDProcessCommand(converterParams); i += length; } return((T)obj); }
public bool ShouldObjectBeReplicated(object LastValue, object CurrentValue) { ExtractMembers(); if (LastValues.Count == 0 && CurrentValue != null) { return(true); } else if (CurrentValue != LastValue) { return(true); } else if (LastValue == null && CurrentValue == null) { return(false); } for (int i = 0; i < Members.Count; i++) { object value = Members[i].GetValue(CurrentValue); if (DataConverters[i].ShouldObjectBeReplicated(LastValues[i], value)) { MDLog.Trace(LOG_CAT, "We should be replicated"); return(true); } } return(false); }
private void RequestTicksMsec(int RequestNumber, uint ServerTimeOfRequest) { // Respond RpcId(GameSession.GetNetworkMaster(), nameof(ResponseTicksMsec), OS.GetTicksMsec(), ServerTimeOfRequest, RequestNumber); MDLog.Trace(LOG_CAT, $"Responded to server request number {RequestNumber} for OS.GetTicksMsec with [{OS.GetTicksMsec()}]"); }
private void NotifyAllNodesSynched() { // Called from client the first time they complete their node synch MDLog.Trace(LOG_CAT, $"Peer [{Multiplayer.GetRpcSenderId()}] has completed node synchronization"); if (!PeerSynchInfo.ContainsKey(Multiplayer.GetRpcSenderId())) { PeerSynchInfo[Multiplayer.GetRpcSenderId()].CompletedNodeSynch = true; } }
/// <summary> /// Remove the uint id from our map /// </summary> /// <param name="id">The id</param> public void RemoveValue(uint id) { if (NetworkIDToKeyMap.ContainsKey(id)) { MDLog.Trace(LOG_CAT, $"Removing id [{id}]"); KeyToNetworkIdMap.Remove(NetworkIDToKeyMap[id]); RemoveBufferId(id); NetworkIDToKeyMap.Remove(id); } }
/// <summary> /// Remove the string key value from our map /// </summary> /// <param name="key">The string key</param> public void RemoveValue(string key) { if (KeyToNetworkIdMap.ContainsKey(key)) { MDLog.Trace(LOG_CAT, $"Removing key [{key}]"); NetworkIDToKeyMap.Remove(KeyToNetworkIdMap[key]); RemoveBufferId(KeyToNetworkIdMap[key]); KeyToNetworkIdMap.Remove(key); } }
/// <summary> /// Check if we got enough of a confidence value in the Msec value of the client to resume the game /// </summary> /// <returns>True if we are ready to resume, false if not</returns> public bool IsClientMSecConfident() { if (TicksList.Count >= GameSynchronizer.GetMinimumMeasurementCountBeforeResume()) { return(true); } MDLog.Trace(LOG_CAT, $"Still waiting for peer [{PeerId}] to get a more secure TickMsec value"); return(false); }
/// <summary> /// Adds the ping to the players ping list and removes any overflow /// </summary> /// <param name="Ping">The ping</param> public void PushPlayerPingToQueue(int Ping) { PingList.Enqueue(Ping); MDLog.Trace(LOG_CAT, $"Peer [{PeerId}] recorded a ping of {Ping}"); if (PingList.Count > SettingAveragePingToKeep) { PingList.Dequeue(); } CalculatePlayerAveragePing(); }
/// /// <summary> /// Invoke the method with the given number on the node /// </summary> /// <param name="MethodNumber">The method number to invoke</param> /// <param name="Parameters">Parameters</param> /// <returns>True if invoked, false if not found</returns> public static bool Invoke(this Node Instance, int MethodNumber, params object[] Parameters) { MethodInfo Info = MDStatics.GetMethodInfo(Instance, MethodNumber); if (Info != null) { MDLog.Trace(LOG_CAT, $"Invoking {Info.Name} with parameters {MDStatics.GetParametersAsString(Parameters)}"); Info.Invoke(Instance, Parameters); return(true); } MDLog.Trace(LOG_CAT, $"Failed to invoke method number {MethodNumber} with parameters {MDStatics.GetParametersAsString(Parameters)}"); return(false); }
private void UnpauseAtTickMsec(uint UnpauseTime, uint GameTickToUnpauseAt) { float waitTime = (UnpauseTime - OS.GetTicksMsec()) / 1000f; MDLog.Trace(LOG_CAT, $"Unpausing game in {waitTime}"); Timer timer = this.CreateUnpausableTimer(RESUME_TIMER_NAME, true, waitTime, true, this, nameof(OnUnpauseTimerTimeout)); timer.Start(); OnSynchCompleteEvent(waitTime); if (GameClock != null && MDStatics.IsClient()) { GameClock.SetCurrentTick(GameTickToUnpauseAt); } }
/// <summary>Attempts to calculate what the remote offset should be based on the current ping</summary> private void CalculateRemoteOffset() { // Check if we got a fixed offset if (RemoteTickOffset > 0) { MDLog.Trace(LOG_CAT, $"Ping offset is set to be static at {RemoteTickOffset}"); CurrentRemoteTickOffsetTarget = RemoteTickOffset + OffsetBuffer; return; } // Check if we got any ping int HighestPing = (int)Mathf.Ceil(GameSynchronizer.GetMaxPlayerPing() * RemoteTickPingModifier); if (HighestPing == 0) { CurrentRemoteTickOffsetTarget = MinimumOffset + OffsetBuffer; MDLog.Trace(LOG_CAT, $"We got no ping setting offset to minimum offset of {MinimumOffset}"); return; } // Calculate offset based on ping int newOffset = (int)Mathf.Ceil(HighestPing / TICK_INTERVAL_MILLISECONDS); // If it is less than minimum set our target to minimum if (newOffset <= MinimumOffset) { CurrentRemoteTickOffsetTarget = MinimumOffset + OffsetBuffer; MDLog.Trace(LOG_CAT, $"Ping offset of {newOffset} is less than our minimum offset of {MinimumOffset}"); return; } // Calculate the difference between new and old float difference = (float)(newOffset - CurrentRemoteTickOffset) / newOffset; difference = Mathf.Abs(difference); // Is the difference larger than our allowed tolerance? if (Mathf.Abs(difference) >= RemoteTickPingTolerance) { MDLog.Trace(LOG_CAT, $"Ping difference is too large adjust remote tick offset target from {CurrentRemoteTickOffsetTarget} to {newOffset}"); // We need to adjust the remote tick offset CurrentRemoteTickOffsetTarget = newOffset + OffsetBuffer; } }
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}"); } } }
public override void _Process(float delta) { if (GetTree().Paused) { return; } // Adjust ourselves in synch with server at most once per tick if (TickSynchAdjustment > 0) { MDLog.Trace(LOG_CAT, $"Tick {CurrentTick}, was skipped because adjustment was {TickSynchAdjustment}"); OnLocalSkippedTick(CurrentTick); CurrentTick++; TickSynchAdjustment--; } else if (TickSynchAdjustment < 0) { MDLog.Trace(LOG_CAT, $"Tick {CurrentTick}, was repeated because adjustment was {TickSynchAdjustment}"); LastTickDuplicated = true; CurrentTick--; TickSynchAdjustment++; } DeltaTickCounter += delta; if (DeltaTickCounter >= TICK_INTERVAL_DELTA) { // Increase tick counter DeltaTickCounter -= TICK_INTERVAL_DELTA; CurrentTick++; // Allow skipping a single tick per update to catch up if (DeltaTickCounter >= TICK_INTERVAL_DELTA) { MDLog.Trace(LOG_CAT, $"Tick {CurrentTick}, was skipped because delta was {delta}"); OnLocalSkippedTick(CurrentTick); DeltaTickCounter -= TICK_INTERVAL_DELTA; CurrentTick++; } } AdjustRemoteTickOffset(); OnGameTick(CurrentTick); }
/// <summary> /// Apply the buffer to a member if it exists /// </summary> /// <param name="ID">The ID to check the buffer for</param> /// <param name="Member">The member to apply this buffer to</param> public void CheckBuffer(uint ID, MDReplicatedMember Member) { if (!ClockedValueBuffer.ContainsKey(ID)) { return; } Dictionary <uint, object[]> buffer = GetBufferForId(ID); foreach (uint tick in buffer.Keys) { object[] value = buffer[tick]; MDLog.Trace(LOG_CAT, $"Updating value to {MDStatics.GetParametersAsString(value)} for {ID} on tick {tick}"); Member.SetValues(tick, value); } buffer.Clear(); ClockedValueBuffer.Remove(ID); }
/// <summary> /// Sets the value of this member /// </summary> /// <param name="member">The member</param> /// <param name="Instance">The instance to set the value for</param> /// <param name="Value">The value</param> public static void SetValue(this MemberInfo member, object Instance, object Value) { MDLog.Trace(LOG_CAT, $"Setting {member.Name}"); switch (member.MemberType) { case MemberTypes.Field: ((FieldInfo)member).SetValue(Instance, Value); break; case MemberTypes.Property: ((PropertyInfo)member).SetValue(Instance, Value); break; default: MDLog.Error(LOG_CAT, $"Input MemberInfo was of type {member.MemberType.ToString()}, it should be of type FieldInfo or PropertyInfo"); break; } }
private void ClockedCall(uint Tick, ClockedRemoteCall.TypeOfCall Type, string NodePath, string Method, MDRemoteMode Mode, params object[] Parameters) { Node Target = GetNodeOrNull(NodePath); if (Target == null) { MDLog.Warn(LOG_CAT, $"Could not find target [{NodePath}] for ClockedRpcCall."); return; } MDLog.Trace(LOG_CAT, $"Got clocked call {Method} on {NodePath} with parameters ({MDStatics.GetParametersAsString(Parameters)})"); ClockedRemoteCall RemoteCall = new ClockedRemoteCall(Tick, Type, WeakRef(Target), Method, Mode, Multiplayer.GetRpcSenderId(), Parameters); // Check if we should already invoke this (if the time has already passed) if (!RemoteCall.Invoke(GameClock.GetRemoteTick())) { ClockedRemoteCallList.Add(RemoteCall); } }
public bool ShouldObjectBeReplicated(object LastValue, object CurrentValue) { if (LastValue == null && CurrentValue == null) { return(false); } else if (LastValue == null || CurrentValue == null) { MDLog.Trace(LOG_CAT, "We should be replicated because something is null"); return(true); } else if (((T)CurrentValue).MDShouldBeReplicated()) { MDLog.Trace(LOG_CAT, "We should be replicated because we have updates"); return(true); } return(false); }
public object ConvertBackToObject(object CurrentObject, object[] Parameters) { ExtractMembers(); MDLog.Trace(LOG_CAT, $"MDCustomClass converting back ({MDStatics.GetParametersAsString(Parameters)})"); if (Parameters.Length == 1 && Parameters[0] == null) { return(null); } T obj; if (CurrentObject != null) { // Replace values in existing object obj = (T)CurrentObject; } else { // Return a new object obj = (T)Activator.CreateInstance(typeof(T)); } for (int i = 0; i < Parameters.Length; i++) { // key0 = index, key1 = length object[] keys = Parameters[i].ToString().Split(SEPARATOR); int index = Convert.ToInt32(keys[0].ToString()); int length = Convert.ToInt32(keys[1].ToString()); // Extract parameters and use data converter object[] converterParams = Parameters.SubArray(i + 1, i + length); object currentValue = Members[index].GetValue(obj); object convertedValue = DataConverters[index].ConvertBackToObject(currentValue, converterParams); // Set the value and increase i based on length of data Members[index].SetValue(obj, convertedValue); i += length; } return(obj); }
/// <summary> /// Finds and loads the file with the given name. /// This will overwrite any existing section+keys with the same value already in our configuration. /// </summary> /// <param name="name">The name of the file to load</param> /// <returns>True if it could be found and loaded, false if not</returns> public bool LoadConfiguration(string name) { string path = FindFile(name); if (path == "") { return(false); } MDLog.Trace(LOG_CAT, $"Loading config file {path}"); ConfigFile conFile = new ConfigFile(); conFile.Load(path); foreach (string section in conFile.GetSections()) { foreach (string key in conFile.GetSectionKeys(section)) { if (!Configuration.ContainsKey(section)) { MDLog.Trace(LOG_CAT, $"Adding section {section}"); // Add a new section Configuration.Add(section, new Dictionary <string, object>()); } if (!Configuration[section].ContainsKey(key)) { MDLog.Trace(LOG_CAT, $"Adding section {section} key {key}"); // Add a new key Configuration[section].Add(key, conFile.GetValue(section, key)); } else { MDLog.Trace(LOG_CAT, $"Overwriting section {section} key {key}"); // Overwrite existing key Configuration[section][key] = conFile.GetValue(section, key); } } } return(true); }
/// <summary> /// Called when the ping timer times out, sends a ping request to the given client. /// </summary> public void OnPingTimerTimeout() { // Check if network is still active if (!MDStatics.IsNetworkActive()) { MDLog.Trace(LOG_CAT, $"Network is no longer active"); return; } // Send ping request if (GameSynchronizer.GameClock == null) { GameSynchronizer.RpcId(PeerId, MDGameSynchronizer.METHOD_REQUEST_PING, OS.GetTicksMsec()); } else { int maxPlayerPing = GameSynchronizer.GetMaxPlayerPing() + (int)Ping; uint estimate = GameSynchronizer.GetPlayerTicksMsec(PeerId) + (uint)Ping; GameSynchronizer.RpcId(PeerId, MDGameSynchronizer.METHOD_REQUEST_PING, OS.GetTicksMsec(), estimate, GameSynchronizer.GameClock.GetTickAtTimeOffset(Ping), maxPlayerPing); } }
public object[] ConvertForSending(object Item, bool Complete) { ExtractMembers(); if (Item == null) { return(new object[] { null }); } List <object> ObjectArray = new List <object>(); List <object> newLastValues = new List <object>(); for (int i = 0; i < Members.Count; i++) { object value = Members[i].GetValue(Item); IMDDataConverter Converter = DataConverters[i]; if (Complete || LastValues.Count == 0 || Converter.ShouldObjectBeReplicated(LastValues[i], value)) { object[] dataArray = Converter.ConvertForSending(value, Complete); if (dataArray != null) { ObjectArray.Add($"{i}{SEPARATOR}{dataArray.Length}"); ObjectArray.AddRange(dataArray); } else { ObjectArray.Add($"{i}{SEPARATOR}{1}"); ObjectArray.Add(null); } } newLastValues.Add(value); } MDLog.Trace(LOG_CAT, $"MDCustomClass converting for sending ({MDStatics.GetParametersAsString(ObjectArray.ToArray())})"); LastValues = newLastValues; return(ObjectArray.ToArray()); }
public object[] ConvertForSending(object Item, bool Complete) { if (Item == null) { return(new object[] { null }); } // Get commands T CReplicator = (T)Item; List <object[]> commands = Complete ? CReplicator.MDGetCommandsForNewPlayer() : CReplicator.MDGetCommands(); List <object> ReturnList = new List <object>(); foreach (object[] command in commands) { // Add length ReturnList.Add(command.Length); ReturnList.AddRange(command); } MDLog.Trace(LOG_CAT, $"MDCommandReplicator converting for sending ({MDStatics.GetParametersAsString(ReturnList.ToArray())})"); return(ReturnList.ToArray()); }
private void RequestPing(uint ServerTimeOfRequest) { // Respond RpcId(Multiplayer.GetRpcSenderId(), nameof(PingResponse), ServerTimeOfRequest); MDLog.Trace(LOG_CAT, "Responded to server request for ping"); }
private void OnNetworkNodeRemoved(Node node) { MDLog.Trace(LOG_CAT, $"Node removed: {node.GetPath()}"); NodeList.Remove(node); }
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> /// 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); } } }
private void OnUnpauseTimerTimeout(Timer timer) { MDLog.Trace(LOG_CAT, "Unpausing game"); timer.RemoveAndFree(); GetTree().Paused = false; }