/// <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); }
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(); }
/// <summary> /// Called when an input event happens /// </summary> /// <param name="Event">The event</param> public void OnInputEvent(InputEvent Event) { MDInputType OldInputType = LastInputType; switch (Event) { case InputEventKey _: case InputEventMouse _: LastInputType = MDInputType.MouseAndKeyboard; break; case InputEventJoypadButton _: case InputEventJoypadMotion _: LastInputType = MDInputType.JoyPad; break; case InputEventScreenTouch _: case InputEventGesture _: LastInputType = MDInputType.Touch; break; default: MDLog.Warn(LOG_CAT, $"Unknown Input Event Type: {Event.AsText()}"); break; } if (OldInputType != LastInputType) { OnInputTypeChanged(OldInputType, LastInputType); } }
public void Disconnect() { MDLog.Info(LOG_CAT, "Disconnected from server"); IsSessionStarted = false; foreach (int PeerId in Players.Keys) { MDPlayerInfo Player = Players[PeerId]; OnPlayerLeftEvent(PeerId); Player.RemoveAndFree(); } NetworkedMultiplayerENet peer = GetPeer(); if (peer != null) { peer.CloseConnection(); SetNetworkPeer(null); } StopUPNP(); Players.Clear(); ClearNetworkedNodes(); OnSessionEndedEvent(); SceneBuffer.Clear(); }
public override void _Ready() { MDStatics.GI = this; // Configuration first CreateConfiguration(); // Init static classes first MDLog.Initialize(GetLogDirectory()); MDArguments.PopulateArgs(); MDProfiler.Initialize(); MDOnScreenDebug.Initialize(); // Hook up events GetTree().Connect("node_added", this, nameof(OnNodeAdded_Internal)); GetTree().Connect("node_removed", this, nameof(OnNodeRemoved_Internal)); // Init instances CreateGameSession(); CreateGameSynchronizer(); CreateReplicator(); CreateInterfaceManager(); RegisterNodeAndChildren(GetTree().Root); }
/// <summary> /// Register a command, with custom help text that can be displayed in the console /// </summary> /// <param name="Instance">The instance to register for</param> /// <param name="Method">The method to register</param> /// <param name="HelpText">Help text to show in console</param> /// <param name="DefaultParams">Default parameters</param> public static void RegisterCommand(object Instance, MethodInfo Method, string HelpText, object[] DefaultParams = null) { if (Method == null) { return; } if (_commandMap == null) { _commandMap = new Dictionary <string, CommandInfo>(); } string MethodName = Method.Name.ToLower(); if (_commandMap.ContainsKey(MethodName)) { MDLog.Warn(LOG_CAT, $"Command with name [{Method.Name}] is already registered, it will be replaced"); } CommandInfo NewCommand; NewCommand.HelpText = HelpText; NewCommand.Instance = Instance; NewCommand.Method = Method; NewCommand.DefaultArgs = DefaultParams; _commandMap[MethodName] = NewCommand; }
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); }
public bool StartServer(int Port, int MaxPlayers = DEFAULT_MAX_PLAYERS) { NetworkedMultiplayerENet peer = new NetworkedMultiplayerENet(); peer.Connect("peer_connected", this, nameof(ServerOnPeerConnected)); peer.Connect("peer_disconnected", this, nameof(ServerOnPeerDisconnected)); ConfigurePeer(peer); Error error = peer.CreateServer(Port, MaxPlayers); bool Success = error == Error.Ok; MDLog.CLog(Success, LOG_CAT, MDLogLevel.Info, $"Starting server on port {Port} with {MaxPlayers} max players."); MDLog.CLog(!Success, LOG_CAT, MDLogLevel.Error, $"Failed to start server on port {Port}"); if (Success) { UPNPPort = Port; SetNetworkPeer(peer); ServerOnStarted(); } else { MDLog.Error(LOG_CAT, "Failed to start server"); OnSessionFailedEvent(); } return(Success); }
// Create and initialize the player object private MDPlayerInfo GetOrCreatePlayerObject(int PeerId) { if (Players.ContainsKey(PeerId)) { return(Players[PeerId]); } Type PlayerType = GameInstance.GetPlayerInfoType(); if (!MDStatics.IsSameOrSubclass(PlayerType, typeof(MDPlayerInfo))) { MDLog.Error(LOG_CAT, $"Provided player type [{PlayerType.Name}] is not a subclass of MDPlayerInfo"); return(null); } MDPlayerInfo Player = Activator.CreateInstance(PlayerType) as MDPlayerInfo; Player.SetPeerId(PeerId); Player.PauseMode = PauseModeEnum.Process; AddChild(Player); Players.Add(PeerId, Player); OnPlayerInfoCreated(Player); return(Player); }
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); } } }
/// <summary> /// Parses the settings we know about /// </summary> /// <param name="SettingsValues">A setting array that has been run through MDReplicator.ParseParameters</param> protected void ParseSettings(MDReplicatedSetting[] SettingsValues) { foreach (MDReplicatedSetting setting in SettingsValues) { switch ((Settings)setting.Key) { case Settings.OnValueChangedEvent: Node Node = NodeRef.GetRef() as Node; OnValueChangedMethodCallback = Node.GetType().GetMethodRecursive(setting.Value.ToString()); if (OnValueChangedMethodCallback == null) { OnValueChangedEventCallback = Node.GetType().GetEvent(setting.Value.ToString()); } MDLog.CError(OnValueChangedMethodCallback == null && OnValueChangedEventCallback == null, LOG_CAT, $"Failed to find method or event with name {setting.Value.ToString()} on Node {Node.GetPath()}"); break; case Settings.Converter: Type DataConverterType = Type.GetType(setting.Value.ToString()); DataConverter = MDStatics.CreateConverterOfType(DataConverterType); break; case Settings.CallOnValueChangedEventLocally: ShouldCallOnValueChangedCallbackLocally = setting.Value as bool? ?? false; break; } } // We got no data converter, get default one if (DataConverter == null) { DataConverter = MDStatics.GetConverterForType(Member.GetUnderlyingType()); } }
// Starts a server or client based on the command args private void CheckArgsForConnectionInfo() { if (MDArguments.HasArg(ARG_STANDALONE)) { StartStandalone(); } // Expects -server=[port] else if (MDArguments.HasArg(ARG_SERVER)) { int Port = MDArguments.GetArgInt(ARG_SERVER); StartServer(Port); } // Expects -client=[IPAddress:Port] else if (MDArguments.HasArg(ARG_CLIENT)) { string ClientArg = MDArguments.GetArg(ARG_CLIENT); string[] HostPort = ClientArg.Split(":"); if (HostPort.Length == 2) { StartClient(HostPort[0], HostPort[1].ToInt()); } else { MDLog.Error(LOG_CAT, $"Failed to parse client arg {ClientArg}, expecting -{ARG_CLIENT}=[IPAddress:Port]"); } } }
/// <summary> /// Spawn a network node /// </summary> /// <param name="ScenePath">The path to the scene to spawn</param> /// <param name="Parent">The parent that the new instance will be a child of</param> /// <param name="NodeName">The name of the new node</param> /// <param name="UseRandomName">If set to true a random number will be added at the end of the node name</param> /// <param name="NetworkMaster">The peer that should own this, default is server</param> /// <param name="SpawnPos">Where the spawn this node</param> /// <returns>The new node</returns> public Node SpawnNetworkedNode(string ScenePath, Node Parent, string NodeName, bool UseRandomName = true, int NetworkMaster = -1, Vector3?SpawnPos = null) { if (this.IsMaster() == false) { MDLog.Error(LOG_CAT, "Only server can spawn networked nodes"); return(null); } if (!Parent.IsInsideTree()) { MDLog.Error(LOG_CAT, $"Parent [{Parent.Name}] is not inside the tree"); return(null); } NodeName = BuildNodeName(NodeName, UseRandomName); int NodeMaster = NetworkMaster != -1 ? NetworkMaster : MDStatics.GetPeerId(); string ParentPath = Parent.GetPath(); Vector3 SpawnPosVal = SpawnPos.GetValueOrDefault(); if (MDStatics.IsNetworkActive()) { Rpc(nameof(SpawnNodeScene), ScenePath, ParentPath, NodeName, NodeMaster, SpawnPosVal); } return(SpawnNodeScene(ScenePath, ParentPath, NodeName, NodeMaster, SpawnPosVal)); }
private void GenerateConfigFile(string Source, string Target) { string existingTarget = FindFile(Target); if (existingTarget == "") { string SourceFilePath = FindFile(Source); File SourceFile = new File(); SourceFile.Open(SourceFilePath, File.ModeFlags.Read); string SourceText = SourceFile.GetAsText(); SourceFile.Close(); if (SourceText == "") { MDLog.Error(LOG_CAT, $"Failed to read config from {SourceFilePath}"); return; } string TargetFilePath = $"res://{Target}"; File TargetFile = new File(); TargetFile.Open(TargetFilePath, File.ModeFlags.Write); TargetFile.StoreString(SourceText); TargetFile.Close(); MDLog.Info(LOG_CAT, $"Copied config file from {SourceFilePath} to {TargetFilePath}"); } else { MDLog.Info(LOG_CAT, $"Config file {existingTarget} already exists"); } }
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> /// 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; } }
/// <summary> /// Gets the number of a method /// </summary> /// <param name="Node">The node to look this up for</param> /// <param name="MethodNumber">The name of the method</param> /// <param name="Parameters">The parameters you intend to send to the method</param> /// <returns>The MethodInfo or -1 if it is not found</returns> public static int GetMethodNumber(Node Node, string Method, params object[] Parameters) { Type nodeType = Node.GetType(); if (!NumberedMethodInfoCache.ContainsKey(nodeType)) { NumberedMethodInfoCache.Add(nodeType, new List <MethodInfo>(nodeType.GetAllMethods())); } MethodInfo info = GetMethodInfo(Node, Method, Parameters); if (info != null) { List <MethodInfo> methodInfos = NumberedMethodInfoCache[nodeType]; for (int i = 0; i < methodInfos.Count; i++) { if (methodInfos[i].Equals(info)) { return(i); } } } MDLog.Warn(LOG_CAT, $"Method number could not be found for {nodeType.ToString()}#{Method}({GetParametersAsString(Parameters)})"); return(-1); }
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> /// 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); }
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 override void _Ready() { MDLog.AddLogCategoryProperties(LOG_CAT, new MDLogProperties(MDLogLevel.Trace)); this.RegisterCommandAttributes(); CheckArgsForConnectionInfo(); }
///<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; } } }
/// <summary> /// Disposes the profiler /// </summary> public void Dispose() { Timer.Stop(); if (_enabledProfiles.Contains(LowerProfileName) || MDArguments.HasArg(LOG_ARG)) { MDLog.Info(LOG_CAT, $"Profiling [{ProfileName}] took {GetMicroSeconds()} us"); } }
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()}]"); }
protected virtual void OnClientSentPlayerName(string ClientName) { MDLog.Debug(LOG_CAT, $"Server received initialization for PeerId [{PeerId}] from owner"); if (HasInitialized == false) { PlayerName = ClientName; MarkPlayerInitializationCompleted(); } }
private void ClientOnConnected() { MDLog.Info(LOG_CAT, "Client connected to server"); int PeerId = MDStatics.GetPeerId(); OnPlayerJoined_Internal(PeerId); OnSessionStartedEvent(); IsSessionStarted = true; }
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); } }
private static PackedScene LoadPackedScene(String path) { String full_path = path; if (!ResourceLoader.Exists(full_path)) { MDLog.Warn(LOG_CAT, $"Can't find: {full_path}"); } return((PackedScene)ResourceLoader.Load(full_path)); }
/// <summary> /// Creates an instance of the type based on the base class T /// </summary> /// <param name="Type">The type to instantiate</param> /// <returns>The instance created or null if it fails</returns> public static T CreateTypeInstance <T>(Type Type) where T : class { if (!IsSameOrSubclass(Type, typeof(T))) { MDLog.Error(LOG_CAT, $"Type [{Type.Name}] is not a subclass of [{typeof(T).Name}]"); return(null); } return(Activator.CreateInstance(Type) as T); }