-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make urdf plugable and revive urdf_parser_plugin #13
Conversation
This makes the urdf package load parser plugins. It includes a new api might_handle() that returns a score for how likely the plugin is to be the one the given file format is meant for. This change also makes the urdf xml parser a plugin instead of a special case that is called directly. This breaks ABI with urdf::Model because the model now stores the class loader instance. Signed-off-by: Shane Loretz<[email protected]> Signed-off-by: Shane Loretz <[email protected]>
This pull request has been mentioned on ROS Discourse. There might be relevant details there: https://discourse.ros.org/t/ros-2-tsc-meeting-minutes-2020-07-16/15468/1 |
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a first pass of review, please take a look.
This package looks like it needs a lot of cleanup work in addition to what you've done here. It's all orthogonal, but while we are looking, can you open up follow-up issues to deal with:
- Merging https://github.com/ros2/urdf and https://github.com/ros/urdf
- Replacing the use of
fprintf
here with something else (maybe console_bridge?) - Removing the deprecated APIs
- Deleting/finishing commented-out code
Thanks.
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]> Co-authored-by: Chris Lalancette <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
…os2-make_urdf_plugable
Signed-off-by: Shane Loretz <[email protected]>
@clalancette I've addressed all comments :) . Mind having a second look? |
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Fixes OSX failing build ros/pluginlib#200 Signed-off-by: Shane Loretz<[email protected]> Signed-off-by: Shane Loretz <[email protected]>
@clalancette Mind having a look at the changes since your last review? 🙇 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question here about the performance test.
urdf/CMakeLists.txt
Outdated
# Write files to enable pluginlib to find urdf_xml_parser in build folder | ||
set(fake_urdf_prefix "${CMAKE_CURRENT_BINARY_DIR}/fake_urdf_install") | ||
# Copy urdf_xml_parser to fake prefix lib/ and bin/ | ||
file(MAKE_DIRECTORY "${fake_urdf_prefix}/lib") | ||
file(MAKE_DIRECTORY "${fake_urdf_prefix}/bin") | ||
add_custom_command(TARGET urdf_xml_parser | ||
POST_BUILD | ||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser> | ||
"${fake_urdf_prefix}/lib/" | ||
) | ||
add_custom_command(TARGET urdf_xml_parser | ||
POST_BUILD | ||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser> | ||
"${fake_urdf_prefix}/bin/" | ||
) | ||
# Fake install plugin description file | ||
file(COPY "urdf_parser_description.xml" DESTINATION "${fake_urdf_prefix}/share/urdf/") | ||
# Fake install package.xml | ||
configure_file("package.xml" "${fake_urdf_prefix}/share/urdf/package.xml" COPYONLY) | ||
# Fake install entry in ament_index telling pluginlib where to find plugin description | ||
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/urdf_parser_plugin__pluginlib__plugin/urdf" | ||
"share/urdf/urdf_parser_description.xml\n") | ||
# Fake install entry in ament_index declaring this a package | ||
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/packages/urdf" "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, yuck. If I'm understanding this properly, this is more-or-less recreating the install directory structure under build. I guess its OK, but it seems pretty fragile to me; if we change the ament structure for any reason, this is going to break.
How valuable do you think this performance test is? I'm wondering if it would be more straightforward to not include it at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, yuck. If I'm understanding this properly, this is more-or-less recreating the install directory structure under build. I guess its OK, but it seems pretty fragile to me; if we change the ament structure for any reason, this is going to break.
Yeah. It will break if how pluginlib uses the ament index is changed. I copied this logic from pluginlib's own tests. A cmake function provided by pluginlib that implemented this would eliminate the duplication if something does change
How valuable do you think this performance test is? I'm wondering if it would be more straightforward to not include it at all.
It's straight forward to delete it, though it does give valuable info about the overhead of the plugin infrastructure, and it does prove the plugin infrastructure works. I would rather robustify it than delete it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a valid point @clalancette, these performance tests we just a guide to make sure that the new plugin-based file type check weren't introducing large amounts of overhead. While no one should be parsing URDF/SDF inside their control loop, we don't want this plugin structure to have a substantial impact on existing code execution times.
I think it would be fine to refrain from including this install deployment. This code was more-or-less intended as a one-off verification that the plugin checking times were reasonable. @sloretz can you recap the results of the tests here? Would they be impacted at all by the size of the URDF/SDFormat file being parsed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be fine to refrain from including this install deployment. This code was more-or-less intended as a one-off verification that the plugin checking times were reasonable.
I would recommend keeping it as it's the only test that makes sure the urdf plugin is loadable.
@sloretz can you recap the results of the tests here?
- direct - 0.015 ms
- plugin - 0.743 ms
- direct - 0.045 ms
- plugin - 3.009 ms
- direct - 0.012 ms
- plugin - 2.371 ms
- direct - 0.017 ms
- plugin - 1.970 ms
Would they be impacted at all by the size of the URDF/SDFormat file being parsed?
Yes since cb348db asks tinyxml2 to parse the whole document to decide the confidence. See discussion on #13 (comment) . The size dependent overhead would apply times every plugin with a similar score plugin - ros/sdformat_urdf#1 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3341988 uses a new pluginlib CMake function that enables testing plugins in the same package that built them: ros/pluginlib#201
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a valuable addition for the community @sloretz. Thanks for the PR. I'm sure @gavanderhoorn would appreciate the fact that you implemented his confidence suggestion from the original SDFormat support proposal: ros#34 (comment)
urdf/CMakeLists.txt
Outdated
# Write files to enable pluginlib to find urdf_xml_parser in build folder | ||
set(fake_urdf_prefix "${CMAKE_CURRENT_BINARY_DIR}/fake_urdf_install") | ||
# Copy urdf_xml_parser to fake prefix lib/ and bin/ | ||
file(MAKE_DIRECTORY "${fake_urdf_prefix}/lib") | ||
file(MAKE_DIRECTORY "${fake_urdf_prefix}/bin") | ||
add_custom_command(TARGET urdf_xml_parser | ||
POST_BUILD | ||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser> | ||
"${fake_urdf_prefix}/lib/" | ||
) | ||
add_custom_command(TARGET urdf_xml_parser | ||
POST_BUILD | ||
COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:urdf_xml_parser> | ||
"${fake_urdf_prefix}/bin/" | ||
) | ||
# Fake install plugin description file | ||
file(COPY "urdf_parser_description.xml" DESTINATION "${fake_urdf_prefix}/share/urdf/") | ||
# Fake install package.xml | ||
configure_file("package.xml" "${fake_urdf_prefix}/share/urdf/package.xml" COPYONLY) | ||
# Fake install entry in ament_index telling pluginlib where to find plugin description | ||
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/urdf_parser_plugin__pluginlib__plugin/urdf" | ||
"share/urdf/urdf_parser_description.xml\n") | ||
# Fake install entry in ament_index declaring this a package | ||
file(WRITE "${fake_urdf_prefix}/share/ament_index/resource_index/packages/urdf" "") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a valid point @clalancette, these performance tests we just a guide to make sure that the new plugin-based file type check weren't introducing large amounts of overhead. While no one should be parsing URDF/SDF inside their control loop, we don't want this plugin structure to have a substantial impact on existing code execution times.
I think it would be fine to refrain from including this install deployment. This code was more-or-less intended as a one-off verification that the plugin checking times were reasonable. @sloretz can you recap the results of the tests here? Would they be impacted at all by the size of the URDF/SDFormat file being parsed?
/// \brief Indicate if data is meant to be parsed by this parser | ||
/// \return confidence that the data is meant for this parser, where smaller | ||
/// values mean more confidence, 0 is absolute certainty, and | ||
/// std::numeric_limits<size_t>::max() is no confidence. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My intuition would be that a score of 1.0 is absolute certainty and 0.0 is no confidence (treating this as a probability), but that would require floating point comparisons. Is there a reason to avoid floating point?
I would have trouble thinking what a score like 1000
would mean in this context. To back out a probability from this score would be something like 1-(x/std::numeric_limits<size_t>::max())
and plugging in our hypothetical 1000
in for x
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to avoid floating point?
No reason to avoid floating point.
(treating this as a probability)
I don't think the score can be treated as a probability. One could take a bunch of robot descriptions, run them through the might_handle()
function, and count the number of URDF or non-URDF files in a range of scores. Then one could say "documents that scored between 29 and 42 were URDF files 50% of the time".
I would have trouble thinking what a score like
1000
would mean in this context.
I don't think 1000
would mean anything on its own; it only has meaning when compared against other scores. If someone scores 10 free throws in a minute, are they an NBA basketball player? I don't think that has an answer. Now say there are two people and one scores 10 free throws in a minute while the other scores 2 - which one is more likely to be an NBA player? The latter is more like what's happening here (with a caveat that the free throw test isn't the same for each plugin since each has it's own might_handle() implementation).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(with a caveat that the free throw test isn't the same for each plugin since each has it's own might_handle() implementation).
I think this is where the intuition for a probability comes in. What seems most likely is that plugins are either going to score a 0
for "yes" or std::numeric_limits<size_t>::max()
for "no", at which point anything in between will be a best guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What seems most likely is that plugins are either going to score a 0 for "yes" or std::numeric_limits<size_t>::max() for "no", at which point anything in between will be a best guess.
Yeah, the scores less than std::numeric_limits<size_t>::max()
are a bit arbitrary. Maybe it would be more intuitive if the documentation was "return the position in the string where you became sure this document was for you"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is where the intuition for a probability comes in. What seems most likely is that plugins are either going to score a
0
for "yes" orstd::numeric_limits<size_t>::max()
for "no", at which point anything in between will be a best guess.
Yeah, and not only that, the writer of a new plugin won't know whether they'll be scored "better" than the other plugins without reading all other available plugins. In which case, this regresses towards the binary 0/1 . I'm now rethinking the whole plan to return anything but a boolean :). That being said, it isn't something I would block on, though a more constrained range and/or better documentation on what exactly to return would be helpful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
69b3d49 defines the score range being the position in the document the parser became confident the format was for it, and 025f816 makes the urdf plugin return data.size()
instead of the max size_t value.
From offline conversation on this topic, 31014c0 makes urdf::Model
try the urdf plugin anyways if no plugin things the file is for it.
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz<[email protected]> Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got a few more open comments.
urdf/src/model.cpp
Outdated
|
||
namespace urdf | ||
{ | ||
class ModelImplementation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this can be a final
class, since nothing will be deriving from it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Final in 06474e8
urdf/src/model.cpp
Outdated
ROS_ERROR_STREAM("Exception while creating planning plugin loader " << ex.what() << ". Will not parse Collada file."); | ||
} | ||
size_t best_score = std::numeric_limits<size_t>::max(); | ||
pluginlib::UniquePtr<urdf::URDFParser> best_plugin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is some possibility that no plugin matches, in which case we wouldn't assign anything to best_plugin
until after the for
loop. I think the "no confidence" logic below that currently prevents us from accessing anything bad, but I'd still feel better if we explicitly assigned this to nullptr
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicit initialize to nullptr in f9fe6f9
Signed-off-by: Shane Loretz <[email protected]>
Signed-off-by: Shane Loretz <[email protected]>
@clalancette May I ask for another look? There are new changes since your last review 🙇 |
This makes the urdf package load parser plugins, so it can use collada or sdformat parser plugins in the future.
It also makes the urdf xml parser a plugin instead of a special case.
A new api
might_handle()
returns a score for how likely it is a given string is meant for the plugin.Right now the implementation is very simple, and a bit fragile.
The URDF plugin uses the position of the text
<robot
as the score.This breaks ABI with urdf::Model because it now stores the class
loader instance, so everything downstream of this change must be rebuilt.
@scpeters @IanTheEngineer FYI
Requires ros/pluginlib#199