From a7e6337cbd63439a47d1e2dc4f699b7878b6f354 Mon Sep 17 00:00:00 2001 From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Thu, 2 May 2024 14:51:21 +1200 Subject: [PATCH] Replace NavMap dictionaries with int[] (#27602) * Replace NavMap dictionaries with int[] * Remove badly named const * Remove unnecessary offset * Prioritize airlocks --- Content.Client/Pinpointer/NavMapSystem.cs | 30 +- Content.Client/Pinpointer/UI/NavMapControl.cs | 360 +++++++----------- .../PowerMonitoringConsoleNavMapControl.cs | 70 ++-- Content.Server/Pinpointer/NavMapSystem.cs | 150 ++++---- .../PowerMonitoringConsoleSystem.cs | 22 +- Content.Shared/Atmos/AtmosDirection.cs | 17 +- Content.Shared/Pinpointer/NavMapComponent.cs | 53 +-- .../Pinpointer/SharedNavMapSystem.cs | 303 ++++----------- .../SharedPowerMonitoringConsoleSystem.cs | 20 + Content.Shared/Tag/TagSystem.cs | 10 +- 10 files changed, 395 insertions(+), 640 deletions(-) diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs index c80e7e85e2..e33bc5d329 100644 --- a/Content.Client/Pinpointer/NavMapSystem.cs +++ b/Content.Client/Pinpointer/NavMapSystem.cs @@ -1,4 +1,3 @@ -using Content.Shared.Atmos; using Content.Shared.Pinpointer; using Robust.Shared.GameStates; @@ -25,12 +24,6 @@ public sealed partial class NavMapSystem : SharedNavMapSystem if (!state.AllChunks!.Contains(index)) component.Chunks.Remove(index); } - - foreach (var beacon in component.Beacons) - { - if (!state.AllBeacons!.Contains(beacon)) - component.Beacons.Remove(beacon); - } } else { @@ -39,34 +32,19 @@ public sealed partial class NavMapSystem : SharedNavMapSystem if (!state.Chunks.ContainsKey(index)) component.Chunks.Remove(index); } - - foreach (var beacon in component.Beacons) - { - if (!state.Beacons.Contains(beacon)) - component.Beacons.Remove(beacon); - } } foreach (var (origin, chunk) in state.Chunks) { var newChunk = new NavMapChunk(origin); - - for (var i = 0; i < NavMapComponent.Categories; i++) - { - var newData = chunk[i]; - - if (newData == null) - continue; - - newChunk.TileData[i] = new(newData); - } - + Array.Copy(chunk, newChunk.TileData, chunk.Length); component.Chunks[origin] = newChunk; } - foreach (var beacon in state.Beacons) + component.Beacons.Clear(); + foreach (var (nuid, beacon) in state.Beacons) { - component.Beacons.Add(beacon); + component.Beacons[nuid] = beacon; } } } diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs index 5aa20b6881..f4d2f8e2fb 100644 --- a/Content.Client/Pinpointer/UI/NavMapControl.cs +++ b/Content.Client/Pinpointer/UI/NavMapControl.cs @@ -18,6 +18,7 @@ using System.Numerics; using JetBrains.Annotations; using Content.Shared.Atmos; using System.Linq; +using Robust.Shared.Utility; namespace Content.Client.Pinpointer.UI; @@ -71,10 +72,10 @@ public partial class NavMapControl : MapGridControl protected float BackgroundOpacity = 0.9f; private int _targetFontsize = 8; - protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookup = new(); - protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookupReversed = new(); - protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookup = new(); - protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookupReversed = new(); + private Dictionary _horizLines = new(); + private Dictionary _horizLinesReversed = new(); + private Dictionary _vertLines = new(); + private Dictionary _vertLinesReversed = new(); // Components private NavMapComponent? _navMap; @@ -376,7 +377,7 @@ public partial class NavMapControl : MapGridControl var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0); var font = new VectorFont(_cache.GetResource("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize); - foreach (var beacon in _navMap.Beacons) + foreach (var beacon in _navMap.Beacons.Values) { var position = beacon.Position - offset; position = ScalePosition(position with { Y = -position.Y }); @@ -485,147 +486,105 @@ public partial class NavMapControl : MapGridControl return; // We'll use the following dictionaries to combine collinear wall lines - HorizLinesLookup.Clear(); - HorizLinesLookupReversed.Clear(); - VertLinesLookup.Clear(); - VertLinesLookupReversed.Clear(); + _horizLines.Clear(); + _horizLinesReversed.Clear(); + _vertLines.Clear(); + _vertLinesReversed.Clear(); + + const int southMask = (int) AtmosDirection.South << (int) NavMapChunkType.Wall; + const int eastMask = (int) AtmosDirection.East << (int) NavMapChunkType.Wall; + const int westMask = (int) AtmosDirection.West << (int) NavMapChunkType.Wall; + const int northMask = (int) AtmosDirection.North << (int) NavMapChunkType.Wall; foreach (var (chunkOrigin, chunk) in _navMap.Chunks) { - for (var j = 0; j < NavMapComponent.Categories; j++) + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) { - var category = (NavMapChunkType) j; - - if (category != NavMapChunkType.Wall) + var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask; + if (tileData == 0) continue; - var data = chunk.TileData[j]; + tileData >>= (int) NavMapChunkType.Wall; - if (data == null) - continue; + var relativeTile = SharedNavMapSystem.GetTileFromIndex(i); + var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; - for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++) + if (tileData != SharedNavMapSystem.AllDirMask) { - var value = (ushort) Math.Pow(2, i); - var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value; - - if (mask == 0x0) - continue; - - var relativeTile = SharedNavMapSystem.GetTile(mask); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; - - if (!_navMapSystem.AllTileEdgesAreOccupied(data, relativeTile)) - { - AddRectForThinWall(data, tile); - continue; - } - - tile = tile with { Y = -tile.Y }; - - NavMapChunk? neighborChunk; - bool neighbor; - - // North edge - if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1) - { - _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk); - var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall]; - - 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; - } - - if (!neighbor) - { - AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), - tile + new Vector2i(_grid.TileSize, -_grid.TileSize), HorizLinesLookup, - HorizLinesLookupReversed); - } - - // East edge - if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1) - { - _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk); - var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall]; - - 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))); + AddRectForThinWall(tileData, tile); + continue; } + + tile = tile with { Y = -tile.Y }; + NavMapChunk? neighborChunk; + + // North edge + var neighborData = 0; + if (relativeTile.Y != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Up, out neighborChunk)) + neighborData = neighborChunk.TileData[i + 1 - SharedNavMapSystem.ChunkSize]; + + if ((neighborData & southMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines, + _horizLinesReversed); + } + + // East edge + neighborData = 0; + if (relativeTile.X != SharedNavMapSystem.ChunkSize - 1) + neighborData = chunk.TileData[i+SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Right, out neighborChunk)) + neighborData = neighborChunk.TileData[i + SharedNavMapSystem.ChunkSize - SharedNavMapSystem.ArraySize]; + + if ((neighborData & westMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize), + tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed); + } + + // South edge + neighborData = 0; + if (relativeTile.Y != 0) + neighborData = chunk.TileData[i-1]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk)) + neighborData = neighborChunk.TileData[i - 1 + SharedNavMapSystem.ChunkSize]; + + if ((neighborData & northMask) == 0) + { + AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines, + _horizLinesReversed); + } + + // West edge + neighborData = 0; + if (relativeTile.X != 0) + neighborData = chunk.TileData[i-SharedNavMapSystem.ChunkSize]; + else if (_navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk)) + neighborData = neighborChunk.TileData[i - SharedNavMapSystem.ChunkSize + SharedNavMapSystem.ArraySize]; + + if ((neighborData & eastMask) == 0) + { + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, _vertLines, + _vertLinesReversed); + } + + // 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 - foreach (var (origin, terminal) in HorizLinesLookup) + foreach (var (origin, terminal) in _horizLines) { - TileLines.Add((origin.Item2, terminal.Item2)); + TileLines.Add((origin, terminal)); } - foreach (var (origin, terminal) in VertLinesLookup) + foreach (var (origin, terminal) in _vertLines) { - TileLines.Add((origin.Item2, terminal.Item2)); + TileLines.Add((origin, terminal)); } } @@ -634,28 +593,23 @@ public partial class NavMapControl : MapGridControl if (_navMap == null || _grid == null) return; - foreach (var (chunkOrigin, chunk) in _navMap.Chunks) + foreach (var chunk in _navMap.Chunks.Values) { - var data = chunk.TileData[(int) NavMapChunkType.Airlock]; - - if (data == null) - continue; - - for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++) + for (var i = 0; i < SharedNavMapSystem.ArraySize; i++) { - var value = (int) Math.Pow(2, i); - var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value; - - if (mask == 0x0) + var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask; + if (tileData == 0) continue; - var relative = SharedNavMapSystem.GetTile(mask); + tileData >>= (int) NavMapChunkType.Airlock; + + var relative = SharedNavMapSystem.GetTileFromIndex(i); 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(data, relative)) + if (tileData != SharedNavMapSystem.AllDirMask) { - AddRectForThinAirlock(data, tile); + AddRectForThinAirlock(tileData, tile); continue; } @@ -669,108 +623,90 @@ public partial class NavMapControl : MapGridControl } } - private void AddRectForThinWall(Dictionary tileData, Vector2i tile) + private void AddRectForThinWall(int tileData, Vector2i tile) { - if (_navMapSystem == null || _grid == null) - return; + var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness); + var rightBottom = new Vector2(0.5f, 0.5f); - var leftTop = new Vector2(-0.5f, -0.5f + ThinWallThickness); - var rightBottom = new Vector2(0.5f, -0.5f); - - foreach (var (direction, mask) in tileData) + for (var i = 0; i < SharedNavMapSystem.Directions; i++) { - var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize); - var flag = (ushort) SharedNavMapSystem.GetFlag(relative); - - if ((mask & flag) == 0) + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) continue; var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - var angle = new Angle(0); - - switch (direction) - { - case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f); break; - case AtmosDirection.South: angle = new Angle(MathF.PI); break; - case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break; - } + // TODO NAVMAP + // Consider using faster rotation operations, given that these are always 90 degree increments + var angle = -((AtmosDirection) dirMask).ToAngle(); TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); } } - private void AddRectForThinAirlock(Dictionary tileData, Vector2i tile) + private void AddRectForThinAirlock(int tileData, Vector2i tile) { - if (_navMapSystem == null || _grid == null) - return; + var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness); + var rightBottom = new Vector2(0.5f - FullWallInstep, 0.5f - FullWallInstep); + var centreTop = new Vector2(0f, 0.5f - FullWallInstep - ThinDoorThickness); + var centreBottom = new Vector2(0f, 0.5f - FullWallInstep); - var leftTop = new Vector2(-0.5f + FullWallInstep, -0.5f + FullWallInstep + ThinDoorThickness); - var rightBottom = new Vector2(0.5f - FullWallInstep, -0.5f + FullWallInstep); - var centreTop = new Vector2(0f, -0.5f + FullWallInstep + ThinDoorThickness); - var centreBottom = new Vector2(0f, -0.5f + FullWallInstep); - - foreach (var (direction, mask) in tileData) + for (var i = 0; i < SharedNavMapSystem.Directions; i++) { - var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize); - var flag = (ushort) SharedNavMapSystem.GetFlag(relative); - - if ((mask & flag) == 0) + var dirMask = 1 << i; + if ((tileData & dirMask) == 0) continue; var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); - var angle = new Angle(0); - - switch (direction) - { - case AtmosDirection.East: angle = new Angle(MathF.PI * 0.5f);break; - case AtmosDirection.South: angle = new Angle(MathF.PI); break; - case AtmosDirection.West: angle = new Angle(MathF.PI * -0.5f); break; - } - + var angle = -((AtmosDirection) dirMask).ToAngle(); TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); } } - protected void AddOrUpdateNavMapLine - (Vector2i origin, + protected void AddOrUpdateNavMapLine( + Vector2i origin, Vector2i terminus, - Dictionary<(int, Vector2i), (int, Vector2i)> lookup, - Dictionary<(int, Vector2i), (int, Vector2i)> lookupReversed, - int index = 0) + Dictionary lookup, + Dictionary lookupReversed) { - (int, Vector2i) foundTermiusTuple; - (int, Vector2i) foundOriginTuple; + Vector2i foundTermius; + Vector2i foundOrigin; - if (lookup.TryGetValue((index, terminus), out foundTermiusTuple) && - lookupReversed.TryGetValue((index, origin), out foundOriginTuple)) + // Does our new line end at the beginning of an existing line? + if (lookup.Remove(terminus, out foundTermius)) { - lookup[foundOriginTuple] = foundTermiusTuple; - lookupReversed[foundTermiusTuple] = foundOriginTuple; + DebugTools.Assert(lookupReversed[foundTermius] == terminus); - lookup.Remove((index, terminus)); - lookupReversed.Remove((index, origin)); + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) + { + // Our new line just connects two existing lines + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = foundTermius; + lookupReversed[foundTermius] = foundOrigin; + } + else + { + // Our new line precedes an existing line, extending it further to the left + lookup[origin] = foundTermius; + lookupReversed[foundTermius] = origin; + } + return; } - else if (lookup.TryGetValue((index, terminus), out foundTermiusTuple)) + // Does our new line start at the end of an existing line? + if (lookupReversed.Remove(origin, out foundOrigin)) { - lookup[(index, origin)] = foundTermiusTuple; - lookup.Remove((index, terminus)); - lookupReversed[foundTermiusTuple] = (index, origin); + // Our new line just extends an existing line further to the right + DebugTools.Assert(lookup[foundOrigin] == origin); + lookup[foundOrigin] = terminus; + lookupReversed[terminus] = foundOrigin; + return; } - else if (lookupReversed.TryGetValue((index, origin), out foundOriginTuple)) - { - lookupReversed[(index, terminus)] = foundOriginTuple; - lookupReversed.Remove(foundOriginTuple); - lookup[foundOriginTuple] = (index, terminus); - } - - else - { - lookup.Add((index, origin), (index, terminus)); - lookupReversed.Add((index, terminus), (index, origin)); - } + // Completely disconnected line segment. + lookup.Add(origin, terminus); + lookupReversed.Add(terminus, origin); } protected Vector2 GetOffset() diff --git a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs index 3d94318be8..d5057416cf 100644 --- a/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs +++ b/Content.Client/Power/PowerMonitoringConsoleNavMapControl.cs @@ -5,6 +5,7 @@ using Robust.Client.Graphics; using Robust.Shared.Collections; using Robust.Shared.Map.Components; using System.Numerics; +using static Content.Shared.Power.SharedPowerMonitoringConsoleSystem; namespace Content.Client.Power; @@ -26,6 +27,11 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl public List PowerCableNetwork = new(); public List FocusCableNetwork = new(); + private Dictionary[] _horizLines = [new(), new(), new()]; + private Dictionary[] _horizLinesReversed = [new(), new(), new()]; + private Dictionary[] _vertLines = [new(), new(), new()]; + private Dictionary[] _vertLinesReversed = [new(), new(), new()]; + private MapGridComponent? _grid; public PowerMonitoringConsoleNavMapControl() : base() @@ -182,28 +188,32 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl if (chunks == null) return decodedOutput; - // We'll use the following dictionaries to combine collinear power cable lines - HorizLinesLookup.Clear(); - HorizLinesLookupReversed.Clear(); - VertLinesLookup.Clear(); - VertLinesLookupReversed.Clear(); + Array.ForEach(_horizLines, x=> x.Clear()); + Array.ForEach(_horizLinesReversed, x=> x.Clear()); + Array.ForEach(_vertLines, x=> x.Clear()); + Array.ForEach(_vertLinesReversed, x=> x.Clear()); - foreach ((var chunkOrigin, var chunk) in chunks) + foreach (var (chunkOrigin, chunk) in chunks) { - for (int cableIdx = 0; cableIdx < chunk.PowerCableData.Length; cableIdx++) + for (var cableIdx = 0; cableIdx < 3; cableIdx++) { + var horizLines = _horizLines[cableIdx]; + var horizLinesReversed = _horizLinesReversed[cableIdx]; + var vertLines = _vertLines[cableIdx]; + var vertLinesReversed = _vertLinesReversed[cableIdx]; + var chunkMask = chunk.PowerCableData[cableIdx]; - for (var chunkIdx = 0; chunkIdx < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; chunkIdx++) + for (var chunkIdx = 0; chunkIdx < ChunkSize * ChunkSize; chunkIdx++) { - var value = (int) Math.Pow(2, chunkIdx); + var value = 1 << chunkIdx; var mask = chunkMask & value; if (mask == 0x0) continue; - var relativeTile = SharedNavMapSystem.GetTile(mask); - var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; + var relativeTile = GetTileFromIndex(chunkIdx); + var tile = (chunk.Origin * ChunkSize + relativeTile) * _grid.TileSize; tile = tile with { Y = -tile.Y }; PowerCableChunk neighborChunk; @@ -212,39 +222,39 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl // Note: we only check the north and east neighbors // East - if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1) + if (relativeTile.X == ChunkSize - 1) { neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) && - (neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0; + (neighborChunk.PowerCableData[cableIdx] & GetFlag(new Vector2i(0, relativeTile.Y))) != 0x0; } else { - var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0)); + var flag = GetFlag(relativeTile + new Vector2i(1, 0)); neighbor = (chunkMask & flag) != 0x0; } if (neighbor) { // Add points - AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed, cableIdx); + AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), horizLines, horizLinesReversed); } // North - if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1) + if (relativeTile.Y == ChunkSize - 1) { neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) && - (neighborChunk.PowerCableData[cableIdx] & SharedNavMapSystem.GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0; + (neighborChunk.PowerCableData[cableIdx] & GetFlag(new Vector2i(relativeTile.X, 0))) != 0x0; } else { - var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1)); + var flag = GetFlag(relativeTile + new Vector2i(0, 1)); neighbor = (chunkMask & flag) != 0x0; } if (neighbor) { // Add points - AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup, VertLinesLookupReversed, cableIdx); + AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, vertLines, vertLinesReversed); } } @@ -253,11 +263,25 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl var gridOffset = new Vector2(_grid.TileSize * 0.5f, -_grid.TileSize * 0.5f); - foreach (var (origin, terminal) in HorizLinesLookup) - decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1)); + for (var index = 0; index < _horizLines.Length; index++) + { + var horizLines = _horizLines[index]; + foreach (var (origin, terminal) in horizLines) + { + decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset, + (PowerMonitoringConsoleLineGroup) index)); + } + } - foreach (var (origin, terminal) in VertLinesLookup) - decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1)); + for (var index = 0; index < _vertLines.Length; index++) + { + var vertLines = _vertLines[index]; + foreach (var (origin, terminal) in vertLines) + { + decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset, + (PowerMonitoringConsoleLineGroup) index)); + } + } return decodedOutput; } diff --git a/Content.Server/Pinpointer/NavMapSystem.cs b/Content.Server/Pinpointer/NavMapSystem.cs index 2b322789e2..dba964753f 100644 --- a/Content.Server/Pinpointer/NavMapSystem.cs +++ b/Content.Server/Pinpointer/NavMapSystem.cs @@ -12,11 +12,7 @@ using JetBrains.Annotations; using Robust.Shared.Map; 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; @@ -44,6 +40,10 @@ public sealed partial class NavMapSystem : SharedNavMapSystem { base.Initialize(); + var categories = Enum.GetNames(typeof(NavMapChunkType)).Length - 1; // -1 due to "Invalid" entry. + if (Categories != categories) + throw new Exception($"{nameof(Categories)} must be equal to the number of chunk types"); + _airtightQuery = GetEntityQuery(); _gridQuery = GetEntityQuery(); _navQuery = GetEntityQuery(); @@ -65,15 +65,12 @@ public sealed partial class NavMapSystem : SharedNavMapSystem SubscribeLocalEvent(OnConfigurableExamined); } - #region: Initialization event handling private void OnStationInit(StationGridAddedEvent ev) { var comp = EnsureComp(ev.GridId); RefreshGrid(ev.GridId, comp, Comp(ev.GridId)); } - #endregion - #region: Grid change event handling private void OnNavMapSplit(ref GridSplitEvent args) @@ -112,16 +109,30 @@ public sealed partial class NavMapSystem : SharedNavMapSystem var chunk = EnsureChunk(navMap, chunkOrigin); // This could be easily replaced in the future to accommodate diagonal tiles - if (ev.NewTile.IsSpace(_tileDefManager)) - UnsetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor); - else - SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor); + var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); + ref var tileData = ref chunk.TileData[GetTileIndex(relative)]; - if (!PruneEmpty((ev.NewTile.GridUid, navMap), chunk)) + if (ev.NewTile.IsSpace(_tileDefManager)) { - chunk.LastUpdate = _gameTiming.CurTick; - Dirty(ev.NewTile.GridUid, navMap); + tileData = 0; + if (PruneEmpty((ev.NewTile.GridUid, navMap), chunk)) + return; } + else + { + tileData = FloorMask; + } + + DirtyChunk((ev.NewTile.GridUid, navMap), chunk); + } + + private void DirtyChunk(Entity entity, NavMapChunk chunk) + { + if (chunk.LastUpdate == _gameTiming.CurTick) + return; + + chunk.LastUpdate = _gameTiming.CurTick; + Dirty(entity); } private void OnAirtightChange(ref AirtightChanged args) @@ -137,15 +148,13 @@ public sealed partial class NavMapSystem : SharedNavMapSystem return; } - // Refresh the affected tile var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize); + var (newValue, chunk) = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile, setFloor: false); - var chunk = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile); - if (!PruneEmpty((gridUid, navMap), chunk)) - { - chunk.LastUpdate = _gameTiming.CurTick; - Dirty(gridUid, navMap); - } + if (newValue == 0 && PruneEmpty((gridUid, navMap), chunk)) + return; + + DirtyChunk((gridUid, navMap), chunk); } #endregion @@ -238,87 +247,63 @@ public sealed partial class NavMapSystem : SharedNavMapSystem var chunk = EnsureChunk(component, chunkOrigin); chunk.LastUpdate = _gameTiming.CurTick; - - // Refresh the floor tile - SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor); - - // Refresh the contents of the tile - RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile); + RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile, setFloor: true); } Dirty(uid, component); } - private NavMapChunk RefreshTileEntityContents(EntityUid uid, NavMapComponent component, MapGridComponent mapGrid, Vector2i chunkOrigin, Vector2i tile) + private (int NewVal, NavMapChunk Chunk) RefreshTileEntityContents(EntityUid uid, + NavMapComponent component, + MapGridComponent mapGrid, + Vector2i chunkOrigin, + Vector2i tile, + bool setFloor) { var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); - var flag = (ushort) GetFlag(relative); - var invFlag = (ushort) ~flag; var chunk = EnsureChunk(component, chunkOrigin); + ref var tileData = ref chunk.TileData[GetTileIndex(relative)]; - // Clear stale data from the tile across all entity associated chunks - foreach (var category in EntityChunkTypes) - { - var data = chunk.EnsureType(category); + // Clear all data except for floor bits + if (setFloor) + tileData = FloorMask; + else + tileData &= FloorMask; - foreach (var direction in data.Keys) - { - data[direction] &= invFlag; - } - } - - // Update the tile data based on what entities are still anchored to the tile var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(uid, mapGrid, tile); - while (enumerator.MoveNext(out var ent)) { if (!_airtightQuery.TryComp(ent, out var airtight)) continue; - var category = GetAssociatedEntityChunkType(ent.Value); - var data = chunk.EnsureType(category); + var category = GetEntityType(ent.Value); + if (category == NavMapChunkType.Invalid) + continue; - foreach (var direction in data.Keys) - { - if ((direction & airtight.AirBlockedDirection) > 0) - { - data[direction] |= flag; - } - } + var directions = (int)airtight.AirBlockedDirection; + tileData |= directions << (int) category; } // Remove walls that intersect with doors (unless they can both physically fit on the same tile) - var wallData = chunk.TileData[(int) NavMapChunkType.Wall]; - var airlockData = chunk.TileData[(int) NavMapChunkType.Airlock]; + // TODO NAVMAP why can this even happen? + // Is this for blast-doors or something? - if (wallData != null && airlockData != null) - { - foreach (var direction in wallData.Keys) - { - var airlockInvFlag = (ushort) ~airlockData[direction]; - wallData[direction] &= airlockInvFlag; - } - } + // Shift airlock bits over to the wall bits + var shiftedAirlockBits = (tileData & AirlockMask) >> ((int) NavMapChunkType.Airlock - (int) NavMapChunkType.Wall); - return chunk; + // And then mask door bits + tileData &= ~shiftedAirlockBits; + + return (tileData, chunk); } private bool PruneEmpty(Entity entity, NavMapChunk chunk) { - for (var i = 0; i < NavMapComponent.Categories; i++) + foreach (var val in chunk.TileData) { - var data = chunk.TileData[i]; - - if (data == null) - continue; - - foreach (var value in data.Values) - { - if (value != 0) - { - return false; - } - } + // TODO NAVMAP SIMD + if (val != 0) + return false; } entity.Comp.Chunks.Remove(chunk.Origin); @@ -341,19 +326,12 @@ public sealed partial class NavMapSystem : SharedNavMapSystem if (!_navQuery.TryComp(xform.GridUid, out var navMap)) return; - var netEnt = GetNetEntity(uid); - var oldBeacon = navMap.Beacons.FirstOrNull(x => x.NetEnt == netEnt); - var changed = false; + var meta = MetaData(uid); + var changed = navMap.Beacons.Remove(meta.NetEntity); - if (oldBeacon != null) + if (TryCreateNavMapBeaconData(uid, component, xform, meta, out var beaconData)) { - navMap.Beacons.Remove(oldBeacon.Value); - changed = true; - } - - if (TryCreateNavMapBeaconData(uid, component, xform, out var beaconData)) - { - navMap.Beacons.Add(beaconData.Value); + navMap.Beacons.Add(meta.NetEntity, beaconData.Value); changed = true; } diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index be1238fd2b..42c84b7f43 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -1,7 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; -using Content.Server.NodeContainer.NodeGroups; using Content.Server.NodeContainer.Nodes; using Content.Server.Power.Components; using Content.Server.Power.Nodes; @@ -13,10 +11,8 @@ using Content.Shared.Power; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Map.Components; -using Robust.Shared.Player; using Robust.Shared.Utility; using System.Linq; -using System.Diagnostics.CodeAnalysis; using Content.Server.GameTicking.Components; namespace Content.Server.Power.EntitySystems; @@ -163,7 +159,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori allChunks = new(); var tile = _sharedMapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates); - var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, SharedNavMapSystem.ChunkSize); + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize); if (!allChunks.TryGetValue(chunkOrigin, out var chunk)) { @@ -171,8 +167,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori allChunks[chunkOrigin] = chunk; } - var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize); - var flag = SharedNavMapSystem.GetFlag(relative); + var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); + var flag = GetFlag(relative); if (args.Anchored) chunk.PowerCableData[(int) component.CableType] |= flag; @@ -884,7 +880,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori continue; var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates); - var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, SharedNavMapSystem.ChunkSize); + var chunkOrigin = SharedMapSystem.GetChunkIndices(tile.GridIndices, ChunkSize); if (!allChunks.TryGetValue(chunkOrigin, out var chunk)) { @@ -892,8 +888,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori allChunks[chunkOrigin] = chunk; } - var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, SharedNavMapSystem.ChunkSize); - var flag = SharedNavMapSystem.GetFlag(relative); + var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize); + var flag = GetFlag(relative); chunk.PowerCableData[(int) cable.CableType] |= flag; } @@ -910,7 +906,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori var xform = Transform(ent); var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates); var gridIndices = tile.GridIndices; - var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, SharedNavMapSystem.ChunkSize); + var chunkOrigin = SharedMapSystem.GetChunkIndices(gridIndices, ChunkSize); if (!component.FocusChunks.TryGetValue(chunkOrigin, out var chunk)) { @@ -918,8 +914,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori component.FocusChunks[chunkOrigin] = chunk; } - var relative = SharedMapSystem.GetChunkRelative(gridIndices, SharedNavMapSystem.ChunkSize); - var flag = SharedNavMapSystem.GetFlag(relative); + var relative = SharedMapSystem.GetChunkRelative(gridIndices, ChunkSize); + var flag = GetFlag(relative); if (TryComp(ent, out var cable)) chunk.PowerCableData[(int) cable.CableType] |= flag; diff --git a/Content.Shared/Atmos/AtmosDirection.cs b/Content.Shared/Atmos/AtmosDirection.cs index 09ba521aa9..a8155ef88d 100644 --- a/Content.Shared/Atmos/AtmosDirection.cs +++ b/Content.Shared/Atmos/AtmosDirection.cs @@ -104,15 +104,14 @@ namespace Content.Shared.Atmos { return direction switch { - AtmosDirection.East => Angle.FromDegrees(90), - AtmosDirection.North => Angle.FromDegrees(180), - AtmosDirection.West => Angle.FromDegrees(270), - AtmosDirection.South => Angle.FromDegrees(0), - - AtmosDirection.NorthEast => Angle.FromDegrees(135), - AtmosDirection.NorthWest => Angle.FromDegrees(205), - AtmosDirection.SouthWest => Angle.FromDegrees(315), - AtmosDirection.SouthEast => Angle.FromDegrees(45), + AtmosDirection.South => Angle.Zero, + AtmosDirection.East => new Angle(MathHelper.PiOver2), + AtmosDirection.North => new Angle(Math.PI), + AtmosDirection.West => new Angle(-MathHelper.PiOver2), + AtmosDirection.NorthEast => new Angle(Math.PI*3/4), + AtmosDirection.NorthWest => new Angle(-Math.PI*3/4), + AtmosDirection.SouthWest => new Angle(-MathHelper.PiOver4), + AtmosDirection.SouthEast => new Angle(MathHelper.PiOver4), _ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."), }; diff --git a/Content.Shared/Pinpointer/NavMapComponent.cs b/Content.Shared/Pinpointer/NavMapComponent.cs index 56b9687b31..d77169d32e 100644 --- a/Content.Shared/Pinpointer/NavMapComponent.cs +++ b/Content.Shared/Pinpointer/NavMapComponent.cs @@ -12,8 +12,6 @@ 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 */ @@ -28,62 +26,37 @@ public sealed partial class NavMapComponent : Component /// List of station beacons. /// [ViewVariables] - public HashSet Beacons = new(); + public Dictionary Beacons = new(); } [Serializable, NetSerializable] -public sealed class NavMapChunk +public sealed class NavMapChunk(Vector2i origin) { /// /// The chunk origin /// - public readonly Vector2i Origin; + [ViewVariables] + public readonly Vector2i Origin = origin; /// - /// Array with each entry corresponding to a . - /// 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 + /// Array containing the chunk's data. The /// - public Dictionary?[] TileData; + [ViewVariables] + public int[] TileData = new int[SharedNavMapSystem.ArraySize]; /// /// The last game tick that the chunk was updated /// [NonSerialized] public GameTick LastUpdate; - - public NavMapChunk(Vector2i origin) - { - Origin = origin; - TileData = new Dictionary?[NavMapComponent.Categories]; - } - - public Dictionary EnsureType(NavMapChunkType chunkType) - { - var data = TileData[(int) chunkType]; - - if (data == null) - { - data = new Dictionary() - { - [AtmosDirection.North] = 0, - [AtmosDirection.East] = 0, - [AtmosDirection.South] = 0, - [AtmosDirection.West] = 0, - }; - - TileData[(int) chunkType] = data; - } - - return data; - } } public enum NavMapChunkType : byte { - Invalid, - Floor, - Wall, - Airlock, - // Update the categories const if you update this. + // Values represent bit shift offsets when retrieving data in the tile array. + Invalid = byte.MaxValue, + Floor = 0, // I believe floors have directional information for diagonal tiles? + Wall = SharedNavMapSystem.Directions, + Airlock = 2 * SharedNavMapSystem.Directions, } + diff --git a/Content.Shared/Pinpointer/SharedNavMapSystem.cs b/Content.Shared/Pinpointer/SharedNavMapSystem.cs index 76dfd83e4e..0edcd5a437 100644 --- a/Content.Shared/Pinpointer/SharedNavMapSystem.cs +++ b/Content.Shared/Pinpointer/SharedNavMapSystem.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; using System.Numerics; -using Content.Shared.Atmos; +using System.Runtime.CompilerServices; using Content.Shared.Tag; using Robust.Shared.GameStates; using Robust.Shared.Serialization; @@ -11,19 +11,22 @@ namespace Content.Shared.Pinpointer; public abstract class SharedNavMapSystem : EntitySystem { - [Dependency] private readonly TagSystem _tagSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; + public const int Categories = 3; + public const int Directions = 4; // Not directly tied to number of atmos directions - public const byte ChunkSize = 4; + public const int ChunkSize = 8; + public const int ArraySize = ChunkSize* ChunkSize; - public readonly NavMapChunkType[] EntityChunkTypes = - { - NavMapChunkType.Invalid, - NavMapChunkType.Wall, - NavMapChunkType.Airlock, - }; + public const int AllDirMask = (1 << Directions) - 1; + public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock; + public const int WallMask = AllDirMask << (int) NavMapChunkType.Wall; + public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor; + + [Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!; + [Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!; private readonly string[] _wallTags = ["Wall", "Window"]; + private EntityQuery _doorQuery; public override void Initialize() { @@ -31,112 +34,49 @@ public abstract class SharedNavMapSystem : EntitySystem // Data handling events SubscribeLocalEvent(OnGetState); + _doorQuery = GetEntityQuery(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetTileIndex(Vector2i relativeTile) + { + return relativeTile.X * ChunkSize + relativeTile.Y; } /// - /// Converts the chunk's tile into a bitflag for the slot. + /// Inverse of /// - public static int GetFlag(Vector2i relativeTile) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2i GetTileFromIndex(int index) { - return 1 << (relativeTile.X * ChunkSize + relativeTile.Y); - } - - /// - /// Converts the chunk's tile into a bitflag for the slot. - /// - public static Vector2i GetTile(int flag) - { - var value = Math.Log2(flag); - var x = (int) value / ChunkSize; - var y = (int) value % ChunkSize; - var result = new Vector2i(x, y); - - DebugTools.Assert(GetFlag(result) == flag); - + var x = index / ChunkSize; + var y = index % ChunkSize; return new Vector2i(x, y); } - public void SetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType) + public NavMapChunkType GetEntityType(EntityUid uid) { - var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); - var flag = (ushort) GetFlag(relative); - var data = chunk.EnsureType(chunkType); + if (_doorQuery.HasComp(uid)) + return NavMapChunkType.Airlock; - foreach (var direction in data.Keys) - { - data[direction] |= flag; - } + if (_tagSystem.HasAnyTag(uid, _wallTags)) + return NavMapChunkType.Wall; + + return NavMapChunkType.Invalid; } - public void UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType) - { - var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize); - var flag = (ushort) GetFlag(relative); - var invFlag = (ushort) ~flag; - - var data = chunk.EnsureType(chunkType); - - foreach (var direction in data.Keys) - { - data[direction] &= invFlag; - } - } - - public ushort GetCombinedEdgesForChunk(Dictionary tile) - { - ushort combined = 0; - - foreach (var value in tile.Values) - { - combined |= value; - } - - return combined; - } - - public bool AllTileEdgesAreOccupied(Dictionary tileData, Vector2i tile) - { - var flag = (ushort) GetFlag(tile); - - foreach (var value in tileData.Values) - { - if ((value & flag) == 0) - return false; - } - - return true; - } - - public NavMapChunkType GetAssociatedEntityChunkType(EntityUid uid) - { - var category = NavMapChunkType.Invalid; - - if (HasComp(uid)) - category = NavMapChunkType.Airlock; - - else if (_tagSystem.HasAnyTag(uid, _wallTags)) - category = NavMapChunkType.Wall; - - return category; - } - - protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, [NotNullWhen(true)] out NavMapBeacon? beaconData) + protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, MetaDataComponent meta, [NotNullWhen(true)] out NavMapBeacon? beaconData) { beaconData = null; if (!component.Enabled || xform.GridUid == null || !xform.Anchored) return false; - string? name = component.Text; - var meta = MetaData(uid); - + var name = component.Text; if (string.IsNullOrEmpty(name)) name = meta.EntityName; - beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition) - { - LastUpdate = _gameTiming.CurTick - }; + beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition); return true; } @@ -145,91 +85,36 @@ public abstract class SharedNavMapSystem : EntitySystem private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args) { - var chunks = new Dictionary?[]>(); - var beacons = new HashSet(); + Dictionary chunks; // Should this be a full component state or a delta-state? if (args.FromTick <= component.CreationTick) { + // Full state + chunks = new(component.Chunks.Count); foreach (var (origin, chunk) in component.Chunks) { - var sentChunk = new Dictionary[NavMapComponent.Categories]; - chunks.Add(origin, sentChunk); - - foreach (var value in Enum.GetValues()) - { - ref var data = ref chunk.TileData[(int) value]; - - if (data == null) - continue; - - var chunkDatum = new Dictionary(data.Count); - - foreach (var (direction, tileData) in data) - { - chunkDatum[direction] = tileData; - } - - sentChunk[(int) value] = chunkDatum; - } + chunks.Add(origin, chunk.TileData); } - var beaconQuery = AllEntityQuery(); - - while (beaconQuery.MoveNext(out var beaconUid, out var beacon, out var xform)) - { - if (xform.GridUid != uid) - continue; - - if (!TryCreateNavMapBeaconData(beaconUid, beacon, xform, out var beaconData)) - continue; - - beacons.Add(beaconData.Value); - } - - args.State = new NavMapComponentState(chunks, beacons); + args.State = new NavMapComponentState(chunks, component.Beacons); return; } + chunks = new(); foreach (var (origin, chunk) in component.Chunks) { if (chunk.LastUpdate < args.FromTick) continue; - var sentChunk = new Dictionary[NavMapComponent.Categories]; - chunks.Add(origin, sentChunk); - - foreach (var value in Enum.GetValues()) - { - ref var data = ref chunk.TileData[(int) value]; - - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (data == null) - continue; - - var chunkDatum = new Dictionary(data.Count); - - foreach (var (direction, tileData) in data) - { - chunkDatum[direction] = tileData; - } - - sentChunk[(int) value] = chunkDatum; - } + chunks.Add(origin, chunk.TileData); } - foreach (var beacon in component.Beacons) - { - if (beacon.LastUpdate < args.FromTick) - continue; - - beacons.Add(beacon); - } - - args.State = new NavMapComponentState(chunks, beacons) + args.State = new NavMapComponentState(chunks, component.Beacons) { + // TODO NAVMAP cache a single AllChunks hashset in the component. + // Or maybe just only send them if a chunk gets removed. AllChunks = new(component.Chunks.Keys), - AllBeacons = new(component.Beacons) }; } @@ -238,22 +123,18 @@ public abstract class SharedNavMapSystem : EntitySystem #region: System messages [Serializable, NetSerializable] - protected sealed class NavMapComponentState : ComponentState, IComponentDeltaState + protected sealed class NavMapComponentState( + Dictionary chunks, + Dictionary beacons) + : ComponentState, IComponentDeltaState { - public Dictionary?[]> Chunks = new(); - public HashSet Beacons = new(); + public Dictionary Chunks = chunks; + public Dictionary Beacons = beacons; // Required to infer deleted/missing chunks for delta states public HashSet? AllChunks; - public HashSet? AllBeacons; - public NavMapComponentState(Dictionary?[]> chunks, HashSet beacons) - { - Chunks = chunks; - Beacons = beacons; - } - - public bool FullState => (AllChunks == null || AllBeacons == null); + public bool FullState => AllChunks == null; public void ApplyToFullState(IComponentState fullState) { @@ -261,32 +142,24 @@ public abstract class SharedNavMapSystem : EntitySystem var state = (NavMapComponentState) fullState; DebugTools.Assert(state.FullState); - // Update chunks foreach (var key in state.Chunks.Keys) { if (!AllChunks!.Contains(key)) state.Chunks.Remove(key); } - foreach (var (chunk, data) in Chunks) + foreach (var (index, data) in Chunks) { - for (var i = 0; i < NavMapComponent.Categories; i++) - { - var chunkData = data[i]; - state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData); - } + if (!state.Chunks.TryGetValue(index, out var stateValue)) + state.Chunks[index] = stateValue = new int[data.Length]; + + Array.Copy(data, stateValue, data.Length); } - // Update beacons - foreach (var beacon in state.Beacons) + state.Beacons.Clear(); + foreach (var (nuid, beacon) in Beacons) { - if (!AllBeacons!.Contains(beacon)) - state.Beacons.Remove(beacon); - } - - foreach (var beacon in Beacons) - { - state.Beacons.Add(beacon); + state.Beacons.Add(nuid, beacon); } } @@ -296,56 +169,26 @@ public abstract class SharedNavMapSystem : EntitySystem var state = (NavMapComponentState) fullState; DebugTools.Assert(state.FullState); - var chunks = new Dictionary?[]>(); - var beacons = new HashSet(); - - foreach (var (chunk, data) in Chunks) + var chunks = new Dictionary(state.Chunks.Count); + foreach (var (index, data) in state.Chunks) { - for (var i = 0; i < NavMapComponent.Categories; i++) - { - var chunkData = data[i]; - state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData); - } + if (!AllChunks!.Contains(index)) + continue; + + var newData = chunks[index] = new int[ArraySize]; + + if (Chunks.TryGetValue(index, out var updatedData)) + Array.Copy(newData, updatedData, ArraySize); + else + Array.Copy(newData, data, ArraySize); } - foreach (var (chunk, data) in state.Chunks) - { - if (AllChunks!.Contains(chunk)) - { - var copied = new Dictionary?[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); + return new NavMapComponentState(chunks, new(Beacons)); } } [Serializable, NetSerializable] - public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position) - { - public GameTick LastUpdate; - } + public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position); #endregion } diff --git a/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs b/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs index dc4af23c23..749f0233aa 100644 --- a/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs +++ b/Content.Shared/Power/SharedPowerMonitoringConsoleSystem.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using JetBrains.Annotations; namespace Content.Shared.Power; @@ -5,4 +6,23 @@ namespace Content.Shared.Power; [UsedImplicitly] public abstract class SharedPowerMonitoringConsoleSystem : EntitySystem { + // Chunk size is limited as we require ChunkSize^2 <= 32 (number of bits in an int) + public const int ChunkSize = 5; + + /// + /// Converts the chunk's tile into a bitflag for the slot. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int GetFlag(Vector2i relativeTile) + { + return 1 << (relativeTile.X * ChunkSize + relativeTile.Y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector2i GetTileFromIndex(int index) + { + var x = index / ChunkSize; + var y = index % ChunkSize; + return new Vector2i(x, y); + } } diff --git a/Content.Shared/Tag/TagSystem.cs b/Content.Shared/Tag/TagSystem.cs index 39a107b94d..fdb7de1f75 100644 --- a/Content.Shared/Tag/TagSystem.cs +++ b/Content.Shared/Tag/TagSystem.cs @@ -541,7 +541,15 @@ public sealed class TagSystem : EntitySystem /// public bool HasAnyTag(TagComponent component, params string[] ids) { - return HasAnyTag(component, ids.AsEnumerable()); + foreach (var id in ids) + { + AssertValidTag(id); + + if (component.Tags.Contains(id)) + return true; + } + + return false; }