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