-using Content.Shared.Atmos;
using Content.Shared.Pinpointer;
using Robust.Shared.GameStates;
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
{
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;
}
}
}
using JetBrains.Annotations;
using Content.Shared.Atmos;
using System.Linq;
+using Robust.Shared.Utility;
namespace Content.Client.Pinpointer.UI;
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<Vector2i, Vector2i> _horizLines = new();
+ private Dictionary<Vector2i, Vector2i> _horizLinesReversed = new();
+ private Dictionary<Vector2i, Vector2i> _vertLines = new();
+ private Dictionary<Vector2i, Vector2i> _vertLinesReversed = new();
// Components
private NavMapComponent? _navMap;
var fontSize = (int) Math.Round(1 / WorldRange * DefaultDisplayedRange * UIScale * _targetFontsize, 0);
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;
position = ScalePosition(position with { Y = -position.Y });
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];
+ AddRectForThinWall(tileData, tile);
+ continue;
+ }
- 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;
- }
+ tile = tile with { Y = -tile.Y };
+ NavMapChunk? neighborChunk;
- if (!neighbor)
- {
- AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
- tile + new Vector2i(_grid.TileSize, 0), VertLinesLookup, VertLinesLookupReversed);
- }
+ // 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];
- // South edge
- if (relativeTile.Y == 0)
- {
- _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Down, out neighborChunk);
- var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
+ if ((neighborData & southMask) == 0)
+ {
+ AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize),
+ tile + new Vector2i(_grid.TileSize, -_grid.TileSize), _horizLines,
+ _horizLinesReversed);
+ }
- 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;
- }
+ // 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 (!neighbor)
- {
- AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), HorizLinesLookup,
- HorizLinesLookupReversed);
- }
+ if ((neighborData & westMask) == 0)
+ {
+ AddOrUpdateNavMapLine(tile + new Vector2i(_grid.TileSize, -_grid.TileSize),
+ tile + new Vector2i(_grid.TileSize, 0), _vertLines, _vertLinesReversed);
+ }
- // West edge
- if (relativeTile.X == 0)
- {
- _navMap.Chunks.TryGetValue(chunkOrigin + Vector2i.Left, out neighborChunk);
- var neighborData = neighborChunk?.TileData[(int) NavMapChunkType.Wall];
+ // 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];
- 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 ((neighborData & northMask) == 0)
+ {
+ AddOrUpdateNavMapLine(tile, tile + new Vector2i(_grid.TileSize, 0), _horizLines,
+ _horizLinesReversed);
+ }
- if (!neighbor)
- {
- AddOrUpdateNavMapLine(tile + new Vector2i(0, -_grid.TileSize), tile, VertLinesLookup,
- VertLinesLookupReversed);
- }
+ // 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];
- // 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)));
+ 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));
}
}
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;
}
}
}
- private void AddRectForThinWall(Dictionary<AtmosDirection, ushort> 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<AtmosDirection, ushort> 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<Vector2i, Vector2i> lookup,
+ Dictionary<Vector2i, Vector2i> 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));
- }
-
- else if (lookup.TryGetValue((index, terminus), out foundTermiusTuple))
- {
- lookup[(index, origin)] = foundTermiusTuple;
- lookup.Remove((index, terminus));
- lookupReversed[foundTermiusTuple] = (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 (lookupReversed.TryGetValue((index, origin), out foundOriginTuple))
+ // Does our new line start at the end of an existing line?
+ if (lookupReversed.Remove(origin, out foundOrigin))
{
- lookupReversed[(index, terminus)] = foundOriginTuple;
- lookupReversed.Remove(foundOriginTuple);
- lookup[foundOriginTuple] = (index, terminus);
+ // 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
- {
- 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()
using Robust.Shared.Collections;
using Robust.Shared.Map.Components;
using System.Numerics;
+using static Content.Shared.Power.SharedPowerMonitoringConsoleSystem;
namespace Content.Client.Power;
public List<PowerMonitoringConsoleLine> PowerCableNetwork = 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;
public PowerMonitoringConsoleNavMapControl() : base()
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;
// 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);
}
}
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;
}
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;
{
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>();
_gridQuery = GetEntityQuery<MapGridComponent>();
_navQuery = GetEntityQuery<NavMapComponent>();
SubscribeLocalEvent<ConfigurableNavMapBeaconComponent, ExaminedEvent>(OnConfigurableExamined);
}
- #region: Initialization event handling
private void OnStationInit(StationGridAddedEvent ev)
{
var comp = EnsureComp<NavMapComponent>(ev.GridId);
RefreshGrid(ev.GridId, comp, Comp<MapGridComponent>(ev.GridId));
}
- #endregion
-
#region: Grid change event handling
private void OnNavMapSplit(ref GridSplitEvent args)
var chunk = EnsureChunk(navMap, chunkOrigin);
// This could be easily replaced in the future to accommodate diagonal tiles
+ var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
+ ref var tileData = ref chunk.TileData[GetTileIndex(relative)];
+
if (ev.NewTile.IsSpace(_tileDefManager))
- UnsetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
+ {
+ tileData = 0;
+ if (PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
+ return;
+ }
else
- SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
-
- if (!PruneEmpty((ev.NewTile.GridUid, navMap), chunk))
{
- chunk.LastUpdate = _gameTiming.CurTick;
- Dirty(ev.NewTile.GridUid, navMap);
+ 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)
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
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);
-
- foreach (var direction in data.Keys)
- {
- data[direction] &= invFlag;
- }
- }
+ // Clear all data except for floor bits
+ if (setFloor)
+ tileData = FloorMask;
+ else
+ tileData &= FloorMask;
- // 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<NavMapComponent> 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);
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;
-
- if (oldBeacon != null)
- {
- navMap.Beacons.Remove(oldBeacon.Value);
- changed = true;
- }
+ var meta = MetaData(uid);
+ var changed = navMap.Beacons.Remove(meta.NetEntity);
- if (TryCreateNavMapBeaconData(uid, component, xform, out var beaconData))
+ if (TryCreateNavMapBeaconData(uid, component, xform, meta, out var beaconData))
{
- navMap.Beacons.Add(beaconData.Value);
+ navMap.Beacons.Add(meta.NetEntity, beaconData.Value);
changed = true;
}
-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;
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;
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))
{
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;
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))
{
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;
}
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))
{
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<CableComponent>(ent, out var cable))
chunk.PowerCableData[(int) cable.CableType] |= flag;
{
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}."),
};
[RegisterComponent, NetworkedComponent]
public sealed partial class NavMapComponent : Component
{
- public const int Categories = 4;
-
/*
* Don't need DataFields as this can be reconstructed
*/
/// List of station beacons.
/// </summary>
[ViewVariables]
- public HashSet<SharedNavMapSystem.NavMapBeacon> Beacons = new();
+ public Dictionary<NetEntity, SharedNavMapSystem.NavMapBeacon> Beacons = new();
}
[Serializable, NetSerializable]
-public sealed class NavMapChunk
+public sealed class NavMapChunk(Vector2i origin)
{
/// <summary>
/// The chunk origin
/// </summary>
- public readonly Vector2i Origin;
+ [ViewVariables]
+ public readonly Vector2i Origin = origin;
/// <summary>
- /// Array with each entry corresponding to a <see cref="NavMapChunkType"/>.
- /// Uses a bitmask for tiles, 1 for occupied and 0 for empty. There is a bitmask for each cardinal direction,
- /// representing each edge of the tile, in case the entities inside it do not entirely fill it
+ /// Array containing the chunk's data. The
/// </summary>
- public Dictionary<AtmosDirection, ushort>?[] TileData;
+ [ViewVariables]
+ public int[] TileData = new int[SharedNavMapSystem.ArraySize];
/// <summary>
/// The last game tick that the chunk was updated
/// </summary>
[NonSerialized]
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
{
- 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,
}
+
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;
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<NavMapDoorComponent> _doorQuery;
public override void Initialize()
{
// Data handling events
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
+ _doorQuery = GetEntityQuery<NavMapDoorComponent>();
}
- /// <summary>
- /// Converts the chunk's tile into a bitflag for the slot.
- /// </summary>
- public static int GetFlag(Vector2i relativeTile)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static int GetTileIndex(Vector2i relativeTile)
{
- return 1 << (relativeTile.X * ChunkSize + relativeTile.Y);
+ return relativeTile.X * ChunkSize + relativeTile.Y;
}
/// <summary>
- /// Converts the chunk's tile into a bitflag for the slot.
+ /// Inverse of <see cref="GetTileIndex"/>
/// </summary>
- public static Vector2i GetTile(int flag)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static Vector2i GetTileFromIndex(int index)
{
- 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)
- {
- var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
- var flag = (ushort) GetFlag(relative);
- var data = chunk.EnsureType(chunkType);
-
- foreach (var direction in data.Keys)
- {
- data[direction] |= flag;
- }
- }
-
- 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<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)
+ public NavMapChunkType GetEntityType(EntityUid uid)
{
- var category = NavMapChunkType.Invalid;
-
- if (HasComp<NavMapDoorComponent>(uid))
- category = NavMapChunkType.Airlock;
+ if (_doorQuery.HasComp(uid))
+ return NavMapChunkType.Airlock;
- else if (_tagSystem.HasAnyTag(uid, _wallTags))
- category = NavMapChunkType.Wall;
+ if (_tagSystem.HasAnyTag(uid, _wallTags))
+ return NavMapChunkType.Wall;
- return category;
+ return NavMapChunkType.Invalid;
}
- 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;
}
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
{
- var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>();
- var beacons = new HashSet<NavMapBeacon>();
+ Dictionary<Vector2i, int[]> 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<AtmosDirection, ushort>[NavMapComponent.Categories];
- 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>();
-
- 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);
+ chunks.Add(origin, chunk.TileData);
}
- 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<AtmosDirection, ushort>[NavMapComponent.Categories];
- chunks.Add(origin, sentChunk);
-
- foreach (var value in Enum.GetValues<NavMapChunkType>())
- {
- ref var data = ref chunk.TileData[(int) value];
-
- // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
- if (data == null)
- continue;
-
- var chunkDatum = new Dictionary<AtmosDirection, ushort>(data.Count);
-
- foreach (var (direction, tileData) in data)
- {
- chunkDatum[direction] = tileData;
- }
-
- sentChunk[(int) value] = chunkDatum;
- }
- }
-
- foreach (var beacon in component.Beacons)
- {
- if (beacon.LastUpdate < args.FromTick)
- continue;
-
- beacons.Add(beacon);
+ chunks.Add(origin, chunk.TileData);
}
- 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)
};
}
#region: System messages
[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 HashSet<NavMapBeacon> Beacons = new();
+ public Dictionary<Vector2i, int[]> Chunks = chunks;
+ public Dictionary<NetEntity, NavMapBeacon> Beacons = beacons;
// Required to infer deleted/missing chunks for delta states
public HashSet<Vector2i>? AllChunks;
- public HashSet<NavMapBeacon>? AllBeacons;
-
- public NavMapComponentState(Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]> chunks, HashSet<NavMapBeacon> beacons)
- {
- Chunks = chunks;
- Beacons = beacons;
- }
- public bool FullState => (AllChunks == null || AllBeacons == null);
+ public bool FullState => AllChunks == null;
public void ApplyToFullState(IComponentState fullState)
{
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];
- // Update beacons
- foreach (var beacon in state.Beacons)
- {
- if (!AllBeacons!.Contains(beacon))
- state.Beacons.Remove(beacon);
+ Array.Copy(data, stateValue, data.Length);
}
- foreach (var beacon in Beacons)
+ state.Beacons.Clear();
+ foreach (var (nuid, beacon) in Beacons)
{
- state.Beacons.Add(beacon);
+ state.Beacons.Add(nuid, beacon);
}
}
var state = (NavMapComponentState) fullState;
DebugTools.Assert(state.FullState);
- var chunks = new Dictionary<Vector2i, Dictionary<AtmosDirection, ushort>?[]>();
- var beacons = new HashSet<NavMapBeacon>();
-
- foreach (var (chunk, data) in Chunks)
+ var chunks = new Dictionary<Vector2i, int[]>(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);
- }
- }
-
- foreach (var (chunk, data) in state.Chunks)
- {
- 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);
- }
- }
+ if (!AllChunks!.Contains(index))
+ continue;
- foreach (var beacon in Beacons)
- {
- beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
- }
+ var newData = chunks[index] = new int[ArraySize];
- foreach (var beacon in state.Beacons)
- {
- if (AllBeacons!.Contains(beacon))
- {
- beacons.Add(new NavMapBeacon(beacon.NetEnt, beacon.Color, beacon.Text, beacon.Position));
- }
+ if (Chunks.TryGetValue(index, out var updatedData))
+ Array.Copy(newData, updatedData, ArraySize);
+ else
+ Array.Copy(newData, data, ArraySize);
}
- 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
}
+using System.Runtime.CompilerServices;
using JetBrains.Annotations;
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;
+
+ /// <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);
+ }
}
/// </exception>
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;
}