So far I have been trying to do the work on dtNavMesh so that the data
could be as read only as possible. But other than that I have not
tinkered too much about parallel stuff.
I like the idea of separating the navmesh data and the query/tools
which use it. Maybe the query class could be always thread safe, and
there would be no reason to add some defaults for the methods for a
common use case or create duplicate functions which does the same
thing by thread safely. Should make the dtNavMesh class cleaner too.
So basically the dtNavMesh would have methods to add, remove and
change data, and dtNavMeshQuery would have all the finds, getters and
querys in there.
One additional pro for the separate dtNavMesh query is that it would
also allow to add some extra costs or path find blockers per query.
_
A related problem I have not been able to solve are:
- How to handle concurrent data changes?
- Is the data allowed to change while the query methods are running?
Locks, task queues?
Adding and removing tiles happen relatively seldom, but changing
polygon flags and types can happen more often. I wonder if locks are
going to work since setting a polygon flag might block until a path
request is finished.
My idea with queues was that all the queries and data changes would go
into one work queue and data change tasks would work like flush, so
they would wait for all the previous tasks to finish until they are
executed. So normally everything would work in parallel.
I don't want to that much functionality to Detour, though. Everybody
has their own job managers and I would rather allow Detour to be
integrated to those. I don't wan to add a thread library to Detour
either. I also want to keep the general use case as simple as it is
now.
These ideas probably reveal my total lack of knowledge on parallel
programming :)
--mikko
For multiple threads to share a node pool (which you want since they
are so large), you need only wrap the functions which change them in a
mutex. We use recast on World of Warcraft and to do some of the
longest possible pathfinds take a matter of milliseconds. Obviously
there wouldn't be concurrent pathfinds going on, but it would allow
different threads to share the data.
As for Mikko's issues of concurrent data changes and whether the data
is allowed to change during a query, a mutex or two or three would
solve the concurrency issue, and basically the answer to changing the
data during a query is no. Sure you lose some performance by sleeping
a thread while you wait for a lock, but even if you could find a
graceful way to recover from changing the data mid-query, I would
guess the overhead required to do so would make it even less useful
than the delay in sleeping for the mutex.
C# has standard syntax for locking objects and makes things really
easy, but it's just syntactic sugar that you don't really need. You
can easily enough do something like:
bool dataMutex;
void addData() {
while (dataMutex) sleep(x);
dataMutex = true;
...
dataMutex = false;
}
This would have essentially the same effect of queuing up changes to
the nav mesh data, but without the need to devise a uniform structure
to build an actual queue of changes to be applied.
Yes, it is right there moving along the tangent towards the off-topic planet ;)
I think the case Bill has can be easily handled by just making a
request queue and handle all that stuff on separate thread if
multithreading is necessary. The case Cam has is that there are
potentially many path requests in flight all the time. Adding mutex
there will kill the performance, and using just single path finder
with queue will not scale.
As a first step, I think I will separate the data and the algorithms.
That should allow similar use that you guys are doing currently, as
well as it should allow to have multiple navigation queries in flight
at the same time. It would be up to the user to sync the data updates.
If there are more input on how the data updates could be handled
nicely, I'm interested to hear that. Or any thoughts on the
separation, although I think it is pretty straight forward.
--mikko
Lots of interesting bits to chew on. I really like the idea of
read/write semantics.
I'm on vacation this week, but I will try to craft a better reply once
I've got a bit time to think about that stuff.
To quickly answer your question, Detour currently does all the queries
as one-shot. My plan is to make it iterative, but only once I have
good interface to do so (i.e. I dont want the navmesh class to have
state).
The current detour navmesh class also has a bit too much stuff in it.
It contains the navigation data which is always treated read-only,
except on several methods which clearly indicate that they add, remove
or change the data.
In addition to that it also contains the open and closed lists, which
are changed by the path finder (and some other searches). I think it
is time to separate the classes as discussed earlier so that it would
be possible to have multiple path find queries running in parallel and
the data would be just data.
There are basically two kinds of queries. Some require "state", like
A* and some directly adjust the output without need for any support
data structures. One such is string pulling another it finding the
nearest navmesh poly. In general the ones that have state also are
really unpredictable on time vs. the others have much more predictable
execution time. It would be interesting to solve, how to make the work
load more consistent, but at first I would like to make a small step
towards concurrency.
There is really nice presentation by the Kynapse guys from GDC'10
regarding parallelism and navigation. Coudn't find the link, though.
The read/write semantics and stages made me think how this all ties to
my idea of navigation loop, and it did get some ideas flowing... so I
think I have my head filled with ideas to get me busy for the rest of
my vacation next week :)
--mikko
I believe the presentation you are talking about is here: http://area.autodesk.com/gdc/ondemand2
One-shot queries can make it tricky to handle parallelism/concurrency if they take so long that other queries aren't possible during a frames time.
If some queries have to be delayed to another frame and navmesh changes are mixed in it is easy to get non-deterministic outputs under parallelism (change-request and path-planning-requests are collected from different threads and therefore the order between the queries from different threads isn't deterministic anymore - during different replays a certain path-planning-request might see a change while during another replay the change-request happens afterwards).
Oh, there is another way to enable parallel request execution other than making requests accessible for an outside parallelism runtime:
the Detour user needs to provide callback-functions/objects for task scheduling/dispatching that are then called internally by Detour.
Imo the first method (offer tasks) allows better parallelization by the user - though it publishes the internal request-representation...
Cheers and have a nice vacation!
Bjoern