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 <leonsfriedrich@gmail.com>
This commit is contained in:
@@ -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<AirtightComponent> _airtightQuery;
|
||||
private EntityQuery<MapGridComponent> _gridQuery;
|
||||
private EntityQuery<NavMapComponent> _navQuery;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
_gridQuery = GetEntityQuery<MapGridComponent>();
|
||||
_navQuery = GetEntityQuery<NavMapComponent>();
|
||||
|
||||
// Initialization events
|
||||
SubscribeLocalEvent<StationGridAddedEvent>(OnStationInit);
|
||||
|
||||
@@ -43,8 +55,7 @@ public sealed partial class NavMapSystem : SharedNavMapSystem
|
||||
SubscribeLocalEvent<GridSplitEvent>(OnNavMapSplit);
|
||||
SubscribeLocalEvent<TileChangedEvent>(OnTileChanged);
|
||||
|
||||
// Airtight structure change event
|
||||
SubscribeLocalEvent<AirtightChanged>(OnAirtightChanged);
|
||||
SubscribeLocalEvent<AirtightChanged>(OnAirtightChange);
|
||||
|
||||
// Beacon events
|
||||
SubscribeLocalEvent<NavMapBeaconComponent, MapInitEvent>(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<MapGridComponent>();
|
||||
|
||||
foreach (var grid in args.NewGrids)
|
||||
{
|
||||
var newComp = EnsureComp<NavMapComponent>(grid);
|
||||
RefreshGrid(grid, newComp, gridQuery.GetComponent(grid));
|
||||
RefreshGrid(grid, newComp, _gridQuery.GetComponent(grid));
|
||||
}
|
||||
|
||||
RefreshGrid(args.Grid, comp, gridQuery.GetComponent(args.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;
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private void OnTileChanged(ref TileChangedEvent ev)
|
||||
{
|
||||
if (!TryComp<NavMapComponent>(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);
|
||||
SetAllEdgesForChunkTile(chunk, tile, NavMapChunkType.Floor);
|
||||
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
navMap.Chunks[(NavMapChunkType.Floor, chunkOrigin)] = chunk;
|
||||
|
||||
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 (!TryComp<NavMapComponent>(gridUid, out var navMap) ||
|
||||
!TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
||||
if (args.AirBlockedChanged)
|
||||
return;
|
||||
|
||||
// Refresh the affected tile
|
||||
var tile = ev.Position.Tile;
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(tile, ChunkSize);
|
||||
var gridUid = args.Position.Grid;
|
||||
|
||||
RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, tile);
|
||||
|
||||
// Update potentially affected chunks
|
||||
foreach (var category in EntityChunkTypes)
|
||||
if (!_navQuery.TryComp(gridUid, out var navMap) ||
|
||||
!_gridQuery.TryComp(gridUid, out var mapGrid))
|
||||
{
|
||||
if (!navMap.Chunks.TryGetValue((category, chunkOrigin), out var chunk))
|
||||
continue;
|
||||
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
navMap.Chunks[(category, chunkOrigin)] = chunk;
|
||||
return;
|
||||
}
|
||||
|
||||
Dirty(gridUid, navMap);
|
||||
// Refresh the affected tile
|
||||
var chunkOrigin = SharedMapSystem.GetChunkIndices(args.Position.Tile, ChunkSize);
|
||||
|
||||
var chunk = RefreshTileEntityContents(gridUid, navMap, mapGrid, chunkOrigin, args.Position.Tile);
|
||||
if (!PruneEmpty((gridUid, navMap), chunk))
|
||||
{
|
||||
chunk.LastUpdate = _gameTiming.CurTick;
|
||||
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);
|
||||
var data = chunk.EnsureType(category);
|
||||
|
||||
foreach (var (direction, _) in chunk.TileData)
|
||||
chunk.TileData[direction] &= invFlag;
|
||||
|
||||
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<AirtightComponent>(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))
|
||||
{
|
||||
foreach (var (direction, _) in wallChunk.TileData)
|
||||
{
|
||||
var airlockInvFlag = (ushort) ~airlockChunk.TileData[direction];
|
||||
wallChunk.TileData[direction] &= airlockInvFlag;
|
||||
}
|
||||
var wallData = chunk.TileData[(int) NavMapChunkType.Wall];
|
||||
var airlockData = chunk.TileData[(int) NavMapChunkType.Airlock];
|
||||
|
||||
wallChunk.LastUpdate = _gameTiming.CurTick;
|
||||
component.Chunks[(NavMapChunkType.Wall, chunkOrigin)] = wallChunk;
|
||||
if (wallData != null && airlockData != null)
|
||||
{
|
||||
foreach (var direction in wallData.Keys)
|
||||
{
|
||||
var airlockInvFlag = (ushort) ~airlockData[direction];
|
||||
wallData[direction] &= airlockInvFlag;
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private bool PruneEmpty(Entity<NavMapComponent> entity, NavMapChunk chunk)
|
||||
{
|
||||
for (var i = 0; i < NavMapComponent.Categories; i++)
|
||||
{
|
||||
var data = chunk.TileData[i];
|
||||
|
||||
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<NavMapComponent>(xform.GridUid, out var navMap))
|
||||
if (!_navQuery.TryComp(xform.GridUid, out var navMap))
|
||||
return;
|
||||
|
||||
var netEnt = GetNetEntity(uid);
|
||||
|
||||
Reference in New Issue
Block a user