diff --git a/Content.Server/NPC/Pathfinding/GridPathfindingChunk.cs b/Content.Server/NPC/Pathfinding/GridPathfindingChunk.cs index 30da7cee1d..71b82c9f7a 100644 --- a/Content.Server/NPC/Pathfinding/GridPathfindingChunk.cs +++ b/Content.Server/NPC/Pathfinding/GridPathfindingChunk.cs @@ -13,6 +13,11 @@ public sealed class GridPathfindingChunk public readonly List[] Polygons = new List[SharedPathfindingSystem.ChunkSize * SharedPathfindingSystem.ChunkSize]; + /// + /// Store the recalculated polygons to know what needs changing. + /// + internal readonly List[] BufferPolygons = new List[SharedPathfindingSystem.ChunkSize * SharedPathfindingSystem.ChunkSize]; + /// /// The relevant polygon for this chunk's portals /// @@ -28,6 +33,7 @@ public sealed class GridPathfindingChunk for (var x = 0; x < Polygons.Length; x++) { Polygons[x] = new List(); + BufferPolygons[x] = new List(); } } } diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs index 422374d785..4a7ada4f71 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.Grid.cs @@ -84,6 +84,10 @@ public sealed partial class PathfindingSystem var updateCount = 0; #endif _stopwatch.Restart(); + var options = new ParallelOptions() + { + MaxDegreeOfParallelism = _parallel.ParallelProcessCount, + }; // We defer chunk updates because rebuilding a navmesh is hella costly // If we're paused then NPCs can't run anyway. @@ -132,7 +136,7 @@ public sealed partial class PathfindingSystem // This is for map <> grid pathfinding // Without parallel this is roughly 3x slower on my desktop. - for (var i = 0; i < dirt.Length; i++) + Parallel.For(0, dirt.Length, options, i => { // Doing the queries per task seems faster. var accessQuery = GetEntityQuery(); @@ -141,8 +145,9 @@ public sealed partial class PathfindingSystem var fixturesQuery = GetEntityQuery(); var physicsQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); - BuildBreadcrumbs(dirt[i], mapGridComp, accessQuery, destructibleQuery, doorQuery, fixturesQuery, physicsQuery, xformQuery); - } + BuildBreadcrumbs(dirt[i], mapGridComp, accessQuery, destructibleQuery, doorQuery, fixturesQuery, + physicsQuery, xformQuery); + }); const int Division = 4; @@ -159,7 +164,7 @@ public sealed partial class PathfindingSystem { var it1 = it; - Parallel.For(0, dirt.Length, j => + Parallel.For(0, dirt.Length, options, j => { var chunk = dirt[j]; // Check if the chunk is safe on this iteration. @@ -375,13 +380,11 @@ public sealed partial class PathfindingSystem var points = chunk.Points; var gridOrigin = chunk.Origin * ChunkSize; var tileEntities = new ValueList(); - - // TODO: Pool this or something - var chunkPolys = new List[ChunkSize * ChunkSize]; + var chunkPolys = chunk.BufferPolygons; for (var i = 0; i < chunkPolys.Length; i++) { - chunkPolys[i] = new List(); + chunkPolys[i].Clear(); } var tilePolys = new ValueList(SubStep); @@ -577,43 +580,6 @@ public sealed partial class PathfindingSystem } } - // Check if the tiles match - for (var x = 0; x < ChunkSize; x++) - { - for (var y = 0; y < ChunkSize; y++) - { - var index = x * ChunkSize + y; - var polys = chunkPolys[index]; - var existing = chunk.Polygons[index]; - - var isEquivalent = true; - - if (polys.Count == existing.Count) - { - // May want to update damage or the likes if it's different but not invalidate the ref. - for (var i = 0; i < existing.Count; i++) - { - var ePoly = existing[i]; - var poly = polys[i]; - - if (!ePoly.IsEquivalent(poly)) - { - isEquivalent = false; - break; - } - - ePoly.Data.Damage = poly.Data.Damage; - } - - if (isEquivalent) - continue; - } - - ClearTilePolys(existing); - existing.AddRange(polys); - } - } - // _sawmill.Debug($"Built breadcrumbs in {sw.Elapsed.TotalMilliseconds}ms"); SendBreadcrumbs(chunk, grid.GridEntityId); } @@ -650,7 +616,48 @@ public sealed partial class PathfindingSystem { var sw = new Stopwatch(); sw.Start(); + + // After the breadcrumbs step need to determine which polygons need rebuilding. Can't do this above + // as we are tampering with neighbor nodes. var chunkPolys = chunk.Polygons; + var bufferPolygons = chunk.BufferPolygons; + + for (var x = 0; x < ChunkSize; x++) + { + for (var y = 0; y < ChunkSize; y++) + { + var index = x * ChunkSize + y; + var polys = bufferPolygons[index]; + var existing = chunkPolys[index]; + + var isEquivalent = true; + + if (polys.Count == existing.Count) + { + // May want to update damage or the likes if it's different but not invalidate the ref. + for (var i = 0; i < existing.Count; i++) + { + var ePoly = existing[i]; + var poly = polys[i]; + + if (!ePoly.IsEquivalent(poly)) + { + isEquivalent = false; + break; + } + + ePoly.Data.Damage = poly.Data.Damage; + } + + if (isEquivalent) + continue; + } + + ClearTilePolys(existing); + existing.AddRange(polys); + } + } + component.Chunks.TryGetValue(chunk.Origin + new Vector2i(-1, 0), out var leftChunk); component.Chunks.TryGetValue(chunk.Origin + new Vector2i(0, -1), out var bottomChunk); component.Chunks.TryGetValue(chunk.Origin + new Vector2i(1, 0), out var rightChunk); diff --git a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs index 4f2e22bcb3..498190fe10 100644 --- a/Content.Server/NPC/Pathfinding/PathfindingSystem.cs +++ b/Content.Server/NPC/Pathfinding/PathfindingSystem.cs @@ -15,6 +15,7 @@ using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; using Robust.Shared.Players; using Robust.Shared.Random; +using Robust.Shared.Threading; using Robust.Shared.Timing; using Robust.Shared.Utility; @@ -37,6 +38,7 @@ namespace Content.Server.NPC.Pathfinding [Dependency] private readonly IAdminManager _adminManager = default!; [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IParallelManager _parallel = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly DestructibleSystem _destructible = default!;