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);
}
}