From 795e414663cdae368848fd1ca319d5aafd52a86b Mon Sep 17 00:00:00 2001 From: wraitii Date: Mon, 13 May 2019 17:02:16 +0000 Subject: [PATCH] Hierarchical pathfinder: Speed up edge update Speed up edge update by being clever, only updating dirty tiles instead of everything (effectively doing to "TODO be clever" here). This is a substantial speed improvement when an update is necessary. Differential Revision: https://code.wildfiregames.com/D1834 This was SVN commit r22279. --- .../helpers/HierarchicalPathfinder.cpp | 150 ++++++++++-------- .../helpers/HierarchicalPathfinder.h | 4 +- 2 files changed, 86 insertions(+), 68 deletions(-) diff --git a/source/simulation2/helpers/HierarchicalPathfinder.cpp b/source/simulation2/helpers/HierarchicalPathfinder.cpp index 3325956854..cd0382f727 100644 --- a/source/simulation2/helpers/HierarchicalPathfinder.cpp +++ b/source/simulation2/helpers/HierarchicalPathfinder.cpp @@ -395,16 +395,9 @@ void HierarchicalPathfinder::Recompute(Grid* grid, } // Construct the search graph over the regions - EdgesMap& edges = m_Edges[passClass]; + RecomputeAllEdges(passClass, edges); - for (int cj = 0; cj < m_ChunksH; ++cj) - { - for (int ci = 0; ci < m_ChunksW; ++ci) - { - FindEdges(ci, cj, passClass, edges); - } - } } if (m_DebugOverlay) @@ -418,11 +411,17 @@ void HierarchicalPathfinder::Update(Grid* grid, const Grid& dir { PROFILE3("Hierarchical Update"); - for (int cj = 0; cj < m_ChunksH; ++cj) + // Algorithm for the partial update: + // 1. Loop over chunks. + // 2. For any dirty chunk: + // - remove all edges, by removing the neighbor connection with them and then deleting us + // - recreate regions inside the chunk + // - reconnect the regions. We may do too much work if we reconnect with a dirty chunk, but that's fine. + for (u8 cj = 0; cj < m_ChunksH; ++cj) { int j0 = cj * CHUNK_SIZE; int j1 = std::min(j0 + CHUNK_SIZE, (int)dirtinessGrid.m_H); - for (int ci = 0; ci < m_ChunksW; ++ci) + for (u8 ci = 0; ci < m_ChunksW; ++ci) { // Skip chunks where no navcells are dirty. int i0 = ci * CHUNK_SIZE; @@ -434,23 +433,26 @@ void HierarchicalPathfinder::Update(Grid* grid, const Grid& dir { pass_class_t passClass = passClassMask.second; Chunk& a = m_Chunks[passClass].at(ci + cj*m_ChunksW); + + // Clean up edges ID + EdgesMap& edgeMap = m_Edges[passClass]; + for (u16 i : a.m_RegionsID) + { + RegionID reg{ci, cj, i}; + for (const RegionID& neighbor : edgeMap[reg]) + { + edgeMap[neighbor].erase(reg); + if (edgeMap[neighbor].empty()) + edgeMap.erase(neighbor); + } + edgeMap.erase(reg); + } + + // Recompute regions inside this chunk. a.InitRegions(ci, cj, grid, passClass); - } - } - } - // TODO: Also be clever with edges - m_Edges.clear(); - for (const std::pair& passClassMask : m_PassClassMasks) - { - pass_class_t passClass = passClassMask.second; - EdgesMap& edges = m_Edges[passClass]; - for (int cj = 0; cj < m_ChunksH; ++cj) - { - for (int ci = 0; ci < m_ChunksW; ++ci) - { - FindEdges(ci, cj, passClass, edges); + UpdateEdges(ci, cj, passClass, edgeMap); } } } @@ -462,15 +464,8 @@ void HierarchicalPathfinder::Update(Grid* grid, const Grid& dir } } -/** - * Find edges between regions in this chunk and the adjacent below/left chunks. - */ -void HierarchicalPathfinder::FindEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges) +void HierarchicalPathfinder::ComputeNeighbors(EdgesMap& edges, Chunk& a, Chunk& b, bool transpose, bool opposite) const { - std::vector& chunks = m_Chunks[passClass]; - - Chunk& a = chunks.at(cj*m_ChunksW + ci); - // For each edge between chunks, we loop over every adjacent pair of // navcells in the two chunks. If they are both in valid regions // (i.e. are passable navcells) then add a graph edge between those regions. @@ -478,49 +473,70 @@ void HierarchicalPathfinder::FindEdges(u8 ci, u8 cj, pass_class_t passClass, Edg // std::set which will drop duplicate entries.) // But as set.insert can be quite slow on large collection, and that we usually // try to insert the same values, we cache the previous one for a fast test. + RegionID raPrev(0,0,0); + RegionID rbPrev(0,0,0); + for (int k = 0; k < CHUNK_SIZE; ++k) + { + u8 aSide = opposite ? CHUNK_SIZE - 1 : 0; + u8 bSide = CHUNK_SIZE - 1 - aSide; + RegionID ra = transpose ? a.Get(k, aSide) : a.Get(aSide, k); + RegionID rb = transpose ? b.Get(k, bSide) : b.Get(bSide, k); + if (ra.r && rb.r) + { + if (ra == raPrev && rb == rbPrev) + continue; + edges[ra].insert(rb); + edges[rb].insert(ra); + raPrev = ra; + rbPrev = rb; + } + } +} + +/** + * Connect a chunk's regions to their neighbors. Not optimised for global recomputing. + */ +void HierarchicalPathfinder::UpdateEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges) +{ + std::vector& chunks = m_Chunks[passClass]; + + Chunk& a = chunks.at(cj*m_ChunksW + ci); if (ci > 0) - { - Chunk& b = chunks.at(cj*m_ChunksW + (ci-1)); - RegionID raPrev(0,0,0); - RegionID rbPrev(0,0,0); - for (int j = 0; j < CHUNK_SIZE; ++j) - { - RegionID ra = a.Get(0, j); - RegionID rb = b.Get(CHUNK_SIZE-1, j); - if (ra.r && rb.r) - { - if (ra == raPrev && rb == rbPrev) - continue; - edges[ra].insert(rb); - edges[rb].insert(ra); - raPrev = ra; - rbPrev = rb; - } - } - } + ComputeNeighbors(edges, a, chunks.at(cj*m_ChunksW + (ci-1)), false, false); + + if (ci < m_ChunksW-1) + ComputeNeighbors(edges, a, chunks.at(cj*m_ChunksW + (ci+1)), false, true); if (cj > 0) + ComputeNeighbors(edges, a, chunks.at((cj-1)*m_ChunksW + ci), true, false); + + if (cj < m_ChunksH - 1) + ComputeNeighbors(edges, a, chunks.at((cj+1)*m_ChunksW + ci), true, true); +} + +/** + * Find edges between regions in all chunks, in an optimised manner (only look at top/left) + */ +void HierarchicalPathfinder::RecomputeAllEdges(pass_class_t passClass, EdgesMap& edges) +{ + std::vector& chunks = m_Chunks[passClass]; + + edges.clear(); + + for (int cj = 0; cj < m_ChunksH; ++cj) { - Chunk& b = chunks.at((cj-1)*m_ChunksW + ci); - RegionID raPrev(0,0,0); - RegionID rbPrev(0,0,0); - for (int i = 0; i < CHUNK_SIZE; ++i) + for (int ci = 0; ci < m_ChunksW; ++ci) { - RegionID ra = a.Get(i, 0); - RegionID rb = b.Get(i, CHUNK_SIZE-1); - if (ra.r && rb.r) - { - if (ra == raPrev && rb == rbPrev) - continue; - edges[ra].insert(rb); - edges[rb].insert(ra); - raPrev = ra; - rbPrev = rb; - } + Chunk& a = chunks.at(cj*m_ChunksW + ci); + + if (ci > 0) + ComputeNeighbors(edges, a, chunks.at(cj*m_ChunksW + (ci-1)), false, false); + + if (cj > 0) + ComputeNeighbors(edges, a, chunks.at((cj-1)*m_ChunksW + ci), true, false); } } - } /** diff --git a/source/simulation2/helpers/HierarchicalPathfinder.h b/source/simulation2/helpers/HierarchicalPathfinder.h index c676f9e43e..afbf9468c0 100644 --- a/source/simulation2/helpers/HierarchicalPathfinder.h +++ b/source/simulation2/helpers/HierarchicalPathfinder.h @@ -165,7 +165,9 @@ private: typedef std::map > EdgesMap; - void FindEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges); + void ComputeNeighbors(EdgesMap& edges, Chunk& a, Chunk& b, bool transpose, bool opposite) const; + void RecomputeAllEdges(pass_class_t passClass, EdgesMap& edges); + void UpdateEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges); void FindReachableRegions(RegionID from, std::set& reachable, pass_class_t passClass) const;