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:
@@ -1,6 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Numerics;
|
||||
using Content.Shared.Atmos;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Content.Shared.Tag;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
@@ -11,19 +11,22 @@ namespace Content.Shared.Pinpointer;
|
||||
|
||||
public abstract class SharedNavMapSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
public const int Categories = 3;
|
||||
public const int Directions = 4; // Not directly tied to number of atmos directions
|
||||
|
||||
public const byte ChunkSize = 4;
|
||||
public const int ChunkSize = 8;
|
||||
public const int ArraySize = ChunkSize* ChunkSize;
|
||||
|
||||
public readonly NavMapChunkType[] EntityChunkTypes =
|
||||
{
|
||||
NavMapChunkType.Invalid,
|
||||
NavMapChunkType.Wall,
|
||||
NavMapChunkType.Airlock,
|
||||
};
|
||||
public const int AllDirMask = (1 << Directions) - 1;
|
||||
public const int AirlockMask = AllDirMask << (int) NavMapChunkType.Airlock;
|
||||
public const int WallMask = AllDirMask << (int) NavMapChunkType.Wall;
|
||||
public const int FloorMask = AllDirMask << (int) NavMapChunkType.Floor;
|
||||
|
||||
[Robust.Shared.IoC.Dependency] private readonly TagSystem _tagSystem = default!;
|
||||
[Robust.Shared.IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
|
||||
private readonly string[] _wallTags = ["Wall", "Window"];
|
||||
private EntityQuery<NavMapDoorComponent> _doorQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -31,112 +34,49 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
|
||||
// Data handling events
|
||||
SubscribeLocalEvent<NavMapComponent, ComponentGetState>(OnGetState);
|
||||
_doorQuery = GetEntityQuery<NavMapDoorComponent>();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetTileIndex(Vector2i relativeTile)
|
||||
{
|
||||
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 int GetFlag(Vector2i relativeTile)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Vector2i GetTileFromIndex(int index)
|
||||
{
|
||||
return 1 << (relativeTile.X * ChunkSize + relativeTile.Y);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
|
||||
var x = index / ChunkSize;
|
||||
var y = index % ChunkSize;
|
||||
return new Vector2i(x, y);
|
||||
}
|
||||
|
||||
public void SetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType)
|
||||
public NavMapChunkType GetEntityType(EntityUid uid)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
var flag = (ushort) GetFlag(relative);
|
||||
var data = chunk.EnsureType(chunkType);
|
||||
if (_doorQuery.HasComp(uid))
|
||||
return NavMapChunkType.Airlock;
|
||||
|
||||
foreach (var direction in data.Keys)
|
||||
{
|
||||
data[direction] |= flag;
|
||||
}
|
||||
if (_tagSystem.HasAnyTag(uid, _wallTags))
|
||||
return NavMapChunkType.Wall;
|
||||
|
||||
return NavMapChunkType.Invalid;
|
||||
}
|
||||
|
||||
public void UnsetAllEdgesForChunkTile(NavMapChunk chunk, Vector2i tile, NavMapChunkType chunkType)
|
||||
{
|
||||
var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
|
||||
var flag = (ushort) GetFlag(relative);
|
||||
var invFlag = (ushort) ~flag;
|
||||
|
||||
var data = chunk.EnsureType(chunkType);
|
||||
|
||||
foreach (var direction in data.Keys)
|
||||
{
|
||||
data[direction] &= invFlag;
|
||||
}
|
||||
}
|
||||
|
||||
public ushort GetCombinedEdgesForChunk(Dictionary<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)
|
||||
protected bool TryCreateNavMapBeaconData(EntityUid uid, NavMapBeaconComponent component, TransformComponent xform, MetaDataComponent meta, [NotNullWhen(true)] out NavMapBeacon? beaconData)
|
||||
{
|
||||
beaconData = null;
|
||||
|
||||
if (!component.Enabled || xform.GridUid == null || !xform.Anchored)
|
||||
return false;
|
||||
|
||||
string? name = component.Text;
|
||||
var meta = MetaData(uid);
|
||||
|
||||
var name = component.Text;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
name = meta.EntityName;
|
||||
|
||||
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition)
|
||||
{
|
||||
LastUpdate = _gameTiming.CurTick
|
||||
};
|
||||
beaconData = new NavMapBeacon(meta.NetEntity, component.Color, name, xform.LocalPosition);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -145,91 +85,36 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
|
||||
private void OnGetState(EntityUid uid, NavMapComponent component, ref ComponentGetState args)
|
||||
{
|
||||
var chunks = new Dictionary<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;
|
||||
}
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
chunks.Add(origin, chunk.TileData);
|
||||
}
|
||||
|
||||
foreach (var beacon in component.Beacons)
|
||||
{
|
||||
if (beacon.LastUpdate < args.FromTick)
|
||||
continue;
|
||||
|
||||
beacons.Add(beacon);
|
||||
}
|
||||
|
||||
args.State = new NavMapComponentState(chunks, beacons)
|
||||
args.State = new NavMapComponentState(chunks, component.Beacons)
|
||||
{
|
||||
// TODO NAVMAP cache a single AllChunks hashset in the component.
|
||||
// Or maybe just only send them if a chunk gets removed.
|
||||
AllChunks = new(component.Chunks.Keys),
|
||||
AllBeacons = new(component.Beacons)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -238,22 +123,18 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
#region: System messages
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
protected sealed class NavMapComponentState : ComponentState, IComponentDeltaState
|
||||
protected sealed class NavMapComponentState(
|
||||
Dictionary<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)
|
||||
{
|
||||
@@ -261,32 +142,24 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
var state = (NavMapComponentState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
// Update chunks
|
||||
foreach (var key in state.Chunks.Keys)
|
||||
{
|
||||
if (!AllChunks!.Contains(key))
|
||||
state.Chunks.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (chunk, data) in Chunks)
|
||||
foreach (var (index, data) in Chunks)
|
||||
{
|
||||
for (var i = 0; i < NavMapComponent.Categories; i++)
|
||||
{
|
||||
var chunkData = data[i];
|
||||
state.Chunks[chunk][i] = chunkData == null ? chunkData : new(chunkData);
|
||||
}
|
||||
if (!state.Chunks.TryGetValue(index, out var stateValue))
|
||||
state.Chunks[index] = stateValue = new int[data.Length];
|
||||
|
||||
Array.Copy(data, stateValue, data.Length);
|
||||
}
|
||||
|
||||
// Update beacons
|
||||
foreach (var beacon in state.Beacons)
|
||||
state.Beacons.Clear();
|
||||
foreach (var (nuid, beacon) in Beacons)
|
||||
{
|
||||
if (!AllBeacons!.Contains(beacon))
|
||||
state.Beacons.Remove(beacon);
|
||||
}
|
||||
|
||||
foreach (var beacon in Beacons)
|
||||
{
|
||||
state.Beacons.Add(beacon);
|
||||
state.Beacons.Add(nuid, beacon);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,56 +169,26 @@ public abstract class SharedNavMapSystem : EntitySystem
|
||||
var state = (NavMapComponentState) fullState;
|
||||
DebugTools.Assert(state.FullState);
|
||||
|
||||
var chunks = new Dictionary<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);
|
||||
}
|
||||
if (!AllChunks!.Contains(index))
|
||||
continue;
|
||||
|
||||
var newData = chunks[index] = new int[ArraySize];
|
||||
|
||||
if (Chunks.TryGetValue(index, out var updatedData))
|
||||
Array.Copy(newData, updatedData, ArraySize);
|
||||
else
|
||||
Array.Copy(newData, data, ArraySize);
|
||||
}
|
||||
|
||||
foreach (var (chunk, data) in state.Chunks)
|
||||
{
|
||||
if (AllChunks!.Contains(chunk))
|
||||
{
|
||||
var copied = new Dictionary<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);
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user