Skip to content

Commit 5d44772

Browse files
mxgreydirk-thomas
andauthored
Add a utility for rigorously initializing a message instance (#448)
* Add a utility for rigorously initializing a message instance Signed-off-by: Michael X. Grey <[email protected]> * Add tests for the message building feature Signed-off-by: Michael X. Grey <[email protected]> * Use macros to reduce code bloat Signed-off-by: Michael X. Grey <[email protected]> * uncrustify Signed-off-by: Michael X. Grey <[email protected]> * move builder functionality into a completely separate header Signed-off-by: Dirk Thomas <[email protected]> * provide templated function <pkgname>::build<MsgType>() for builder pattern Signed-off-by: Dirk Thomas <[email protected]> * include the builder header by default Signed-off-by: Dirk Thomas <[email protected]> * inline template specialization Signed-off-by: Dirk Thomas <[email protected]> * fix clang warnings Signed-off-by: Dirk Thomas <[email protected]> * style Signed-off-by: Dirk Thomas <[email protected]> * special case for EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME Signed-off-by: Dirk Thomas <[email protected]> Co-authored-by: Dirk Thomas <[email protected]>
1 parent 4caf9a8 commit 5d44772

File tree

9 files changed

+431
-0
lines changed

9 files changed

+431
-0
lines changed

rosidl_generator_cpp/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ if(BUILD_TESTING)
4949
${rosidl_generator_c_INCLUDE_DIRS})
5050

5151
ament_add_gtest(test_bounded_vector test/test_bounded_vector.cpp)
52+
ament_add_gtest(test_msg_builder test/test_msg_builder.cpp)
53+
if(TARGET test_msg_builder)
54+
add_dependencies(test_msg_builder ${PROJECT_NAME})
55+
endif()
5256
ament_add_gtest(test_msg_initialization test/test_msg_initialization.cpp)
5357
if(TARGET test_msg_initialization)
5458
add_dependencies(test_msg_initialization ${PROJECT_NAME})

rosidl_generator_cpp/cmake/rosidl_generator_cpp_generate_interfaces.cmake

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ foreach(_abs_idl_file ${rosidl_generate_interfaces_ABS_IDL_FILES})
2222

2323
list(APPEND _generated_headers
2424
"${_output_path}/${_parent_folder}/${_header_name}.hpp"
25+
"${_output_path}/${_parent_folder}/${_header_name}__builder.hpp"
2526
"${_output_path}/${_parent_folder}/${_header_name}__struct.hpp"
2627
"${_output_path}/${_parent_folder}/${_header_name}__traits.hpp"
2728
)
@@ -41,13 +42,17 @@ endforeach()
4142
set(target_dependencies
4243
"${rosidl_generator_cpp_BIN}"
4344
${rosidl_generator_cpp_GENERATOR_FILES}
45+
"${rosidl_generator_cpp_TEMPLATE_DIR}/action__builder.hpp.em"
4446
"${rosidl_generator_cpp_TEMPLATE_DIR}/action__struct.hpp.em"
4547
"${rosidl_generator_cpp_TEMPLATE_DIR}/action__traits.hpp.em"
4648
"${rosidl_generator_cpp_TEMPLATE_DIR}/idl.hpp.em"
49+
"${rosidl_generator_cpp_TEMPLATE_DIR}/idl__builder.hpp.em"
4750
"${rosidl_generator_cpp_TEMPLATE_DIR}/idl__struct.hpp.em"
4851
"${rosidl_generator_cpp_TEMPLATE_DIR}/idl__traits.hpp.em"
52+
"${rosidl_generator_cpp_TEMPLATE_DIR}/msg__builder.hpp.em"
4953
"${rosidl_generator_cpp_TEMPLATE_DIR}/msg__struct.hpp.em"
5054
"${rosidl_generator_cpp_TEMPLATE_DIR}/msg__traits.hpp.em"
55+
"${rosidl_generator_cpp_TEMPLATE_DIR}/srv__builder.hpp.em"
5156
"${rosidl_generator_cpp_TEMPLATE_DIR}/srv__struct.hpp.em"
5257
"${rosidl_generator_cpp_TEMPLATE_DIR}/srv__traits.hpp.em"
5358
${rosidl_generate_interfaces_ABS_IDL_FILES}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
@# Included from rosidl_generator_cpp/resource/idl__builder.hpp.em
2+
@{
3+
TEMPLATE(
4+
'msg__builder.hpp.em',
5+
package_name=package_name, interface_path=interface_path,
6+
message=action.goal, include_directives=include_directives)
7+
}@
8+
9+
@{
10+
TEMPLATE(
11+
'msg__builder.hpp.em',
12+
package_name=package_name, interface_path=interface_path,
13+
message=action.result, include_directives=include_directives)
14+
}@
15+
16+
@{
17+
TEMPLATE(
18+
'msg__builder.hpp.em',
19+
package_name=package_name, interface_path=interface_path,
20+
message=action.feedback, include_directives=include_directives)
21+
}@
22+
23+
@{
24+
TEMPLATE(
25+
'srv__builder.hpp.em',
26+
package_name=package_name, interface_path=interface_path,
27+
service=action.send_goal_service, include_directives=include_directives)
28+
}@
29+
30+
@{
31+
TEMPLATE(
32+
'srv__builder.hpp.em',
33+
package_name=package_name, interface_path=interface_path,
34+
service=action.get_result_service, include_directives=include_directives)
35+
}@
36+
37+
@{
38+
TEMPLATE(
39+
'msg__builder.hpp.em',
40+
package_name=package_name, interface_path=interface_path,
41+
message=action.feedback_message, include_directives=include_directives)
42+
}@

rosidl_generator_cpp/resource/idl.hpp.em

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ include_base = '/'.join(include_parts)
2121
#define @(header_guard_variable)
2222

2323
#include "@(include_base)__struct.hpp"
24+
#include "@(include_base)__builder.hpp"
2425
#include "@(include_base)__traits.hpp"
2526

2627
#endif // @(header_guard_variable)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// generated from rosidl_generator_cpp/resource/idl__builder.hpp.em
2+
// with input from @(package_name):@(interface_path)
3+
// generated code does not contain a copyright notice
4+
@
5+
@#######################################################################
6+
@# EmPy template for generating <idl>__builder.hpp files
7+
@#
8+
@# Context:
9+
@# - package_name (string)
10+
@# - interface_path (Path relative to the directory named after the package)
11+
@# - content (IdlContent, list of elements, e.g. Messages or Services)
12+
@#######################################################################
13+
@{
14+
from rosidl_cmake import convert_camel_case_to_lower_case_underscore
15+
include_parts = [package_name] + list(interface_path.parents[0].parts) + \
16+
[convert_camel_case_to_lower_case_underscore(interface_path.stem)]
17+
include_base = '/'.join(include_parts)
18+
header_guard_variable = '__'.join([x.upper() for x in include_parts]) + \
19+
'__BUILDER_HPP_'
20+
21+
include_directives = set()
22+
}@
23+
24+
#ifndef @(header_guard_variable)
25+
#define @(header_guard_variable)
26+
27+
#include "@(include_base)__struct.hpp"
28+
#include <rosidl_generator_cpp/message_initialization.hpp>
29+
#include <algorithm>
30+
#include <utility>
31+
32+
@#######################################################################
33+
@# Handle message
34+
@#######################################################################
35+
@{
36+
from rosidl_parser.definition import Message
37+
}@
38+
@[for message in content.get_elements_of_type(Message)]@
39+
@{
40+
TEMPLATE(
41+
'msg__builder.hpp.em',
42+
package_name=package_name, interface_path=interface_path,
43+
message=message, include_directives=include_directives)
44+
}@
45+
46+
@[end for]@
47+
@
48+
@#######################################################################
49+
@# Handle service
50+
@#######################################################################
51+
@{
52+
from rosidl_parser.definition import Service
53+
}@
54+
@[for service in content.get_elements_of_type(Service)]@
55+
@{
56+
TEMPLATE(
57+
'srv__builder.hpp.em',
58+
package_name=package_name, interface_path=interface_path, service=service,
59+
include_directives=include_directives)
60+
}@
61+
62+
@[end for]@
63+
@
64+
@#######################################################################
65+
@# Handle action
66+
@#######################################################################
67+
@{
68+
from rosidl_parser.definition import Action
69+
}@
70+
@[for action in content.get_elements_of_type(Action)]@
71+
@{
72+
TEMPLATE(
73+
'action__builder.hpp.em',
74+
package_name=package_name, interface_path=interface_path, action=action,
75+
include_directives=include_directives)
76+
}@
77+
78+
@[end for]@
79+
#endif // @(header_guard_variable)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
@# Included from rosidl_generator_cpp/resource/idl__builder.hpp.em
2+
@{
3+
from rosidl_parser.definition import EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME
4+
5+
message_typename = '::'.join(message.structure.namespaced_type.namespaced_name())
6+
}@
7+
8+
@[for ns in message.structure.namespaced_type.namespaces]@
9+
namespace @(ns)
10+
{
11+
12+
@[end for]@
13+
@[if len(message.structure.members) != 1 or message.structure.members[0].name != EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@
14+
namespace builder
15+
{
16+
17+
@[ for index in range(len(message.structure.members) - 1, -1, -1)]@
18+
@{
19+
field_name = message.structure.members[index].name
20+
if index < len(message.structure.members) - 1:
21+
next_field_name = message.structure.members[index + 1].name
22+
}@
23+
class Init_@(message.structure.namespaced_type.name)_@(field_name)
24+
{
25+
public:
26+
@
27+
@[ if index != 0]@
28+
explicit @
29+
@[ end if]@
30+
Init_@(message.structure.namespaced_type.name)_@(field_name)@
31+
@[ if index == 0]@
32+
()
33+
: msg_(::rosidl_generator_cpp::MessageInitialization::SKIP)
34+
@[ else]@
35+
(::@(message_typename) & msg)
36+
: msg_(msg)
37+
@[ end if]@
38+
{}
39+
@[ if index == len(message.structure.members) - 1]@
40+
::@(message_typename) @
41+
@[ else]@
42+
Init_@(message.structure.namespaced_type.name)_@(next_field_name) @
43+
@[ end if]@
44+
@(field_name)(::@(message_typename)::_@(field_name)_type arg)
45+
{
46+
msg_.@(field_name) = std::move(arg);
47+
@[ if index == len(message.structure.members) - 1]@
48+
return std::move(msg_);
49+
@[ else]@
50+
return Init_@(message.structure.namespaced_type.name)_@(next_field_name)(msg_);
51+
@[ end if]@
52+
}
53+
54+
private:
55+
::@(message_typename) msg_;
56+
};
57+
58+
@[ end for]@
59+
} // namespace builder
60+
@[end if]@
61+
@
62+
@[for i, ns in reversed(list(enumerate(message.structure.namespaced_type.namespaces)))]@
63+
64+
@[ if i == 0]@
65+
template<typename MessageType>
66+
auto build();
67+
68+
template<>
69+
inline
70+
auto build<::@(message_typename)>()
71+
{
72+
@[ if len(message.structure.members) == 1 and message.structure.members[0].name == EMPTY_STRUCTURE_REQUIRED_MEMBER_NAME]@
73+
return ::@(message_typename)(rosidl_generator_cpp::MessageInitialization::ZERO);
74+
@[ else]@
75+
return @('::'.join(message.structure.namespaced_type.namespaces))::builder::Init_@(message.structure.namespaced_type.name)_@(message.structure.members[0].name)();
76+
@[ end if]@
77+
}
78+
79+
@[ end if]@
80+
} // namespace @(ns)
81+
@[end for]@
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@# Included from rosidl_generator_cpp/resource/idl__builder.hpp.em
2+
@{
3+
TEMPLATE(
4+
'msg__builder.hpp.em',
5+
package_name=package_name, interface_path=interface_path,
6+
message=service.request_message, include_directives=include_directives)
7+
}@
8+
9+
@{
10+
TEMPLATE(
11+
'msg__builder.hpp.em',
12+
package_name=package_name, interface_path=interface_path,
13+
message=service.response_message, include_directives=include_directives)
14+
}@

rosidl_generator_cpp/rosidl_generator_cpp/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
def generate_cpp(generator_arguments_file):
3232
mapping = {
3333
'idl.hpp.em': '%s.hpp',
34+
'idl__builder.hpp.em': '%s__builder.hpp',
3435
'idl__struct.hpp.em': '%s__struct.hpp',
3536
'idl__traits.hpp.em': '%s__traits.hpp',
3637
}

0 commit comments

Comments
 (0)