-
Notifications
You must be signed in to change notification settings - Fork 72
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
Add plugin support for generated C++ message headers #32
Conversation
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 like the idea of both options (full replacement vs. macro based extensibility). While you can easily shoot yourself in the foot with this it provides advanced users with a very powerful option to extend the generated code.
This repo currently uses a single branch for all active ROS distros. I am not sure if such an invasive change should be applied to all ROS distros though (even though it intends to be backward compatible / not changing anything until explicitly being used). I would lean towards either only targeting future ROS distros or Kinetic a newer (as a compromise).
I also added a few comments inline.
cmake/gencpp-extras.cmake.em
Outdated
) | ||
list(APPEND ALL_GEN_OUTPUT_FILES_cpp ${GEN_OUTPUT_FILE}) | ||
# check if a user-provided header file exists | ||
if(EXISTS ${PROJECT_SOURCE_DIR}/include/${ARG_PKG}/${MSG_SHORT_NAME}.h) |
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.
Nitpick: please wrap ${PROJECT_SOURCE_DIR}/include/${ARG_PKG}/${MSG_SHORT_NAME}.h
in quotes.
scripts/msg.h.template
Outdated
print('#include <%s/plugin/%s.h>'%(spec.package, spec.short_name)); | ||
else: | ||
have_plugin = False | ||
}@ |
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.
Nitpick: rewrite as:
@{
import os
has_plugin = os.path.exists('include/%s/plugin/%s.h' % (spec.package, spec.short_name))
}@
and then use it the same way as for the other macros:
@[if has_plugin]@
#include <@(spec.package)/plugin/@(spec.short_name).h>
@[end if]@
scripts/msg.h.template
Outdated
@@ -64,6 +75,9 @@ struct @(spec.short_name)_ | |||
typedef @(spec.short_name)_<ContainerAllocator> Type; | |||
|
|||
@# constructors (with and without allocator) | |||
@[if have_plugin]@ | |||
#ifndef @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_CUSTOM_CONSTRUCTOR |
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.
The use case of initializing the fields with different values would imo be better addressed by specifying those default values in the .msg
file (as it is possible in ROS 2). That approach ensures a consistent initialization across all languages. Therefore I would rather not have a CUSTOM_CONSTRUCTOR
macro.
Or do you have other use cases in mind beside that?
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 agree that specifying default values in the msg file would be preferable in most cases. But this feature is not available in ROS 1 to my best knowledge and custom constructors could go further, e.g. initialize values nested inside a message field. An example use case would be quaternions nested inside messages that represent orientation and rotation (like in geometry_msgs/Transform
) which could be initialized to a unit quaternion in that context, while this is not necessarily true for arbitrary quaternions.
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.
(just my 2 cents on this, in the hypothesis that we had default values in ROS1)
If it was possible, I do believe that users should be able specify default values for nested fields as for any other field type and generate a constructor accordingly. (so in your example geometry_msgs/Transform.msg
would specify the default value of the rotation
field to be a unit Quaternion and the constructor of the Transform
message in every language would initialize the rotation
field accordingly)
scripts/msg.h.template
Outdated
@@ -210,6 +235,9 @@ namespace ros | |||
namespace serialization | |||
{ | |||
|
|||
@[if have_plugin]@ | |||
#ifndef @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_CUSTOM_SERIALIZER |
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 beneficial if all macro names share a common prefix which includes the term "plugin", e.g. @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_PLUGIN_
. Maybe the "custom" part can be skipped since by providing the macro it is kind of implied that a custom version is provided?
That would name this macro: @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_PLUGIN_SERIALIZER
.
scripts/msg.h.template
Outdated
@@ -237,6 +270,9 @@ namespace message_operations | |||
{ | |||
|
|||
@# Printer operation | |||
@[if have_plugin]@ | |||
#ifndef @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_CUSTOM_PRINTER |
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.
Same as above: @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_PLUGIN_PRINTER
.
scripts/msg.h.template
Outdated
@# Plugins | ||
@[if have_plugin]@ | ||
#ifdef @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_PLUGIN | ||
@(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_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.
I think this macro name should contain something in the name which clarifies that it is "additional stuff in the class body".
Together with the feedback from another comment below maybe: @(spec.package.upper())_MESSAGE_@(spec.short_name.upper())_PLUGIN_CLASS_BODY
.
Thanks for your feedback, @dirk-thomas. I will try to address your comments as soon as possible. We simply add this version of gencpp to a custom workspace if needed, and it does not have any side effects so far. So it would not be strictly necessary to have this patch in released versions of gencpp, but I thought it might be useful for other users, too. |
I finally addressed @dirk-thomas' comments and pushed an update version of the plugin patch. Feel free to create a separate branch for kinetic and newer and change the target branch of the PR. But as the patch has almost no risk of breaking existing packages and has no effect on generated message headers unless a plugin header would exist, I do not see a reason to not release it for indigo, too. |
…ter the initial build Without this dependency, if a plugin header has been added after the initial build of a message package, it would not be picked up even if a cmake invocation is enforced with the `--force-cmake` argument or manually because cmake considers the generated header as up-to-date. The patch also adds some cmake output to indicate that a user-defined message or plugin header has been found in the package source.
792046d
to
c612b51
Compare
Great - thank you. In 270a736 I just refactored the order to do the following which I think is easier to read:
|
@meyerj Can you please document the new feature in the wiki with a note that it is / will be available as of version 0.6.0 of the package. Thanks. |
Thanks for merging! Very helpful to have that feature merged upstream. I'll update the ROS wiki soon. Which page would you prefer?
I think a new section in (*) would fit best. |
Sounds good to me. I leave it up to your judgement. Please post a link to the diff here for future readers to easily find it. Thanks! |
New section added here: Customizing generated message headers for C++. |
This customization of the CMake for copying over message headers is no longer necessary as of gencpp 0.6.0 (after ros/gencpp#32 has been merged). Signed-off-by: Hamal Marino <hamal.marino@intermodalics.eu>
Thank you for writing the docs. I updated the version as of when this feature is available since the feature will become available in older ROS distros once a the new version of this package is released for them (since they all share the same branch). |
Thanks for the useful PR, I've already made use of it :) |
@peci1 To avoid that your comment gets lost I would suggest to create a new ticket rather than commenting on an already closed one.
Depending on what you mean with free functions you might be able to just put them into the |
This pull requests adds plugin support to gencpp. It is motivated by the plugin feature in Eigen to extend the
MatrixBase
class, but uses a slightly different approach. Please consider it as a proof-of-concept, a request for comments and a starting point for discussions.It is basically a combination of two patches:
The possibility to provide user-defined message headers for all or a subset of messages and skip generation completely. Check https://github.com/orocos/kdl_msgs for an example use case, where we completely replace the generated struct with a C++11 template alias for a native KDL type.
The possibility to provide a user-defined plugin header. Given that the message type is
foo_msgs/Bar
, a header placed atinclude/foo_msgs/plugin/Bar.h
inside the package's source folder can define macros toFOO_MSGS_MESSAGE_PLUGIN_CLASS_BODY
)FOO_MSGS_MESSAGE_BAR_PLUGIN_CONSTRUCTOR
)FOO_MSGS_MESSAGE_BAR_PLUGIN_SERIALIZER
)FOO_MSGS_MESSAGE_BAR_PLUGIN_PRINTER
)In any case, if none of the two additional files exists, the generated message header should be exactly the same as without the patch.
Of course as a developer you have to know what you are doing to not break the expected message API or compatibility with other languages. There clearly are some limitations and caveats. The approach is kind of complementary to what is called C++ type adaptation in the roscpp tutorials, where another, existing type is adapted to behave like a ROS message for publishing and subscribing, without identifying them. But in some cases extending or customizing an existing message type is more convenient than to adapt another type, especially because those extensions will immediately apply to nested instances, too.
Just to make my point - not necessarily as a serious suggestion - here are some examples that could be applied to messages similar to the ones in geometry_msgs:
Pose
constructor that initializes it with aposition
andorientation
(C++11 has list initialization as an alternative)Quaternion
with one that initializes the scalar component to 1 if desired (unit quaternion), or only for theTransform
messageoperator[]
for vectors or matricessetZero()
,isZero()
,isValid()
, ...Or, in case of sensor_msgs, additional headers like image_encodings.h could be included by default from the plugin header file.
What I consider as less appropriate is to add conversion functions to and from other types that would add extra dependencies. A separate header file with free functions, like they already exist in various packages today, is the better choice for that purpose.
I assume a similar mechanism could be applied to other generators like genpy, too, for the sake of consistency.