Replace NavMap dictionaries with int[] (#27602)

* Replace NavMap dictionaries with int[]

* Remove badly named const

* Remove unnecessary offset

* Prioritize airlocks
This commit is contained in:
Leon Friedrich
2024-05-02 14:51:21 +12:00
committed by GitHub
parent 11a4f9d21f
commit a7e6337cbd
10 changed files with 395 additions and 640 deletions

View File

@@ -1,4 +1,3 @@
using Content.Shared.Atmos;
using Content.Shared.Pinpointer; using Content.Shared.Pinpointer;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
@@ -25,12 +24,6 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
if (!state.AllChunks!.Contains(index)) if (!state.AllChunks!.Contains(index))
component.Chunks.Remove(index); component.Chunks.Remove(index);
} }
foreach (var beacon in component.Beacons)
{
if (!state.AllBeacons!.Contains(beacon))
component.Beacons.Remove(beacon);
}
} }
else else
{ {
@@ -39,34 +32,19 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
if (!state.Chunks.ContainsKey(index)) if (!state.Chunks.ContainsKey(index))
component.Chunks.Remove(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) foreach (var (origin, chunk) in state.Chunks)
{ {
var newChunk = new NavMapChunk(origin); var newChunk = new NavMapChunk(origin);
Array.Copy(chunk, newChunk.TileData, chunk.Length);
for (var i = 0; i < NavMapComponent.Categories; i++)
{
var newData = chunk[i];
if (newData == null)
continue;
newChunk.TileData[i] = new(newData);
}
component.Chunks[origin] = newChunk; 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;
} }
} }
} }

View File

@@ -18,6 +18,7 @@ using System.Numerics;
using JetBrains.Annotations; using JetBrains.Annotations;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using System.Linq; using System.Linq;
using Robust.Shared.Utility;
namespace Content.Client.Pinpointer.UI; namespace Content.Client.Pinpointer.UI;
@@ -71,10 +72,10 @@ public partial class NavMapControl : MapGridControl
protected float BackgroundOpacity = 0.9f; protected float BackgroundOpacity = 0.9f;
private int _targetFontsize = 8; private int _targetFontsize = 8;
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookup = new(); private Dictionary<Vector2i, Vector2i> _horizLines = new();
protected Dictionary<(int, Vector2i), (int, Vector2i)> HorizLinesLookupReversed = new(); private Dictionary<Vector2i, Vector2i> _horizLinesReversed = new();
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookup = new(); private Dictionary<Vector2i, Vector2i> _vertLines = new();
protected Dictionary<(int, Vector2i), (int, Vector2i)> VertLinesLookupReversed = new(); private Dictionary<Vector2i, Vector2i> _vertLinesReversed = new();
// Components // Components
private NavMapComponent? _navMap; private NavMapComponent? _navMap;
@@ -376,7 +377,7 @@ public partial class NavMapControl : MapGridControl
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0); var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize); var font = new VectorFont(_cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Bold.ttf"), fontSize);
foreach (var beacon in _navMap.Beacons) foreach (var beacon in _navMap.Beacons.Values)
{ {
var position = beacon.Position - offset; var position = beacon.Position - offset;
position = ScalePosition(position with { Y = -position.Y }); position = ScalePosition(position with { Y = -position.Y });
@@ -485,147 +486,105 @@ public partial class NavMapControl : MapGridControl
return; return;
// We'll use the following dictionaries to combine collinear wall lines // We'll use the following dictionaries to combine collinear wall lines
HorizLinesLookup.Clear(); _horizLines.Clear();
HorizLinesLookupReversed.Clear(); _horizLinesReversed.Clear();
VertLinesLookup.Clear(); _vertLines.Clear();
VertLinesLookupReversed.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) 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; var tileData = chunk.TileData[i] & SharedNavMapSystem.WallMask;
if (tileData == 0)
if (category != NavMapChunkType.Wall)
continue; continue;
var data = chunk.TileData[j]; tileData >>= (int) NavMapChunkType.Wall;
if (data == null) var relativeTile = SharedNavMapSystem.GetTileFromIndex(i);
continue; 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); AddRectForThinWall(tileData, tile);
var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value; continue;
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)));
} }
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 // 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) if (_navMap == null || _grid == null)
return; return;
foreach (var (chunkOrigin, chunk) in _navMap.Chunks) foreach (var chunk in _navMap.Chunks.Values)
{ {
var data = chunk.TileData[(int) NavMapChunkType.Airlock]; for (var i = 0; i < SharedNavMapSystem.ArraySize; i++)
if (data == null)
continue;
for (var i = 0; i < SharedNavMapSystem.ChunkSize * SharedNavMapSystem.ChunkSize; i++)
{ {
var value = (int) Math.Pow(2, i); var tileData = chunk.TileData[i] & SharedNavMapSystem.AirlockMask;
var mask = _navMapSystem.GetCombinedEdgesForChunk(data) & value; if (tileData == 0)
if (mask == 0x0)
continue; continue;
var relative = SharedNavMapSystem.GetTile(mask); tileData >>= (int) NavMapChunkType.Airlock;
var relative = SharedNavMapSystem.GetTileFromIndex(i);
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relative) * _grid.TileSize; 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 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; continue;
} }
@@ -669,108 +623,90 @@ public partial class NavMapControl : MapGridControl
} }
} }
private void AddRectForThinWall(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile) private void AddRectForThinWall(int tileData, Vector2i tile)
{ {
if (_navMapSystem == null || _grid == null) var leftTop = new Vector2(-0.5f, 0.5f - ThinWallThickness);
return; var rightBottom = new Vector2(0.5f, 0.5f);
var leftTop = new Vector2(-0.5f, -0.5f + ThinWallThickness); for (var i = 0; i < SharedNavMapSystem.Directions; i++)
var rightBottom = new Vector2(0.5f, -0.5f);
foreach (var (direction, mask) in tileData)
{ {
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize); var dirMask = 1 << i;
var flag = (ushort) SharedNavMapSystem.GetFlag(relative); if ((tileData & dirMask) == 0)
if ((mask & flag) == 0)
continue; continue;
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); 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)); TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
} }
} }
private void AddRectForThinAirlock(Dictionary<AtmosDirection, ushort> tileData, Vector2i tile) private void AddRectForThinAirlock(int tileData, Vector2i tile)
{ {
if (_navMapSystem == null || _grid == null) var leftTop = new Vector2(-0.5f + FullWallInstep, 0.5f - FullWallInstep - ThinDoorThickness);
return; 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); for (var i = 0; i < SharedNavMapSystem.Directions; i++)
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)
{ {
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize); var dirMask = 1 << i;
var flag = (ushort) SharedNavMapSystem.GetFlag(relative); if ((tileData & dirMask) == 0)
if ((mask & flag) == 0)
continue; continue;
var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f); var tilePosition = new Vector2(tile.X + 0.5f, -tile.Y - 0.5f);
var angle = new Angle(0); var angle = -((AtmosDirection) dirMask).ToAngle();
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;
}
TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition)); TileRects.Add((angle.RotateVec(leftTop) + tilePosition, angle.RotateVec(rightBottom) + tilePosition));
TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition)); TileLines.Add((angle.RotateVec(centreTop) + tilePosition, angle.RotateVec(centreBottom) + tilePosition));
} }
} }
protected void AddOrUpdateNavMapLine protected void AddOrUpdateNavMapLine(
(Vector2i origin, Vector2i origin,
Vector2i terminus, Vector2i terminus,
Dictionary<(int, Vector2i), (int, Vector2i)> lookup, Dictionary<Vector2i, Vector2i> lookup,
Dictionary<(int, Vector2i), (int, Vector2i)> lookupReversed, Dictionary<Vector2i, Vector2i> lookupReversed)
int index = 0)
{ {
(int, Vector2i) foundTermiusTuple; Vector2i foundTermius;
(int, Vector2i) foundOriginTuple; Vector2i foundOrigin;
if (lookup.TryGetValue((index, terminus), out foundTermiusTuple) && // Does our new line end at the beginning of an existing line?
lookupReversed.TryGetValue((index, origin), out foundOriginTuple)) if (lookup.Remove(terminus, out foundTermius))
{ {
lookup[foundOriginTuple] = foundTermiusTuple; DebugTools.Assert(lookupReversed[foundTermius] == terminus);
lookupReversed[foundTermiusTuple] = foundOriginTuple;
lookup.Remove((index, terminus)); // Does our new line start at the end of an existing line?
lookupReversed.Remove((index, origin)); 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; // Our new line just extends an existing line further to the right
lookup.Remove((index, terminus)); DebugTools.Assert(lookup[foundOrigin] == origin);
lookupReversed[foundTermiusTuple] = (index, origin); lookup[foundOrigin] = terminus;
lookupReversed[terminus] = foundOrigin;
return;
} }
else if (lookupReversed.TryGetValue((index, origin), out foundOriginTuple)) // Completely disconnected line segment.
{ lookup.Add(origin, terminus);
lookupReversed[(index, terminus)] = foundOriginTuple; lookupReversed.Add(terminus, origin);
lookupReversed.Remove(foundOriginTuple);
lookup[foundOriginTuple] = (index, terminus);
}
else
{
lookup.Add((index, origin), (index, terminus));
lookupReversed.Add((index, terminus), (index, origin));
}
} }
protected Vector2 GetOffset() protected Vector2 GetOffset()

View File

@@ -5,6 +5,7 @@ using Robust.Client.Graphics;
using Robust.Shared.Collections; using Robust.Shared.Collections;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using System.Numerics; using System.Numerics;
using static Content.Shared.Power.SharedPowerMonitoringConsoleSystem;
namespace Content.Client.Power; namespace Content.Client.Power;
@@ -26,6 +27,11 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
public List<PowerMonitoringConsoleLine> PowerCableNetwork = new(); public List<PowerMonitoringConsoleLine> PowerCableNetwork = new();
public List<PowerMonitoringConsoleLine> FocusCableNetwork = new(); public List<PowerMonitoringConsoleLine> FocusCableNetwork = new();
private Dictionary<Vector2i, Vector2i>[] _horizLines = [new(), new(), new()];
private Dictionary<Vector2i, Vector2i>[] _horizLinesReversed = [new(), new(), new()];
private Dictionary<Vector2i, Vector2i>[] _vertLines = [new(), new(), new()];
private Dictionary<Vector2i, Vector2i>[] _vertLinesReversed = [new(), new(), new()];
private MapGridComponent? _grid; private MapGridComponent? _grid;
public PowerMonitoringConsoleNavMapControl() : base() public PowerMonitoringConsoleNavMapControl() : base()
@@ -182,28 +188,32 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
if (chunks == null) if (chunks == null)
return decodedOutput; return decodedOutput;
// We'll use the following dictionaries to combine collinear power cable lines Array.ForEach(_horizLines, x=> x.Clear());
HorizLinesLookup.Clear(); Array.ForEach(_horizLinesReversed, x=> x.Clear());
HorizLinesLookupReversed.Clear(); Array.ForEach(_vertLines, x=> x.Clear());
VertLinesLookup.Clear(); Array.ForEach(_vertLinesReversed, x=> x.Clear());
VertLinesLookupReversed.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]; 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; var mask = chunkMask & value;
if (mask == 0x0) if (mask == 0x0)
continue; continue;
var relativeTile = SharedNavMapSystem.GetTile(mask); var relativeTile = GetTileFromIndex(chunkIdx);
var tile = (chunk.Origin * SharedNavMapSystem.ChunkSize + relativeTile) * _grid.TileSize; var tile = (chunk.Origin * ChunkSize + relativeTile) * _grid.TileSize;
tile = tile with { Y = -tile.Y }; tile = tile with { Y = -tile.Y };
PowerCableChunk neighborChunk; PowerCableChunk neighborChunk;
@@ -212,39 +222,39 @@ public sealed partial class PowerMonitoringConsoleNavMapControl : NavMapControl
// Note: we only check the north and east neighbors // Note: we only check the north and east neighbors
// East // East
if (relativeTile.X == SharedNavMapSystem.ChunkSize - 1) if (relativeTile.X == ChunkSize - 1)
{ {
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(1, 0), out neighborChunk) && 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 else
{ {
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(1, 0)); var flag = GetFlag(relativeTile + new Vector2i(1, 0));
neighbor = (chunkMask & flag) != 0x0; neighbor = (chunkMask & flag) != 0x0;
} }
if (neighbor) if (neighbor)
{ {
// Add points // Add points
AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup, HorizLinesLookupReversed, cableIdx); AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), horizLines, horizLinesReversed);
} }
// North // North
if (relativeTile.Y == SharedNavMapSystem.ChunkSize - 1) if (relativeTile.Y == ChunkSize - 1)
{ {
neighbor = chunks.TryGetValue(chunkOrigin + new Vector2i(0, 1), out neighborChunk) && 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 else
{ {
var flag = SharedNavMapSystem.GetFlag(relativeTile + new Vector2i(0, 1)); var flag = GetFlag(relativeTile + new Vector2i(0, 1));
neighbor = (chunkMask & flag) != 0x0; neighbor = (chunkMask & flag) != 0x0;
} }
if (neighbor) if (neighbor)
{ {
// Add points // 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); var gridOffset = new Vector2(_grid.TileSize * 0.5f, -_grid.TileSize * 0.5f);
foreach (var (origin, terminal) in HorizLinesLookup) for (var index = 0; index < _horizLines.Length; index++)
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1)); {
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) for (var index = 0; index < _vertLines.Length; index++)
decodedOutput.Add(new PowerMonitoringConsoleLine(origin.Item2 + gridOffset, terminal.Item2 + gridOffset, (PowerMonitoringConsoleLineGroup) origin.Item1)); {
var vertLines = _vertLines[index];
foreach (var (origin, terminal) in vertLines)
{
decodedOutput.Add(new PowerMonitoringConsoleLine(origin + gridOffset, terminal + gridOffset,
(PowerMonitoringConsoleLineGroup) index));
}
}
return decodedOutput; return decodedOutput;
} }

View File

@@ -12,11 +12,7 @@ using JetBrains.Annotations;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Timing; using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Content.Shared.Atmos;
using Content.Shared.Doors.Components;
namespace Content.Server.Pinpointer; namespace Content.Server.Pinpointer;
@@ -44,6 +40,10 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
{ {
base.Initialize(); 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<AirtightComponent>(); _airtightQuery = GetEntityQuery<AirtightComponent>();
_gridQuery = GetEntityQuery<MapGridComponent>(); _gridQuery = GetEntityQuery<MapGridComponent>();
_navQuery = GetEntityQuery<NavMapComponent>(); _navQuery = GetEntityQuery<NavMapComponent>();
@@ -65,15 +65,12 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, ExaminedEvent>(OnConfigurableExamined); SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, ExaminedEvent>(OnConfigurableExamined);
} }
#region: Initialization event handling
private void OnStationInit(StationGridAddedEvent ev) private void OnStationInit(StationGridAddedEvent ev)
{ {
var comp = EnsureComp<NavMapComponent>(ev.GridId); var comp = EnsureComp<NavMapComponent>(ev.GridId);
RefreshGrid(ev.GridId, comp, Comp<MapGridComponent>(ev.GridId)); RefreshGrid(ev.GridId, comp, Comp<MapGridComponent>(ev.GridId));
} }
#endregion
#region: Grid change event handling #region: Grid change event handling
private void OnNavMapSplit(ref GridSplitEvent args) private void OnNavMapSplit(ref GridSplitEvent args)
@@ -112,16 +109,30 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
var chunk = EnsureChunk(navMap, chunkOrigin); var chunk = EnsureChunk(navMap, chunkOrigin);
// This could be easily replaced in the future to accommodate diagonal tiles // This could be easily replaced in the future to accommodate diagonal tiles
if (ev.NewTile.IsSpace(_tileDefManager)) var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
UnsetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor); ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
else
SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
if (!PruneEmpty((ev.NewTile.GridUid, navMap), chunk)) if (ev.NewTile.IsSpace(_tileDefManager))
{ {
chunk.LastUpdate = _gameTiming.CurTick; tileData = 0;
Dirty(ev.NewTile.GridUid, navMap); if (PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
return;
} }
else
{
tileData = FloorMask;
}
DirtyChunk((ev.NewTile.GridUid, navMap), chunk);
}
private void DirtyChunk(Entity<NavMapComponent> entity, NavMapChunk chunk)
{
if (chunk.LastUpdate == _gameTiming.CurTick)
return;
chunk.LastUpdate = _gameTiming.CurTick;
Dirty(entity);
} }
private void OnAirtightChange(ref AirtightChanged args) private void OnAirtightChange(ref AirtightChanged args)
@@ -137,15 +148,13 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
return; return;
} }
// Refresh the affected tile
var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize); 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 (newValue == 0 && PruneEmpty((gridUid, navMap), chunk))
if (!PruneEmpty((gridUid, navMap), chunk)) return;
{
chunk.LastUpdate = _gameTiming.CurTick; DirtyChunk((gridUid, navMap), chunk);
Dirty(gridUid, navMap);
}
} }
#endregion #endregion
@@ -238,87 +247,63 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
var chunk = EnsureChunk(component, chunkOrigin); var chunk = EnsureChunk(component, chunkOrigin);
chunk.LastUpdate = _gameTiming.CurTick; chunk.LastUpdate = _gameTiming.CurTick;
RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile, setFloor: true);
// Refresh the floor tile
SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
// Refresh the contents of the tile
RefreshTileEntityContents(uid, component, mapGrid, chunkOrigin, tile);
} }
Dirty(uid, component); 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 relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
var flag = (ushort) GetFlag(relative);
var invFlag = (ushort) ~flag;
var chunk = EnsureChunk(component, chunkOrigin); var chunk = EnsureChunk(component, chunkOrigin);
ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
// Clear stale data from the tile across all entity associated chunks // Clear all data except for floor bits
foreach (var category in EntityChunkTypes) if (setFloor)
{ tileData = FloorMask;
var data = chunk.EnsureType(category); 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); var enumerator = _mapSystem.GetAnchoredEntitiesEnumerator(uid, mapGrid, tile);
while (enumerator.MoveNext(out var ent)) while (enumerator.MoveNext(out var ent))
{ {
if (!_airtightQuery.TryComp(ent, out var airtight)) if (!_airtightQuery.TryComp(ent, out var airtight))
continue; continue;
var category = GetAssociatedEntityChunkType(ent.Value); var category = GetEntityType(ent.Value);
var data = chunk.EnsureType(category); if (category == NavMapChunkType.Invalid)
continue;
foreach (var direction in data.Keys) var directions = (int)airtight.AirBlockedDirection;
{ tileData |= directions << (int) category;
if ((direction & airtight.AirBlockedDirection) > 0)
{
data[direction] |= flag;
}
}
} }
// Remove walls that intersect with doors (unless they can both physically fit on the same tile) // Remove walls that intersect with doors (unless they can both physically fit on the same tile)
var wallData = chunk.TileData[(int) NavMapChunkType.Wall]; // TODO NAVMAP why can this even happen?
var airlockData = chunk.TileData[(int) NavMapChunkType.Airlock]; // Is this for blast-doors or something?
if (wallData != null && airlockData != null) // Shift airlock bits over to the wall bits
{ var shiftedAirlockBits = (tileData & AirlockMask) >> ((int) NavMapChunkType.Airlock - (int) NavMapChunkType.Wall);
foreach (var direction in wallData.Keys)
{
var airlockInvFlag = (ushort) ~airlockData[direction];
wallData[direction] &= airlockInvFlag;
}
}
return chunk; // And then mask door bits
tileData &= ~shiftedAirlockBits;
return (tileData, chunk);
} }
private bool PruneEmpty(Entity<NavMapComponent> entity, NavMapChunk chunk) private bool PruneEmpty(Entity<NavMapComponent> entity, NavMapChunk chunk)
{ {
for (var i = 0; i < NavMapComponent.Categories; i++) foreach (var val in chunk.TileData)
{ {
var data = chunk.TileData[i]; // TODO NAVMAP SIMD
if (val != 0)
if (data == null) return false;
continue;
foreach (var value in data.Values)
{
if (value != 0)
{
return false;
}
}
} }
entity.Comp.Chunks.Remove(chunk.Origin); entity.Comp.Chunks.Remove(chunk.Origin);
@@ -341,19 +326,12 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
if (!_navQuery.TryComp(xform.GridUid, out var navMap)) if (!_navQuery.TryComp(xform.GridUid, out var navMap))
return; return;
var netEnt = GetNetEntity(uid); var meta = MetaData(uid);
var oldBeacon = navMap.Beacons.FirstOrNull(x => x.NetEnt == netEnt); var changed = navMap.Beacons.Remove(meta.NetEntity);
var changed = false;
if (oldBeacon != null) if (TryCreateNavMapBeaconData(uid, component, xform, meta, out var beaconData))
{ {
navMap.Beacons.Remove(oldBeacon.Value); navMap.Beacons.Add(meta.NetEntity, beaconData.Value);
changed = true;
}
if (TryCreateNavMapBeaconData(uid, component, xform, out var beaconData))
{
navMap.Beacons.Add(beaconData.Value);
changed = true; changed = true;
} }

View File

@@ -1,7 +1,5 @@
using Content.Server.GameTicking.Rules.Components;
using Content.Server.NodeContainer; using Content.Server.NodeContainer;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.NodeContainer.Nodes; using Content.Server.NodeContainer.Nodes;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Power.Nodes; using Content.Server.Power.Nodes;
@@ -13,10 +11,8 @@ using Content.Shared.Power;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Map.Components; using Robust.Shared.Map.Components;
using Robust.Shared.Player;
using Robust.Shared.Utility; using Robust.Shared.Utility;
using System.Linq; using System.Linq;
using System.Diagnostics.CodeAnalysis;
using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Components;
namespace Content.Server.Power.EntitySystems; namespace Content.Server.Power.EntitySystems;
@@ -163,7 +159,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
allChunks = new(); allChunks = new();
var tile = _sharedMapSystem.LocalToTile(xform.GridUid.Value, grid, xform.Coordinates); 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)) if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
{ {
@@ -171,8 +167,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
allChunks[chunkOrigin] = chunk; allChunks[chunkOrigin] = chunk;
} }
var relative = SharedMapSystem.GetChunkRelative(tile, SharedNavMapSystem.ChunkSize); var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
var flag = SharedNavMapSystem.GetFlag(relative); var flag = GetFlag(relative);
if (args.Anchored) if (args.Anchored)
chunk.PowerCableData[(int) component.CableType] |= flag; chunk.PowerCableData[(int) component.CableType] |= flag;
@@ -884,7 +880,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
continue; continue;
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, entXform.Coordinates); 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)) if (!allChunks.TryGetValue(chunkOrigin, out var chunk))
{ {
@@ -892,8 +888,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
allChunks[chunkOrigin] = chunk; allChunks[chunkOrigin] = chunk;
} }
var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, SharedNavMapSystem.ChunkSize); var relative = SharedMapSystem.GetChunkRelative(tile.GridIndices, ChunkSize);
var flag = SharedNavMapSystem.GetFlag(relative); var flag = GetFlag(relative);
chunk.PowerCableData[(int) cable.CableType] |= flag; chunk.PowerCableData[(int) cable.CableType] |= flag;
} }
@@ -910,7 +906,7 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
var xform = Transform(ent); var xform = Transform(ent);
var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates); var tile = _sharedMapSystem.GetTileRef(gridUid, grid, xform.Coordinates);
var gridIndices = tile.GridIndices; 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)) if (!component.FocusChunks.TryGetValue(chunkOrigin, out var chunk))
{ {
@@ -918,8 +914,8 @@ internal sealed partial class PowerMonitoringConsoleSystem : SharedPowerMonitori
component.FocusChunks[chunkOrigin] = chunk; component.FocusChunks[chunkOrigin] = chunk;
} }
var relative = SharedMapSystem.GetChunkRelative(gridIndices, SharedNavMapSystem.ChunkSize); var relative = SharedMapSystem.GetChunkRelative(gridIndices, ChunkSize);
var flag = SharedNavMapSystem.GetFlag(relative); var flag = GetFlag(relative);
if (TryComp<CableComponent>(ent, out var cable)) if (TryComp<CableComponent>(ent, out var cable))
chunk.PowerCableData[(int) cable.CableType] |= flag; chunk.PowerCableData[(int) cable.CableType] |= flag;

View File

@@ -104,15 +104,14 @@ namespace Content.Shared.Atmos
{ {
return direction switch return direction switch
{ {
AtmosDirection.East => Angle.FromDegrees(90), AtmosDirection.South => Angle.Zero,
AtmosDirection.North => Angle.FromDegrees(180), AtmosDirection.East => new Angle(MathHelper.PiOver2),
AtmosDirection.West => Angle.FromDegrees(270), AtmosDirection.North => new Angle(Math.PI),
AtmosDirection.South => Angle.FromDegrees(0), AtmosDirection.West => new Angle(-MathHelper.PiOver2),
AtmosDirection.NorthEast => new Angle(Math.PI*3/4),
AtmosDirection.NorthEast => Angle.FromDegrees(135), AtmosDirection.NorthWest => new Angle(-Math.PI*3/4),
AtmosDirection.NorthWest => Angle.FromDegrees(205), AtmosDirection.SouthWest => new Angle(-MathHelper.PiOver4),
AtmosDirection.SouthWest => Angle.FromDegrees(315), AtmosDirection.SouthEast => new Angle(MathHelper.PiOver4),
AtmosDirection.SouthEast => Angle.FromDegrees(45),
_ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."), _ => throw new ArgumentOutOfRangeException(nameof(direction), $"It was {direction}."),
}; };

View File

@@ -12,8 +12,6 @@ namespace Content.Shared.Pinpointer;
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent]
public sealed partial class NavMapComponent : Component public sealed partial class NavMapComponent : Component
{ {
public const int Categories = 4;
/* /*
* Don't need DataFields as this can be reconstructed * Don't need DataFields as this can be reconstructed
*/ */
@@ -28,62 +26,37 @@ public sealed partial class NavMapComponent : Component
/// List of station beacons. /// List of station beacons.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public HashSet<SharedNavMapSystem.NavMapBeacon> Beacons = new(); public Dictionary<NetEntity, SharedNavMapSystem.NavMapBeacon> Beacons = new();
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed class NavMapChunk public sealed class NavMapChunk(Vector2i origin)
{ {
/// <summary> /// <summary>
/// The chunk origin /// The chunk origin
/// </summary> /// </summary>
public readonly Vector2i Origin; [ViewVariables]
public readonly Vector2i Origin = origin;
/// <summary> /// <summary>
/// Array with each entry corresponding to a <see cref="NavMapChunkType"/>. /// Array containing the chunk's data. The
/// 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> /// </summary>
public Dictionary<AtmosDirection, ushort>?[] TileData; [ViewVariables]
public int[] TileData = new int[SharedNavMapSystem.ArraySize];
/// <summary> /// <summary>
/// The last game tick that the chunk was updated /// The last game tick that the chunk was updated
/// </summary> /// </summary>
[NonSerialized] [NonSerialized]
public GameTick LastUpdate; public GameTick LastUpdate;
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];
if (data == null)
{
data = new Dictionary<AtmosDirection, ushort>()
{
[AtmosDirection.North] = 0,
[AtmosDirection.East] = 0,
[AtmosDirection.South] = 0,
[AtmosDirection.West] = 0,
};
TileData[(int) chunkType] = data;
}
return data;
}
} }
public enum NavMapChunkType : byte public enum NavMapChunkType : byte
{ {
Invalid, // Values represent bit shift offsets when retrieving data in the tile array.
Floor, Invalid = byte.MaxValue,
Wall, Floor = 0, // I believe floors have directional information for diagonal tiles?
Airlock, Wall = SharedNavMapSystem.Directions,
// Update the categories const if you update this. Airlock = 2 * SharedNavMapSystem.Directions,
} }

View File

@@ -1,6 +1,6 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Numerics; using System.Numerics;
using Content.Shared.Atmos; using System.Runtime.CompilerServices;
using Content.Shared.Tag; using Content.Shared.Tag;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -11,19 +11,22 @@ namespace Content.Shared.Pinpointer;
public abstract class SharedNavMapSystem : EntitySystem public abstract class SharedNavMapSystem : EntitySystem
{ {
[Dependency] private readonly TagSystem _tagSystem = default!; public const int Categories = 3;
[Dependency] private readonly IGameTiming _gameTiming = default!; 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 = public const int AllDirMask = (1 << Directions) - 1;
{ public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
NavMapChunkType.Invalid, public const int WallMask = AllDirMask << (int) NavMapChunkType.Wall;
NavMapChunkType.Wall, public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
NavMapChunkType.Airlock,
}; [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 readonly string[] _wallTags = ["Wall", "Window"];
private EntityQuery<NavMapDoorComponent> _doorQuery;
public override void Initialize() public override void Initialize()
{ {
@@ -31,112 +34,49 @@ public abstract class SharedNavMapSystem : EntitySystem
// Data handling events // Data handling events
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState); SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
_doorQuery = GetEntityQuery<NavMapDoorComponent>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetTileIndex(Vector2i relativeTile)
{
return relativeTile.X * ChunkSize + relativeTile.Y;
} }
/// <summary> /// <summary>
/// Converts the chunk's tile into a bitflag for the slot. /// Inverse of <see cref="GetTileIndex"/>
/// </summary> /// </summary>
public static int GetFlag(Vector2i relativeTile) [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector2i GetTileFromIndex(int index)
{ {
return 1 << (relativeTile.X * ChunkSize + relativeTile.Y); var x = index / ChunkSize;
} var y = index % ChunkSize;
/// <summary>
/// Converts the chunk's tile into a bitflag for the slot.
/// </summary>
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);
return new Vector2i(x, y); 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); if (_doorQuery.HasComp(uid))
var flag = (ushort) GetFlag(relative); return NavMapChunkType.Airlock;
var data = chunk.EnsureType(chunkType);
foreach (var direction in data.Keys) if (_tagSystem.HasAnyTag(uid, _wallTags))
{ return NavMapChunkType.Wall;
data[direction] |= flag;
} return NavMapChunkType.Invalid;
} }
public void UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType) protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, MetaDataComponent meta, [NotNullWhen(true)] out NavMapBeacon? beaconData)
{
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<AtmosDirection, ushort> tile)
{
ushort combined = 0;
foreach (var value in tile.Values)
{
combined |= value;
}
return combined;
}
public bool AllTileEdgesAreOccupied(Dictionary<AtmosDirection, ushort> 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<NavMapDoorComponent>(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)
{ {
beaconData = null; beaconData = null;
if (!component.Enabled || xform.GridUid == null || !xform.Anchored) if (!component.Enabled || xform.GridUid == null || !xform.Anchored)
return false; return false;
string? name = component.Text; var name = component.Text;
var meta = MetaData(uid);
if (string.IsNullOrEmpty(name)) if (string.IsNullOrEmpty(name))
name = meta.EntityName; name = meta.EntityName;
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition) beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition);
{
LastUpdate = _gameTiming.CurTick
};
return true; return true;
} }
@@ -145,91 +85,36 @@ public abstract class SharedNavMapSystem : EntitySystem
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args) private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
{ {
var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>(); Dictionary<Vector2i, int[]> chunks;
var beacons = new HashSet<NavMapBeacon>();
// Should this be a full component state or a delta-state? // Should this be a full component state or a delta-state?
if (args.FromTick <= component.CreationTick) if (args.FromTick <= component.CreationTick)
{ {
// Full state
chunks = new(component.Chunks.Count);
foreach (var (origin, chunk) in component.Chunks) foreach (var (origin, chunk) in component.Chunks)
{ {
var sentChunk = new Dictionary<AtmosDirection, ushort>[NavMapComponent.Categories]; chunks.Add(origin, chunk.TileData);
chunks.Add(origin, sentChunk);
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;
}
sentChunk[(int) value] = chunkDatum;
}
} }
var beaconQuery = AllEntityQuery<NavMapBeaconComponent, TransformComponent>(); args.State = new NavMapComponentState(chunks, component.Beacons);
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);
return; return;
} }
chunks = new();
foreach (var (origin, chunk) in component.Chunks) foreach (var (origin, chunk) in component.Chunks)
{ {
if (chunk.LastUpdate < args.FromTick) if (chunk.LastUpdate < args.FromTick)
continue; continue;
var sentChunk = new Dictionary<AtmosDirection, ushort>[NavMapComponent.Categories]; chunks.Add(origin, chunk.TileData);
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 data)
{
chunkDatum[direction] = tileData;
}
sentChunk[(int) value] = chunkDatum;
}
} }
foreach (var beacon in component.Beacons) args.State = new NavMapComponentState(chunks, component.Beacons)
{
if (beacon.LastUpdate < args.FromTick)
continue;
beacons.Add(beacon);
}
args.State = new NavMapComponentState(chunks, 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), AllChunks = new(component.Chunks.Keys),
AllBeacons = new(component.Beacons)
}; };
} }
@@ -238,22 +123,18 @@ public abstract class SharedNavMapSystem : EntitySystem
#region: System messages #region: System messages
[Serializable, NetSerializable] [Serializable, NetSerializable]
protected sealed class NavMapComponentState : ComponentState, IComponentDeltaState protected sealed class NavMapComponentState(
Dictionary<Vector2i, int[]> chunks,
Dictionary<NetEntity, NavMapBeacon> beacons)
: ComponentState, IComponentDeltaState
{ {
public Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]> Chunks = new(); public Dictionary<Vector2i, int[]> Chunks = chunks;
public HashSet<NavMapBeacon> Beacons = new(); public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
// Required to infer deleted/missing chunks for delta states // Required to infer deleted/missing chunks for delta states
public HashSet<Vector2i>? AllChunks; public HashSet<Vector2i>? AllChunks;
public HashSet<NavMapBeacon>? AllBeacons;
public NavMapComponentState(Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]> chunks, HashSet<NavMapBeacon> beacons) public bool FullState => AllChunks == null;
{
Chunks = chunks;
Beacons = beacons;
}
public bool FullState => (AllChunks == null || AllBeacons == null);
public void ApplyToFullState(IComponentState fullState) public void ApplyToFullState(IComponentState fullState)
{ {
@@ -261,32 +142,24 @@ public abstract class SharedNavMapSystem : EntitySystem
var state = (NavMapComponentState) fullState; var state = (NavMapComponentState) fullState;
DebugTools.Assert(state.FullState); DebugTools.Assert(state.FullState);
// Update chunks
foreach (var key in state.Chunks.Keys) foreach (var key in state.Chunks.Keys)
{ {
if (!AllChunks!.Contains(key)) if (!AllChunks!.Contains(key))
state.Chunks.Remove(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++) if (!state.Chunks.TryGetValue(index, out var stateValue))
{ state.Chunks[index] = stateValue = new int[data.Length];
var chunkData = data[i];
state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData); Array.Copy(data, stateValue, data.Length);
}
} }
// Update beacons state.Beacons.Clear();
foreach (var beacon in state.Beacons) foreach (var (nuid, beacon) in Beacons)
{ {
if (!AllBeacons!.Contains(beacon)) state.Beacons.Add(nuid, beacon);
state.Beacons.Remove(beacon);
}
foreach (var beacon in Beacons)
{
state.Beacons.Add(beacon);
} }
} }
@@ -296,56 +169,26 @@ public abstract class SharedNavMapSystem : EntitySystem
var state = (NavMapComponentState) fullState; var state = (NavMapComponentState) fullState;
DebugTools.Assert(state.FullState); DebugTools.Assert(state.FullState);
var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>(); var chunks = new Dictionary<Vector2i, int[]>(state.Chunks.Count);
var beacons = new HashSet<NavMapBeacon>(); foreach (var (index, data) in state.Chunks)
foreach (var (chunk, data) in Chunks)
{ {
for (var i = 0; i < NavMapComponent.Categories; i++) if (!AllChunks!.Contains(index))
{ continue;
var chunkData = data[i];
state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData); 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) return new NavMapComponentState(chunks, new(Beacons));
{
if (AllChunks!.Contains(chunk))
{
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);
} }
} }
[Serializable, NetSerializable] [Serializable, NetSerializable]
public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position) public record struct NavMapBeacon(NetEntity NetEnt, Color Color, string Text, Vector2 Position);
{
public GameTick LastUpdate;
}
#endregion #endregion
} }

View File

@@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using JetBrains.Annotations; using JetBrains.Annotations;
namespace Content.Shared.Power; namespace Content.Shared.Power;
@@ -5,4 +6,23 @@ namespace Content.Shared.Power;
[UsedImplicitly] [UsedImplicitly]
public abstract class SharedPowerMonitoringConsoleSystem : EntitySystem 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;
/// <summary>
/// Converts the chunk's tile into a bitflag for the slot.
/// </summary>
[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);
}
} }

View File

@@ -541,7 +541,15 @@ public sealed class TagSystem : EntitySystem
/// </exception> /// </exception>
public bool HasAnyTag(TagComponent component, params string[] ids) 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;
} }