Add method to iterate bodies without copy - #1499
Conversation
|
If we're going to do this, might as well have a return value for the passed in function, where user could control body removal. |
I guess would it be better to have two functions ( My assumption is that callers are locking the environment before calling Iterate though, so maybe there's no need to optimize for the concurrent case? |
Okay, how did you come to this assumption? No function in EnvironmentBase assumes the environment lock is locked before it is called, so it is dangerous if you all of a sudden start doing that without documenting anything(!) That being said, an API to access environment services without having to check the lock every time is necessary if we want to optimize even further. One idea I have been flirting with is to pass in a reference to |
This function is written such that it shouldn't require that you lock the env to call it, but again my assumption about the openrave locking model is that if you want to reliably read/write the data on a body you have acquired a pointer to, you need to hold the environment lock. Checking this, I found the following in the openrave architecture doc, emphasis added:
I suppose that a caller could re-acquire the lock inside their mapped function for each body, meaning you could have concurrent operations, but that seems less likely than taking the lock once then iterating.
Yeah, I think that adding granularity along the lines of having both void DoFunction(...args) {
DECLARE_LOCK(..);
DoFunctionLocked(...args);
}
void DoFunctionLocked(...args) {
// ...
}might be useful in allowing for more fine-grained lock control. I don't have any exact use cases off the top of my head though. |
All functions from EnvironmentBase are multi-thread safe at the moment, meaning that a user can call them without locking the environment. In order to have a non-thread safe function, it needs to be super explicit since otherwise users can forget. my best solution for this is to force passing in the env lock. for example by declaring: virtual void IterateBodies(const std::function<BodyReturnCode(const KinBodyPtr&)>& mapFunction, EnvironmentLock& envlock) = 0; |
|
Updated to take locks internal to the iteration functions. Also added FilterBodies; you could definitely do fold as filter but it seems a bit more foolproof / easier to read if you see code that does an |
| /// The environment interface mutex is locked internally. | ||
| /// The callback function must not call any methods that would cause bodies to be added or removed from the environment, | ||
| /// as this would cause a deadlock attempting to exclusively lock the interface mutex while the thread already holds it in shared mode. | ||
| virtual void IterateBodies(const std::function<void(const KinBodyPtr&)>& mapFunction) = 0; |
There was a problem hiding this comment.
Perhaps the function pointer can take in KinBody& and allow for modification?
There was a problem hiding this comment.
You're right, no need to expose the encapsulation here. Changed both instances.
There was a problem hiding this comment.
Remembered why this contract includes the pointer - it's to allow using this as a way to query a subset of bodies more efficiently, e.g:
std::vector<KinBodyPtr> bodiesMatchingCriteria;
env->IterateBodies([&bodiesMatchingCriteria](const KinBodyPtr& body) {
if (body->MeetsCriteria()) {
bodiesMatchingCriteria.emplace_back(body);
}
});
// Outside of interface lock, actually process these bodies...If you have many thousands of bodies, this is a lot more performant than copying all bodies then erasing a bunch of them again. Have reverted the contract here (and explained why in the docstring), but for FilterBodies still only take a kinbody reference.
| /// Note that removal of bodies happens concurrently with body iteration. | ||
| /// The environment interface mutex is locked internally in exclusive mode, | ||
| /// so the predicate must not make any calls that would also attempt to lock this mutex. | ||
| virtual void FilterBodies(const std::function<bool(const KinBody&)>& predicate) = 0; |
There was a problem hiding this comment.
@rschlaikjer perhaps a better name would be RemoveBodiesIf
For environments with large numbers of bodies, the existing pattern of
GetBodiesinto local vector -> iterate -> clear vector results in significant overhead from creating and destroying all the extra shared pointers.Add an API to allow mapping a function over the bodies in the environment directly, removing the need to allocate memory / increment and decrement refcounts.
As written, this implementation adds a safety check to prevent users modifying the bodies in the env while an iteration is occurring - this is pretty low-overhead, but it could be removed if we want to let users shoot themselves in the foot.