Skip to content

Commit 2ae42e7

Browse files
paudrowwjwwoodemersonknappjacobperron
authored
REP 2007: Type Adaptation (ros-infrastructure#303)
* Add draft of rep-2007 Signed-off-by: Audrow Nash <[email protected]> * Update draft of 2007 Signed-off-by: Audrow Nash <[email protected]> * Minor edits Signed-off-by: Audrow Nash <[email protected]> * Update type to Standards Track Signed-off-by: Audrow Nash <[email protected]> * Change status to "Draft" Signed-off-by: Audrow Nash <[email protected]> * Update wording of the abstract Signed-off-by: Audrow Nash <[email protected]> * Change to `AdaptType` and update including "Type" discussion Signed-off-by: Audrow Nash <[email protected]> * Add a link to the REPL Signed-off-by: Audrow Nash <[email protected]> * Update examples to use `make_shared` Signed-off-by: Audrow Nash <[email protected]> * Remove placeholders Signed-off-by: Audrow Nash <[email protected]> * Update abstract Signed-off-by: Audrow Nash <[email protected]> * Number publishers created in examples Signed-off-by: Audrow Nash <[email protected]> * Add performance reason to motivation Signed-off-by: Audrow Nash <[email protected]> * Apply suggestions from code review Co-authored-by: Emerson Knapp <[email protected]> * Add ticks around cv::Mat Co-authored-by: Jacob Perron <[email protected]> * Update title and abstract Signed-off-by: Audrow Nash <[email protected]> * Update the specification to include services and actions Signed-off-by: Audrow Nash <[email protected]> * Change Adapt to Adapter Signed-off-by: Audrow Nash <[email protected]> * Include note that ROS types can be used with custom types Signed-off-by: Audrow Nash <[email protected]> * Add several design considerations Signed-off-by: Audrow Nash <[email protected]> * Fix typo Signed-off-by: Audrow Nash <[email protected]> * Improve wording Signed-off-by: Audrow Nash <[email protected]> * Fix glossary Signed-off-by: Audrow Nash <[email protected]> * Make headings consistent Signed-off-by: Audrow Nash <[email protected]> * Add feature progress section Signed-off-by: Audrow Nash <[email protected]> * Fix bullet list Signed-off-by: Audrow Nash <[email protected]> * Apply suggestions from code review Signed-off-by: Audrow Nash <[email protected]> Co-authored-by: William Woodall <[email protected]> * Update feature progress with issues and PRs Signed-off-by: Audrow Nash <[email protected]> * Update meta info Signed-off-by: Audrow Nash <[email protected]> Co-authored-by: William Woodall <[email protected]> Co-authored-by: Emerson Knapp <[email protected]> Co-authored-by: Jacob Perron <[email protected]>
1 parent b0746a4 commit 2ae42e7

File tree

1 file changed

+392
-0
lines changed

1 file changed

+392
-0
lines changed

rep-2007.rst

+392
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
REP: 2007
2+
Title: Type Adaptation Feature
3+
Author: Audrow Nash <[email protected]>, William Woodall <[email protected]>
4+
Status: Accepted
5+
Type: Standards Track
6+
Content-Type: text/x-rst
7+
Created: 28-Jan-2021
8+
Post-History:
9+
10+
11+
Abstract
12+
========
13+
14+
This REP proposes an extension to `rclcpp` that will make it easier to convert between ROS types and custom, user-defined types for Topics, Services, and Actions.
15+
16+
17+
Motivation
18+
==========
19+
20+
The primary reason for this change is to improve the developer's experience working with ROS 2.
21+
Currently, developers often write code to convert a ROS interface into another custom data structure for use in their programs.
22+
This can be trivial, in the case accessing the ``data`` field in ``std_msgs::msg::String``;
23+
or more involved such as when converting OpenCV's ``cv::Map`` to ROS's ``sensor_msgs/msg/Image`` type [1]_.
24+
Such conversions are additional work for the programmer and are potential sources of errors.
25+
26+
An additional reason for this change is performance.
27+
This interface will allow us to define methods for serializing directly to the user requested type, and/or using that type in intra-process communication without ever converting it.
28+
Whereas without this feature, even to send a ``cv::Mat`` (or a data structure containing a ``cv::Mat``) from one publisher to a subscription in the same process would require converting it to a ``sensor_msgs::msg::Image`` first and then back again to ``cv::Mat``.
29+
30+
31+
Terminology
32+
===========
33+
34+
:Custom type (in code, ``CustomType``):
35+
A data structure that *is not* a ROS 2 interface, such as ``std::string`` or ``cv::Mat``.
36+
:ROS type (in code, ``RosType``):
37+
A data structure that *is* a ROS 2 interface, such as ``std_msgs::msg::String`` or ``sensor_msgs::msg::Image``.
38+
39+
40+
Specification
41+
=============
42+
43+
Defining a Type Adapter
44+
-----------------------
45+
46+
In order to adapt a custom type to a ROS type, the user must create a template specialization of this structure for the custom type.
47+
In that specialization they must:
48+
49+
- change ``is_specialized`` to ``std::true_type``,
50+
- specify the custom type with ``using custom_type = ...``,
51+
- specify the ROS type with ``using ros_message_type = ...``,
52+
- provide static convert functions with the signatures:
53+
- ``static void convert_to_ros_message(const custom_type &, ros_message_type &)``,
54+
- ``static void convert_to_custom(const ros_message_type &, custom_type &)``
55+
56+
The convert functions must convert from one type to the other.
57+
58+
For example, here is a theoretical example for adapting ``std::string`` to the ``std_msgs::msg::String`` ROS message type:
59+
60+
.. code-block:: cpp
61+
62+
template<>
63+
struct rclcpp::TypeAdapter<
64+
std::string,
65+
std_msgs::msg::String
66+
>
67+
{
68+
using is_specialized = std::true_type;
69+
using custom_type = std::string;
70+
using ros_message_type = std_msgs::msg::String;
71+
72+
static
73+
void
74+
convert_to_ros_message(
75+
const custom_type & source,
76+
ros_message_type & destination)
77+
{
78+
destination.data = source;
79+
}
80+
81+
static
82+
void
83+
convert_to_custom(
84+
const ros_message_type & source,
85+
custom_type & destination)
86+
{
87+
destination = source.data;
88+
}
89+
};
90+
91+
Type Adaptation in Topics
92+
-------------------------
93+
94+
The adapter can then be used when creating a publisher or subscription, e.g.:
95+
96+
.. code-block:: cpp
97+
98+
using MyAdaptedType = TypeAdapter<std::string, std_msgs::msg::String>;
99+
auto pub = node->create_publisher<MyAdaptedType>("topic", 10);
100+
auto sub = node->create_subscription<MyAdaptedType>(
101+
"topic",
102+
10,
103+
[](const std::string & msg) {...});
104+
105+
You can also be more declarative by using the ``adapt_type::as`` metafunctions, which are a bit less ambiguous to read:
106+
107+
.. code-block:: cpp
108+
109+
using AdaptedType = rclcpp::adapt_type<std::string>::as<std_msgs::msg::String>;
110+
auto pub = node->create_publisher<AdaptedType>(...);
111+
112+
If you wish, you may associate a custom type with a single ROS message type, allowing you to be a bit more brief when creating entities, e.g.:
113+
114+
.. code-block:: cpp
115+
116+
// First you must declare the association, this is similar to how you
117+
// would avoid using the namespace in C++ by doing `using std::vector;`.
118+
RCLCPP_USING_CUSTOM_TYPE_AS_ROS_MESSAGE_TYPE(std::string, std_msgs::msg::String);
119+
120+
// Then you can create things with just the custom type, and the ROS
121+
// message type is implied based on the previous statement.
122+
auto pub = node->create_publisher<std::string>(...);
123+
124+
Note that it is also possible to use a ROS type with a publisher or subscriber that has been specialized to use a custom message, e.g.:
125+
126+
.. code-block:: cpp
127+
128+
using AdaptedType = rclcpp::adapt_type<std::string>::as<std_msgs::msg::String>;
129+
auto pub = node->create_publisher<AdaptedType>(...);
130+
131+
// Publish a std::string
132+
std::string custom_msg = "My std::string"
133+
pub->publish(custom_msg);
134+
135+
// Publish a std_msgs::msg::String;
136+
std_msgs::msg::String ros_msg;
137+
ros_msg.data = "My std_msgs::msg::String";
138+
pub->publish(ros_msg);
139+
140+
Type Adaptation in Services
141+
---------------------------
142+
143+
Type adaptation can be used with a client and service by creating a ``struct`` that defines a type adapter for the request and the response. For example:
144+
145+
.. code-block:: cpp
146+
147+
using MyAdaptedRequestType = TypeAdapter<std::string, std_msgs::msg::String>;
148+
using MyAdaptedResponseType = TypeAdapter<bool, std_msgs::msg::Bool>;
149+
150+
struct MyServiceTypeAdapter {
151+
using Request = MyAdaptedRequestType;
152+
using Response = MyAdaptedResponseType;
153+
};
154+
155+
auto client = node->create_client<MyServiceTypeAdapter>("service");
156+
auto service = node->create_service<MyServiceTypeAdapter>(
157+
"service",
158+
[](const std::string & request) {...});
159+
160+
Similarly, either the request or response can be adapted:
161+
162+
.. code-block:: cpp
163+
164+
using MyAdaptedRequestType = TypeAdapter<bool, std_msgs::msg::Bool>;
165+
166+
struct MySetBoolTypeAdapter {
167+
using Request = MyAdaptedRequestType;
168+
using Response = std_srvs::srv::SetBool::Response;
169+
};
170+
171+
Type Adaptation in Actions
172+
--------------------------
173+
174+
Similar to services, type adaptation can be used with action clients and action services by creating a ``struct`` that defines a type adapter for the request, feedback, and result.
175+
As with services, the ROS type for a request, feedback, or result can be specified for use in this structure as well.
176+
177+
.. code-block:: cpp
178+
179+
struct MyActionTypeAdapter {
180+
using Goal = MyAdaptedGoalType;
181+
using Feedback = MyAdaptedFeedbackType;
182+
using Result = MyAdaptedResultType;
183+
};
184+
185+
auto node = rclcpp::Node::make_shared("action_node");
186+
auto action_client = rclcpp_action::create_client<MyActionTypeAdapter>(node, "action");
187+
auto action_server = rclcpp_action::create_server<MyActionTypeAdapter>(
188+
node,
189+
"action",
190+
handle_goal,
191+
handle_cancel,
192+
handle_accepted);
193+
194+
195+
Rationale
196+
=========
197+
198+
Selecting a term
199+
----------------
200+
201+
There are various terms that may be suitable for type adapting feature described.
202+
In selecting a term,
203+
204+
:High priority:
205+
206+
* Clearly communicate the described feature
207+
* Clearly communicate the order of custom type and ROS type arguments
208+
209+
:Low priority:
210+
211+
* The custom type should be the first argument so that
212+
* the custom type is the first argument in both the explicit and implicit syntax
213+
* the custom type is read first, for convenience
214+
* The syntax reads well
215+
216+
Candidate terms
217+
^^^^^^^^^^^^^^^
218+
219+
Several possible terms were considered.
220+
Here is a brief summary of the discussion around different terms.
221+
222+
Masquerade
223+
""""""""""
224+
225+
There is some precedent for using masquerade in similar settings, IP Masquerading in the Linux kernel [2]_ for example.
226+
"Masquerade" is also a verb, which may make it easier to discuss among developers.
227+
However, it was thought that "Masquerade" would be a confusing word for non-English and non-French speakers.
228+
One disadvantage of "Masquerade" is that there is ambiguity in its usage.
229+
For example,
230+
231+
.. code-block:: cpp
232+
233+
Masquerade<std_msgs::msg::String>::as<std::string>
234+
235+
and
236+
237+
.. code-block:: cpp
238+
239+
Masquerade<std::string>::as<std_msgs::msg::String>
240+
241+
both seem to make sense.
242+
This ambiguity may result in frustration on the part of the ROS 2 developer:
243+
244+
* frequently having to refer back to documentation
245+
* possibly opaque error messages
246+
247+
Facade
248+
""""""
249+
250+
"Facade" seems to be a more common English word than "masquerade".
251+
It also is commonly used as a design pattern in object oriented programming.
252+
However, the "Facade pattern" is typically used to simplify a complex interface [3]_, which is not the major feature being proposed here.
253+
254+
It was thought to use "Facade" in the following form:
255+
256+
.. code-block:: cpp
257+
258+
Facade<std::string>::instead_of<std_msgs::msg::String>
259+
260+
261+
Adapter
262+
"""""""
263+
264+
"Adapter" is certainly a common English word, and the "Adapter pattern" is a common design pattern for adjusting an interface [4]_, which matches well with the feature being suggested here.
265+
Also, using "Adapter" is consistent with the documentation of a similar feature in ROS 1 (i.e., "Adapting C++ Types" [5]_).
266+
267+
"Adapter" also has the advantage of being a noun and of being related to the verb "Adapt".
268+
This flexibility may make it easier for developers to discuss its use.
269+
270+
"Adapter" could be used in the following syntax:
271+
272+
.. code-block:: cpp
273+
274+
TypeAdapter<std::string>::as<std_msgs::msg::String>
275+
276+
Additional terms considered
277+
"""""""""""""""""""""""""""
278+
279+
Here is a brief listing of additional terms that were considered and why they were not selected:
280+
281+
:Convert: Passed in favor of "Adapter", which expresses a similar idea and has a common design pattern.
282+
283+
:Decorate: Passed in favor of "Fascade", which seems to be more common.
284+
285+
:Mask: Overloaded as a computer science term [6]_.
286+
287+
:Map: Expresses the idea well, but has a lot of meanings in math and programming.
288+
289+
:Use: Possibly confusing with C++'s ``using`` keyword; also not terribly descriptive.
290+
291+
:Wrap: Passed in favor of "Adapt", which seems to be more common.
292+
293+
294+
Including "Type" in the name
295+
----------------------------
296+
297+
Most of the terms being considered refer to general design patterns and, thus, using just the pattern's name may cause naming collisions or confusion as those design patterns may be used in other parts of the ROS codebase.
298+
To reduce ambiguity, including the term selected with "Type" would make its usage clearer and help avoid name collisions;
299+
it should also make it easier for developers to find relevant documentation.
300+
301+
If the word "Type" should be appended or prepended to the selected term will largely be a matter of how it reads.
302+
For example, "TypeAdapter" is perhaps more natural than "AdapterType".
303+
304+
Adding this feature in ``rclcpp``
305+
---------------------------------
306+
307+
Placing this feature in ROS 2's client support library, ``rcl``, would allow this feature to be used in other client libraries, such as ``rclcpp`` and ``rclpy``.
308+
However, we believe that the concrete benefits for C++ currently outweigh the potential benefits for existing or theoretical client libraries in other languages.
309+
For example, placing this feature in ``rclcpp`` allows us to avoid type erasure (which would be necessary to place this functionality into ``rcl``) and to use ownership mechanics (unique and shared pointer) to ensure it is safely implemented.
310+
Another added advantage of placing this feature in ``rclcpp`` is that it reduce the number of function calls and calls that potentially are to functions in separate shared libraries.
311+
312+
Perhaps we can support a form of this feature in other languages in ``rcl`` or ``rmw`` in the future.
313+
One challenge in doing this is that it may require custom type support, which may be middleware specific.
314+
This possibility will be further explored in the future.
315+
316+
On the Location for Specifying the Type Adapter
317+
-----------------------------------------------
318+
319+
It was suggested that we only template the ``Publisher::publish`` method, but in addition to being more convenient, specifying a type adapter for the publisher at instantiation rather than in ``Publisher::publish`` allows the intra process system to be setup to expect a custom type.
320+
Similarly, it is preferable to specify the adapted type at instantiation for subscriptions, service clients, service servers, action clients, and action servers.
321+
322+
Comparison to ROS 1's Type Adaptation
323+
-------------------------------------
324+
325+
Although intended to be similar in functionality, the proposed feature and ROS 1's type adaptation support [5]_ have a few important differences:
326+
327+
* This feature will support both convert and (de)serialize functions, and require at least one or the other, but also allow both. The reason for this is that convert is superior for intra-process communication and the (de)serialization functions are better for inter-process communication.
328+
* This feature will also require the user to write less code when creating an adapter, as compared to the ROS 1 implementation.
329+
* An advantage of following the ROS 1 approach is that an extra copy can be avoided; although it is likely much more challenging to implement this feature the ROS 1 way because of the middleware.
330+
331+
332+
Backwards Compatibility
333+
=======================
334+
335+
The proposed feature adds new functionality while not modifying existing functionality.
336+
337+
338+
339+
Feature Progress
340+
================
341+
342+
The type adaptation API has been implemented for publishers and subscribers (`ros2/rclcpp#1557 <https://github.com/ros2/rclcpp/pull/1557>`_).
343+
Examples
344+
(`ros2/examples#300 <https://github.com/ros2/examples/pull/300>`_)
345+
and demos
346+
(`ros2/demos#482 <https://github.com/ros2/demos/pull/482>`_)
347+
for using type adaptation have also been created.
348+
349+
There are several other features specified in this REP that have not been implemented. You can check the issues below to see the state of the reference implementation.
350+
351+
* integrate into intra-process manager (`ros2/rclcpp#1664 <https://github.com/ros2/rclcpp/issues/1664>`_)
352+
* support serialize/deserialize functions in addition to the convert functions (`ros2/rclcpp#1665 <https://github.com/ros2/rclcpp/issues/1665>`_)
353+
* support services (`ros2/rclcpp#1666 <https://github.com/ros2/rclcpp/issues/1666>`_)
354+
* support actions (`ros2/rclcpp#1667 <https://github.com/ros2/rclcpp/issues/1667>`_)
355+
356+
357+
References
358+
==========
359+
360+
.. [1] ``cam2image.cpp`` demo
361+
(https://github.com/ros2/demos/blob/11e00ecf7eec25320f950227531119940496d615/image_tools/src/cam2image.cpp#L277-L291)
362+
363+
.. [2] IP Masquerading in the Linux Kernel
364+
(http://linuxdocs.org/HOWTOs/IP-Masquerade-HOWTO-2.html)
365+
366+
.. [3] Facade Pattern
367+
(https://en.wikipedia.org/wiki/Facade_pattern)
368+
369+
.. [4] Adapter pattern
370+
(https://en.wikipedia.org/wiki/Adapter_pattern)
371+
372+
.. [5] Adapting C++ Types
373+
(http://wiki.ros.org/roscpp/Overview/MessagesSerializationAndAdaptingTypes#Adapting_C.2B-.2B-_Types)
374+
375+
.. [6] Masking (computing)
376+
(https://en.wikipedia.org/wiki/Mask_(computing))
377+
378+
379+
Copyright
380+
=========
381+
382+
This document has been placed in the public domain.
383+
384+
385+
..
386+
Local Variables:
387+
mode: indented-text
388+
indent-tabs-mode: nil
389+
sentence-end-double-space: t
390+
fill-column: 70
391+
coding: utf-8
392+
End:

0 commit comments

Comments
 (0)