diff --git a/Robust.Server/GameStates/PvsSystem.cs b/Robust.Server/GameStates/PvsSystem.cs index 5cc42061e..e3d39be8a 100644 --- a/Robust.Server/GameStates/PvsSystem.cs +++ b/Robust.Server/GameStates/PvsSystem.cs @@ -35,10 +35,16 @@ internal sealed partial class PvsSystem : EntitySystem public const float ChunkSize = 8; // TODO make this a cvar. Make it in terms of seconds and tie it to tick rate? + // Main issue is that I CBF figuring out the logic for handling it changing mid-game. public const int DirtyBufferSize = 20; // Note: If a client has ping higher than TickBuffer / TickRate, then the server will treat every entity as if it // had entered PVS for the first time. Note that due to the PVS budget, this buffer is easily overwhelmed. + /// + /// See . + /// + public int ForceAckThreshold { get; private set; } + /// /// Maximum number of pooled objects /// @@ -139,6 +145,7 @@ internal sealed partial class PvsSystem : EntitySystem _configManager.OnValueChanged(CVars.NetPVS, SetPvs, true); _configManager.OnValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged, true); + _configManager.OnValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged, true); _serverGameStateManager.ClientAck += OnClientAck; _serverGameStateManager.ClientRequestFull += OnClientRequestFull; @@ -156,6 +163,7 @@ internal sealed partial class PvsSystem : EntitySystem _configManager.UnsubValueChanged(CVars.NetPVS, SetPvs); _configManager.UnsubValueChanged(CVars.NetMaxUpdateRange, OnViewsizeChanged); + _configManager.UnsubValueChanged(CVars.NetForceAckThreshold, OnForceAckChanged); _serverGameStateManager.ClientAck -= OnClientAck; _serverGameStateManager.ClientRequestFull -= OnClientRequestFull; @@ -211,6 +219,11 @@ internal sealed partial class PvsSystem : EntitySystem _viewSize = obj * 2; } + private void OnForceAckChanged(int value) + { + ForceAckThreshold = value; + } + private void SetPvs(bool value) { _seenAllEnts.Clear(); diff --git a/Robust.Server/GameStates/ServerGameStateManager.cs b/Robust.Server/GameStates/ServerGameStateManager.cs index ad3bebf69..e95a9ae0a 100644 --- a/Robust.Server/GameStates/ServerGameStateManager.cs +++ b/Robust.Server/GameStates/ServerGameStateManager.cs @@ -373,6 +373,21 @@ Oldest acked clients: {string.Join(", ", players)} // If the state is too big we let Lidgren send it reliably. This is to avoid a situation where a state is so // large that it (or part of it) consistently gets dropped. When we send reliably, we immediately update the // ack so that the next state will not also be huge. + // + // We also do this if the client's last ack is too old. This helps prevent things like the entity deletion + // history from becoming too bloated if a bad client fails to send acks for whatever reason. + + if (_gameTiming.CurTick.Value > lastAck.Value + _pvs.ForceAckThreshold) + { + stateUpdateMessage.ForceSendReliably = true; + + // Aside from the time shortly after connecting, this shouldn't be common. If it is happening, + // something is probably wrong. If it is more frequent than I think, this can be downgraded to a warning. + var connectedTime = (DateTime.UtcNow - session.ConnectedTime).TotalMinutes; + if (lastAck > GameTick.Zero && connectedTime > 1) + _logger.Error($"Client {session} exceeded ack-tick threshold. Last ack: {lastAck}. Cur tick: {_gameTiming.CurTick}. Connect time: {connectedTime} minutes"); + } + if (stateUpdateMessage.ShouldSendReliably()) { sessionData.LastReceivedAck = _gameTiming.CurTick; diff --git a/Robust.Shared/CVars.cs b/Robust.Shared/CVars.cs index 432585fe0..72a333684 100644 --- a/Robust.Shared/CVars.cs +++ b/Robust.Shared/CVars.cs @@ -172,6 +172,13 @@ namespace Robust.Shared public static readonly CVarDef NetMaxUpdateRange = CVarDef.Create("net.maxupdaterange", 12.5f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + /// + /// Maximum allowed delay between the current tick and a client's last acknowledged tick before we send the + /// next game state reliably and simply force update the acked tick, + /// + public static readonly CVarDef NetForceAckThreshold = + CVarDef.Create("net.force_ack_threshold", 40, CVar.ARCHIVE | CVar.SERVERONLY); + /// /// This limits the number of new entities that can be sent to a client in a single game state. This exists to /// avoid stuttering on the client when it has to spawn a bunch of entities in a single tick. If ever entity diff --git a/Robust.Shared/Network/Messages/MsgState.cs b/Robust.Shared/Network/Messages/MsgState.cs index 0191b9646..38362e116 100644 --- a/Robust.Shared/Network/Messages/MsgState.cs +++ b/Robust.Shared/Network/Messages/MsgState.cs @@ -95,6 +95,8 @@ namespace Robust.Shared.Network.Messages MsgSize = buffer.LengthBytes; } + public bool ForceSendReliably; + /// /// Whether this state message is large enough to warrant being sent reliably. /// This is only valid after @@ -103,7 +105,7 @@ namespace Robust.Shared.Network.Messages public bool ShouldSendReliably() { DebugTools.Assert(_hasWritten, "Attempted to determine sending method before determining packet size."); - return MsgSize > ReliableThreshold; + return ForceSendReliably || MsgSize > ReliableThreshold; } public override NetDeliveryMethod DeliveryMethod