From cd3a85ea044fae52e5cf3eb12d5acd4040f22e2c Mon Sep 17 00:00:00 2001 From: Acruid Date: Wed, 13 Jan 2021 01:02:08 -0800 Subject: [PATCH] Replicated CVars (#1489) --- Robust.Client/BaseClient.cs | 80 ++--- Robust.Client/GameController.cs | 3 +- .../GameStates/ClientGameStateManager.cs | 8 +- Robust.Server/BaseServer.cs | 14 +- Robust.Server/Player/PlayerManager.cs | 19 +- Robust.Shared/CVars.cs | 4 +- .../Configuration/ConfigurationManager.cs | 32 +- .../Configuration/NetConfigurationManager.cs | 311 ++++++++++++++++++ .../Configuration/IConfigurationManager.cs | 6 +- .../IConfigurationManagerInternal.cs | 2 +- .../Interfaces/Timing/IGameTiming.cs | 15 +- Robust.Shared/Network/Messages/MsgConVars.cs | 143 ++++++++ .../Network/Messages/MsgServerInfo.cs | 34 -- .../Network/Messages/MsgServerInfoReq.cs | 26 -- Robust.Shared/SharedIoC.cs | 7 +- Robust.Shared/Timing/GameTiming.cs | 8 +- 16 files changed, 570 insertions(+), 142 deletions(-) create mode 100644 Robust.Shared/Configuration/NetConfigurationManager.cs create mode 100644 Robust.Shared/Network/Messages/MsgConVars.cs delete mode 100644 Robust.Shared/Network/Messages/MsgServerInfo.cs delete mode 100644 Robust.Shared/Network/Messages/MsgServerInfoReq.cs diff --git a/Robust.Client/BaseClient.cs b/Robust.Client/BaseClient.cs index 246b8ee50..0c631197a 100644 --- a/Robust.Client/BaseClient.cs +++ b/Robust.Client/BaseClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Net; using Robust.Client.Interfaces; using Robust.Client.Interfaces.Debugging; @@ -7,8 +7,8 @@ using Robust.Client.Interfaces.GameStates; using Robust.Client.Interfaces.Utility; using Robust.Client.Player; using Robust.Shared; +using Robust.Shared.Configuration; using Robust.Shared.Enums; -using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Interfaces.Map; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Timing; @@ -25,7 +25,7 @@ namespace Robust.Client { [Dependency] private readonly IClientNetManager _net = default!; [Dependency] private readonly IPlayerManager _playMan = default!; - [Dependency] private readonly IConfigurationManager _configManager = default!; + [Dependency] private readonly INetConfigurationManager _configManager = default!; [Dependency] private readonly IClientEntityManager _entityManager = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IDiscordRichPresence _discord = default!; @@ -50,9 +50,7 @@ namespace Robust.Client /// public void Initialize() { - _net.RegisterNetMessage(MsgServerInfo.NAME, HandleServerInfo); _net.RegisterNetMessage(MsgSetTickRate.NAME, HandleSetTickRate); - _net.RegisterNetMessage(MsgServerInfoReq.NAME); _net.Connected += OnConnected; _net.ConnectFailed += OnConnectFailed; _net.Disconnect += OnNetDisconnect; @@ -98,9 +96,44 @@ namespace Robust.Client private void OnConnected(object? sender, NetChannelArgs args) { - // request base info about the server - var msgInfo = _net.CreateNetMessage(); - _net.ClientSendMessage(msgInfo); + _configManager.SyncWithServer(); + _configManager.ReceivedInitialNwVars += OnReceivedClientData; + } + + private void OnReceivedClientData(object? sender, EventArgs e) + { + _configManager.ReceivedInitialNwVars -= OnReceivedClientData; + + var info = GameInfo; + + var serverName = _configManager.GetCVar("game.hostname"); + if (info == null) + { + GameInfo = info = new ServerInfo(serverName); + } + else + { + info.ServerName = serverName; + } + + var maxPlayers = _configManager.GetCVar("game.maxplayers"); + info.ServerMaxPlayers = maxPlayers; + + var tickrate = _configManager.GetCVar("net.tickrate"); + info.TickRate = (byte) tickrate; + _timing.TickRate = (byte) tickrate; + Logger.InfoS("client", $"Tickrate changed to: {tickrate}"); + + var userName = _net.ServerChannel!.UserName; + var userId = _net.ServerChannel.UserId; + _discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString()); + // start up player management + _playMan.Startup(_net.ServerChannel!); + + _playMan.LocalPlayer!.UserId = userId; + _playMan.LocalPlayer.Name = userName; + + _playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged; } /// @@ -152,6 +185,7 @@ namespace Robust.Client LastDisconnectReason = args.Reason; + IoCManager.Resolve().FlushMessages(); _gameStates.Reset(); _playMan.Shutdown(); _entityManager.Shutdown(); @@ -160,36 +194,6 @@ namespace Robust.Client Reset(); } - private void HandleServerInfo(MsgServerInfo msg) - { - var info = GameInfo; - - if (info == null) - { - GameInfo = info = new ServerInfo(msg.ServerName); - } - else - { - info.ServerName = msg.ServerName; - } - - info.ServerMaxPlayers = msg.ServerMaxPlayers; - info.TickRate = msg.TickRate; - _timing.TickRate = msg.TickRate; - Logger.InfoS("client", $"Tickrate changed to: {msg.TickRate}"); - - var userName = msg.MsgChannel.UserName; - var userId = msg.MsgChannel.UserId; - _discord.Update(info.ServerName, userName, info.ServerMaxPlayers.ToString()); - // start up player management - _playMan.Startup(_net.ServerChannel!); - - _playMan.LocalPlayer!.UserId = userId; - _playMan.LocalPlayer.Name = userName; - - _playMan.LocalPlayer.StatusChanged += OnLocalStatusChanged; - } - private void HandleSetTickRate(MsgSetTickRate message) { _timing.TickRate = message.NewTickRate; diff --git a/Robust.Client/GameController.cs b/Robust.Client/GameController.cs index 12313b73b..e42f99205 100644 --- a/Robust.Client/GameController.cs +++ b/Robust.Client/GameController.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Net; using System.Threading.Tasks; @@ -164,6 +164,7 @@ namespace Robust.Client _userInterfaceManager.Initialize(); _networkManager.Initialize(false); + IoCManager.Resolve().SetupNetworking(); _serializer.Initialize(); _inputManager.Initialize(); _console.Initialize(); diff --git a/Robust.Client/GameStates/ClientGameStateManager.cs b/Robust.Client/GameStates/ClientGameStateManager.cs index 7b8f288fd..7d6371eaa 100644 --- a/Robust.Client/GameStates/ClientGameStateManager.cs +++ b/Robust.Client/GameStates/ClientGameStateManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Robust.Client.GameObjects.EntitySystems; using Robust.Client.Interfaces; @@ -11,6 +11,7 @@ using Robust.Shared.IoC; using Robust.Shared.Network.Messages; using Robust.Client.Player; using Robust.Shared; +using Robust.Shared.Configuration; using Robust.Shared.GameObjects; using Robust.Shared.Input; using Robust.Shared.Interfaces.Configuration; @@ -41,7 +42,7 @@ namespace Robust.Client.GameStates [Dependency] private readonly IBaseClient _client = default!; [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IGameTiming _timing = default!; - [Dependency] private readonly IConfigurationManager _config = default!; + [Dependency] private readonly INetConfigurationManager _config = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IComponentManager _componentManager = default!; [Dependency] private readonly IInputManager _inputManager = default!; @@ -177,7 +178,7 @@ namespace Robust.Client.GameStates var i = 0; for (; i < applyCount; i++) { - _timing.CurTick = _lastProcessedTick + 1; + _timing.LastRealTick = _timing.CurTick = _lastProcessedTick + 1; // TODO: We could theoretically communicate with the GameStateProcessor better here. // Since game states are sliding windows, it is possible that we need less than applyCount applies here. @@ -380,6 +381,7 @@ namespace Robust.Client.GameStates private List ApplyGameState(GameState curState, GameState? nextState) { + _config.TickProcessMessages(); _mapManager.ApplyGameStatePre(curState.MapData); var createdEntities = _entities.ApplyEntityStates(curState.EntityStates, curState.EntityDeletions, nextState?.EntityStates); diff --git a/Robust.Server/BaseServer.cs b/Robust.Server/BaseServer.cs index d89fa2922..67f7f330e 100644 --- a/Robust.Server/BaseServer.cs +++ b/Robust.Server/BaseServer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -288,6 +288,8 @@ namespace Robust.Server // Initialize Tier 2 services IoCManager.Resolve().InSimulation = true; + + IoCManager.Resolve().SetupNetworking(); _stateManager.Initialize(); IoCManager.Resolve().Initialize(MaxPlayers); @@ -487,6 +489,8 @@ namespace Robust.Server // called right before main loop returns, do all saving/cleanup in here private void Cleanup() { + IoCManager.Resolve().FlushMessages(); + // shut down networking, kicking all players. _network.Shutdown($"Server shutting down: {_shutdownReason}"); @@ -540,6 +544,9 @@ namespace Robust.Server ServerCurTick.Set(_time.CurTick.Value); ServerCurTime.Set(_time.CurTime.TotalSeconds); + // These are always the same on the server, there is no prediction. + _time.LastRealTick = _time.CurTick; + UpdateTitle(); using (TickUsage.WithLabels("PreEngine").NewTimer()) @@ -547,6 +554,11 @@ namespace Robust.Server _modLoader.BroadcastUpdate(ModUpdateLevel.PreEngine, frameEventArgs); } + using (TickUsage.WithLabels("NetworkedCVar").NewTimer()) + { + IoCManager.Resolve().TickProcessMessages(); + } + using (TickUsage.WithLabels("Timers").NewTimer()) { timerManager.UpdateTimers(frameEventArgs); diff --git a/Robust.Server/Player/PlayerManager.cs b/Robust.Server/Player/PlayerManager.cs index b3f903ec4..f04dc66ac 100644 --- a/Robust.Server/Player/PlayerManager.cs +++ b/Robust.Server/Player/PlayerManager.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Prometheus; using Robust.Server.Interfaces; using Robust.Server.Interfaces.Player; +using Robust.Shared.Configuration; using Robust.Shared.Enums; using Robust.Shared.GameStates; using Robust.Shared.Input; @@ -91,8 +92,6 @@ namespace Robust.Server.Player MaxPlayers = maxPlayers; - _network.RegisterNetMessage(MsgServerInfoReq.NAME, HandleWelcomeMessageReq); - _network.RegisterNetMessage(MsgServerInfo.NAME); _network.RegisterNetMessage(MsgPlayerListReq.NAME, HandlePlayerListReq); _network.RegisterNetMessage(MsgPlayerList.NAME); @@ -378,6 +377,8 @@ namespace Robust.Server.Player } PlayerCountMetric.Set(PlayerCount); + + IoCManager.Resolve().SyncConnectingClient(args.Channel); } private void OnPlayerStatusChanged(IPlayerSession session, SessionStatus oldStatus, SessionStatus newStatus) @@ -414,18 +415,6 @@ namespace Robust.Server.Player Dirty(); } - private void HandleWelcomeMessageReq(MsgServerInfoReq message) - { - var channel = message.MsgChannel; - var netMsg = channel.CreateNetMessage(); - - netMsg.ServerName = _baseServer.ServerName; - netMsg.ServerMaxPlayers = _baseServer.MaxPlayers; - netMsg.TickRate = _timing.TickRate; - - channel.SendMessage(netMsg); - } - private void HandlePlayerListReq(MsgPlayerListReq message) { var channel = message.MsgChannel; diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index cde3253ec..9c5b95e62 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -165,10 +165,10 @@ namespace Robust.Shared */ public static readonly CVarDef GameMaxPlayers = - CVarDef.Create("game.maxplayers", 32, CVar.ARCHIVE | CVar.SERVERONLY); + CVarDef.Create("game.maxplayers", 32, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); public static readonly CVarDef GameHostName = - CVarDef.Create("game.hostname", "MyServer", CVar.ARCHIVE | CVar.SERVERONLY); + CVarDef.Create("game.hostname", "MyServer", CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); /* * LOG diff --git a/Robust.Shared/Configuration/ConfigurationManager.cs b/Robust.Shared/Configuration/ConfigurationManager.cs index 1b4cb6fab..16e260771 100644 --- a/Robust.Shared/Configuration/ConfigurationManager.cs +++ b/Robust.Shared/Configuration/ConfigurationManager.cs @@ -1,4 +1,4 @@ -using Nett; +using Nett; using Robust.Shared.Interfaces.Configuration; using Robust.Shared.Log; using System; @@ -13,12 +13,12 @@ namespace Robust.Shared.Configuration /// /// Stores and manages global configuration variables. /// - internal sealed class ConfigurationManager : IConfigurationManagerInternal + internal class ConfigurationManager : IConfigurationManagerInternal { private const char TABLE_DELIMITER = '.'; - private readonly Dictionary _configVars = new(); + protected readonly Dictionary _configVars = new(); private string? _configFile; - private bool _isServer; + protected bool _isServer; /// /// Constructs a new ConfigurationManager. @@ -79,6 +79,7 @@ namespace Robust.Shared.Configuration else // this is a key, add CVar { // if the CVar has already been registered + var tomlValue = TypeConvert(obj); if (_configVars.TryGetValue(tablePath, out var cfgVar)) { if ((cfgVar.Flags & CVar.SECURE) != 0) @@ -90,13 +91,14 @@ namespace Robust.Shared.Configuration return; } // overwrite the value with the saved one - cfgVar.Value = TypeConvert(obj); + cfgVar.Value = tomlValue; cfgVar.ValueChanged?.Invoke(cfgVar.Value); } else { //or add another unregistered CVar - cfgVar = new ConfigVar(tablePath, null, CVar.NONE) { Value = TypeConvert(obj) }; + //Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered. + cfgVar = new ConfigVar(tablePath, 0, CVar.NONE) { Value = tomlValue }; _configVars.Add(tablePath, cfgVar); } @@ -131,6 +133,8 @@ namespace Robust.Shared.Configuration continue; } + // Don't write if Archive flag is not set. + // Don't write if the cVar is the default value. if (!cVar.ConfigModified && (cVar.Flags & CVar.ARCHIVE) == 0 || value.Equals(cVar.DefaultValue)) { @@ -192,6 +196,7 @@ namespace Robust.Shared.Configuration } public void RegisterCVar(string name, T defaultValue, CVar flags = CVar.NONE, Action? onValueChanged = null) + where T : notnull { Action? valueChangedDelegate = null; if (onValueChanged != null) @@ -202,7 +207,7 @@ namespace Robust.Shared.Configuration RegisterCVar(name, typeof(T), defaultValue, flags, valueChangedDelegate); } - private void RegisterCVar(string name, Type type, object? defaultValue, CVar flags, Action? onValueChanged) + private void RegisterCVar(string name, Type type, object defaultValue, CVar flags, Action? onValueChanged) { DebugTools.Assert(!type.IsEnum || type.GetEnumUnderlyingType() == typeof(int), $"{name}: Enum cvars must have int as underlying type."); @@ -305,7 +310,7 @@ namespace Robust.Shared.Configuration } /// - public void SetCVar(string name, object value) + public virtual void SetCVar(string name, object value) { SetCVarInternal(name, value); } @@ -394,13 +399,14 @@ namespace Robust.Shared.Configuration else { //or add another unregistered CVar - var cVar = new ConfigVar(key, null, CVar.NONE) { OverrideValue = value }; + //Note: the defaultValue is arbitrarily 0, it will get overwritten when the cvar is registered. + var cVar = new ConfigVar(key, 0, CVar.NONE) { OverrideValue = value }; _configVars.Add(key, cVar); } } } - private object ParseOverrideValue(string value, Type? type) + private static object ParseOverrideValue(string value, Type? type) { if (type == typeof(int)) { @@ -451,7 +457,7 @@ namespace Robust.Shared.Configuration /// /// Holds the data for a single configuration variable. /// - private class ConfigVar + protected class ConfigVar { /// /// Constructs a CVar. @@ -461,7 +467,7 @@ namespace Robust.Shared.Configuration /// everything after is the CVar name in the TOML document. /// The default value of this CVar. /// Optional flags to modify the behavior of this CVar. - public ConfigVar(string name, object? defaultValue, CVar flags) + public ConfigVar(string name, object defaultValue, CVar flags) { Name = name; DefaultValue = defaultValue; @@ -476,7 +482,7 @@ namespace Robust.Shared.Configuration /// /// The default value of this CVar. /// - public object? DefaultValue { get; set; } + public object DefaultValue { get; set; } /// /// Optional flags to modify the behavior of this CVar. diff --git a/Robust.Shared/Configuration/NetConfigurationManager.cs b/Robust.Shared/Configuration/NetConfigurationManager.cs new file mode 100644 index 000000000..a0ccbf0bb --- /dev/null +++ b/Robust.Shared/Configuration/NetConfigurationManager.cs @@ -0,0 +1,311 @@ +using System; +using System.Collections.Generic; +using Robust.Shared.Interfaces.Configuration; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Interfaces.Timing; +using Robust.Shared.IoC; +using Robust.Shared.Log; +using Robust.Shared.Network; +using Robust.Shared.Network.Messages; +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(); + + 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(MsgConVars.NAME, 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?.Invoke(this, EventArgs.Empty); + + _receivedInitialNwVars = true; + _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); + } + + private List<(string name, object value)> GetReplicatedVars() + { + var nwVars = new List<(string name, object value)>(); + + foreach (var cVar in _configVars.Values) + { + if (!cVar.Registered) + return nwVars; + + 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; + } + } +} diff --git a/Robust.Shared/Interfaces/Configuration/IConfigurationManager.cs b/Robust.Shared/Interfaces/Configuration/IConfigurationManager.cs index 32d84f25a..2f453f9e8 100644 --- a/Robust.Shared/Interfaces/Configuration/IConfigurationManager.cs +++ b/Robust.Shared/Interfaces/Configuration/IConfigurationManager.cs @@ -1,6 +1,5 @@ -using System; +using System; using System.Collections.Generic; -using System.Reflection; using Robust.Shared.Configuration; namespace Robust.Shared.Interfaces.Configuration @@ -24,7 +23,8 @@ namespace Robust.Shared.Interfaces.Configuration /// The default Value of the CVar. /// Optional flags to change behavior of the CVar. /// Invoked whenever the CVar value changes. - void RegisterCVar(string name, T defaultValue, CVar flags = CVar.NONE, Action? onValueChanged = null); + void RegisterCVar(string name, T defaultValue, CVar flags = CVar.NONE, Action? onValueChanged = null) + where T : notnull; /// /// Is the named CVar already registered? diff --git a/Robust.Shared/Interfaces/Configuration/IConfigurationManagerInternal.cs b/Robust.Shared/Interfaces/Configuration/IConfigurationManagerInternal.cs index 3c6d4f0dd..b28bba725 100644 --- a/Robust.Shared/Interfaces/Configuration/IConfigurationManagerInternal.cs +++ b/Robust.Shared/Interfaces/Configuration/IConfigurationManagerInternal.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Reflection; using Robust.Shared.Configuration; diff --git a/Robust.Shared/Interfaces/Timing/IGameTiming.cs b/Robust.Shared/Interfaces/Timing/IGameTiming.cs index 2c797fd6a..e31503efe 100644 --- a/Robust.Shared/Interfaces/Timing/IGameTiming.cs +++ b/Robust.Shared/Interfaces/Timing/IGameTiming.cs @@ -1,4 +1,4 @@ -using System; +using System; using JetBrains.Annotations; using Robust.Shared.IoC; using Robust.Shared.Timing; @@ -120,8 +120,21 @@ namespace Robust.Shared.Interfaces.Timing /// void ResetRealTime(); + /// + /// Is this the first time CurTick has been predicted? + /// bool IsFirstTimePredicted { get; } + /// + /// Is CurTick ahead of LastRealTick, meaning we are inside predicted ticks? + /// + bool InPrediction { get; } + + /// + /// The last real non-predicted tick that was processed. + /// + GameTick LastRealTick { get; set; } + void StartPastPrediction(); void EndPastPrediction(); diff --git a/Robust.Shared/Network/Messages/MsgConVars.cs b/Robust.Shared/Network/Messages/MsgConVars.cs new file mode 100644 index 000000000..2b5f8886b --- /dev/null +++ b/Robust.Shared/Network/Messages/MsgConVars.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using Lidgren.Network; +using Robust.Shared.Interfaces.Network; +using Robust.Shared.Log; +using Robust.Shared.Timing; + +namespace Robust.Shared.Network.Messages +{ + internal class MsgConVars : NetMessage + { + // Max buffer could potentially be 255 * 128 * 1024 = ~33MB, so if MaxMessageSize starts being a problem it can be increased. + private const int MaxMessageSize = 0x4000; // Arbitrarily chosen as a 'sane' value as the maximum size of the entire message. + private const int MaxNameSize = 4 * 32; // UTF8 Max char size is 4 bytes, 32 chars. + private const int MaxStringValSize = 4 * 256; // UTF8 Max char size is 4 bytes, 256 chars. + + #region REQUIRED + public static readonly MsgGroups GROUP = MsgGroups.Command; + public static readonly string NAME = nameof(MsgConVars); + public MsgConVars(INetChannel channel) : base(NAME, GROUP) { } + #endregion + + public GameTick Tick; + public List<(string name, object value)> NetworkedVars = null!; + + /// + public override void ReadFromBuffer(NetIncomingMessage buffer) + { + if(buffer.LengthBytes > MaxMessageSize) + Logger.WarningS("net", $"{MsgChannel}: received a large {nameof(MsgConVars)}, {buffer.LengthBytes}B > {MaxMessageSize}B"); + + Tick = new GameTick(buffer.ReadVariableUInt32()); + var nVars = buffer.ReadByte(); + + NetworkedVars = new List<(string name, object value)>(nVars); + + for (int i = 0; i < nVars; i++) + { + // give the string a smaller bounds than int.MaxValue + var nameSize = buffer.PeekStringSize(); + if (0 >= nameSize || nameSize > MaxNameSize) + throw new InvalidOperationException($"Cvar name size '{nameSize}' is out of bounds (1-{MaxNameSize} bytes)."); + + var name = buffer.ReadString(); + var valType = (CvarType)buffer.ReadByte(); + + object value; + switch (valType) + { + case CvarType.Int: + value = buffer.ReadInt32(); + break; + case CvarType.Long: + value = buffer.ReadInt64(); + break; + case CvarType.Bool: + value = buffer.ReadBoolean(); + break; + case CvarType.String: + + // give the string a smaller bounds than int.MaxValue + var strSize = buffer.PeekStringSize(); + if (0 > strSize || strSize > MaxStringValSize) + throw new InvalidOperationException($"Cvar string value size '{nameSize}' for cvar '{name}' is out of bounds (0-{MaxStringValSize} bytes)."); + + value = buffer.ReadString(); + break; + case CvarType.Float: + value = buffer.ReadFloat(); + break; + case CvarType.Double: + value = buffer.ReadDouble(); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + NetworkedVars.Add((name, value)); + } + } + + /// + public override void WriteToBuffer(NetOutgoingMessage buffer) + { + if(NetworkedVars == null) + throw new InvalidOperationException($"{nameof(NetworkedVars)} collection is null."); + + if(NetworkedVars.Count > byte.MaxValue) + throw new InvalidOperationException($"{nameof(NetworkedVars)} collection count is greater than {short.MaxValue}."); + + buffer.WriteVariableUInt32(Tick.Value); + buffer.Write((byte)NetworkedVars.Count); + + foreach (var (name, value) in NetworkedVars) + { + buffer.Write(name); + + switch (value) + { + case int val: + buffer.Write((byte)CvarType.Int); + buffer.Write(val); + break; + case long val: + buffer.Write((byte)CvarType.Long); + buffer.Write(val); + break; + case bool val: + buffer.Write((byte)CvarType.Bool); + buffer.Write(val); + break; + case string val: + buffer.Write((byte)CvarType.String); + buffer.Write(val); + break; + case float val: + buffer.Write((byte)CvarType.Float); + buffer.Write(val); + break; + case double val: + buffer.Write((byte)CvarType.Double); + buffer.Write(val); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + private enum CvarType : byte + { + // ReSharper disable once UnusedMember.Local + None, + + Int, + Long, + Bool, + String, + Float, + Double + } + } +} diff --git a/Robust.Shared/Network/Messages/MsgServerInfo.cs b/Robust.Shared/Network/Messages/MsgServerInfo.cs deleted file mode 100644 index 4e62914f0..000000000 --- a/Robust.Shared/Network/Messages/MsgServerInfo.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Lidgren.Network; -using Robust.Shared.Interfaces.Network; - -#nullable disable - -namespace Robust.Shared.Network.Messages -{ - public class MsgServerInfo : NetMessage - { - #region REQUIRED - public static readonly MsgGroups GROUP = MsgGroups.Core; - public static readonly string NAME = nameof(MsgServerInfo); - public MsgServerInfo(INetChannel channel) : base(NAME, GROUP) { } - #endregion - - public string ServerName { get; set; } - public int ServerMaxPlayers { get; set; } - public byte TickRate { get; set; } - - public override void ReadFromBuffer(NetIncomingMessage buffer) - { - ServerName = buffer.ReadString(); - ServerMaxPlayers = buffer.ReadInt32(); - TickRate = buffer.ReadByte(); - } - - public override void WriteToBuffer(NetOutgoingMessage buffer) - { - buffer.Write(ServerName); - buffer.Write(ServerMaxPlayers); - buffer.Write(TickRate); - } - } -} diff --git a/Robust.Shared/Network/Messages/MsgServerInfoReq.cs b/Robust.Shared/Network/Messages/MsgServerInfoReq.cs deleted file mode 100644 index fa8b4b7ae..000000000 --- a/Robust.Shared/Network/Messages/MsgServerInfoReq.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Lidgren.Network; -using Robust.Shared.Interfaces.Network; - -#nullable disable - -namespace Robust.Shared.Network.Messages -{ - public class MsgServerInfoReq : NetMessage - { - #region REQUIRED - public static readonly MsgGroups GROUP = MsgGroups.Core; - public static readonly string NAME = nameof(MsgServerInfoReq); - public MsgServerInfoReq(INetChannel channel) : base(NAME, GROUP) { } - #endregion - - public override void ReadFromBuffer(NetIncomingMessage buffer) - { - // Nothing - } - - public override void WriteToBuffer(NetOutgoingMessage buffer) - { - // Nothing - } - } -} diff --git a/Robust.Shared/SharedIoC.cs b/Robust.Shared/SharedIoC.cs index 311d6d43d..20c3a46aa 100644 --- a/Robust.Shared/SharedIoC.cs +++ b/Robust.Shared/SharedIoC.cs @@ -1,4 +1,4 @@ -using Robust.Shared.Asynchronous; +using Robust.Shared.Asynchronous; using Robust.Shared.Configuration; using Robust.Shared.ContentPack; using Robust.Shared.Exceptions; @@ -35,8 +35,9 @@ namespace Robust.Shared public static void RegisterIoC() { IoCManager.Register(); - IoCManager.Register(); - IoCManager.Register(); + IoCManager.Register(); + IoCManager.Register(); + IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); IoCManager.Register(); diff --git a/Robust.Shared/Timing/GameTiming.cs b/Robust.Shared/Timing/GameTiming.cs index ff6d9470b..4bb2a2dd4 100644 --- a/Robust.Shared/Timing/GameTiming.cs +++ b/Robust.Shared/Timing/GameTiming.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -238,6 +238,12 @@ namespace Robust.Shared.Timing public bool IsFirstTimePredicted { get; private set; } = true; + /// + public bool InPrediction => CurTick > LastRealTick; + + /// + public GameTick LastRealTick { get; set; } + public void StartPastPrediction() { // Don't allow recursive predictions.