Index: ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json +++ ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json @@ -144,6 +144,7 @@ { "nick": "kingadami", "name": "Adam Winsor" }, { "nick": "kingbasil", "name": "Giannis Fafalios" }, { "nick": "Krinkle", "name": "Timo Tijhof" }, + { "nick": "Kuba386", "name": "Jakub Kośmicki" }, { "nick": "lafferjm", "name": "Justin Lafferty" }, { "nick": "Langbart" }, { "nick": "LeanderH", "name": "Leander Hemelhof" }, Index: ps/trunk/binaries/data/mods/public/simulation/data/pathfinder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/pathfinder.xml +++ ps/trunk/binaries/data/mods/public/simulation/data/pathfinder.xml @@ -2,7 +2,7 @@ - 64 + 20 Index: ps/trunk/source/simulation2/components/CCmpPathfinder.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpPathfinder.cpp +++ ps/trunk/source/simulation2/components/CCmpPathfinder.cpp @@ -59,10 +59,16 @@ m_AtlasOverlay = NULL; - m_VertexPathfinder = std::make_unique(m_GridSize, m_TerrainOnlyGrid); + size_t workerThreads = Threading::TaskManager::Instance().GetNumberOfWorkers(); + // Store one vertex pathfinder for each thread (including the main thread). + while (m_VertexPathfinders.size() < workerThreads + 1) + m_VertexPathfinders.emplace_back(m_GridSize, m_TerrainOnlyGrid); m_LongPathfinder = std::make_unique(); m_PathfinderHier = std::make_unique(); + // Set up one future for each worker thread. + m_Futures.resize(workerThreads); + // Register Relax NG validator CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); @@ -75,14 +81,13 @@ // Paths are computed: // - Before MT_Update // - Before MT_MotionUnitFormation - // - 'in-between' turns (effectively at the start until threading is implemented). + // - asynchronously between turn end and turn start. // The latter of these must compute all outstanding requests, but the former two are capped - // to avoid spending too much time there (since the latter are designed to be threaded and thus not block the GUI). + // to avoid spending too much time there (since the latter are threaded and thus much 'cheaper'). // This loads that maximum number (note that it's per computation call, not per turn for now). const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder"); m_MaxSameTurnMoves = (u16)pathingSettings.GetChild("MaxSameTurnMoves").ToInt(); - const CParamNode::ChildrenMap& passClasses = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses").GetChildren(); for (CParamNode::ChildrenMap::const_iterator it = passClasses.begin(); it != passClasses.end(); ++it) { @@ -99,6 +104,12 @@ void CCmpPathfinder::Deinit() { SetDebugOverlay(false); // cleans up memory + + // Wait on all pathfinding tasks. + for (Future& future : m_Futures) + future.Cancel(); + m_Futures.clear(); + SAFE_DELETE(m_AtlasOverlay); SAFE_DELETE(m_Grid); @@ -749,7 +760,7 @@ WaypointPath CCmpPathfinder::ComputeShortPathImmediate(const ShortPathRequest& request) const { - return m_VertexPathfinder->ComputeShortPath(request, CmpPtr(GetSystemEntity())); + return m_VertexPathfinders.front().ComputeShortPath(request, CmpPtr(GetSystemEntity())); } template @@ -785,9 +796,14 @@ if (!m_LongPathRequests.m_ComputeDone || !m_ShortPathRequests.m_ComputeDone) { - m_ShortPathRequests.Compute(*this, *m_VertexPathfinder); + // Also start computing on the main thread to finish faster. + m_ShortPathRequests.Compute(*this, m_VertexPathfinders.front()); m_LongPathRequests.Compute(*this, *m_LongPathfinder); } + // We're done, clear futures. + // Use CancelOrWait instead of just Cancel to ensure determinism. + for (Future& future : m_Futures) + future.CancelOrWait(); { PROFILE2("PostMessages"); @@ -811,8 +827,22 @@ { m_ShortPathRequests.PrepareForComputation(useMax ? m_MaxSameTurnMoves : 0); m_LongPathRequests.PrepareForComputation(useMax ? m_MaxSameTurnMoves : 0); + + Threading::TaskManager& taskManager = Threading::TaskManager::Instance(); + for (size_t i = 0; i < m_Futures.size(); ++i) + { + ENSURE(!m_Futures[i].Valid()); + // Pass the i+1th vertex pathfinder to keep the first for the main thread, + // each thread get its own instance to avoid conflicts in cached data. + m_Futures[i] = taskManager.PushTask([&pathfinder=*this, &vertexPfr=m_VertexPathfinders[i + 1]]() { + PROFILE2("Async pathfinding"); + pathfinder.m_ShortPathRequests.Compute(pathfinder, vertexPfr); + pathfinder.m_LongPathRequests.Compute(pathfinder, *pathfinder.m_LongPathfinder); + }); + } } + ////////////////////////////////////////////////////////// bool CCmpPathfinder::IsGoalReachable(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) Index: ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h =================================================================== --- ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h +++ ps/trunk/source/simulation2/components/CCmpPathfinder_Common.h @@ -35,6 +35,7 @@ #include "graphics/Terrain.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" +#include "ps/TaskManager.h" #include "renderer/TerrainOverlay.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/helpers/Grid.h" @@ -94,19 +95,22 @@ GridUpdateInformation m_AIPathfinderDirtinessInformation; bool m_TerrainDirty; - std::unique_ptr m_VertexPathfinder; + std::vector m_VertexPathfinders; std::unique_ptr m_PathfinderHier; std::unique_ptr m_LongPathfinder; + // One per live asynchronous path computing task. + std::vector> m_Futures; + template class PathRequests { public: std::vector m_Requests; std::vector m_Results; // This is the array index of the next path to compute. - size_t m_NextPathToCompute = 0; + std::atomic m_NextPathToCompute = 0; // This is false until all scheduled paths have been computed. - bool m_ComputeDone = true; + std::atomic m_ComputeDone = true; void ClearComputed() { Index: ps/trunk/source/simulation2/helpers/VertexPathfinder.h =================================================================== --- ps/trunk/source/simulation2/helpers/VertexPathfinder.h +++ ps/trunk/source/simulation2/helpers/VertexPathfinder.h @@ -113,8 +113,8 @@ }; /** - * 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. + * There are several vertex pathfinders running asynchronously, so 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. */