Changeset View
Standalone View
source/simulation2/components/CCmpPathfinder.cpp
Show First 20 Lines • Show All 53 Lines • ▼ Show 20 Lines | void CCmpPathfinder::Init(const CParamNode& UNUSED(paramNode)) | ||||
m_TerrainOnlyGrid = NULL; | m_TerrainOnlyGrid = NULL; | ||||
FlushAIPathfinderDirtinessInformation(); | FlushAIPathfinderDirtinessInformation(); | ||||
m_NextAsyncTicket = 1; | m_NextAsyncTicket = 1; | ||||
m_AtlasOverlay = NULL; | m_AtlasOverlay = NULL; | ||||
m_VertexPathfinder = std::make_unique<VertexPathfinder>(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<LongPathfinder>(); | m_LongPathfinder = std::make_unique<LongPathfinder>(); | ||||
m_PathfinderHier = std::make_unique<HierarchicalPathfinder>(); | m_PathfinderHier = std::make_unique<HierarchicalPathfinder>(); | ||||
// Set up one future for each worker thread. | |||||
m_Futures.resize(workerThreads); | |||||
// Register Relax NG validator | // Register Relax NG validator | ||||
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); | CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); | ||||
// Since this is used as a system component (not loaded from an entity template), | // Since this is used as a system component (not loaded from an entity template), | ||||
// we can't use the real paramNode (it won't get handled properly when deserializing), | // we can't use the real paramNode (it won't get handled properly when deserializing), | ||||
// so load the data from a special XML file. | // so load the data from a special XML file. | ||||
CParamNode externalParamNode; | CParamNode externalParamNode; | ||||
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); | CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); | ||||
// Paths are computed: | // Paths are computed: | ||||
// - Before MT_Update | // - Before MT_Update | ||||
// - Before MT_MotionUnitFormation | // - 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 | // 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). | // This loads that maximum number (note that it's per computation call, not per turn for now). | ||||
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder"); | const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder"); | ||||
m_MaxSameTurnMoves = (u16)pathingSettings.GetChild("MaxSameTurnMoves").ToInt(); | m_MaxSameTurnMoves = (u16)pathingSettings.GetChild("MaxSameTurnMoves").ToInt(); | ||||
Stan: If you want to change this line replace the c style cast by a c++ cast :) | |||||
Done Inline ActionsDone in next diff. Kuba386: Done in next diff. | |||||
Not Done Inline ActionsDo we have a conversion to unsigned int ? Stan: Do we have a conversion to unsigned int ? | |||||
const CParamNode::ChildrenMap& passClasses = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses").GetChildren(); | const CParamNode::ChildrenMap& passClasses = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses").GetChildren(); | ||||
for (CParamNode::ChildrenMap::const_iterator it = passClasses.begin(); it != passClasses.end(); ++it) | for (CParamNode::ChildrenMap::const_iterator it = passClasses.begin(); it != passClasses.end(); ++it) | ||||
{ | { | ||||
std::string name = it->first; | std::string name = it->first; | ||||
ENSURE((int)m_PassClasses.size() <= PASS_CLASS_BITS); | ENSURE((int)m_PassClasses.size() <= PASS_CLASS_BITS); | ||||
Not Done Inline ActionsSame here If you want to change this line replace the c style cast by a c++ cast :) Stan: Same here If you want to change this line replace the c style cast by a c++ cast :) | |||||
Done Inline ActionsDone in next diff. Same for others. :) Kuba386: Done in next diff. Same for others. :) | |||||
pass_class_t mask = PASS_CLASS_MASK_FROM_INDEX(m_PassClasses.size()); | pass_class_t mask = PASS_CLASS_MASK_FROM_INDEX(m_PassClasses.size()); | ||||
m_PassClasses.push_back(PathfinderPassability(mask, it->second)); | m_PassClasses.push_back(PathfinderPassability(mask, it->second)); | ||||
m_PassClassMasks[name] = mask; | m_PassClassMasks[name] = mask; | ||||
} | } | ||||
} | } | ||||
CCmpPathfinder::~CCmpPathfinder() {}; | CCmpPathfinder::~CCmpPathfinder() {}; | ||||
Not Done Inline ActionsIs it useful? Stan: Is it useful? | |||||
void CCmpPathfinder::Deinit() | void CCmpPathfinder::Deinit() | ||||
{ | { | ||||
SetDebugOverlay(false); // cleans up memory | SetDebugOverlay(false); // cleans up memory | ||||
// Wait on all pathfinding tasks. | |||||
for (Future<void>& future : m_Futures) | |||||
future.Cancel(); | |||||
m_Futures.clear(); | |||||
Done Inline ActionsIt's not good to access atlas stuff from simulation. You need to use a common source, like a thread manager. The same for the number of threads. vladislavbelov: It's not good to access atlas stuff from simulation. You need to use a common source, like a… | |||||
Done Inline ActionsSorry, I don't understand what you mean here. You're saying we should implement a "Thread Manager" class at a 'top-level' global that tells us how many threads we want for pathfinding? Also I somewhat intend to remove this as if we had proper mutex protection it wouldn't be needed. wraitii: Sorry, I don't understand what you mean here. You're saying we should implement a "Thread… | |||||
Done Inline Actionswell, in this part of code threads are going to be created, they do not exists at this point so no concurency here. wantedThreads is local (in scope of function) variable - > no problem even with threads as every would have its own unique variable with no option to see or change the one from another thread g_Atlas & & g_Atlas->running is only reading, as far as I know you can safely read from variable with as many threads as you wish and it is safe but again this function is running in main thread only Silier: well, in this part of code threads are going to be created, they do not exists at this point so… | |||||
Done Inline Actions
I mean that the component shouldn't decide about the number of threads and shouldn't communicate with the Atlas directly. We may use ThreadUtil to calculate right number of threads. Because we might have separate threads for other purposes, and we need to manage them somehow.
It's not the syntax error but the semantic for me. vladislavbelov: > Sorry, I don't understand what you mean here. You're saying we should implement a "Thread… | |||||
Done Inline ActionsOK, makes sense for ThreadUtil. wraitii: OK, makes sense for ThreadUtil. | |||||
SAFE_DELETE(m_AtlasOverlay); | SAFE_DELETE(m_AtlasOverlay); | ||||
Not Done Inline ActionsWe don't use auto as a general rule of thumb. Stan: We don't use auto as a general rule of thumb. | |||||
SAFE_DELETE(m_Grid); | SAFE_DELETE(m_Grid); | ||||
SAFE_DELETE(m_TerrainOnlyGrid); | SAFE_DELETE(m_TerrainOnlyGrid); | ||||
Not Done Inline ActionsWe don't use auto as a general rule of thumb. Stan: We don't use auto as a general rule of thumb. | |||||
} | } | ||||
Done Inline ActionsLOGINFO wraitii: LOGINFO | |||||
template<> | template<> | ||||
Not Done Inline Actionscpp cast here as well. Stan: cpp cast here as well. | |||||
Not Done Inline ActionsThough it might be better to use an u8 everywhere ? Stan: Though it might be better to use an u8 everywhere ? | |||||
Not Done Inline ActionsWhy 64 and not 255 ? Stan: Why 64 and not 255 ? | |||||
Done Inline Actions64 is max value in UI, and so should be there. Why not 255? Kuba386: 64 is max value in UI, and so should be there. Why not 255?
This case is intended to protect… | |||||
struct SerializeHelper<LongPathRequest> | struct SerializeHelper<LongPathRequest> | ||||
{ | { | ||||
Done Inline ActionsUse std::unique_ptr instead, raw pointers are dangerous. vladislavbelov: Use `std::unique_ptr` instead, raw pointers are dangerous. | |||||
Done Inline ActionsIndeed. wraitii: Indeed. | |||||
template<typename S> | template<typename S> | ||||
void operator()(S& serialize, const char* UNUSED(name), Serialize::qualify<S, LongPathRequest> value) | void operator()(S& serialize, const char* UNUSED(name), Serialize::qualify<S, LongPathRequest> value) | ||||
Done Inline Actionselse wraitii: else | |||||
{ | { | ||||
serialize.NumberU32_Unbounded("ticket", value.ticket); | serialize.NumberU32_Unbounded("ticket", value.ticket); | ||||
serialize.NumberFixed_Unbounded("x0", value.x0); | serialize.NumberFixed_Unbounded("x0", value.x0); | ||||
serialize.NumberFixed_Unbounded("z0", value.z0); | serialize.NumberFixed_Unbounded("z0", value.z0); | ||||
Serializer(serialize, "goal", value.goal); | Serializer(serialize, "goal", value.goal); | ||||
serialize.NumberU16_Unbounded("pass class", value.passClass); | serialize.NumberU16_Unbounded("pass class", value.passClass); | ||||
serialize.NumberU32_Unbounded("notify", value.notify); | serialize.NumberU32_Unbounded("notify", value.notify); | ||||
} | } | ||||
}; | }; | ||||
template<> | template<> | ||||
struct SerializeHelper<ShortPathRequest> | struct SerializeHelper<ShortPathRequest> | ||||
{ | { | ||||
template<typename S> | template<typename S> | ||||
Done Inline ActionsAlso missed these two. wraitii: Also missed these two. | |||||
void operator()(S& serialize, const char* UNUSED(name), Serialize::qualify<S, ShortPathRequest> value) | void operator()(S& serialize, const char* UNUSED(name), Serialize::qualify<S, ShortPathRequest> value) | ||||
{ | { | ||||
serialize.NumberU32_Unbounded("ticket", value.ticket); | serialize.NumberU32_Unbounded("ticket", value.ticket); | ||||
serialize.NumberFixed_Unbounded("x0", value.x0); | serialize.NumberFixed_Unbounded("x0", value.x0); | ||||
serialize.NumberFixed_Unbounded("z0", value.z0); | serialize.NumberFixed_Unbounded("z0", value.z0); | ||||
serialize.NumberFixed_Unbounded("clearance", value.clearance); | serialize.NumberFixed_Unbounded("clearance", value.clearance); | ||||
serialize.NumberFixed_Unbounded("range", value.range); | serialize.NumberFixed_Unbounded("range", value.range); | ||||
Serializer(serialize, "goal", value.goal); | Serializer(serialize, "goal", value.goal); | ||||
serialize.NumberU16_Unbounded("pass class", value.passClass); | serialize.NumberU16_Unbounded("pass class", value.passClass); | ||||
serialize.Bool("avoid moving units", value.avoidMovingUnits); | serialize.Bool("avoid moving units", value.avoidMovingUnits); | ||||
serialize.NumberU32_Unbounded("group", value.group); | serialize.NumberU32_Unbounded("group", value.group); | ||||
serialize.NumberU32_Unbounded("notify", value.notify); | serialize.NumberU32_Unbounded("notify", value.notify); | ||||
} | } | ||||
}; | }; | ||||
template<typename S> | template<typename S> | ||||
void CCmpPathfinder::SerializeCommon(S& serialize) | void CCmpPathfinder::SerializeCommon(S& serialize) | ||||
{ | { | ||||
Done Inline ActionsIn general here: I don't like the NDEBUG stuff, either it's worth having as LOGINFO or it's not and then delete. wraitii: In general here: I don't like the NDEBUG stuff, either it's worth having as LOGINFO or it's not… | |||||
Not Done Inline ActionsI've just removed these since count of pathfinder threads is not likely to be different from desired one because of a bug. Kuba386: I've just removed these since count of pathfinder threads is not likely to be different from… | |||||
Serializer(serialize, "long requests", m_LongPathRequests.m_Requests); | Serializer(serialize, "long requests", m_LongPathRequests.m_Requests); | ||||
Serializer(serialize, "short requests", m_ShortPathRequests.m_Requests); | Serializer(serialize, "short requests", m_ShortPathRequests.m_Requests); | ||||
serialize.NumberU32_Unbounded("next ticket", m_NextAsyncTicket); | serialize.NumberU32_Unbounded("next ticket", m_NextAsyncTicket); | ||||
serialize.NumberU16_Unbounded("grid size", m_GridSize); | serialize.NumberU16_Unbounded("grid size", m_GridSize); | ||||
} | } | ||||
void CCmpPathfinder::Serialize(ISerializer& serialize) | void CCmpPathfinder::Serialize(ISerializer& serialize) | ||||
{ | { | ||||
SerializeCommon(serialize); | SerializeCommon(serialize); | ||||
} | } | ||||
Not Done Inline ActionsComments start with Capitals. Stan: Comments start with Capitals. | |||||
void CCmpPathfinder::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) | void CCmpPathfinder::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) | ||||
{ | { | ||||
Init(paramNode); | Init(paramNode); | ||||
SerializeCommon(deserialize); | SerializeCommon(deserialize); | ||||
} | } | ||||
void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global)) | void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global)) | ||||
{ | { | ||||
switch (msg.GetType()) | switch (msg.GetType()) | ||||
Not Done Inline ActionsMaybe all the cases should have curly braces. No strong feeling. Stan: Maybe all the cases should have curly braces. No strong feeling. | |||||
{ | { | ||||
case MT_RenderSubmit: | case MT_RenderSubmit: | ||||
{ | { | ||||
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg); | ||||
RenderSubmit(msgData.collector); | RenderSubmit(msgData.collector); | ||||
break; | break; | ||||
} | } | ||||
case MT_TerrainChanged: | case MT_TerrainChanged: | ||||
Show All 11 Lines | void CCmpPathfinder::HandleMessage(const CMessage& msg, bool UNUSED(global)) | ||||
case MT_Deserialized: | case MT_Deserialized: | ||||
UpdateGrid(); | UpdateGrid(); | ||||
// In case we were serialised with requests pending, we need to process them. | // In case we were serialised with requests pending, we need to process them. | ||||
if (!m_ShortPathRequests.m_Requests.empty() || !m_LongPathRequests.m_Requests.empty()) | if (!m_ShortPathRequests.m_Requests.empty() || !m_LongPathRequests.m_Requests.empty()) | ||||
{ | { | ||||
ENSURE(CmpPtr<ICmpObstructionManager>(GetSystemEntity())); | ENSURE(CmpPtr<ICmpObstructionManager>(GetSystemEntity())); | ||||
StartProcessingMoves(false); | StartProcessingMoves(false); | ||||
} | } | ||||
break; | break; | ||||
Done Inline ActionsWill need to be untapped as that's how we do switch cases (don't ask me why). wraitii: Will need to be untapped as that's how we do switch cases (don't ask me why). | |||||
} | } | ||||
} | } | ||||
void CCmpPathfinder::RenderSubmit(SceneCollector& collector) | void CCmpPathfinder::RenderSubmit(SceneCollector& collector) | ||||
{ | { | ||||
g_VertexPathfinderDebugOverlay.RenderSubmit(collector); | g_VertexPathfinderDebugOverlay.RenderSubmit(collector); | ||||
m_PathfinderHier->RenderSubmit(collector); | m_PathfinderHier->RenderSubmit(collector); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 526 Lines • ▼ Show 20 Lines | |||||
u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) | u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) | ||||
{ | { | ||||
LongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, notify }; | LongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, notify }; | ||||
m_LongPathRequests.m_Requests.push_back(req); | m_LongPathRequests.m_Requests.push_back(req); | ||||
return req.ticket; | return req.ticket; | ||||
} | } | ||||
u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance, entity_pos_t range, | u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance, entity_pos_t range, | ||||
Done Inline ActionsUse std::unique_ptr. vladislavbelov: Use `std::unique_ptr`. | |||||
Not Done Inline ActionsI guess one cannot check for m_kill there? Stan: I guess one cannot check for m_kill there? | |||||
const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, | const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, | ||||
entity_id_t group, entity_id_t notify) | entity_id_t group, entity_id_t notify) | ||||
{ | { | ||||
ShortPathRequest req = { m_NextAsyncTicket++, x0, z0, clearance, range, goal, passClass, avoidMovingUnits, group, notify }; | ShortPathRequest req = { m_NextAsyncTicket++, x0, z0, clearance, range, goal, passClass, avoidMovingUnits, group, notify }; | ||||
m_ShortPathRequests.m_Requests.push_back(req); | m_ShortPathRequests.m_Requests.push_back(req); | ||||
return req.ticket; | return req.ticket; | ||||
Not Done Inline ActionsMerge loops ? Stan: Merge loops ? | |||||
} | } | ||||
Not Done Inline Actionsbreak? Stan: break? | |||||
Done Inline ActionsMight as well return. wraitii: Might as well return. | |||||
void CCmpPathfinder::ComputePathImmediate(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, WaypointPath& ret) const | void CCmpPathfinder::ComputePathImmediate(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, WaypointPath& ret) const | ||||
{ | { | ||||
Not Done Inline Actions+ debug_SetThreadName("Pathfinder thread " + std::to_string(index)); Stan: + debug_SetThreadName("Pathfinder thread " + std::to_string(index)); | |||||
Done Inline ActionsFine, didn't know about that. Kuba386: Fine, didn't know about that. | |||||
Done Inline ActionsIt didn't work on linux until very recently thanks to @linkmauve Stan: It didn't work on linux until very recently thanks to @linkmauve | |||||
m_LongPathfinder->ComputePath(*m_PathfinderHier, x0, z0, goal, passClass, ret); | m_LongPathfinder->ComputePath(*m_PathfinderHier, x0, z0, goal, passClass, ret); | ||||
} | } | ||||
Done Inline ActionsI don't think that the busy waiting is good here, why not to use std::condition_variable? vladislavbelov: I don't think that the busy waiting is good here, why not to use `std::condition_variable`? | |||||
Done Inline ActionsYou can't use a condition_variable alone to wait on multiple threads. Adding the proper synchronisation would require more code, which has a higher chance of mistakes now and down the line. wraitii: You can't use a `condition_variable` alone to [[ https://stackoverflow. | |||||
Done Inline ActionsYes, in a direct way, but you can wrap it. Like https://stackoverflow.com/questions/24465533/implementing-boostbarrier-in-c11/24465624#24465624. Or use something like boost::barrier.
It's not much more code and it's relatively simple. I'd prefer user's comfort instead of not saving few lines of code. In fact you may freeze the user machine (if it's not so good to predict busy-waiting). Also it may slow down other threads, I mean like renderer one, if it'd exist.
It's useful but it doesn't guarantee a good stable work. It may wake up every tick or sleep for a long time. Could you measure how many percents it and threads do busy waiting? vladislavbelov: Yes, in a direct way, but you can wrap it. Like https://stackoverflow. | |||||
Done Inline ActionsI've ran some benchmarks:
Overall I'll switch to using condition_variable. I think it's a complicated construct, but at the end of the day we can't occupy 100% of a process spin locking. wraitii: I've ran some benchmarks:
- Yield() isn't useful on OS X, and when spin looping hard we are… | |||||
WaypointPath CCmpPathfinder::ComputeShortPathImmediate(const ShortPathRequest& request) const | WaypointPath CCmpPathfinder::ComputeShortPathImmediate(const ShortPathRequest& request) const | ||||
{ | { | ||||
return m_VertexPathfinder->ComputeShortPath(request, CmpPtr<ICmpObstructionManager>(GetSystemEntity())); | return m_VertexPathfinders.front().ComputeShortPath(request, CmpPtr<ICmpObstructionManager>(GetSystemEntity())); | ||||
} | } | ||||
template<typename T> | template<typename T> | ||||
template<typename U> | template<typename U> | ||||
void CCmpPathfinder::PathRequests<T>::Compute(const CCmpPathfinder& cmpPathfinder, const U& pathfinder) | void CCmpPathfinder::PathRequests<T>::Compute(const CCmpPathfinder& cmpPathfinder, const U& pathfinder) | ||||
{ | { | ||||
Done Inline ActionsWhat does this function do ? Name seems misleading. Stan: What does this function do ? Name seems misleading. | |||||
Done Inline ActionsIt's just there to be specialised, and it pushes requests so I don't think so? wraitii: It's just there to be specialised, and it pushes requests so I don't think so? | |||||
Done Inline ActionsThat line doesn't pushes request does it ? Stan: That line doesn't pushes request does it ? | |||||
Done Inline ActionsIt inserts them at the end (by moving), so that's similar to pushing. wraitii: It inserts them at the end (by moving), so that's similar to pushing. | |||||
Done Inline Actionsstatic_assert(sizeof(T) == 0, "Only specializations can be used"); What part of that statement does that ? Oo There is only one line in that function Stan: static_assert(sizeof(T) == 0, "Only specializations can be used"); What part of that statement… | |||||
Done Inline ActionsAs said above, this is just the template function which gets specialised by L709/714. This base template cannot be used (it's not implemented but there's no clean way to write that because of issues in XCode/VS, see https://stackoverflow.com/a/37593094). wraitii: As said above, this is just the template function which gets specialised by L709/714. This base… | |||||
Not Done Inline ActionsAh thanks for the explanation :) Stan: Ah thanks for the explanation :) | |||||
static_assert((std::is_same_v<T, LongPathRequest> && std::is_same_v<U, LongPathfinder>) || | static_assert((std::is_same_v<T, LongPathRequest> && std::is_same_v<U, LongPathfinder>) || | ||||
(std::is_same_v<T, ShortPathRequest> && std::is_same_v<U, VertexPathfinder>)); | (std::is_same_v<T, ShortPathRequest> && std::is_same_v<U, VertexPathfinder>)); | ||||
size_t maxN = m_Results.size(); | size_t maxN = m_Results.size(); | ||||
size_t startIndex = m_Requests.size() - m_Results.size(); | size_t startIndex = m_Requests.size() - m_Results.size(); | ||||
do | do | ||||
{ | { | ||||
size_t workIndex = m_NextPathToCompute++; | size_t workIndex = m_NextPathToCompute++; | ||||
Not Done Inline ActionsI think you are missing unlock here, or consider std::lock_guard Silier: I think you are missing unlock here, or consider std::lock_guard | |||||
Not Done Inline Actionswell, ok it is unique lock and it unlocks itself when goes out of scope so it is fine I am just thinking that some unlocks could be removed as they are not needed for unique locks? Silier: well, ok it is unique lock and it unlocks itself when goes out of scope so it is fine
I am… | |||||
Not Done Inline Actionsand here as well Silier: and here as well | |||||
if (workIndex >= maxN) | if (workIndex >= maxN) | ||||
break; | break; | ||||
const T& req = m_Requests[startIndex + workIndex]; | const T& req = m_Requests[startIndex + workIndex]; | ||||
PathResult& result = m_Results[workIndex]; | PathResult& result = m_Results[workIndex]; | ||||
result.ticket = req.ticket; | result.ticket = req.ticket; | ||||
result.notify = req.notify; | result.notify = req.notify; | ||||
if constexpr (std::is_same_v<T, LongPathRequest>) | if constexpr (std::is_same_v<T, LongPathRequest>) | ||||
pathfinder.ComputePath(*cmpPathfinder.m_PathfinderHier, req.x0, req.z0, req.goal, req.passClass, result.path); | pathfinder.ComputePath(*cmpPathfinder.m_PathfinderHier, req.x0, req.z0, req.goal, req.passClass, result.path); | ||||
else | else | ||||
result.path = pathfinder.ComputeShortPath(req, CmpPtr<ICmpObstructionManager>(cmpPathfinder.GetSystemEntity())); | result.path = pathfinder.ComputeShortPath(req, CmpPtr<ICmpObstructionManager>(cmpPathfinder.GetSystemEntity())); | ||||
if (workIndex == maxN - 1) | if (workIndex == maxN - 1) | ||||
m_ComputeDone = true; | m_ComputeDone = true; | ||||
} | } | ||||
while (true); | while (true); | ||||
} | } | ||||
Not Done Inline ActionsWhy the scope ? Stan: Why the scope ? | |||||
Done Inline Actionslock gets release automatically at the end of the scope. wraitii: lock gets release automatically at the end of the scope. | |||||
void CCmpPathfinder::SendRequestedPaths() | void CCmpPathfinder::SendRequestedPaths() | ||||
Not Done Inline ActionsIs it hard to do ? Stan: Is it hard to do ? | |||||
Done Inline ActionsIt's trickier, but it's also a micro-improvement that needs further testing. wraitii: It's trickier, but it's also a micro-improvement that needs further testing. | |||||
{ | { | ||||
PROFILE2("SendRequestedPaths"); | PROFILE2("SendRequestedPaths"); | ||||
if (!m_LongPathRequests.m_ComputeDone || !m_ShortPathRequests.m_ComputeDone) | 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); | m_LongPathRequests.Compute(*this, *m_LongPathfinder); | ||||
Done Inline ActionsRealised last night that I need to make sure the results are ordered consistently for threaded/non-threaded/different # of threads here. I think they are by accident right now, but and "ENSURE (is_sorted)" is probably worth adding. wraitii: Realised last night that I need to make sure the results are ordered consistently for… | |||||
} | } | ||||
Not Done Inline ActionsHow hard is it to do? + Profile? Stan: How hard is it to do? + Profile? | |||||
Done Inline ActionsIt's hard enough. There's a high chance of dumb bugs, and this whole diff is already stupidly faster than svn. wraitii: It's hard enough. There's a high chance of dumb bugs, and this whole diff is already stupidly… | |||||
// We're done, clear futures. | |||||
// Use CancelOrWait instead of just Cancel to ensure determinism. | |||||
for (Future<void>& future : m_Futures) | |||||
future.CancelOrWait(); | |||||
{ | { | ||||
PROFILE2("PostMessages"); | PROFILE2("PostMessages"); | ||||
for (PathResult& path : m_ShortPathRequests.m_Results) | for (PathResult& path : m_ShortPathRequests.m_Results) | ||||
{ | { | ||||
CMessagePathResult msg(path.ticket, path.path); | CMessagePathResult msg(path.ticket, path.path); | ||||
GetSimContext().GetComponentManager().PostMessage(path.notify, msg); | GetSimContext().GetComponentManager().PostMessage(path.notify, msg); | ||||
} | } | ||||
for (PathResult& path : m_LongPathRequests.m_Results) | for (PathResult& path : m_LongPathRequests.m_Results) | ||||
{ | { | ||||
CMessagePathResult msg(path.ticket, path.path); | CMessagePathResult msg(path.ticket, path.path); | ||||
GetSimContext().GetComponentManager().PostMessage(path.notify, msg); | GetSimContext().GetComponentManager().PostMessage(path.notify, msg); | ||||
} | } | ||||
Not Done Inline ActionsInvert, early return? Stan: Invert, early return? | |||||
} | } | ||||
m_ShortPathRequests.ClearComputed(); | m_ShortPathRequests.ClearComputed(); | ||||
m_LongPathRequests.ClearComputed(); | m_LongPathRequests.ClearComputed(); | ||||
} | } | ||||
void CCmpPathfinder::StartProcessingMoves(bool useMax) | void CCmpPathfinder::StartProcessingMoves(bool useMax) | ||||
{ | { | ||||
m_ShortPathRequests.PrepareForComputation(useMax ? m_MaxSameTurnMoves : 0); | m_ShortPathRequests.PrepareForComputation(useMax ? m_MaxSameTurnMoves : 0); | ||||
m_LongPathRequests.PrepareForComputation(useMax ? m_MaxSameTurnMoves : 0); | m_LongPathRequests.PrepareForComputation(useMax ? m_MaxSameTurnMoves : 0); | ||||
Threading::TaskManager& taskManager = Threading::TaskManager::Instance(); | |||||
Done Inline Actionswe could lock/unlock only when using multithreads, branch is already here so ... Silier: we could lock/unlock only when using multithreads, branch is already here so ... | |||||
for (size_t i = 0; i < m_Futures.size(); ++i) | |||||
{ | |||||
ENSURE(!m_Futures[i].Valid()); | |||||
Not Done Inline ActionsCan't you just move that out of the loop and check for !stop ? Stan: Can't you just move that out of the loop and check for !stop ? | |||||
Done Inline ActionsThis is another spin-lock: we continue looping until all threads are done computing, so it needs to be reset at the beginning of the loop anyways. Putting it outside the loop isn't much neater imo, but the whole thing might be rewritten in a slightly more functional way I think. wraitii: This is another spin-lock: we continue looping until all threads are done computing, so it… | |||||
// 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) | bool CCmpPathfinder::IsGoalReachable(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) | ||||
{ | { | ||||
PROFILE2("IsGoalReachable"); | PROFILE2("IsGoalReachable"); | ||||
u16 i, j; | u16 i, j; | ||||
Pathfinding::NearestNavcell(x0, z0, i, j, m_GridSize, m_GridSize); | Pathfinding::NearestNavcell(x0, z0, i, j, m_GridSize, m_GridSize); | ||||
if (!IS_PASSABLE(m_Grid->get(i, j), passClass)) | if (!IS_PASSABLE(m_Grid->get(i, j), passClass)) | ||||
Done Inline ActionsThis is a bit of a weird syntax in C++, but it's basically worker[into].insert() in JS. The alternative in C++ would be a macro or an explicit IF, but I think that's fine to use. wraitii: This is a bit of a weird syntax in C++, but it's basically `worker[into].insert()` in JS. The… | |||||
Done Inline ActionsToo unsafe for me. I'd replace the function by: template <typename T, typename U> void CCmpPathfinder::PushRequestsToWorkers(std::vector<T>& from, std::deque<U>& into) With probably a type validation like std::is_same<U, Async*PathRequest>::value. vladislavbelov: Too unsafe for me. I'd replace the function by:
```lang=cpp
template <typename T, typename U>… | |||||
Done Inline ActionsOne can't actually, because I need to call either the longRequest or the shortRequest of the worker in the inner loop. This is basically rewritable as CCmpPathfinder::PushRequestsToWorkers(std::vector<T>& from, bool toLongRequests) [...] if (toLongRequests) worker->m_LongRequests.insert([...]) else worker->m_ShortRequests.insert([...]) If I pass the worker, I lose the whole point of the function as the for-loop has to be outside the function. wraitii: One can't actually, because I need to call either the longRequest or the shortRequest of the… | |||||
Done Inline ActionsIf you wanna leave the function loop, I'd prefer to add more code but with an explicit access. Like: template<typename T> CCmpPathfinder::PushRequestsToWorkers(std::vector<T>& from) { // ... worker->AddRequests<T>( std::make_move_iterator(from.end() - amount), std::make_move_iterator(from.end()) ); // ... } template<typename T> void AsyncPathfinderWorkerThread::AddRequests(... it_begin, ... it_end) { auto& container = RequestsContainer<T>(); container.insert(container.begin(), std::make_move_iterator(it_begin), std::make_move_iterator(it_end)); } template<typename T> void AsyncPathfinderWorkerThread::RequestsContainer(); template<> std::deque<AsyncShortPathRequest>& RequestsContainer() { return m_ShortRequests; } // ... It'd be the same by performance, but safer I think. As you can't pass an arbitrary container member. vladislavbelov: If you wanna leave the function loop, I'd prefer to add more code but with an explicit access. | |||||
Done Inline ActionsI suppose ultimately this would be if-constexpr-ed. I'll go with your way as it does seem more readable and I didn't think of it. wraitii: I suppose ultimately this would be if-constexpr-ed. I'll go with your way as it does seem more… | |||||
Done Inline ActionsDone in a slightly simpler manner. wraitii: Done in a slightly simpler manner. | |||||
m_PathfinderHier->FindNearestPassableNavcell(i, j, passClass); | m_PathfinderHier->FindNearestPassableNavcell(i, j, passClass); | ||||
Not Done Inline Actionsrange loop ? Stan: range loop ? | |||||
Done Inline Actionssure :) Kuba386: sure :) | |||||
return m_PathfinderHier->IsGoalReachable(i, j, goal, passClass); | return m_PathfinderHier->IsGoalReachable(i, j, goal, passClass); | ||||
} | } | ||||
Not Done Inline Actionsshould not this comment be elsewhere ? Silier: should not this comment be elsewhere ? | |||||
Not Done Inline ActionsMy intention was that if people did something stupid, they would trigger the assert, and then that comment would tell them why this was a bad idea. wraitii: My intention was that if people did something stupid, they would trigger the assert, and then… | |||||
Not Done Inline Actionsdo we want to just prevent pushing or really fail if some worker is computing ? Silier: do we want to just prevent pushing or really fail if some worker is computing ?
Is computing… | |||||
Not Done Inline ActionsAt this point, we are about to start computing pathes for new turn. Kuba386: At this point, we are about to start computing pathes for new turn.
Fact that we are starting… | |||||
Done Inline ActionsWell, not exaclty 'for new turn', because StartProcessingRequests is called three times during single turn. Kuba386: Well, not exaclty 'for new turn', because StartProcessingRequests is called three times during… | |||||
bool CCmpPathfinder::CheckMovement(const IObstructionTestFilter& filter, | bool CCmpPathfinder::CheckMovement(const IObstructionTestFilter& filter, | ||||
entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, | entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, | ||||
pass_class_t passClass) const | pass_class_t passClass) const | ||||
{ | { | ||||
PROFILE2_IFSPIKE("Check Movement", 0.001); | PROFILE2_IFSPIKE("Check Movement", 0.001); | ||||
Not Done Inline ActionsCould not be here used atomic variable instead locking this? Silier: Could not be here used atomic variable instead locking this? | |||||
// Test against obstructions first. filter may discard pathfinding-blocking obstructions. | // Test against obstructions first. filter may discard pathfinding-blocking obstructions. | ||||
// Use more permissive version of TestLine to allow unit-unit collisions to overlap slightly. | // Use more permissive version of TestLine to allow unit-unit collisions to overlap slightly. | ||||
// This makes movement smoother and more natural for units, overall. | // This makes movement smoother and more natural for units, overall. | ||||
CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); | CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSystemEntity()); | ||||
if (!cmpObstructionManager || cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r, true)) | if (!cmpObstructionManager || cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r, true)) | ||||
return false; | return false; | ||||
Show All 10 Lines | ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter, | ||||
if (!cmpObstructionManager) | if (!cmpObstructionManager) | ||||
return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR; | return ICmpObstruction::FOUNDATION_CHECK_FAIL_ERROR; | ||||
if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL)) | if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL)) | ||||
return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION; | return ICmpObstruction::FOUNDATION_CHECK_FAIL_OBSTRUCTS_FOUNDATION; | ||||
// Test against terrain and static obstructions: | // Test against terrain and static obstructions: | ||||
u16 i, j; | u16 i, j; | ||||
Not Done Inline Actions? lyv: ? | |||||
Done Inline Actionsmutex spin-lock wraitii: mutex spin-lock | |||||
Pathfinding::NearestNavcell(x, z, i, j, m_GridSize, m_GridSize); | Pathfinding::NearestNavcell(x, z, i, j, m_GridSize, m_GridSize); | ||||
if (!IS_PASSABLE(m_Grid->get(i, j), passClass)) | if (!IS_PASSABLE(m_Grid->get(i, j), passClass)) | ||||
return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; | return ICmpObstruction::FOUNDATION_CHECK_FAIL_TERRAIN_CLASS; | ||||
// (Static obstructions will be redundantly tested against in both the | // (Static obstructions will be redundantly tested against in both the | ||||
// obstruction-shape test and navcell-passability test, which is slightly | // obstruction-shape test and navcell-passability test, which is slightly | ||||
// inefficient but shouldn't affect behaviour) | // inefficient but shouldn't affect behaviour) | ||||
return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; | return ICmpObstruction::FOUNDATION_CHECK_SUCCESS; | ||||
Not Done Inline ActionsThis would throw a compiler warning. for (;;) is the usual standard for infinite loops. lyv: This would throw a compiler warning. `for (;;)` is the usual standard for infinite loops.
It… | |||||
} | } | ||||
ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter, | ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter, | ||||
Not Done Inline Actions(we use the actual type instead of auto, so that the reader doesn't have to guess) elexis: (we use the actual type instead of auto, so that the reader doesn't have to guess) | |||||
entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, | entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, | ||||
entity_pos_t h, entity_id_t id, pass_class_t passClass) const | entity_pos_t h, entity_id_t id, pass_class_t passClass) const | ||||
{ | { | ||||
return CCmpPathfinder::CheckBuildingPlacement(filter, x, z, a, w, h, id, passClass, false); | return CCmpPathfinder::CheckBuildingPlacement(filter, x, z, a, w, h, id, passClass, false); | ||||
} | } | ||||
ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter, | ICmpObstruction::EFoundationCheck CCmpPathfinder::CheckBuildingPlacement(const IObstructionTestFilter& filter, | ||||
▲ Show 20 Lines • Show All 43 Lines • Show Last 20 Lines |
If you want to change this line replace the c style cast by a c++ cast :)