From f1467d10fdcb80d55aedee1246578f7f317d938b Mon Sep 17 00:00:00 2001 From: wraitii Date: Mon, 17 May 2021 09:38:24 +0000 Subject: [PATCH] Make pathfinder debug rendering thread-safe. Also cleans up the jump point cache const-correctness. Differential Revision: https://code.wildfiregames.com/D3966 This was SVN commit r25452. --- .../simulation2/components/CCmpPathfinder.cpp | 4 +- source/simulation2/helpers/LongPathfinder.cpp | 201 ++++++++++++++---- source/simulation2/helpers/LongPathfinder.h | 126 ++--------- .../simulation2/helpers/VertexPathfinder.cpp | 37 +++- source/simulation2/helpers/VertexPathfinder.h | 40 +++- 5 files changed, 234 insertions(+), 174 deletions(-) diff --git a/source/simulation2/components/CCmpPathfinder.cpp b/source/simulation2/components/CCmpPathfinder.cpp index fe1af89f92..897289e751 100644 --- a/source/simulation2/components/CCmpPathfinder.cpp +++ b/source/simulation2/components/CCmpPathfinder.cpp @@ -196,7 +196,7 @@ void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global)) void CCmpPathfinder::RenderSubmit(SceneCollector& collector) { - m_VertexPathfinder->RenderSubmit(collector); + g_VertexPathfinderDebugOverlay.RenderSubmit(collector); m_PathfinderHier->RenderSubmit(collector); } @@ -207,7 +207,7 @@ void CCmpPathfinder::SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGo void CCmpPathfinder::SetDebugOverlay(bool enabled) { - m_VertexPathfinder->SetDebugOverlay(enabled); + g_VertexPathfinderDebugOverlay.SetDebugOverlay(enabled); m_LongPathfinder->SetDebugOverlay(enabled); } diff --git a/source/simulation2/helpers/LongPathfinder.cpp b/source/simulation2/helpers/LongPathfinder.cpp index bd1f9b1d2c..64e7dceb9f 100644 --- a/source/simulation2/helpers/LongPathfinder.cpp +++ b/source/simulation2/helpers/LongPathfinder.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -25,6 +25,13 @@ #include "Geometry.h" #include "HierarchicalPathfinder.h" +#include + +namespace +{ +static std::mutex g_DebugMutex; +} + /** * Jump point cache. * @@ -87,7 +94,7 @@ class JumpPointCache * Returns the coordinate of the next jump point xp (where x < xp), * and whether it's an obstruction point or jump point. */ - void Get(int x, int& xp, bool& obstruction) + void Get(int x, int& xp, bool& obstruction) const { ENSURE(0 <= x && x < (int)data.size()); xp = data[x] >> 1; @@ -178,7 +185,7 @@ class JumpPointCache data.swap(tree); } - void Get(int x, int& xp, bool& obstruction) + void Get(int x, int& xp, bool& obstruction) const { // Search the binary tree for an interval which contains x int i = 0; @@ -323,7 +330,7 @@ public: * at (ip, j) where i < ip. * Returns i if there is no such point. */ - int GetJumpPointRight(int i, int j, const PathGoal& goal) + int GetJumpPointRight(int i, int j, const PathGoal& goal) const { int ip; bool obstruction; @@ -336,7 +343,7 @@ public: return i; } - int GetJumpPointLeft(int i, int j, const PathGoal& goal) + int GetJumpPointLeft(int i, int j, const PathGoal& goal) const { int mip; // mirrored value, because m_JumpPointsLeft is generated from a mirrored map bool obstruction; @@ -347,7 +354,7 @@ public: return i; } - int GetJumpPointUp(int i, int j, const PathGoal& goal) + int GetJumpPointUp(int i, int j, const PathGoal& goal) const { int jp; bool obstruction; @@ -357,7 +364,7 @@ public: return j; } - int GetJumpPointDown(int i, int j, const PathGoal& goal) + int GetJumpPointDown(int i, int j, const PathGoal& goal) const { int mjp; // mirrored value bool obstruction; @@ -373,18 +380,10 @@ public: LongPathfinder::LongPathfinder() : m_UseJPSCache(false), - m_Grid(NULL), m_GridSize(0), - m_DebugOverlay(NULL), m_DebugGrid(NULL), m_DebugPath(NULL) + m_Grid(NULL), m_GridSize(0) { } -LongPathfinder::~LongPathfinder() -{ - SAFE_DELETE(m_DebugOverlay); - SAFE_DELETE(m_DebugGrid); - SAFE_DELETE(m_DebugPath); -} - #define PASSABLE(i, j) IS_PASSABLE(state.terrain->get(i, j), state.passClass) // Calculate heuristic cost from tile i,j to goal @@ -715,20 +714,25 @@ void LongPathfinder::AddJumpedDiag(int i, int j, int di, int dj, PathCost g, Pat void LongPathfinder::ComputeJPSPath(const HierarchicalPathfinder& hierPath, entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal, pass_class_t passClass, WaypointPath& path) const { - PROFILE("ComputePathJPS"); - PROFILE2_IFSPIKE("ComputePathJPS", 0.0002); + PROFILE2("ComputePathJPS"); PathfinderState state = { 0 }; - std::map >::const_iterator it = m_JumpPointCache.find(passClass); - if (it != m_JumpPointCache.end()) - state.jpc = it->second.get(); - - if (m_UseJPSCache && !state.jpc) + if (m_UseJPSCache) { - state.jpc = new JumpPointCache; - state.jpc->reset(m_Grid, passClass); - debug_printf("PATHFINDER: JPC memory: %d kB\n", (int)state.jpc->GetMemoryUsage() / 1024); - m_JumpPointCache[passClass] = shared_ptr(state.jpc); + // Needs to lock for construction, or several threads might try doing that at the same time. + static std::mutex JPCMutex; + std::unique_lock lock(JPCMutex); + std::map >::const_iterator it = m_JumpPointCache.find(passClass); + if (it != m_JumpPointCache.end()) + state.jpc = it->second.get(); + + if (!state.jpc) + { + m_JumpPointCache[passClass] = std::make_shared(); + m_JumpPointCache[passClass]->reset(m_Grid, passClass); + debug_printf("PATHFINDER: JPC memory: %d kB\n", (int)state.jpc->GetMemoryUsage() / 1024); + state.jpc = m_JumpPointCache[passClass].get(); + } } // Convert the start coordinates to tile indexes @@ -901,10 +905,16 @@ void LongPathfinder::ComputeJPSPath(const HierarchicalPathfinder& hierPath, enti ImprovePathWaypoints(path, passClass, origGoal.maxdist, x0, z0); // Save this grid for debug display - delete m_DebugGrid; - m_DebugGrid = state.tiles; - m_DebugSteps = state.steps; - m_DebugGoal = state.goal; + if (m_Debug.Overlay) + { + std::lock_guard lock(g_DebugMutex); + delete m_Debug.Grid; + m_Debug.Grid = state.tiles; + m_Debug.Steps = state.steps; + m_Debug.Goal = state.goal; + } + else + SAFE_DELETE(state.tiles); } #undef PASSABLE @@ -963,36 +973,30 @@ void LongPathfinder::ImprovePathWaypoints(WaypointPath& path, pass_class_t passC void LongPathfinder::GetDebugDataJPS(u32& steps, double& time, Grid& grid) const { - steps = m_DebugSteps; - time = m_DebugTime; + steps = m_Debug.Steps; + time = m_Debug.Time; - if (!m_DebugGrid) + if (!m_Debug.Grid) return; - u16 iGoal, jGoal; - Pathfinding::NearestNavcell(m_DebugGoal.x, m_DebugGoal.z, iGoal, jGoal, m_GridSize, m_GridSize); + std::lock_guard lock(g_DebugMutex); - grid = Grid(m_DebugGrid->m_W, m_DebugGrid->m_H); + u16 iGoal, jGoal; + Pathfinding::NearestNavcell(m_Debug.Goal.x, m_Debug.Goal.z, iGoal, jGoal, m_GridSize, m_GridSize); + + grid = Grid(m_Debug.Grid->m_W, m_Debug.Grid->m_H); for (u16 j = 0; j < grid.m_H; ++j) { for (u16 i = 0; i < grid.m_W; ++i) { if (i == iGoal && j == jGoal) continue; - PathfindTile t = m_DebugGrid->get(i, j); + PathfindTile t = m_Debug.Grid->get(i, j); grid.set(i, j, (t.IsOpen() ? 1 : 0) | (t.IsClosed() ? 2 : 0)); } } } -void LongPathfinder::SetDebugOverlay(bool enabled) -{ - if (enabled && !m_DebugOverlay) - m_DebugOverlay = new LongOverlay(*this); - else if (!enabled && m_DebugOverlay) - SAFE_DELETE(m_DebugOverlay); -} - void LongPathfinder::ComputePath(const HierarchicalPathfinder& hierPath, entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal, pass_class_t passClass, WaypointPath& path) const { @@ -1045,3 +1049,108 @@ void LongPathfinder::GenerateSpecialMap(pass_class_t passClass, std::vector debugGrid; + m_Pathfinder.GetDebugData(steps, time, debugGrid); + + // Render navcell passability + u8* p = data; + for (size_t j = 0; j < h; ++j) + { + for (size_t i = 0; i < w; ++i) + { + SColor4ub color(0, 0, 0, 0); + if (!IS_PASSABLE(m_Pathfinder.m_Grid->get((int)i, (int)j), m_Pathfinder.m_Debug.PassClass)) + color = SColor4ub(255, 0, 0, 127); + + if (debugGrid.m_W && debugGrid.m_H) + { + u8 n = debugGrid.get((int)i, (int)j); + + if (n == 1) + color = SColor4ub(255, 255, 0, 127); + else if (n == 2) + color = SColor4ub(0, 255, 0, 127); + + if (m_Pathfinder.m_Debug.Goal.NavcellContainsGoal(i, j)) + color = SColor4ub(0, 0, 255, 127); + } + + *p++ = color.R; + *p++ = color.G; + *p++ = color.B; + *p++ = color.A; + } + } + + // Render the most recently generated path + if (m_Pathfinder.m_Debug.Path && !m_Pathfinder.m_Debug.Path->m_Waypoints.empty()) + { + std::vector& waypoints = m_Pathfinder.m_Debug.Path->m_Waypoints; + u16 ip = 0, jp = 0; + for (size_t k = 0; k < waypoints.size(); ++k) + { + u16 i, j; + Pathfinding::NearestNavcell(waypoints[k].x, waypoints[k].z, i, j, m_Pathfinder.m_GridSize, m_Pathfinder.m_GridSize); + if (k == 0) + { + ip = i; + jp = j; + } + else + { + bool firstCell = true; + do + { + if (data[(jp*w + ip)*4+3] == 0) + { + data[(jp*w + ip)*4+0] = 0xFF; + data[(jp*w + ip)*4+1] = 0xFF; + data[(jp*w + ip)*4+2] = 0xFF; + data[(jp*w + ip)*4+3] = firstCell ? 0xA0 : 0x60; + } + ip = ip < i ? ip+1 : ip > i ? ip-1 : ip; + jp = jp < j ? jp+1 : jp > j ? jp-1 : jp; + firstCell = false; + } + while (ip != i || jp != j); + } + } + } + } +}; + +// These two functions must come below LongOverlay's definition. +void LongPathfinder::SetDebugOverlay(bool enabled) +{ + if (enabled && !m_Debug.Overlay) + m_Debug.Overlay = new LongOverlay(*this); + else if (!enabled && m_Debug.Overlay) + SAFE_DELETE(m_Debug.Overlay); +} + +LongPathfinder::~LongPathfinder() +{ + SAFE_DELETE(m_Debug.Overlay); + SAFE_DELETE(m_Debug.Grid); + SAFE_DELETE(m_Debug.Path); +} diff --git a/source/simulation2/helpers/LongPathfinder.h b/source/simulation2/helpers/LongPathfinder.h index 3c32048064..8620c89fd8 100644 --- a/source/simulation2/helpers/LongPathfinder.h +++ b/source/simulation2/helpers/LongPathfinder.h @@ -154,7 +154,7 @@ struct PathfinderState PathCost hBest; // heuristic of closest discovered tile to goal u16 iBest, jBest; // closest tile - JumpPointCache* jpc; + const JumpPointCache* jpc; }; class LongOverlay; @@ -171,14 +171,14 @@ public: void SetDebugPath(const HierarchicalPathfinder& hierPath, entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) { - if (!m_DebugOverlay) + if (!m_Debug.Overlay) return; - SAFE_DELETE(m_DebugGrid); - delete m_DebugPath; - m_DebugPath = new WaypointPath(); - ComputePath(hierPath, x0, z0, goal, passClass, *m_DebugPath); - m_DebugPassClass = passClass; + SAFE_DELETE(m_Debug.Grid); + delete m_Debug.Path; + m_Debug.Path = new WaypointPath(); + ComputePath(hierPath, x0, z0, goal, passClass, *m_Debug.Path); + m_Debug.PassClass = passClass; } void Reload(Grid* passabilityGrid) @@ -225,15 +225,20 @@ public: u16 m_GridSize; // Debugging - output from last pathfind operation. - // mutable as making these const would require a lot of boilerplate code - // and they do not change the behavioural const-ness of the pathfinder. - mutable LongOverlay* m_DebugOverlay; - mutable PathfindTileGrid* m_DebugGrid; - mutable u32 m_DebugSteps; - mutable double m_DebugTime; - mutable PathGoal m_DebugGoal; - mutable WaypointPath* m_DebugPath; - mutable pass_class_t m_DebugPassClass; + struct Debug + { + // Atomic - used to toggle debugging. + std::atomic Overlay = nullptr; + // Mutable - set by ComputeJPSPath (thus possibly from different threads). + // Synchronized via mutex if necessary. + mutable PathfindTileGrid* Grid = nullptr; + mutable u32 Steps; + mutable double Time; + mutable PathGoal Goal; + + WaypointPath* Path = nullptr; + pass_class_t PassClass; + } m_Debug; private: PathCost CalculateHeuristic(int i, int j, int iGoal, int jGoal) const; @@ -280,93 +285,4 @@ private: mutable std::map > m_JumpPointCache; }; -/** - * Terrain overlay for pathfinder debugging. - * Renders a representation of the most recent pathfinding operation. - */ -class LongOverlay : public TerrainTextureOverlay -{ -public: - LongPathfinder& m_Pathfinder; - - LongOverlay(LongPathfinder& pathfinder) : - TerrainTextureOverlay(Pathfinding::NAVCELLS_PER_TERRAIN_TILE), m_Pathfinder(pathfinder) - { - } - - virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) - { - // Grab the debug data for the most recently generated path - u32 steps; - double time; - Grid debugGrid; - m_Pathfinder.GetDebugData(steps, time, debugGrid); - - // Render navcell passability - u8* p = data; - for (size_t j = 0; j < h; ++j) - { - for (size_t i = 0; i < w; ++i) - { - SColor4ub color(0, 0, 0, 0); - if (!IS_PASSABLE(m_Pathfinder.m_Grid->get((int)i, (int)j), m_Pathfinder.m_DebugPassClass)) - color = SColor4ub(255, 0, 0, 127); - - if (debugGrid.m_W && debugGrid.m_H) - { - u8 n = debugGrid.get((int)i, (int)j); - - if (n == 1) - color = SColor4ub(255, 255, 0, 127); - else if (n == 2) - color = SColor4ub(0, 255, 0, 127); - - if (m_Pathfinder.m_DebugGoal.NavcellContainsGoal(i, j)) - color = SColor4ub(0, 0, 255, 127); - } - - *p++ = color.R; - *p++ = color.G; - *p++ = color.B; - *p++ = color.A; - } - } - - // Render the most recently generated path - if (m_Pathfinder.m_DebugPath && !m_Pathfinder.m_DebugPath->m_Waypoints.empty()) - { - std::vector& waypoints = m_Pathfinder.m_DebugPath->m_Waypoints; - u16 ip = 0, jp = 0; - for (size_t k = 0; k < waypoints.size(); ++k) - { - u16 i, j; - Pathfinding::NearestNavcell(waypoints[k].x, waypoints[k].z, i, j, m_Pathfinder.m_GridSize, m_Pathfinder.m_GridSize); - if (k == 0) - { - ip = i; - jp = j; - } - else - { - bool firstCell = true; - do - { - if (data[(jp*w + ip)*4+3] == 0) - { - data[(jp*w + ip)*4+0] = 0xFF; - data[(jp*w + ip)*4+1] = 0xFF; - data[(jp*w + ip)*4+2] = 0xFF; - data[(jp*w + ip)*4+3] = firstCell ? 0xA0 : 0x60; - } - ip = ip < i ? ip+1 : ip > i ? ip-1 : ip; - jp = jp < j ? jp+1 : jp > j ? jp-1 : jp; - firstCell = false; - } - while (ip != i || jp != j); - } - } - } - } -}; - #endif // INCLUDED_LONGPATHFINDER diff --git a/source/simulation2/helpers/VertexPathfinder.cpp b/source/simulation2/helpers/VertexPathfinder.cpp index 3156dd5ad7..10b4572259 100644 --- a/source/simulation2/helpers/VertexPathfinder.cpp +++ b/source/simulation2/helpers/VertexPathfinder.cpp @@ -43,6 +43,15 @@ #include "simulation2/helpers/Render.h" #include "simulation2/system/SimContext.h" +#include + +namespace +{ +static std::mutex g_DebugMutex; +} + +VertexPathfinderDebugOverlay g_VertexPathfinderDebugOverlay; + /* Quadrant optimisation: * (loosely based on GPG2 "Optimizing Points-of-Visibility Pathfinding") * @@ -515,7 +524,7 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, { PROFILE2("ComputeShortPath"); - DebugRenderGoal(cmpObstructionManager->GetSimContext(), request.goal); + g_VertexPathfinderDebugOverlay.DebugRenderGoal(cmpObstructionManager->GetSimContext(), request.goal); // Create impassable edges at the max-range boundary, so we can't escape the region // where we're meant to be searching @@ -694,7 +703,7 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, ENSURE(m_Vertexes.size() < 65536); // We store array indexes as u16. - DebugRenderGraph(cmpObstructionManager->GetSimContext(), m_Vertexes, m_Edges, m_EdgeSquares); + g_VertexPathfinderDebugOverlay.DebugRenderGraph(cmpObstructionManager->GetSimContext(), m_Vertexes, m_Edges, m_EdgeSquares); // Do an A* search over the vertex/visibility graph: @@ -786,7 +795,7 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, CheckVisibility(m_Vertexes[curr.id].p, npos, m_EdgesUnaligned); // Render the edges that we examine. - DebugRenderEdges(cmpObstructionManager->GetSimContext(), visible, m_Vertexes[curr.id].p, npos); + g_VertexPathfinderDebugOverlay.DebugRenderEdges(cmpObstructionManager->GetSimContext(), visible, m_Vertexes[curr.id].p, npos); if (visible) { @@ -854,12 +863,14 @@ WaypointPath VertexPathfinder::ComputeShortPath(const ShortPathRequest& request, return path; } -void VertexPathfinder::DebugRenderGoal(const CSimContext& simContext, const PathGoal& goal) const +void VertexPathfinderDebugOverlay::DebugRenderGoal(const CSimContext& simContext, const PathGoal& goal) { if (!m_DebugOverlay) return; - m_DebugOverlayShortPathLines.clear(); + std::lock_guard lock(g_DebugMutex); + + g_VertexPathfinderDebugOverlay.m_DebugOverlayShortPathLines.clear(); // Render the goal shape m_DebugOverlayShortPathLines.push_back(SOverlayLine()); @@ -887,11 +898,13 @@ void VertexPathfinder::DebugRenderGoal(const CSimContext& simContext, const Path } } -void VertexPathfinder::DebugRenderGraph(const CSimContext& simContext, const std::vector& vertexes, const std::vector& edges, const std::vector& edgeSquares) const +void VertexPathfinderDebugOverlay::DebugRenderGraph(const CSimContext& simContext, const std::vector& vertexes, const std::vector& edges, const std::vector& edgeSquares) { if (!m_DebugOverlay) return; + std::lock_guard lock(g_DebugMutex); + #define PUSH_POINT(p) STMT(xz.push_back(p.X.ToFloat()); xz.push_back(p.Y.ToFloat())) // Render the vertexes as little Pac-Man shapes to indicate quadrant direction for (size_t i = 0; i < vertexes.size(); ++i) @@ -962,11 +975,13 @@ void VertexPathfinder::DebugRenderGraph(const CSimContext& simContext, const std } } -void VertexPathfinder::DebugRenderEdges(const CSimContext& UNUSED(simContext), bool UNUSED(visible), CFixedVector2D UNUSED(curr), CFixedVector2D UNUSED(npos)) const +void VertexPathfinderDebugOverlay::DebugRenderEdges(const CSimContext& UNUSED(simContext), bool UNUSED(visible), CFixedVector2D UNUSED(curr), CFixedVector2D UNUSED(npos)) { if (!m_DebugOverlay) return; + std::lock_guard lock(g_DebugMutex); + // Disabled by default. /* m_DebugOverlayShortPathLines.push_back(SOverlayLine()); @@ -983,11 +998,13 @@ void VertexPathfinder::DebugRenderEdges(const CSimContext& UNUSED(simContext), b */ } -void VertexPathfinder::RenderSubmit(SceneCollector& collector) +void VertexPathfinderDebugOverlay::RenderSubmit(SceneCollector& collector) { if (!m_DebugOverlay) return; - for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i) - collector.Submit(&m_DebugOverlayShortPathLines[i]); + std::lock_guard lock(g_DebugMutex); + m_DebugOverlayShortPathLines.swap(m_DebugOverlayShortPathLinesSubmitted); + for (size_t i = 0; i < m_DebugOverlayShortPathLinesSubmitted.size(); ++i) + collector.Submit(&m_DebugOverlayShortPathLinesSubmitted[i]); } diff --git a/source/simulation2/helpers/VertexPathfinder.h b/source/simulation2/helpers/VertexPathfinder.h index eeee1e0cdb..a5d0320b6e 100755 --- a/source/simulation2/helpers/VertexPathfinder.h +++ b/source/simulation2/helpers/VertexPathfinder.h @@ -75,7 +75,9 @@ class SceneCollector; class VertexPathfinder { public: - VertexPathfinder(const u16& gridSize, Grid* const & terrainOnlyGrid) : m_GridSize(gridSize), m_TerrainOnlyGrid(terrainOnlyGrid), m_DebugOverlay(false) {}; + VertexPathfinder(const u16& gridSize, Grid* const & terrainOnlyGrid) : m_GridSize(gridSize), m_TerrainOnlyGrid(terrainOnlyGrid) {}; + VertexPathfinder(const VertexPathfinder&) = delete; + VertexPathfinder(VertexPathfinder&& o) : m_GridSize(o.m_GridSize), m_TerrainOnlyGrid(o.m_TerrainOnlyGrid) {} /** * Compute a precise path from the given point to the goal, and return the set of waypoints. @@ -86,22 +88,12 @@ public: */ WaypointPath ComputeShortPath(const ShortPathRequest& request, CmpPtr cmpObstructionManager) const; - void SetDebugOverlay(bool enabled) { m_DebugOverlay = enabled; } - void RenderSubmit(SceneCollector& collector); - private: - void DebugRenderGoal(const CSimContext& simContext, const PathGoal& goal) const; - void DebugRenderGraph(const CSimContext& simContext, const std::vector& vertexes, const std::vector& edges, const std::vector& edgeSquares) const; - void DebugRenderEdges(const CSimContext& simContext, bool visible, CFixedVector2D curr, CFixedVector2D npos) const; - // References to the Pathfinder for convenience. const u16& m_GridSize; Grid* const & m_TerrainOnlyGrid; - std::atomic m_DebugOverlay; - mutable std::vector m_DebugOverlayShortPathLines; - // These vectors are expensive to recreate on every call, so we cache them here. // They are made mutable to allow using them in the otherwise const ComputeShortPath. @@ -120,4 +112,30 @@ private: mutable std::vector m_EdgeSquares; // Axis-aligned squares; equivalent to 4 edges. }; +/** + * If there are several vertex pathfinders running asynchronously, their debug output might conflict. + * To remain thread-safe, this single class will handle the debug data. + * NB: though threadsafe, the way things are setup means you can have a few + * more graphs and edges than you'd expect showing up in the rendered graph. + */ +class VertexPathfinderDebugOverlay +{ + friend class VertexPathfinder; +public: + void SetDebugOverlay(bool enabled) { m_DebugOverlay = enabled; } + void RenderSubmit(SceneCollector& collector); +protected: + + void DebugRenderGoal(const CSimContext& simContext, const PathGoal& goal); + void DebugRenderGraph(const CSimContext& simContext, const std::vector& vertexes, const std::vector& edges, const std::vector& edgeSquares); + void DebugRenderEdges(const CSimContext& simContext, bool visible, CFixedVector2D curr, CFixedVector2D npos); + + std::atomic m_DebugOverlay = false; + // The data is double buffered: the first is the 'work-in-progress' state, the second the last RenderSubmit state. + std::vector m_DebugOverlayShortPathLines; + std::vector m_DebugOverlayShortPathLinesSubmitted; +}; + +extern VertexPathfinderDebugOverlay g_VertexPathfinderDebugOverlay; + #endif // INCLUDED_VERTEXPATHFINDER