/// <summary> /// Called when a node is removed /// </summary> /// <param name="RemovedNode">The removed node</param> public void OnNodeRemoved(Node RemovedNode) { if (MDStatics.IsNetworkActive() == false) { return; } bool WasNetworked = NetworkedTypes.Remove(RemovedNode); if (WasNetworked == false) { WasNetworked = NetworkedScenes.Remove(RemovedNode); } if (WasNetworked == false) { return; } OrderedNetworkedNodes.Remove(RemovedNode); string NodePath = RemovedNode.GetPath(); OnNetworkNodeRemoved(RemovedNode); if (this.IsMaster() && MDStatics.IsNetworkActive()) { Rpc(nameof(RemoveAndFreeNode), NodePath); } }
/// <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)); }
/// <summary> /// Adds some basic information on creation, can be toggled in config /// </summary> public void AddBasicInfo() { AddOnScreenDebugInfo("FPS", () => Engine.GetFramesPerSecond().ToString(CultureInfo.InvariantCulture), Colors.Red); AddOnScreenDebugInfo("Static Memory", () => MDStatics.HumanReadableSize(OS.GetStaticMemoryUsage()), Colors.Red); AddOnScreenDebugInfo("Network Active: ", () => MDStatics.IsNetworkActive().ToString(), Colors.Red); AddOnScreenDebugInfo("PeerId: ", () => MDStatics.GetPeerId().ToString(), Colors.Red); }
/// <summary> /// Same as RsetUnreliable except it checks if the network is activate first /// </summary> /// <param name="PeerId">The peer to send to</param> /// <param name="Property">The property to set</param> /// <param name="Value">The value</param> public static void MDRsetUnreliableId(this Node Instance, int PeerId, string Property, object Value) { if (!MDStatics.IsNetworkActive() && !MDStatics.IsServer()) { return; } MDStatics.GetReplicator().SendClockedRset(PeerId, MDReliability.Unreliable, Instance, Property, Value); }
/// <summary> /// Same as Rset except it checks if the network is activate first /// </summary> /// <param name="Property">The property to set</param> /// <param name="Value">The value</param> public static void MDRset(this Node Instance, string Property, object Value) { if (!MDStatics.IsNetworkActive()) { return; } MDStatics.GetReplicator().SendClockedRset(-1, MDReliability.Reliable, Instance, Property, Value); }
/// <summary> /// Same as RpcUnreliableId except it checks if the network is activate first and takes game clock into account /// </summary> /// <param name="PeerId">The id of the peer to send to</param> /// <param name="Method">The method to call</param> /// <param name="Args">Arguments</param> public static void MDRpcUnreliableId(this Node Instance, int PeerId, string Method, params object[] Args) { if (!MDStatics.IsNetworkActive() && !MDStatics.IsServer()) { return; } // Send through replicator MDStatics.GetReplicator().SendClockedRpc(PeerId, MDReliability.Unreliable, Instance, Method, Args); }
// /// <summary> /// Same as Rpc except it checks if the network is activate first and takes game clock 1o account /// </summary> /// <param name="Method">The method to call</param> /// <param name="Args">Arguments</param> public static void MDRpc(this Node Instance, string Method, params object[] Args) { if (!MDStatics.IsNetworkActive()) { return; } // Send through replicator MDStatics.GetReplicator().SendClockedRpc(-1, MDReliability.Reliable, Instance, Method, Args); }
/// <summary> /// Sends a clocked rset to another client /// </summary> /// <param name="PeerId">The peer to send to</param> /// <param name="Reliability">Reliability to send at</param> /// <param name="Target">The node that is the target of our rpc call</param> /// <param name="MemberName">The name of the member to set</param> /// <param name="Value">The value to set</param> public void SendClockedRset(int PeerId, MDReliability Reliability, Node Target, string MemberName, object Value) { if (PeerId == MDStatics.GetPeerId() || (MDStatics.IsServer() && !MDStatics.IsNetworkActive() && PeerId == MDStatics.GetServerId())) { // This is to ourselves so just set Target.SetMemberValue(MemberName, Value); return; } MDRemoteMode Mode = MDStatics.GetMemberRpcType(Target, MemberName); MemberInfo info = MDStatics.GetMemberInfo(Target, MemberName); IMDDataConverter Converter = MDStatics.GetConverterForType(info.GetUnderlyingType()); switch (Mode) { case MDRemoteMode.Master: if (!Target.IsNetworkMaster()) { // Remote invoke master only SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RSET, Reliability, Target.GetPath(), MemberName, Mode, Converter.ConvertForSending(Value, true)); } break; case MDRemoteMode.MasterSync: if (!Target.IsNetworkMaster()) { // Remote invoke master only SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RSET, Reliability, Target.GetPath(), MemberName, Mode, Converter.ConvertForSending(Value, true)); } Target.SetMemberValue(MemberName, Value); break; case MDRemoteMode.Puppet: case MDRemoteMode.Remote: // Remote invoke SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RSET, Reliability, Target.GetPath(), MemberName, Mode, Converter.ConvertForSending(Value, true)); break; case MDRemoteMode.PuppetSync: case MDRemoteMode.RemoteSync: // Remote invoke and local invoke SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RSET, Reliability, Target.GetPath(), MemberName, Mode, Converter.ConvertForSending(Value, true)); Target.SetMemberValue(MemberName, Value); break; } }
// Broadcasts out replicated modified variables if we're the server, propagates changes recieved from the server if client. private void TickReplication() { bool paused = GetTree().Paused; #if DEBUG using (MDProfiler Profiler = new MDProfiler("MDReplicator.TickReplication")) #endif { if (MDStatics.IsNetworkActive() == false) { return; } // First process any outstanding clocked calls CheckClockedRemoteCalls(); int JIPPeerId = CheckForNewPlayer(); HashSet <MDReplicatedMember> CurrentReplicationList = GroupManager.GetMembersToReplicate(); for (int i = NodeList.Count - 1; i >= 0; --i) { ReplicatedNode RepNode = NodeList[i]; Node Instance = RepNode.Instance.GetRef() as Node; if (!IsInstanceValid(Instance)) { NodeList.RemoveAt(i); } else { RepNode.CheckForNetworkChanges(Instance.GetNetworkMaster()); foreach (MDReplicatedMember RepMember in RepNode.Members) { RepMember.CheckForValueUpdate(); if (!RepMember.ShouldReplicate() || paused && !RepMember.ProcessWhilePaused || RepMember.GetReplicatedType() == MDReplicatedType.JoinInProgress && JIPPeerId == -1) { continue; } MDLog.CTrace(JIPPeerId != -1, LOG_CAT, $"Replicating {RepMember.GetUniqueKey()} to JIP Player {JIPPeerId}"); MDLog.CTrace(JIPPeerId == -1, LOG_CAT, $"Replicating {RepMember.GetUniqueKey()}"); RepMember.Replicate(JIPPeerId, CurrentReplicationList.Contains(RepMember)); } } } } }
/// <summary> /// Notifies all clients that a new player has initialized /// </summary> /// <param name="Joiner">The PeerID of the joining client</param> protected void BroadcastNewPlayerInitialized(int Joiner) { if (MDStatics.IsNetworkActive()) { foreach (int PeerId in Players.Keys) { if (PeerId == SERVER_ID) { continue; } MDLog.Debug(LOG_CAT, $"Notifying Peer [{PeerId}] that Peer [{Joiner}] has initialized"); RpcId(PeerId, nameof(OnPlayerInitialized), Joiner); } } }
/// <summary> /// Change the network master of a node. This only works on the server. /// </summary> /// <param name="Node">The node to change network master of</param> /// <param name="NewNetworkMaster">The new network master</param> /// <returns>True if network master was changed, false if not</returns> public bool ChangeNetworkMaster(Node Node, int NewNetworkMaster) { if (!GetAllPeerIds().Contains(NewNetworkMaster)) { // Invalid network master return(false); } // Only server can change network master if (MDStatics.IsNetworkActive() && MDStatics.IsServer()) { Node.SetNetworkMaster(NewNetworkMaster); Rpc(nameof(ChangeNetworkMasterOnClients), Node.GetPath(), NewNetworkMaster); return(true); } return(false); }
/// <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); } }
/// <summary> /// Spawn a network node /// </summary> /// <param name="NodeType">The type of node 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(Type NodeType, 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 (!MDStatics.IsSameOrSubclass(NodeType, typeof(Node))) { MDLog.Error(LOG_CAT, $"Provided type [{NodeType.Name}] is not a subclass of Node"); 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 NodeTypeString = NodeType.AssemblyQualifiedName; string ParentPath = Parent.GetPath(); Vector3 SpawnPosVal = SpawnPos.GetValueOrDefault(); if (MDStatics.IsNetworkActive()) { Rpc(nameof(SpawnNodeType), NodeTypeString, ParentPath, NodeName, NodeMaster, SpawnPosVal); } return(SpawnNodeType(NodeTypeString, ParentPath, NodeName, NodeMaster, SpawnPosVal)); }
/// <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); } } }
/// <summary> /// Returns true if the local peer is the network master of the node or we're not networking /// </summary> /// <returns>True if we are master of the node, false if not</returns> public static bool IsMaster(this Node Instance) { return(MDStatics.IsNetworkActive() == false || Instance.GetNetworkMaster() == MDStatics.GetPeerId()); }
/// <summary> /// Sends a clocked RPC call to another client /// </summary> /// <param name="PeerId">The peer to send to</param> /// <param name="Reliability">Reliability to send at</param> /// <param name="Target">The node that is the target of our rpc call</param> /// <param name="Method">The method we want to invoke on the node</param> /// <param name="Parameters">Parameters for the call</param> public void SendClockedRpc(int PeerId, MDReliability Reliability, Node Target, string Method, params object[] Parameters) { if (PeerId == MDStatics.GetPeerId() || (MDStatics.IsServer() && !MDStatics.IsNetworkActive() && PeerId == MDStatics.GetServerId())) { // This is to ourselves so just invoke RpcSenderId = PeerId; Target.Invoke(Method, Parameters); RpcSenderId = -1; return; } MDRemoteMode Mode = MDStatics.GetMethodRpcType(Target, Method, Parameters); int MethodNumber = MDStatics.GetMethodNumber(Target, Method, Parameters); if (MethodNumber == -1) { // Abort MDLog.Fatal(LOG_CAT, $"Could not find method number for {Target.GetType().ToString()}#{Method}({MDStatics.GetParametersAsString(Parameters)})"); return; } MethodInfo MethodInfo = MDStatics.GetMethodInfo(Target, MethodNumber); object[] SendingParams = MDStatics.ConvertParametersForSending(MethodInfo, Parameters); switch (Mode) { case MDRemoteMode.Master: if (!Target.IsNetworkMaster()) { // Remote invoke master only SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RPC, Reliability, Target.GetPath(), MethodNumber.ToString(), Mode, SendingParams); } break; case MDRemoteMode.MasterSync: if (!Target.IsNetworkMaster()) { // Remote invoke master only SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RPC, Reliability, Target.GetPath(), MethodNumber.ToString(), Mode, SendingParams); } Target.Invoke(Method, Parameters); break; case MDRemoteMode.Puppet: case MDRemoteMode.Remote: // Remote invoke SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RPC, Reliability, Target.GetPath(), MethodNumber.ToString(), Mode, SendingParams); break; case MDRemoteMode.PuppetSync: case MDRemoteMode.RemoteSync: // Remote invoke and local invoke SendClockedCall(PeerId, ClockedRemoteCall.TypeOfCall.RPC, Reliability, Target.GetPath(), MethodNumber.ToString(), Mode, SendingParams); Target.Invoke(Method, Parameters); break; } }