-
Notifications
You must be signed in to change notification settings - Fork 137
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
Message generation refactoring #80
Conversation
There is currently a limitaiton where I'm using copy functions (ros2/rosidl#650) that are not yet in Foxy and Galactic. Jacob wanted to take a look at backporting those. However, I could also try to do a polyfill for them if we don't want to wait that long. |
9dcea47
to
a91ab7c
Compare
What would be involved in the creation of a "polyfill"? I'm not familiar with the process. |
I'd try implementing the functions from that PR in Rust, using |
@nnmm awesome work, thanks! CI seems to be failing because of a missing message, the file that's causing the problem is |
Oh, forgot to add it, fixed now. CI will still fail because of the issue mentioned above (the copy functions being Rolling-only for now). I still opened this PR for review to parallelize reviewing + work on that issue. |
I think implementing the functions in Rust for now might be good, provided the work isn't too difficult. That way, if I get pulled away to work on other projects you won't be stuck waiting on me for this to get merged/working. |
@@ -96,7 +97,7 @@ impl Node { | |||
qos: QoSProfile, | |||
) -> Result<Publisher<T>, RclReturnCode> | |||
where | |||
T: MessageDefinition<T>, |
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.
Why was the generic type parameter dropped?
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 just didn't see any reason why it should be there.
@@ -0,0 +1,30 @@ | |||
cmake_minimum_required(VERSION 3.5) |
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.
Instead of adding rclrs_example_msgs
, I'd prefer using the example_interfaces
or test_interface_files
packages.
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 test_interface_files
looks good, thanks for the tip.
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.
We need ros2/test_interface_files#18 to make that work, I believe
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'd propose to merge the version with the rclrs_example_msgs
if the PR I linked isn't approved by the time the rest of the review is finished, and then later remove it once it is.
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.
Seems like bringing in test_interface_files
is more complicated than we thought. Would you be fine with leaving in rclrs_example_msgs
for now?
@nnmm I just did a first pass. This is really awesome, you truly outdid yourself dude! I'll do a more extensive review later, but this looks great. |
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.
Mostly a few comments/clarifications that I'd like to know about. Looks excellent, though!
// If there is no more capacity for the next element, resize to the | ||
// next power of two. | ||
// | ||
// A pedantic implementation would check for usize overflow here, but |
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.
Should we be pedantic, though? If ros2_rust
is being used on safety-critical systems, they may want this sort of check. Or is there a better value to test against? What would the performance impact be?
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.
Yeah, good question. I'm not worried about performance impact, but about code complexity/overengineering.
I think ROS 2 messages will never reach this limit – certainly not on 64-bit machines, and I don't know if anyone is building ROS 2 for 32-bit systems. There is also a 1 GB message size limit, so even if we have a 32-bit system, there won't be any sequences with more than usize/2
elements. Besides, there are probably plenty of unchecked additions in rcl and rmw that would cause trouble too.
So I propose to not be pedantic about usize overflow, but to just leave comments like this one when such an issue is encountered. If anyone ever really wants to use ros2_rust
in a safety-critical context, they'll need to go through the code and refactor a whole lot of stuff anyway, and then at least there's a comment to point to potential issues.
54e662c
to
f26454c
Compare
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 another comment, mostly a stylistic thing. Otherwise, this looks good to me!
Thank you very much for your work!
impl Clone for $string { | ||
fn clone(&self) -> Self { | ||
let mut msg = Self::default(); | ||
// SAFETY: This is doing the same thing as rosidl_runtime_c__String__copy. |
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'm not sure this comment addresses why this is safe. I feel like these safety comments should be written so that someone won't need to dive into an external C library to verify, unless they really wanted to.
Something like: "This is safe because neither self.data
nor self.size
are mutated, and self.data
is guaranteed to be pointing to valid memory by this point, and self.size
is guaranteed to be correct"?
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 rephrased it to be a little clearer. The safety argument is basically: The rosidl_runtime_c__String__copy
function is implemented exactly the same way as this Clone
instance: https://github.com/ros2/rosidl/blob/caef9b8f820dfcb16e6a2df9118c5669cc816642/rosidl_runtime_c/src/string_functions.c#L135-L143
We may assume that the libraries we're binding to are correct, so when we do the same thing as that library, it's safe.
I'd say that direct access to a field is the most flexible and ergonomic option, and only when that for some reason is not allowed (e.g. because it would allow violating some invariant) are getters and setters needed. This PR also has |
@esteve Do you know how to turn off these |
@@ -94,17 +95,16 @@ where | |||
}) | |||
} | |||
|
|||
pub fn publish(&self, message: &T) -> Result<(), RclReturnCode> { |
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.
Re-add reference to message 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.
Done, in a sense.
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 do you mean by "in a sense", are there any caveats?
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.
It's not message: &T
now, it's message: impl MessageCow
, which accepts T
and &T
(aka theConvert
trait from the Matrix chat).
Suggestions for a better name welcome. In case you dislike this design, we could also split it up into two functions, publish_owned()
and publish_ref()
.
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 don't get it, you seemed to be very keen on not having a separate create_subscription function for RMW-compatible types, but publish_owned
and publish_ref
are ok. Could you elaborate?
Let's keep it as is now, though, and we could refine the APIs later if needed, though it'll be more painful.
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.
Sure. The distinction between publish_owned()
and publish_ref()
is of course orthogonal to the question of which message type is published. But you're right that both are a "have two functions for doing the thing vs. having one more general function" question.
In this case here, I'm more on the fence between splitting vs unifying, since the more general function comes at the cost of a more complicated signature: The MessageCow
trait. In the case of the extra create_subscription
function, the create_subscription
function would look the same whether or not we also have a create_rmw_subscription
, so there's no extra complexity in the unified version. Being simpler is not the only argument in favor of one subscription function, but I'll keep that discussion in the other thread.
@@ -109,7 +110,7 @@ impl Node { | |||
callback: F, |
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.
Given that the RmwMessage
API is specific to ros2-rust
, I prefer if there are two separate functions here. One for the expected normal ROS2 API (i.e. idiomatic), and another (e.g. called create_subscription_rmw_type
) for RmwMessage
. That way it's more explicit and the latter can be marked as an optimization for Rust.
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.
Why would we have a redundant function like this? The current Subscription
API can receive Messages
, and both idiomatic and RMW-compatible messages are instances of that. Adding a second API that only supports the latter type is redundant and confuses only, imo. Uniformity is nice.
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.
Precisely for uniformity with the other bindings, no other bindings accept these so-call rmw types, all of them support idiomatic types only. Adding a second API clears that confusion by explicitly signaling that it's specific to ros2-rust
. Rust, very much like Python, and unlike C++, encourages explicitness vs implicitness. Explicitness is nice too.
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 don't want the more efficient message type to become a second-class citizen by only being accessible through less-than-canonical APIs. There shouldn't be any confusion with the two message types, since (1) I tried to document it really well and (2) even if someone skips the documentation, they will not be confused, they simply won't know that there's also the RMW-compatible message type. For such a user, the API will arguably appear more uniform with other languages than if there was an create_subscription_rmw_type
function they need to look up.
Besides, there is also lots of stuff that is specific to rclcpp
, and none of it is marked as such (topic statistics, generic pub/sub, intraprocess, etc.).
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.
through less-than-canonical APIs
The RMW-compatible message API is already non-canonical, because it does not have any counterpart in the other bindings. APIs are not only about names, but also semantics, the arguments they accept and the values they return. Having a distinctly different name emphasizes that it behaves differently from the other bindings, and that users should expect differences.
Besides, there is also lots of stuff that is specific to rclcpp, and none of it is marked as such (topic statistics, generic pub/sub, intraprocess, etc.).
Because the goal is that eventually that functionality might be incorporated into other bindings. For example, rclc
could potentially get support for intraprocess.
Anyway, I rather not waste more time on this, but it's important to get APIs right early on, because changing them later is far more difficult
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.
As for counterparts in other bindings, here's my perspective for posterity:
- Python is slow anyway, and usability of a raw C message (= our "RMW-compatible" message) would be absymal from a Python perspective, so of course the messages in that language are an idiomatic version that are converted to/from the C message.
- C and C++ have the privilege of being natively RMW-compatible, so they can be both idiomatic and maximally efficient. They are the counterpart to the RMW-compatible and the idiomatic messages.
- I assume other language bindings also use their own idiomatic message types that are converted to/from C messages. However, I believe none of the other languages are quite as performance-focused as Rust/C/C++.
Hence, Rust is kind of unique, in that its users expect maximum performance but it can't achieve that (yet) by serializing/deserializing its standard library types to/from CDR directly. So you could argue that it would be canonical to use RMW-compatible messages because that's what the other performance-focused language bindings do. Or you could argue that it would be canonical to use idiomatic messages because that's what all language bindings do.
I think the solution in this PR, to offer both, isn't too bad. And I placed the idiomatic messages in the <pkg>::msg
namespace and the RMW-compatible ones in <pkg>::msg::rmw
, because they can't be both in the same namespace, but I believe we shouldn't hide the RMW-compatible messages more than that if it's not technically necessary.
I don't think the time spent discussing on this is wasted. Hope I'm not too stubborn! If you insist, I'll add the separate function. Or I can also poll the ROS + Rust people I know to get more input. But since you approved, I take it that it's okay to merge as it is now and potentially discuss it again later.
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 assume other language bindings also use their own idiomatic message types that are converted to/from C messages. However, I believe none of the other languages are quite as performance-focused as Rust/C/C++.
Ada reuses the C data layout in memory, so there is no overhead involved. The price is somewhat un-idiomatic use when sequences are involved that I hope to alleviate at some point in the future.
I'll look into it, neither of those tests should run on Rust files and there are no more C files to be checked. |
Thanks for fixing the linter @esteve. Now this PR is ready to merge from my side: I added the workaround for the |
Thanks, there is still some unanswered feedback, could you address it? CI is not passing either, do you know why? |
4c0f6ae
to
3aecf1f
Compare
Previously, only messages consisting of basic types and strings were supported. Now, all message types will work, including those that have fields of nested types, bounded types, or arrays. Changes: - The "rsext" library is deleted - Unused messages in "rosidl_generator_rs" are deleted - There is a new package, "rosidl_runtime_rs", see below - The RMW-compatible messages from C, which do not require an extra conversion step, are exposed in addition to the "idiomatic" messages - Publisher and subscription are changed to work with both idiomatic and rmw types, through the unifying `Message` trait On `rosidl_runtime_rs`: This package is the successor of `rclrs_msg_utilities` package, but doesn't have much in common with it anymore. It provides common types and functionality for messages. The `String` and `Sequence` types and their variants in that package essentially wrap C types from the `rosidl_runtime_c` package and C messages generated by the "rosidl_generator_c" package. A number of functions and traits are implemented on these types, so that they feel as ergonomic as possible, for instance, a `seq!` macro for creating a sequence. There is also some documentation and doctests. The memory for the (non-pretty) message types is managed by the C allocator. Not yet implemented: - long double - constants - Services/clients - @verbatim comments - ndarray for sequences/arrays of numeric types - implementing `Eq`, `Ord` and `Hash` when a message contains no floats
This requires a cfg for conditionally compiling the code depending on the ROS distro. I'm setting that cfg in the build script instead of colcon-ros-cargo so that building with cargo doesn't become too annoying.
Previously, only messages consisting of basic types and strings were supported. Now, all message types will work, including those that have fields of nested types, bounded types, or arrays.
Changes
Message
traitOn
rosidl_runtime_rs
: This package is the successor ofrclrs_msg_utilities
package, but doesn't have much in common with it anymore.It provides common types and functionality for messages. The
String
andSequence
types and their variants in that package essentially wrap C types from therosidl_runtime_c
package and C messages generated by the "rosidl_generator_c" package.A number of functions and traits are implemented on these types, so that they feel as ergonomic as possible, for instance, a
seq!
macro for creating a sequence. There is also some documentation and doctests.The memory for the (non-pretty) message types is managed by the C allocator.
Not yet implemented
Eq
,Ord
andHash
when a message contains no floatsHow to review
I know that this is a really large PR. If desired, I can split it up for reviewing. Here are some steps I suggest to get familiar with the changes:
message_demo
and read the codecargo doc
s for therosidl_runtime_rs
crate and read themmsg.rs.em
and__init__.py
files