diff --git a/MSBuild/Robust.Engine.Version.props b/MSBuild/Robust.Engine.Version.props
index bb21edd8f8d..9446c39bdc3 100644
--- a/MSBuild/Robust.Engine.Version.props
+++ b/MSBuild/Robust.Engine.Version.props
@@ -1,4 +1,4 @@
- 180.1.0
+ 180.2.0
diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md
index 5b168756a6c..1a309d6024a 100644
--- a/RELEASE-NOTES.md
+++ b/RELEASE-NOTES.md
@@ -54,6 +54,18 @@ END TEMPLATE-->
*None yet*
+## 180.2.0
+
+### New features
+
+* Add EnsureEntity variants that take in collections.
+* Add more MapSystem helper methods.
+
+### Internal
+
+* Cache some more PVS data to avoid re-allocating every tick.
+
+
## 180.1.0
### New features
diff --git a/Robust.Server/GameStates/PvsSystem.cs b/Robust.Server/GameStates/PvsSystem.cs
index a70d164588f..36597913ac9 100644
--- a/Robust.Server/GameStates/PvsSystem.cs
+++ b/Robust.Server/GameStates/PvsSystem.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
@@ -271,6 +272,7 @@ private void OnEntityDeleted(EntityUid e, MetaDataComponent metadata)
{
sessionData.LastSeenAt.Remove(metadata.NetEntity);
sessionData.LastLeftView.Remove(metadata.NetEntity);
+
if (sessionData.SentEntities.TryGetValue(previousTick, out var ents))
ents.Remove(metadata.NetEntity);
}
@@ -433,10 +435,15 @@ private void OnMapCreated(MapChangedEvent e)
#endregion
- public (List<(int, IChunkIndexLocation)> , HashSet[], EntityUid[][] viewers) GetChunks(ICommonSession[] sessions)
+ public List<(int, IChunkIndexLocation)> GetChunks(
+ ICommonSession[] sessions,
+ ref HashSet[] playerChunks,
+ ref EntityUid[][] viewerEntities)
{
- var playerChunks = new HashSet[sessions.Length];
- var viewerEntities = new EntityUid[sessions.Length][];
+ // Pass these in to avoid allocating new ones every tick, 99% of the time sessions length is going to be the same size.
+ // These values will get overridden here and the old values have already been returned to the pool by this point.
+ Array.Resize(ref playerChunks, sessions.Length);
+ Array.Resize(ref viewerEntities, sessions.Length);
_chunkList.Clear();
// Keep track of the index of each chunk we use for a faster index lookup.
@@ -459,8 +466,8 @@ private void OnMapCreated(MapChangedEvent e)
var session = sessions[i];
playerChunks[i] = _playerChunkPool.Get();
- var viewers = GetSessionViewers(session);
- viewerEntities[i] = viewers;
+ ref var viewers = ref viewerEntities[i];
+ GetSessionViewers(session, ref viewers);
for (var j = 0; j < viewers.Length; j++)
{
@@ -552,7 +559,7 @@ private void OnMapCreated(MapChangedEvent e)
}
}
- return (_chunkList, playerChunks, viewerEntities);
+ return _chunkList;
}
public void RegisterNewPreviousChunkTrees(
@@ -569,23 +576,20 @@ public void RegisterNewPreviousChunkTrees(
_reusedTrees.Add(chunks[i]);
}
- var previousIndices = _previousTrees.Keys.ToArray();
- for (var i = 0; i < previousIndices.Length; i++)
+ foreach (var (index, chunk) in _previousTrees)
{
- var index = previousIndices[i];
// ReSharper disable once InconsistentlySynchronizedField
- if (_reusedTrees.Contains(index)) continue;
- var chunk = _previousTrees[index];
- if (chunk.HasValue)
+ if (_reusedTrees.Contains(index))
+ continue;
+
+ if (chunk != null)
{
_chunkCachePool.Return(chunk.Value.metadata);
_treePool.Return(chunk.Value.tree);
}
if (!chunks.Contains(index))
- {
_previousTrees.Remove(index);
- }
}
_previousTrees.EnsureCapacity(chunks.Count);
@@ -1313,26 +1317,44 @@ private EntityState GetFullEntityState(ICommonSession player, EntityUid entityUi
return entState;
}
- private EntityUid[] GetSessionViewers(ICommonSession session)
+ private void GetSessionViewers(ICommonSession session, [NotNull] ref EntityUid[]? viewers)
{
if (session.Status != SessionStatus.InGame)
- return Array.Empty();
+ {
+ viewers = Array.Empty();
+ return;
+ }
// Fast path
if (session.ViewSubscriptions.Count == 0)
{
if (session.AttachedEntity == null)
- return Array.Empty();
+ {
+ viewers = Array.Empty();
+ return;
+ }
- return new[] { session.AttachedEntity.Value };
+ Array.Resize(ref viewers, 1);
+ viewers[0] = session.AttachedEntity.Value;
+ return;
}
- var viewers = new HashSet();
- if (session.AttachedEntity != null)
- viewers.Add(session.AttachedEntity.Value);
+ int i = 0;
+ if (session.AttachedEntity is { } local)
+ {
+ DebugTools.Assert(!session.ViewSubscriptions.Contains(local));
+ Array.Resize(ref viewers, session.ViewSubscriptions.Count + 1);
+ viewers[i++] = local;
+ }
+ else
+ {
+ Array.Resize(ref viewers, session.ViewSubscriptions.Count);
+ }
- viewers.UnionWith(session.ViewSubscriptions);
- return viewers.ToArray();
+ foreach (var ent in session.ViewSubscriptions)
+ {
+ viewers[i++] = ent;
+ }
}
// Read Safe
diff --git a/Robust.Server/GameStates/ServerGameStateManager.cs b/Robust.Server/GameStates/ServerGameStateManager.cs
index 1f1db6a3ce3..2e04bdb675e 100644
--- a/Robust.Server/GameStates/ServerGameStateManager.cs
+++ b/Robust.Server/GameStates/ServerGameStateManager.cs
@@ -37,6 +37,9 @@ public sealed class ServerGameStateManager : IServerGameStateManager, IPostInjec
// Mapping of net UID of clients -> last known acked state.
private GameTick _lastOldestAck = GameTick.Zero;
+ private HashSet[] _playerChunks = Array.Empty>();
+ private EntityUid[][] _viewerEntities = Array.Empty();
+
private PvsSystem _pvs = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
@@ -267,7 +270,7 @@ private struct PvsData
private PvsData? GetPVSData(ICommonSession[] players)
{
- var (chunks, playerChunks, viewerEntities) = _pvs.GetChunks(players);
+ var chunks= _pvs.GetChunks(players, ref _playerChunks, ref _viewerEntities);
const int ChunkBatchSize = 2;
var chunksCount = chunks.Count;
var chunkBatches = (int)MathF.Ceiling((float)chunksCount / ChunkBatchSize);
@@ -310,8 +313,8 @@ private struct PvsData
ArrayPool.Shared.Return(reuse);
return new PvsData()
{
- PlayerChunks = playerChunks,
- ViewerEntities = viewerEntities,
+ PlayerChunks = _playerChunks,
+ ViewerEntities = _viewerEntities,
ChunkCache = chunkCache,
};
}
diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
index 847b95f3b99..35694ff81a0 100644
--- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.Grid.cs
@@ -1291,6 +1291,9 @@ public bool CollidesWithGrid(EntityUid uid, MapGridComponent grid, Vector2i indi
}
public Vector2i GridTileToChunkIndices(EntityUid uid, MapGridComponent grid, Vector2i gridTile)
+ => GridTileToChunkIndices(grid, gridTile);
+
+ public Vector2i GridTileToChunkIndices(MapGridComponent grid, Vector2i gridTile)
{
var x = (int)Math.Floor(gridTile.X / (float) grid.ChunkSize);
var y = (int)Math.Floor(gridTile.Y / (float) grid.ChunkSize);
@@ -1333,6 +1336,36 @@ public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, Vector2i indices
return true;
}
+ public bool TryGetTile(MapGridComponent grid, Vector2i indices, out Tile tile)
+ {
+ var chunkIndices = GridTileToChunkIndices(grid, indices);
+ if (!grid.Chunks.TryGetValue(chunkIndices, out var chunk))
+ {
+ tile = default;
+ return false;
+ }
+
+ var cTileIndices = chunk.GridTileToChunkTile(indices);
+ tile = chunk.Tiles[cTileIndices.X, cTileIndices.Y];
+ return true;
+ }
+
+ ///
+ /// Attempts to get the for the tile at the given grid indices. This will throw an
+ /// exception if the tile at this location has no registered tile definition.
+ ///
+ public bool TryGetTileDef(MapGridComponent grid, Vector2i indices, [NotNullWhen(true)] out ITileDefinition? tileDef)
+ {
+ if (!TryGetTile(grid, indices, out var tile))
+ {
+ tileDef = null;
+ return false;
+ }
+
+ tileDef = _tileMan[tile.TypeId];
+ return true;
+ }
+
public bool TryGetTileRef(EntityUid uid, MapGridComponent grid, EntityCoordinates coords, out TileRef tile)
{
return TryGetTileRef(uid, grid, CoordinatesToTile(uid, grid, coords), out tile);
diff --git a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs
index 002235c59d7..42f7feac44d 100644
--- a/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs
+++ b/Robust.Shared/GameObjects/Systems/SharedMapSystem.cs
@@ -12,6 +12,7 @@ namespace Robust.Shared.GameObjects
{
public abstract partial class SharedMapSystem : EntitySystem
{
+ [Dependency] private readonly ITileDefinitionManager _tileMan = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] private readonly IMapManagerInternal _mapInternal = default!;
diff --git a/Robust.Shared/Map/ITileDefinitionManager.cs b/Robust.Shared/Map/ITileDefinitionManager.cs
index 08eff0bb80a..4175350f5ba 100644
--- a/Robust.Shared/Map/ITileDefinitionManager.cs
+++ b/Robust.Shared/Map/ITileDefinitionManager.cs
@@ -30,6 +30,7 @@ public interface ITileDefinitionManager : IEnumerable
/// The ID of the tile definition.
/// The tile definition.
ITileDefinition this[int id] { get; }
+ // TODO add a try get and get-or-null variant.
///
/// The number of tile definitions contained inside of this manager.