]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Optimise navmaps significantly (#27528)
authormetalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
Thu, 2 May 2024 00:18:38 +0000 (10:18 +1000)
committerGitHub <noreply@github.com>
Thu, 2 May 2024 00:18:38 +0000 (10:18 +1000)
* Optimise navmaps significantly

- Reduce the delta state size significantly.
- Remove AirtightChangedEvent because this will spam them out constantly.

* weh

* review

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
Content.Client/Pinpointer/NavMapSystem.cs
Content.Client/Pinpointer/UI/NavMapControl.cs
Content.Server/Atmos/EntitySystems/AirtightSystem.cs
Content.Server/Pinpointer/NavMapSystem.cs
Content.Shared/Pinpointer/NavMapComponent.cs
Content.Shared/Pinpointer/SharedNavMapSystem.cs
Content.Shared/Tag/TagSystem.cs

index 868bf1fbc48be558d4855527126e3e49c09de6f4..c80e7e85e280dd7943ebeb34f9d12d737396a077 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Shared.Atmos;
 using Content.Shared.Pinpointer;
 using Robust.Shared.GameStates;
 
@@ -31,7 +32,6 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
                     component.Beacons.Remove(beacon);
             }
         }
-
         else
         {
             foreach (var index in component.Chunks.Keys)
@@ -47,17 +47,26 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
             }
         }
 
-        foreach (var ((category, origin), chunk) in state.Chunks)
+        foreach (var (origin, chunk) in state.Chunks)
         {
             var newChunk = new NavMapChunk(origin);
 
-            foreach (var (atmosDirection, value) in chunk)
-                newChunk.TileData[atmosDirection] = value;
+            for (var i = 0; i < NavMapComponent.Categories; i++)
+            {
+                var newData = chunk[i];
+
+                if (newData == null)
+                    continue;
 
-            component.Chunks[(category, origin)] = newChunk;
+                newChunk.TileData[i] = new(newData);
+            }
+
+            component.Chunks[origin] = newChunk;
         }
 
         foreach (var beacon in state.Beacons)
+        {
             component.Beacons.Add(beacon);
+        }
     }
 }
index 3309e7c8df573d9718a7c2e4cb90b110cc8ee893..5aa20b688148704b158e2655f14cb9135af89030 100644 (file)
@@ -490,108 +490,143 @@ public partial class NavMapControl : MapGridControl
         VertLinesLookup.Clear();
         VertLinesLookupReversed.Clear();
 
-        foreach ((var (category, chunkOrigin), var chunk) in _navMap.Chunks)
+        foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
         {
-            if (category != NavMapChunkType.Wall)
-                continue;
-
-            for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
+            for (var j = 0; j < NavMapComponent.Categories; j++)
             {
-                var value = (ushort) Math.Pow(2, i);
-                var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
+                var category = (NavMapChunkType) j;
 
-                if (mask == 0x0)
+                if (category != NavMapChunkType.Wall)
                     continue;
 
-                var relativeTile = SharedNavMapSystem.GetTile(mask);
-                var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
+                var data = chunk.TileData[j];
 
-                if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relativeTile))
-                {
-                    AddRectForThinWall(chunk.TileData, tile);
+                if (data == null)
                     continue;
-                }
 
-                tile = tile with { Y = -tile.Y };
+                for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
+                {
+                    var value = (ushort) Math.Pow(2, i);
+                    var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value;
 
-                NavMapChunk? neighborChunk;
-                bool neighbor;
+                    if (mask == 0x0)
+                        continue;
 
-                // North edge
-                if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
-                {
-                    neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, 1)), out neighborChunk) &&
-                                  (neighborChunk.TileData[AtmosDirection.South] &
-                                   SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
-                }
-                else
-                {
-                    var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1));
-                    neighbor = (chunk.TileData[AtmosDirection.South] & flag) != 0x0;
-                }
+                    var relativeTile = SharedNavMapSystem.GetTile(mask);
+                    var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize;
 
-                if (!neighbor)
-                    AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, -_grid.TileSize), HorizLinesLookup, HorizLinesLookupReversed);
+                    if (!_navMapSystem.AllTileEdgesAreOccupied(data, relativeTile))
+                    {
+                        AddRectForThinWall(data, tile);
+                        continue;
+                    }
 
-                // East edge
-                if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
-                {
-                    neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(1, 0)), out neighborChunk) &&
-                               (neighborChunk.TileData[AtmosDirection.West] &
-                                SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
-                }
-                else
-                {
-                    var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0));
-                    neighbor = (chunk.TileData[AtmosDirection.West] & flag) != 0x0;
-                }
+                    tile = tile with { Y = -tile.Y };
 
-                if (!neighbor)
-                    AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), tile + new Vector2i(_grid.TileSize, 0), VertLinesLookup, VertLinesLookupReversed);
+                    NavMapChunk? neighborChunk;
+                    bool neighbor;
 
-                // South edge
-                if (relativeTile.Y == 0)
-                {
-                    neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(0, -1)), out neighborChunk) &&
-                               (neighborChunk.TileData[AtmosDirection.North] &
-                                SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
-                }
-                else
-                {
-                    var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, -1));
-                    neighbor = (chunk.TileData[AtmosDirection.North] & flag) != 0x0;
-                }
+                    // North edge
+                    if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1)
+                    {
+                        _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk);
+                        var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
 
-                if (!neighbor)
-                    AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed);
+                        neighbor = neighborData != null &&
+                                   (neighborData[AtmosDirection.South] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0;
+                    }
+                    else
+                    {
+                        var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Up);
+                        neighbor = (data[AtmosDirection.South] & flag) != 0x0;
+                    }
 
-                // West edge
-                if (relativeTile.X == 0)
-                {
-                    neighbor = _navMap.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin + new Vector2i(-1, 0)), out neighborChunk) &&
-                               (neighborChunk.TileData[AtmosDirection.East] &
-                                SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
-                }
-                else
-                {
-                    var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(-1, 0));
-                    neighbor = (chunk.TileData[AtmosDirection.East] & flag) != 0x0;
-                }
+                    if (!neighbor)
+                    {
+                        AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize),
+                            tile + new Vector2i(_grid.TileSize, -_grid.TileSize), HorizLinesLookup,
+                            HorizLinesLookupReversed);
+                    }
 
-                if (!neighbor)
-                    AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed);
+                    // East edge
+                    if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1)
+                    {
+                        _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk);
+                        var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
 
-                // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these
-                TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0)));
+                        neighbor = neighborData != null &&
+                                   (neighborData[AtmosDirection.West] & SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0;
+                    }
+                    else
+                    {
+                        var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Right);
+                        neighbor = (data[AtmosDirection.West] & flag) != 0x0;
+                    }
+
+                    if (!neighbor)
+                    {
+                        AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
+                            tile + new Vector2i(_grid.TileSize, 0), VertLinesLookup, VertLinesLookupReversed);
+                    }
+
+                    // South edge
+                    if (relativeTile.Y == 0)
+                    {
+                        _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk);
+                        var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
+
+                        neighbor = neighborData != null &&
+                                   (neighborData[AtmosDirection.North] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, SharedNavMapSystem.ChunkSize - 1))) != 0x0;
+                    }
+                    else
+                    {
+                        var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Down);
+                        neighbor = (data[AtmosDirection.North] & flag) != 0x0;
+                    }
+
+                    if (!neighbor)
+                    {
+                        AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup,
+                            HorizLinesLookupReversed);
+                    }
+
+                    // West edge
+                    if (relativeTile.X == 0)
+                    {
+                        _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk);
+                        var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
+
+                        neighbor = neighborData != null &&
+                                   (neighborData[AtmosDirection.East] & SharedNavMapSystem.GetFlag(new Vector2i(SharedNavMapSystem.ChunkSize - 1, relativeTile.Y))) != 0x0;
+                    }
+                    else
+                    {
+                        var flag = SharedNavMapSystem.GetFlag(relativeTile + Vector2i.Left);
+                        neighbor = (data[AtmosDirection.East] & flag) != 0x0;
+                    }
+
+                    if (!neighbor)
+                    {
+                        AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup,
+                            VertLinesLookupReversed);
+                    }
+
+                    // Add a diagonal line for interiors. Unless there are a lot of double walls, there is no point combining these
+                    TileLines.Add((tile + new Vector2(0, -_grid.TileSize), tile + new Vector2(_grid.TileSize, 0)));
+                }
             }
         }
 
-        // Record the combined lines 
+        // Record the combined lines
         foreach (var (origin, terminal) in HorizLinesLookup)
+        {
             TileLines.Add((origin.Item2, terminal.Item2));
+        }
 
         foreach (var (origin, terminal) in VertLinesLookup)
+        {
             TileLines.Add((origin.Item2, terminal.Item2));
+        }
     }
 
     private void UpdateNavMapAirlocks()
@@ -599,15 +634,17 @@ public partial class NavMapControl : MapGridControl
         if (_navMap == null || _grid == null)
             return;
 
-        foreach (var ((category, _), chunk) in _navMap.Chunks)
+        foreach (var (chunkOrigin, chunk) in _navMap.Chunks)
         {
-            if (category != NavMapChunkType.Airlock)
+            var data = chunk.TileData[(int) NavMapChunkType.Airlock];
+
+            if (data == null)
                 continue;
 
             for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
             {
                 var value = (int) Math.Pow(2, i);
-                var mask = _navMapSystem.GetCombinedEdgesForChunk(chunk.TileData) & value;
+                var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value;
 
                 if (mask == 0x0)
                     continue;
@@ -616,9 +653,9 @@ public partial class NavMapControl : MapGridControl
                 var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize;
 
                 // If the edges of an airlock tile are not all occupied, draw a thin airlock for each edge
-                if (!_navMapSystem.AllTileEdgesAreOccupied(chunk.TileData, relative))
+                if (!_navMapSystem.AllTileEdgesAreOccupied(data, relative))
                 {
-                    AddRectForThinAirlock(chunk.TileData, tile);
+                    AddRectForThinAirlock(data, tile);
                     continue;
                 }
 
index 5a9134e3b4db40786a0f5802b373cc4e22ee936c..cd07da71127ce12889c96baf78cca4b5148fb9bc 100644 (file)
@@ -36,7 +36,7 @@ namespace Content.Server.Atmos.EntitySystems
             airtight.Comp.CurrentAirBlockedDirection =
                 (int) Rotate((AtmosDirection) airtight.Comp.InitialAirBlockedDirection, xform.LocalRotation);
             UpdatePosition(airtight, xform);
-            var airtightEv = new AirtightChanged(airtight, airtight, default);
+            var airtightEv = new AirtightChanged(airtight, airtight, false, default);
             RaiseLocalEvent(airtight, ref airtightEv, true);
         }
 
@@ -64,7 +64,7 @@ namespace Content.Server.Atmos.EntitySystems
             airtight.LastPosition = (gridId.Value, tilePos);
             InvalidatePosition(gridId.Value, tilePos);
 
-            var airtightEv = new AirtightChanged(uid, airtight, (gridId.Value, tilePos));
+            var airtightEv = new AirtightChanged(uid, airtight, false, (gridId.Value, tilePos));
             RaiseLocalEvent(uid, ref airtightEv, true);
         }
 
@@ -76,7 +76,7 @@ namespace Content.Server.Atmos.EntitySystems
                 airtight.LastPosition = (gridId, args.TilePos);
                 InvalidatePosition(gridId, args.TilePos);
 
-                var airtightEv = new AirtightChanged(uid, airtight, (gridId, args.TilePos));
+                var airtightEv = new AirtightChanged(uid, airtight, false, (gridId, args.TilePos));
                 RaiseLocalEvent(uid, ref airtightEv, true);
             }
         }
@@ -87,7 +87,7 @@ namespace Content.Server.Atmos.EntitySystems
             airtight.CurrentAirBlockedDirection = (int) Rotate((AtmosDirection)airtight.InitialAirBlockedDirection, ev.NewRotation);
             var pos = airtight.LastPosition;
             UpdatePosition(ent, ev.Component);
-            var airtightEv = new AirtightChanged(owner, airtight, pos);
+            var airtightEv = new AirtightChanged(owner, airtight, false, pos);
             RaiseLocalEvent(owner, ref airtightEv, true);
         }
 
@@ -102,7 +102,7 @@ namespace Content.Server.Atmos.EntitySystems
             var pos = airtight.Comp.LastPosition;
             airtight.Comp.AirBlocked = airblocked;
             UpdatePosition(airtight, xform);
-            var airtightEv = new AirtightChanged(airtight, airtight, pos);
+            var airtightEv = new AirtightChanged(airtight, airtight, true, pos);
             RaiseLocalEvent(airtight, ref airtightEv, true);
         }
 
@@ -149,6 +149,13 @@ namespace Content.Server.Atmos.EntitySystems
         }
     }
 
+    /// <summary>
+    /// Raised upon the airtight status being changed via anchoring, movement, etc.
+    /// </summary>
+    /// <param name="Entity"></param>
+    /// <param name="Airtight"></param>
+    /// <param name="AirBlockedChanged">Whether the <see cref="AirtightComponent.AirBlocked"/> changed</param>
+    /// <param name="Position"></param>
     [ByRefEvent]
-    public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, (EntityUid Grid, Vector2i Tile) Position);
+    public readonly record struct AirtightChanged(EntityUid Entity, AirtightComponent Airtight, bool AirBlockedChanged, (EntityUid Grid, Vector2i Tile) Position);
 }
index 0aa6ab1908287bc20766be3d3454e8d7f8d0ce05..2b322789e2942f62472b5590540d7f9b1e29423e 100644 (file)
@@ -14,6 +14,9 @@ using Robust.Shared.Map.Components;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using Content.Shared.Atmos;
+using Content.Shared.Doors.Components;
 
 namespace Content.Server.Pinpointer;
 
@@ -28,14 +31,23 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
     [Dependency] private readonly SharedTransformSystem _transformSystem = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
     [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly ITileDefinitionManager _tileDefManager = default!;
 
     public const float CloseDistance = 15f;
     public const float FarDistance = 30f;
 
+    private EntityQuery<AirtightComponent> _airtightQuery;
+    private EntityQuery<MapGridComponent> _gridQuery;
+    private EntityQuery<NavMapComponent> _navQuery;
+
     public override void Initialize()
     {
         base.Initialize();
 
+        _airtightQuery = GetEntityQuery<AirtightComponent>();
+        _gridQuery = GetEntityQuery<MapGridComponent>();
+        _navQuery = GetEntityQuery<NavMapComponent>();
+
         // Initialization events
         SubscribeLocalEvent<StationGridAddedEvent>(OnStationInit);
 
@@ -43,8 +55,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
         SubscribeLocalEvent<GridSplitEvent>(OnNavMapSplit);
         SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
 
-        // Airtight structure change event
-        SubscribeLocalEvent<AirtightChanged>(OnAirtightChanged);
+        SubscribeLocalEvent<AirtightChanged>(OnAirtightChange);
 
         // Beacon events
         SubscribeLocalEvent<NavMapBeaconComponent, MapInitEvent>(OnNavMapBeaconMapInit);
@@ -67,69 +78,74 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
 
     private void OnNavMapSplit(ref GridSplitEvent args)
     {
-        if (!TryComp(args.Grid, out NavMapComponent? comp))
+        if (!_navQuery.TryComp(args.Grid, out var comp))
             return;
 
-        var gridQuery = GetEntityQuery<MapGridComponent>();
-
         foreach (var grid in args.NewGrids)
         {
             var newComp = EnsureComp<NavMapComponent>(grid);
-            RefreshGrid(grid, newComp, gridQuery.GetComponent(grid));
+            RefreshGrid(grid, newComp, _gridQuery.GetComponent(grid));
+        }
+
+        RefreshGrid(args.Grid, comp, _gridQuery.GetComponent(args.Grid));
+    }
+
+    private NavMapChunk EnsureChunk(NavMapComponent component, Vector2i origin)
+    {
+        if (!component.Chunks.TryGetValue(origin, out var chunk))
+        {
+            chunk = new(origin);
+            component.Chunks[origin] = chunk;
         }
 
-        RefreshGrid(args.Grid, comp, gridQuery.GetComponent(args.Grid));
+        return chunk;
     }
 
     private void OnTileChanged(ref TileChangedEvent ev)
     {
-        if (!TryComp<NavMapComponent>(ev.NewTile.GridUid, out var navMap))
+        if (!ev.EmptyChanged || !_navQuery.TryComp(ev.NewTile.GridUid, out var navMap))
             return;
 
         var tile = ev.NewTile.GridIndices;
         var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
 
-        if (!navMap.Chunks.TryGetValue((NavMapChunkType.Floor, chunkOrigin), out var chunk))
-            chunk = new(chunkOrigin);
+        var chunk = EnsureChunk(navMap, chunkOrigin);
 
         // This could be easily replaced in the future to accommodate diagonal tiles
-        if (ev.NewTile.IsSpace())
-            chunk = UnsetAllEdgesForChunkTile(chunk, tile);
-
+        if (ev.NewTile.IsSpace(_tileDefManager))
+            UnsetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
         else
-            chunk = SetAllEdgesForChunkTile(chunk, tile);
-
-        chunk.LastUpdate = _gameTiming.CurTick;
-        navMap.Chunks[(NavMapChunkType.Floor, chunkOrigin)] = chunk;
+            SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
 
-        Dirty(ev.NewTile.GridUid, navMap);
+        if (!PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
+        {
+            chunk.LastUpdate = _gameTiming.CurTick;
+            Dirty(ev.NewTile.GridUid, navMap);
+        }
     }
 
-    private void OnAirtightChanged(ref AirtightChanged ev)
+    private void OnAirtightChange(ref AirtightChanged args)
     {
-        var gridUid = ev.Position.Grid;
+        if (args.AirBlockedChanged)
+            return;
 
-        if (!TryComp<NavMapComponent>(gridUid, out var navMap) ||
-            !TryComp<MapGridComponent>(gridUid, out var mapGrid))
+        var gridUid = args.Position.Grid;
+
+        if (!_navQuery.TryComp(gridUid, out var navMap) ||
+            !_gridQuery.TryComp(gridUid, out var mapGrid))
+        {
             return;
+        }
 
         // Refresh the affected tile
-        var tile = ev.Position.Tile;
-        var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
-
-        RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, tile);
+        var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize);
 
-        // Update potentially affected chunks
-        foreach (var category in EntityChunkTypes)
+        var chunk = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile);
+        if (!PruneEmpty((gridUid, navMap), chunk))
         {
-            if (!navMap.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
-                continue;
-
             chunk.LastUpdate = _gameTiming.CurTick;
-            navMap.Chunks[(category, chunkOrigin)] = chunk;
+            Dirty(gridUid, navMap);
         }
-
-        Dirty(gridUid, navMap);
     }
 
     #endregion
@@ -220,13 +236,11 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
             var tile = tileRef.GridIndices;
             var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
 
-            if (!component.Chunks.TryGetValue((NavMapChunkType.Floor, chunkOrigin), out var chunk))
-                chunk = new(chunkOrigin);
-
+            var chunk = EnsureChunk(component, chunkOrigin);
             chunk.LastUpdate = _gameTiming.CurTick;
 
             // Refresh the floor tile
-            component.Chunks[(NavMapChunkType.Floor, chunkOrigin)] = SetAllEdgesForChunkTile(chunk, tile);
+            SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
 
             // Refresh the contents of the tile
             RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile);
@@ -235,23 +249,22 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
         Dirty(uid, component);
     }
 
-    private void RefreshTileEntityContents(EntityUid uid, NavMapComponent component, MapGridComponent mapGrid, Vector2i chunkOrigin, Vector2i tile)
+    private NavMapChunk RefreshTileEntityContents(EntityUid uid, NavMapComponent component, MapGridComponent mapGrid, Vector2i chunkOrigin, Vector2i tile)
     {
         var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
         var flag = (ushort) GetFlag(relative);
         var invFlag = (ushort) ~flag;
+        var chunk = EnsureChunk(component, chunkOrigin);
 
         // Clear stale data from the tile across all entity associated chunks
         foreach (var category in EntityChunkTypes)
         {
-            if (!component.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
-                chunk = new(chunkOrigin);
-
-            foreach (var (direction, _) in chunk.TileData)
-                chunk.TileData[direction] &= invFlag;
+            var data = chunk.EnsureType(category);
 
-            chunk.LastUpdate = _gameTiming.CurTick;
-            component.Chunks[(category, chunkOrigin)] = chunk;
+            foreach (var direction in data.Keys)
+            {
+                data[direction] &= invFlag;
+            }
         }
 
         // Update the tile data based on what entities are still anchored to the tile
@@ -259,37 +272,58 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
 
         while (enumerator.MoveNext(out var ent))
         {
-            if (!TryComp<AirtightComponent>(ent, out var entAirtight))
+            if (!_airtightQuery.TryComp(ent, out var airtight))
                 continue;
 
             var category = GetAssociatedEntityChunkType(ent.Value);
+            var data = chunk.EnsureType(category);
 
-            if (!component.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
-                continue;
-
-            foreach (var (direction, _) in chunk.TileData)
+            foreach (var direction in data.Keys)
             {
-                if ((direction & entAirtight.AirBlockedDirection) > 0)
-                    chunk.TileData[direction] |= flag;
+                if ((direction & airtight.AirBlockedDirection) > 0)
+                {
+                    data[direction] |= flag;
+                }
             }
-
-            chunk.LastUpdate = _gameTiming.CurTick;
-            component.Chunks[(category, chunkOrigin)] = chunk;
         }
 
         // Remove walls that intersect with doors (unless they can both physically fit on the same tile)
-        if (component.Chunks.TryGetValue((NavMapChunkType.Wall, chunkOrigin), out var wallChunk) &&
-            component.Chunks.TryGetValue((NavMapChunkType.Airlock, chunkOrigin), out var airlockChunk))
+        var wallData = chunk.TileData[(int) NavMapChunkType.Wall];
+        var airlockData = chunk.TileData[(int) NavMapChunkType.Airlock];
+
+        if (wallData != null && airlockData != null)
         {
-            foreach (var (direction, _) in wallChunk.TileData)
+            foreach (var direction in wallData.Keys)
             {
-                var airlockInvFlag = (ushort) ~airlockChunk.TileData[direction];
-                wallChunk.TileData[direction] &= airlockInvFlag;
+                var airlockInvFlag = (ushort) ~airlockData[direction];
+                wallData[direction] &= airlockInvFlag;
             }
+        }
+
+        return chunk;
+    }
+
+    private bool PruneEmpty(Entity<NavMapComponent> entity, NavMapChunk chunk)
+    {
+        for (var i = 0; i < NavMapComponent.Categories; i++)
+        {
+            var data = chunk.TileData[i];
 
-            wallChunk.LastUpdate = _gameTiming.CurTick;
-            component.Chunks[(NavMapChunkType.Wall, chunkOrigin)] = wallChunk;
+            if (data == null)
+                continue;
+
+            foreach (var value in data.Values)
+            {
+                if (value != 0)
+                {
+                    return false;
+                }
+            }
         }
+
+        entity.Comp.Chunks.Remove(chunk.Origin);
+        Dirty(entity);
+        return true;
     }
 
     #endregion
@@ -304,7 +338,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
         if (xform.GridUid == null)
             return;
 
-        if (!TryComp<NavMapComponent>(xform.GridUid, out var navMap))
+        if (!_navQuery.TryComp(xform.GridUid, out var navMap))
             return;
 
         var netEnt = GetNetEntity(uid);
index 61315b3db145248bc74ab11da99031be2a6c8959..56b9687b31cdcc121548d3fd2852d799e7f1f71f 100644 (file)
@@ -1,3 +1,4 @@
+using System.Linq;
 using Content.Shared.Atmos;
 using Robust.Shared.GameStates;
 using Robust.Shared.Serialization;
@@ -11,6 +12,8 @@ namespace Content.Shared.Pinpointer;
 [RegisterComponent, NetworkedComponent]
 public sealed partial class NavMapComponent : Component
 {
+    public const int Categories = 4;
+
     /*
      * Don't need DataFields as this can be reconstructed
      */
@@ -19,7 +22,7 @@ public sealed partial class NavMapComponent : Component
     /// Bitmasks that represent chunked tiles.
     /// </summary>
     [ViewVariables]
-    public Dictionary<(NavMapChunkType, Vector2i), NavMapChunk> Chunks = new();
+    public Dictionary<Vector2i, NavMapChunk> Chunks = new();
 
     /// <summary>
     /// List of station beacons.
@@ -37,10 +40,11 @@ public sealed class NavMapChunk
     public readonly Vector2i Origin;
 
     /// <summary>
-    /// Bitmask for tiles, 1 for occupied and 0 for empty. There is a bitmask for each cardinal direction,
+    /// Array with each entry corresponding to a <see cref="NavMapChunkType"/>.
+    /// Uses a bitmask for tiles, 1 for occupied and 0 for empty. There is a bitmask for each cardinal direction,
     /// representing each edge of the tile, in case the entities inside it do not entirely fill it
     /// </summary>
-    public Dictionary<AtmosDirection, ushort> TileData;
+    public Dictionary<AtmosDirection, ushort>?[] TileData;
 
     /// <summary>
     /// The last game tick that the chunk was updated
@@ -51,14 +55,27 @@ public sealed class NavMapChunk
     public NavMapChunk(Vector2i origin)
     {
         Origin = origin;
+        TileData = new Dictionary<AtmosDirection, ushort>?[NavMapComponent.Categories];
+    }
+
+    public Dictionary<AtmosDirection, ushort> EnsureType(NavMapChunkType chunkType)
+    {
+        var data = TileData[(int) chunkType];
 
-        TileData = new()
+        if (data == null)
         {
-            [AtmosDirection.North] = 0,
-            [AtmosDirection.East] = 0,
-            [AtmosDirection.South] = 0,
-            [AtmosDirection.West] = 0,
-        };
+            data = new Dictionary<AtmosDirection, ushort>()
+            {
+                [AtmosDirection.North] = 0,
+                [AtmosDirection.East] = 0,
+                [AtmosDirection.South] = 0,
+                [AtmosDirection.West] = 0,
+            };
+
+            TileData[(int) chunkType] = data;
+        }
+
+        return data;
     }
 }
 
@@ -68,4 +85,5 @@ public enum NavMapChunkType : byte
     Floor,
     Wall,
     Airlock,
+    // Update the categories const if you update this.
 }
index ebc4f33f0f1326180fbf99c45fc1d067d36f30ce..76dfd83e4e2dd84c35b8871ae5cebe7e608e4ddb 100644 (file)
@@ -56,35 +56,40 @@ public abstract class SharedNavMapSystem : EntitySystem
         return new Vector2i(x, y);
     }
 
-    public NavMapChunk SetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile)
+    public void SetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType)
     {
         var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
         var flag = (ushort) GetFlag(relative);
+        var data = chunk.EnsureType(chunkType);
 
-        foreach (var (direction, _) in chunk.TileData)
-            chunk.TileData[direction] |= flag;
-
-        return chunk;
+        foreach (var direction in data.Keys)
+        {
+            data[direction] |= flag;
+        }
     }
 
-    public NavMapChunk UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile)
+    public void UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType)
     {
         var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
         var flag = (ushort) GetFlag(relative);
         var invFlag = (ushort) ~flag;
 
-        foreach (var (direction, _) in chunk.TileData)
-            chunk.TileData[direction] &= invFlag;
+        var data = chunk.EnsureType(chunkType);
 
-        return chunk;
+        foreach (var direction in data.Keys)
+        {
+            data[direction] &= invFlag;
+        }
     }
 
     public ushort GetCombinedEdgesForChunk(Dictionary<AtmosDirection, ushort> tile)
     {
         ushort combined = 0;
 
-        foreach (var kvp in tile)
-            combined |= kvp.Value;
+        foreach (var value in tile.Values)
+        {
+            combined |= value;
+        }
 
         return combined;
     }
@@ -93,9 +98,9 @@ public abstract class SharedNavMapSystem : EntitySystem
     {
         var flag = (ushort) GetFlag(tile);
 
-        foreach (var kvp in tileData)
+        foreach (var value in tileData.Values)
         {
-            if ((kvp.Value & flag) == 0)
+            if ((value & flag) == 0)
                 return false;
         }
 
@@ -140,20 +145,33 @@ public abstract class SharedNavMapSystem : EntitySystem
 
     private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
     {
-        var chunks = new Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>>();
+        var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>();
         var beacons = new HashSet<NavMapBeacon>();
 
         // Should this be a full component state or a delta-state?
         if (args.FromTick <= component.CreationTick)
         {
-            foreach (var ((category, origin), chunk) in component.Chunks)
+            foreach (var (origin, chunk) in component.Chunks)
             {
-                var chunkDatum = new Dictionary<AtmosDirection, ushort>(chunk.TileData.Count);
+                var sentChunk = new Dictionary<AtmosDirection, ushort>[NavMapComponent.Categories];
+                chunks.Add(origin, sentChunk);
 
-                foreach (var (direction, tileData) in chunk.TileData)
-                    chunkDatum[direction] = tileData;
+                foreach (var value in Enum.GetValues<NavMapChunkType>())
+                {
+                    ref var data = ref chunk.TileData[(int) value];
+
+                    if (data == null)
+                        continue;
+
+                    var chunkDatum = new Dictionary<AtmosDirection, ushort>(data.Count);
+
+                    foreach (var (direction, tileData) in data)
+                    {
+                        chunkDatum[direction] = tileData;
+                    }
 
-                chunks.Add((category, origin), chunkDatum);
+                    sentChunk[(int) value] = chunkDatum;
+                }
             }
 
             var beaconQuery = AllEntityQuery<NavMapBeaconComponent, TransformComponent>();
@@ -173,17 +191,31 @@ public abstract class SharedNavMapSystem : EntitySystem
             return;
         }
 
-        foreach (var ((category, origin), chunk) in component.Chunks)
+        foreach (var (origin, chunk) in component.Chunks)
         {
             if (chunk.LastUpdate < args.FromTick)
                 continue;
 
-            var chunkDatum = new Dictionary<AtmosDirection, ushort>(chunk.TileData.Count);
+            var sentChunk = new Dictionary<AtmosDirection, ushort>[NavMapComponent.Categories];
+            chunks.Add(origin, sentChunk);
+
+            foreach (var value in Enum.GetValues<NavMapChunkType>())
+            {
+                ref var data = ref chunk.TileData[(int) value];
+
+                // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+                if (data == null)
+                    continue;
+
+                var chunkDatum = new Dictionary<AtmosDirection, ushort>(data.Count);
 
-            foreach (var (direction, tileData) in chunk.TileData)
-                chunkDatum[direction] = tileData;
+                foreach (var (direction, tileData) in data)
+                {
+                    chunkDatum[direction] = tileData;
+                }
 
-            chunks.Add((category, origin), chunkDatum);
+                sentChunk[(int) value] = chunkDatum;
+            }
         }
 
         foreach (var beacon in component.Beacons)
@@ -208,14 +240,14 @@ public abstract class SharedNavMapSystem : EntitySystem
     [Serializable, NetSerializable]
     protected sealed class NavMapComponentState : ComponentState, IComponentDeltaState
     {
-        public Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>> Chunks = new();
+        public Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]> Chunks = new();
         public HashSet<NavMapBeacon> Beacons = new();
 
         // Required to infer deleted/missing chunks for delta states
-        public HashSet<(NavMapChunkType, Vector2i)>? AllChunks;
+        public HashSet<Vector2i>? AllChunks;
         public HashSet<NavMapBeacon>? AllBeacons;
 
-        public NavMapComponentState(Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>> chunks, HashSet<NavMapBeacon> beacons)
+        public NavMapComponentState(Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]> chunks, HashSet<NavMapBeacon> beacons)
         {
             Chunks = chunks;
             Beacons = beacons;
@@ -237,7 +269,13 @@ public abstract class SharedNavMapSystem : EntitySystem
             }
 
             foreach (var (chunk, data) in Chunks)
-                state.Chunks[chunk] = new(data);
+            {
+                for (var i = 0; i < NavMapComponent.Categories; i++)
+                {
+                    var chunkData = data[i];
+                    state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData);
+                }
+            }
 
             // Update beacons
             foreach (var beacon in state.Beacons)
@@ -247,7 +285,9 @@ public abstract class SharedNavMapSystem : EntitySystem
             }
 
             foreach (var beacon in Beacons)
+            {
                 state.Beacons.Add(beacon);
+            }
         }
 
         public IComponentState CreateNewFullState(IComponentState fullState)
@@ -256,25 +296,45 @@ public abstract class SharedNavMapSystem : EntitySystem
             var state = (NavMapComponentState) fullState;
             DebugTools.Assert(state.FullState);
 
-            var chunks = new Dictionary<(NavMapChunkType, Vector2i), Dictionary<AtmosDirection, ushort>>();
+            var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>();
             var beacons = new HashSet<NavMapBeacon>();
 
             foreach (var (chunk, data) in Chunks)
-                chunks[chunk] = new(data);
+            {
+                for (var i = 0; i < NavMapComponent.Categories; i++)
+                {
+                    var chunkData = data[i];
+                    state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData);
+                }
+            }
 
             foreach (var (chunk, data) in state.Chunks)
             {
                 if (AllChunks!.Contains(chunk))
-                    chunks.TryAdd(chunk, new(data));
+                {
+                    var copied = new Dictionary<AtmosDirection, ushort>?[NavMapComponent.Categories];
+
+                    for (var i = 0; i < NavMapComponent.Categories; i++)
+                    {
+                        var chunkData = data[i];
+                        copied[i] = chunkData == null ? chunkData : new(chunkData);
+                    }
+
+                    chunks.TryAdd(chunk, copied);
+                }
             }
 
             foreach (var beacon in Beacons)
+            {
                 beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
+            }
 
             foreach (var beacon in state.Beacons)
             {
                 if (AllBeacons!.Contains(beacon))
+                {
                     beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
+                }
             }
 
             return new NavMapComponentState(chunks, beacons);
index c2ea28d4c3cbc3d7434a8e04ec53cac01822df0c..39a107b94d4f66e6874b83512e2a6c3e2c547a9d 100644 (file)
@@ -9,9 +9,12 @@ public sealed class TagSystem : EntitySystem
 {
     [Dependency] private readonly IPrototypeManager _proto = default!;
 
+    private EntityQuery<TagComponent> _tagQuery;
+
     public override void Initialize()
     {
         base.Initialize();
+        _tagQuery = GetEntityQuery<TagComponent>();
         SubscribeLocalEvent<TagComponent, ComponentGetState>(OnTagGetState);
         SubscribeLocalEvent<TagComponent, ComponentHandleState>(OnTagHandleState);
 
@@ -124,7 +127,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool TryAddTag(EntityUid entity, string id)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                AddTag(entity, component, id);
     }
 
@@ -142,7 +145,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool TryAddTags(EntityUid entity, params string[] ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                AddTags(entity, component, ids);
     }
 
@@ -160,7 +163,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool TryAddTags(EntityUid entity, IEnumerable<string> ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                AddTags(entity, component, ids);
     }
 
@@ -175,7 +178,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool HasTag(EntityUid entity, string id)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                HasTag(component, id);
     }
 
@@ -210,7 +213,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool HasAllTags(EntityUid entity, List<string> ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                HasAllTags(component, ids);
     }
 
@@ -225,7 +228,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool HasAllTags(EntityUid entity, IEnumerable<string> ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                HasAllTags(component, ids);
     }
 
@@ -255,7 +258,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool HasAnyTag(EntityUid entity, params string[] ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                HasAnyTag(component, ids);
     }
 
@@ -281,7 +284,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool HasAnyTag(EntityUid entity, List<string> ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                HasAnyTag(component, ids);
     }
 
@@ -311,7 +314,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool HasAnyTag(EntityUid entity, IEnumerable<string> ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                HasAnyTag(component, ids);
     }
 
@@ -328,7 +331,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool RemoveTag(EntityUid entity, string id)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                RemoveTag(entity, component, id);
     }
 
@@ -345,7 +348,7 @@ public sealed class TagSystem : EntitySystem
     /// </returns>
     public bool RemoveTags(EntityUid entity, params string[] ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                RemoveTags(entity, component, ids);
     }
 
@@ -362,7 +365,7 @@ public sealed class TagSystem : EntitySystem
     /// </exception>
     public bool RemoveTags(EntityUid entity, IEnumerable<string> ids)
     {
-        return TryComp<TagComponent>(entity, out var component) &&
+        return _tagQuery.TryComp(entity, out var component) &&
                RemoveTags(entity, component, ids);
     }