-
Notifications
You must be signed in to change notification settings - Fork 1
feat(components): add assignment methods (c++) #224
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
Conversation
|
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
|
I have read and understood the Contributor License Agreement and hereby sign 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.
Looking at this, I would find it programmatically much easier to treat the assignment like a parameter:
// header
state_representation::ParameterMap assignment_map_;
// source
void add_assignment(name, ParameterType) {
// checks that you already added correctly
assignment_map_.set_parameter(std::shared_ptr<ParameterInterface>(name, type));
}
void trigger_assignment(name, value) {
assignment_map_.set_parameter_value(name, value); // this will already handle all type compatibility for us and raise otherwise
modulo_interfaces::msg::Assignment message;
message.node = this->node_base_->get_fully_qualified_name();
message.assignment = name;
message.value = modulo_core::translators::write_parameter(assignment_map_.get_parameter(name)).to_parameter_msg().value;
}
No additional Assignment class would be required
@eeberhard
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
It's a fair point, since all it does for now is to check for a valid type. I guess it would be needed if we (plan to) add more functionality, such as checking for the last assigned value and avoid republishing (which has its own problems). Personally, I think it's more future-proof and extensible to keep the extra class. |
I don't think we should have a mechanism to avoid republishing if the value hasn't changed. Thinking of an assignment like an output parameter, it should definitely publish any value if the user wants that. If we think of a classification component that has an assignment representing the detected class, I want it to publish the result every time (potentially at quite high intervals) it classifies an image |
eeberhard
left a 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.
I agree with @domire8 that it would certainly be easier to store assignments like a parameter map. That way we get the type and the value, plus more code re-use.
Right now you don't store the last assignment value, which is technically fine because that was not a requirement for the API spec. But if you do store the assignment value, then it would allow us to have get_assignment for components to check for themselves what the last assignment was, and use that for example to conditionally decide not to republish the assignment.
It also makes using the Parameter message (instead of ParameterValue) for the message even simpler - we care about a named and typed value, which the parameter provides.
The analogy of course is set_predicate and get_predicate, where components could check if the predicate has changed or has a current true or false value without needing to store values explicitly or interact with the ROS API.
| if (assignment_name.empty()) { | ||
| RCLCPP_ERROR(this->node_logging_->get_logger(), "Failed to add assignment: Provide a non empty string as a name."); | ||
| return; | ||
| } |
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 should validate or sanitize the assignment name. The schema allows assignments to have the following pattern: ^[a-zA-Z0-9]([a-zA-Z0-9_.-]?[a-zA-Z0-9])*$
I didn't check the existing code but I suppose this kind of sanitization already exists for signal or parameter names
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
| const std::string& assignment_name, const state_representation::ParameterType& type) { | ||
| // Reusing parse_topic_name. Not very elegant. Could add a validate_assignment_name | ||
| // similar to services but it's a lot of code duplication | ||
| std::string parsed_name = modulo_utils::parsing::parse_topic_name(assignment_name); |
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.
This is fair only if the parsing in there allows the patterns specified in Enricos previous 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.
As long as it's a valid subset of the permissible assignment names in the schema, it's fine to keep (and can be made more complete and comprehensive later). But if it allows invalid names, then it will be annoying to change later.
| return true; | ||
| } | ||
|
|
||
| void ComponentInterface::add_assignment( |
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.
Another design point, TBD with @eeberhard
Now that I see this, I think that the add_assignment should even be templated, like parameters.
add_assignment<double>("double_assignment")
This is more in line with parameters and templating we use across the components already. We would then add to the assignments_map_ with Parameter<T>(name) instead of ParameterInterface(name, type)
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 I am in favor.
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.
yes, I think that would be very nice for C++, and saves the user from having to deal with ParameterType as an arg.
domire8
left a 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.
Looking good
| /** | ||
| * @brief Get an assignment value. | ||
| * @tparam T The type of the assignment value | ||
| * @param assignment_name The name of the assignment to get |
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.
This could raise for two reasons (existence and type), we should catch it and throw an AssignmentException
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.
Yes, I expected this comment and I had a question. The get_parameter will throw a InvalidParameterException if it does not exist, and then the get_parameter_value a InvalidParameterCastException in case of casting to wrong type, or EmptyStateException if the parameter has not been set yet. So I was wondering whether I should catch and rethrow or leave as 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.
It's probably best to catch all and throw and AssignmentException to avoid confusing a user with Parameter exceptions
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.
While we could define InvalidAssignmentException and InvalidAssignmentCastException, this will probably have much less usage that get_parameter, so I would be in favor of a single InvalidAssignmentException covering both not existing and not matching type (i.e., catch InvalidParameterException and InvalidParameterCastException and rethrow as AssignmentException).
The case of EmptyStateException should be documented as a possible @throws but not caught IMO since it's already general and informative.
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.
Agreed on the dedicated AssignmentException in general.
Just a thought on the empty state; I think a need might come up to poll the assignment and check if it has been used. Are we going to do this by catching the EmptyStateException, or we should make something dedicated?
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 a need might come up to poll the assignment and check if it has been used
since the component is the only one who gets to set assignments, there are plenty of other ways to check this (using a bool attribute as a flag for example). This is different to parameters whose values can be changed externally, or predicates whose values might be based on some callback function and change arbitrarily during runtime. So throwing an exception if the assignment has not been set is correct behavior in my opinion, while still keeping a distinction between the AssignmentError and EmptyStateException to disambiguate an undeclared assignment from an unused assignment.
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
| } | ||
| try { | ||
| assignment->set_parameter_value<T>(assignment_value); | ||
| } catch (const state_representation::exceptions::InvalidParameterCastException&){ |
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.
Are you sure this exception might be thrown? From what I can see, this is only used in get_parameter?
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 far as I tested, yes, that's what is thrown. Tests also succeed.
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.
Interesting
Co-authored-by: Dominic Reber <71256590+domire8@users.noreply.github.com>
eeberhard
left a 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.
Looking good!
| /** | ||
| * @brief Get an assignment value. | ||
| * @tparam T The type of the assignment value | ||
| * @param assignment_name The name of the assignment to get |
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.
While we could define InvalidAssignmentException and InvalidAssignmentCastException, this will probably have much less usage that get_parameter, so I would be in favor of a single InvalidAssignmentException covering both not existing and not matching type (i.e., catch InvalidParameterException and InvalidParameterCastException and rethrow as AssignmentException).
The case of EmptyStateException should be documented as a possible @throws but not caught IMO since it's already general and informative.
| return true; | ||
| } | ||
|
|
||
| void ComponentInterface::add_assignment( |
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.
yes, I think that would be very nice for C++, and saves the user from having to deal with ParameterType as an arg.
| const std::string& assignment_name, const state_representation::ParameterType& type) { | ||
| // Reusing parse_topic_name. Not very elegant. Could add a validate_assignment_name | ||
| // similar to services but it's a lot of code duplication | ||
| std::string parsed_name = modulo_utils::parsing::parse_topic_name(assignment_name); |
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 long as it's a valid subset of the permissible assignment names in the schema, it's fine to keep (and can be made more complete and comprehensive later). But if it allows invalid names, then it will be annoying to change later.
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
source/modulo_components/include/modulo_components/ComponentInterface.hpp
Outdated
Show resolved
Hide resolved
Co-authored-by: Dominic Reber <71256590+domire8@users.noreply.github.com>
domire8
left a 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.
This looks good to me, pending approval of Enrico 👍
bpapaspyros
left a 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.
pending Enrico and a CHANGELOG entry perhaps
eeberhard
left a 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.
This looks great! Sorry for the late final review.
pending Enrico and a CHANGELOG entry perhaps
It's going into a staging branch so as long as we can include the (combined) changelog entry before going into main it will be fine
Description
This PR introduces a few basic wrapper functions to add, trigger and publish assignments for a consumer. There are some comments in places where I would definitely appreciate an opinion.
Review guidelines
Estimated Time of Review: 15 minutes
Checklist before merging: