From 205a8a6479e9218dabc37ebc280ba05e1e199e50 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Tue, 14 Jul 2026 16:48:50 +1000 Subject: [PATCH] Pool PVS entity state collections (#6752) --- .../GameStates/PvsSystem.GetStates.cs | 11 +++-- Robust.Server/GameStates/PvsSystem.Pooling.cs | 41 +++++++++++++++++++ .../GameStates/PvsSystem.Serialize.cs | 2 +- .../GameStates/PvsSystem.ToSendSet.cs | 4 ++ .../Replays/ReplayRecordingManager.cs | 4 +- 5 files changed, 55 insertions(+), 7 deletions(-) diff --git a/Robust.Server/GameStates/PvsSystem.GetStates.cs b/Robust.Server/GameStates/PvsSystem.GetStates.cs index 87786ceace..b2247c32c6 100644 --- a/Robust.Server/GameStates/PvsSystem.GetStates.cs +++ b/Robust.Server/GameStates/PvsSystem.GetStates.cs @@ -22,10 +22,10 @@ internal sealed partial class PvsSystem /// New entity State for the given entity. private EntityState GetEntityState(ICommonSession? player, EntityUid entityUid, GameTick fromTick, MetaDataComponent meta) { - var changed = new List(); + var changed = GetComponentChangeList(meta.NetComponents.Count); bool sendCompList = meta.LastComponentRemoved > fromTick; - HashSet? netComps = sendCompList ? new() : null; + HashSet? netComps = sendCompList ? GetNetComponentSet() : null; var stateEv = new ComponentGetState(player, fromTick); foreach (var (netId, component) in meta.NetComponents) @@ -83,10 +83,10 @@ internal sealed partial class PvsSystem private EntityState GetFullEntityState(ICommonSession player, EntityUid entityUid, MetaDataComponent meta) { var bus = EntityManager.EventBusInternal; - var changed = new List(); + var changed = GetComponentChangeList(meta.NetComponents.Count); var stateEv = new ComponentGetState(player, GameTick.Zero); - HashSet netComps = new(); + HashSet netComps = GetNetComponentSet(); foreach (var (netId, component) in meta.NetComponents) { @@ -197,6 +197,7 @@ Entity: {ToPrettyString(uid)} Last modified: {md.EntityLastModifiedTick} Metadata last modified: {md.LastModifiedTick} Transform last modified: {Transform(uid).LastModifiedTick}"); + ReturnEntityState(state); continue; } @@ -217,6 +218,8 @@ Transform last modified: {Transform(uid).LastModifiedTick}"); var state = GetEntityState(session, uid, fromTick, md); if (!state.Empty) pvsSession.States.Add(state); + else + ReturnEntityState(state); } } } diff --git a/Robust.Server/GameStates/PvsSystem.Pooling.cs b/Robust.Server/GameStates/PvsSystem.Pooling.cs index 871bd8f69a..49c5735a91 100644 --- a/Robust.Server/GameStates/PvsSystem.Pooling.cs +++ b/Robust.Server/GameStates/PvsSystem.Pooling.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.ObjectPool; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; using Robust.Shared.Utility; using SharpZstd.Interop; @@ -19,9 +20,15 @@ internal sealed partial class PvsSystem private readonly ObjectPool> _entDataListPool = new DefaultObjectPool>(new ListPolicy(), MaxVisPoolSize); + private readonly ObjectPool> _componentChangeListPool + = new DefaultObjectPool>(new ListPolicy(), MaxVisPoolSize); + private readonly ObjectPool> _uidSetPool = new DefaultObjectPool>(new SetPolicy(), MaxVisPoolSize); + private readonly ObjectPool> _netComponentSetPool + = new DefaultObjectPool>(new SetPolicy(), MaxVisPoolSize); + private readonly ObjectPool _chunkPool = new DefaultObjectPool(new PvsChunkPolicy(), 256); @@ -63,4 +70,38 @@ internal sealed partial class PvsSystem CompressionContext.Dispose(); } } + + private List GetComponentChangeList(int capacity) + { + var list = _componentChangeListPool.Get(); + list.EnsureCapacity(capacity); + return list; + } + + private HashSet GetNetComponentSet() + { + return _netComponentSetPool.Get(); + } + + private void ReturnEntityState(EntityState state) + { + if (state.ComponentChanges.Value is List changes) + _componentChangeListPool.Return(changes); + + if (state.NetComponents is { } netComps) + { + _netComponentSetPool.Return(netComps); + state.NetComponents = null; + } + } + + internal void ClearSessionState(PvsSession session) + { + foreach (var state in session.States) + { + ReturnEntityState(state); + } + + session.ClearState(); + } } diff --git a/Robust.Server/GameStates/PvsSystem.Serialize.cs b/Robust.Server/GameStates/PvsSystem.Serialize.cs index 25a72ec864..7ffb6e79ec 100644 --- a/Robust.Server/GameStates/PvsSystem.Serialize.cs +++ b/Robust.Server/GameStates/PvsSystem.Serialize.cs @@ -71,6 +71,6 @@ internal sealed partial class PvsSystem _serializer.SerializeDirect(data.StateStream, data.State); } - data.ClearState(); + ClearSessionState(data); } } diff --git a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs index 99e3e86b5c..00aaa7185b 100644 --- a/Robust.Server/GameStates/PvsSystem.ToSendSet.cs +++ b/Robust.Server/GameStates/PvsSystem.ToSendSet.cs @@ -112,6 +112,8 @@ internal sealed partial class PvsSystem if (!entState.Empty) session.States.Add(entState); + else + ReturnEntityState(entState); } /// @@ -189,6 +191,8 @@ internal sealed partial class PvsSystem if (!entState.Empty) session.States.Add(entState); + else + ReturnEntityState(entState); return true; } diff --git a/Robust.Server/Replays/ReplayRecordingManager.cs b/Robust.Server/Replays/ReplayRecordingManager.cs index 47c7dc74a1..9654958929 100644 --- a/Robust.Server/Replays/ReplayRecordingManager.cs +++ b/Robust.Server/Replays/ReplayRecordingManager.cs @@ -47,7 +47,7 @@ internal sealed partial class ReplayRecordingManager : SharedReplayRecordingMana _pvs.ComputeSessionState(_pvsSession); Update(_pvsSession.State); - _pvsSession.ClearState(); + _pvs.ClearSessionState(_pvsSession); _pvsSession.LastReceivedAck = Timing.CurTick; } @@ -55,6 +55,6 @@ internal sealed partial class ReplayRecordingManager : SharedReplayRecordingMana { base.Reset(); _pvsSession.LastReceivedAck = GameTick.Zero; - _pvsSession.ClearState(); + _pvs.ClearSessionState(_pvsSession); } }