using System; using System.Collections.Generic; using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Network; using Robust.Shared.Network.Messages; using Robust.Shared.Timing; using Robust.Shared.Utility; namespace Robust.Shared.Configuration { /// /// A networked configuration manager that controls the replication of /// console variables between client and server. /// public interface INetConfigurationManager : IConfigurationManager { /// /// Sets up the networking for the config manager. /// void SetupNetworking(); /// /// Get a replicated client CVar for a specific client. /// /// CVar type. /// channel of the connected client. /// Name of the CVar. /// Replicated CVar of the client. T GetClientCVar(INetChannel channel, string name); /// /// Synchronize the CVars marked with with the client. /// This needs to be called once during the client connection. /// /// Client's NetChannel to sync replicated CVars with. void SyncConnectingClient(INetChannel client); /// /// Synchronize the CVars marked with with the server. /// This needs to be called once when connecting. /// void SyncWithServer(); /// /// Called every tick to process any incoming network messages. /// void TickProcessMessages(); /// /// Flushes any NwCVar messages in the receive buffer. /// void FlushMessages(); /// /// Clears internal flag for . /// Must be called upon disconnect. /// void ClearReceivedInitialNwVars(); public event EventHandler ReceivedInitialNwVars; } /// internal class NetConfigurationManager : ConfigurationManager, INetConfigurationManager { [Dependency] private readonly INetManager _netManager = null!; [Dependency] private readonly IGameTiming _timing = null!; private readonly Dictionary> _replicatedCVars = new(); private readonly List _netVarsMessages = new(); public event EventHandler? ReceivedInitialNwVars; private bool _receivedInitialNwVars; /// public void SetupNetworking() { if(_isServer) { _netManager.Connected += PeerConnected; _netManager.Disconnect += PeerDisconnected; } _netManager.RegisterNetMessage(HandleNetVarMessage); } private void PeerConnected(object? sender, NetChannelArgs e) { _replicatedCVars.Add(e.Channel, new Dictionary()); } private void PeerDisconnected(object? sender, NetDisconnectedArgs e) { _replicatedCVars.Remove(e.Channel); } private void HandleNetVarMessage(MsgConVars message) { if(!_receivedInitialNwVars) { _receivedInitialNwVars = true; // apply the initial set immediately, so that they are available to // for the rest of connection building ApplyNetVarChange(message.MsgChannel, message.NetworkedVars); ReceivedInitialNwVars?.Invoke(this, EventArgs.Empty); } else _netVarsMessages.Add(message); } /// public void TickProcessMessages() { if(!_timing.InSimulation || _timing.InPrediction) return; for (var i = 0; i < _netVarsMessages.Count; i++) { var msg = _netVarsMessages[i]; if (msg.Tick > _timing.LastRealTick) continue; ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars); if(msg.Tick < _timing.LastRealTick) Logger.WarningS("cfg", $"{msg.MsgChannel}: Received late nwVar message ({msg.Tick} < {_timing.LastRealTick} )."); _netVarsMessages.RemoveSwap(i); i--; } } /// public void FlushMessages() { _netVarsMessages.Sort(((a, b) => a.Tick.Value.CompareTo(b.Tick.Value))); foreach (var msg in _netVarsMessages) { ApplyNetVarChange(msg.MsgChannel, msg.NetworkedVars); } _netVarsMessages.Clear(); } private void ApplyNetVarChange(INetChannel msgChannel, List<(string name, object value)> networkedVars) { Logger.DebugS("cfg", "Handling replicated cvars..."); foreach (var (name, value) in networkedVars) { if (_netManager.IsClient) // Server sent us a CVar update. { // Actually set the CVar base.SetCVar(name, value); Logger.DebugS("cfg", $"name={name}, val={value}"); } else // Client sent us a CVar update { if (!_configVars.TryGetValue(name, out var cVar)) { Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unknown CVar '{name}.'"); continue; } if (!cVar.Registered) { Logger.WarningS("cfg", $"{msgChannel} tried to replicate an unregistered CVar '{name}.'"); continue; } if((cVar.Flags & CVar.REPLICATED) != 0) { var clientCVars = _replicatedCVars[msgChannel]; if (clientCVars.ContainsKey(name)) clientCVars[name] = value; else clientCVars.Add(name, value); Logger.DebugS("cfg", $"name={name}, val={value}"); } else { Logger.WarningS("cfg", $"{msgChannel} tried to replicate an un-replicated CVar '{name}.'"); } } } } /// public T GetClientCVar(INetChannel channel, string name) { if (!_configVars.TryGetValue(name, out var cVar) || !cVar.Registered) throw new InvalidConfigurationException($"Trying to get unregistered variable '{name}'"); if (_replicatedCVars.TryGetValue(channel, out var clientCVars) && clientCVars.TryGetValue(name, out var value)) { return (T)value; } return (T)(cVar.DefaultValue!); } /// public override void SetCVar(string name, object value) { if (_configVars.TryGetValue(name, out var cVar) && cVar.Registered) { if (_netManager.IsClient) { if (_netManager.IsConnected) { if ((cVar.Flags & CVar.NOT_CONNECTED) != 0) { Logger.WarningS("cfg", $"'{name}' can only be changed when not connected to a server."); return; } } if ((cVar.Flags & CVar.SERVER) != 0) { Logger.WarningS("cfg", $"Only the server can change '{name}'."); return; } } } else { throw new InvalidConfigurationException($"Trying to set unregistered variable '{name}'"); } // Actually set the CVar base.SetCVar(name, value); var cvar = _configVars[name]; // replicate if needed if (_netManager.IsClient) { if ((cvar.Flags & CVar.REPLICATED) == 0) return; var msg = _netManager.CreateNetMessage(); msg.Tick = _timing.CurTick; msg.NetworkedVars = new List<(string name, object value)> { (name, value) }; _netManager.ClientSendMessage(msg); } else // Server { if ((cvar.Flags & CVar.REPLICATED) == 0) return; var msg = _netManager.CreateNetMessage(); msg.Tick = _timing.CurTick; msg.NetworkedVars = new List<(string name, object value)> { (name, value) }; _netManager.ServerSendToAll(msg); } } /// public void SyncConnectingClient(INetChannel client) { DebugTools.Assert(_netManager.IsConnected); DebugTools.Assert(_netManager.IsServer); Logger.InfoS("cfg", $"{client}: Sending server info..."); var msg = _netManager.CreateNetMessage(); msg.Tick = _timing.CurTick; msg.NetworkedVars = GetReplicatedVars(); _netManager.ServerSendMessage(msg, client); } /// public void SyncWithServer() { DebugTools.Assert(_netManager.IsConnected); DebugTools.Assert(_netManager.IsClient); Logger.InfoS("cfg", "Sending client info..."); var msg = _netManager.CreateNetMessage(); msg.Tick = _timing.CurTick; msg.NetworkedVars = GetReplicatedVars(); _netManager.ClientSendMessage(msg); } public void ClearReceivedInitialNwVars() { _receivedInitialNwVars = false; } private List<(string name, object value)> GetReplicatedVars() { var nwVars = new List<(string name, object value)>(); foreach (var cVar in _configVars.Values) { if (!cVar.Registered) continue; if ((cVar.Flags & CVar.REPLICATED) == 0) continue; if (_netManager.IsClient && (cVar.Flags & CVar.SERVER) != 0) continue; nwVars.Add((cVar.Name, cVar.Value ?? cVar.DefaultValue)); Logger.DebugS("cfg", $"name={cVar.Name}, val={(cVar.Value ?? cVar.DefaultValue)}"); } return nwVars; } } }