Skip to content
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

RSDK-3589 add wrapper for navigation service #323

Merged
merged 36 commits into from
Nov 18, 2024
Merged

RSDK-3589 add wrapper for navigation service #323

merged 36 commits into from
Nov 18, 2024

Conversation

abe-winter
Copy link
Member

@abe-winter abe-winter commented Nov 14, 2024

What changed

  • add abstract Navigation class, navigation server and client, register them
  • incidental: utils for repeatedPtrToVec and vecToRepeatedPtr

Why

So people can write nav modules in C++.

Follow-ups

  • someone with deeper understanding of the interface should rewrite docstrings in navigation.hpp
  • maybe add example
  • refactor repeatedPtrToVec and friends

Copy link
Contributor

@lia-viam lia-viam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

overall this looks really solid, just added some suggestions about SDK coding standards changes which are currently in progress!

@@ -184,5 +184,21 @@ bool from_dm_from_extra(const ProtoStruct& extra) {
return false;
}

template <typename Src, typename Dst>
void vecToRepeatedPtr(const std::vector<Src>& vec, google::protobuf::RepeatedPtrField<Dst>& dest) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that we've announced this super loudly but we're currently trying to cut back on API and google:: types in headers, so I think we'll want to delete these helper functions even though they're being used a couple times.

As a suggested inline replacement, I think this could just be

dest.Assign(vec.begin(), vec.end());

You can also do that with the Add method, but note that if dest already had stuff then this function would be under-reserving potentially and adding to existing elements which is maybe not what I'd expect from an xToY function
As a suggested replacement for this one, can you just write inline

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call -- clearing the destination in d334572 to protect against adding to non-empty collection

I think Assign(begin, end) doesn't work because the types are different -- tried to get it working with constructor/operator implicit conversion approach, couldn't, guessing even harder now that the constructor/operators have been moved out of common headers

void repeatedPtrToVec(const google::protobuf::RepeatedPtrField<Src>& src, std::vector<Dst>& vec) {
vec.reserve(src.size());
for (auto& x : src) {
vec.push_back(x);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly I think this could be vec.assign(src.Begin(), src.End()), also with similar caveats as above about assigning vs appending

src/viam/sdk/services/navigation.hpp Outdated Show resolved Hide resolved
};

struct Waypoint {
Waypoint(const viam::service::navigation::v1::Waypoint& proto)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to the other comment about keeping API stuff out of headers, @stuqdog is currently working on a PR to take these kinds of conversion functions out of public headers, so maybe you guys can talk so that we have a similar approach in this PR as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but one option for example is, since I think these are only being used in navigation_client.cpp to just inline them there

Copy link
Member Author

@abe-winter abe-winter Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay I removed all *.pb.h includes from the wrapper header (navigation.hpp)

how:

  • I moved the implicit conversions for Waypoint and Path to functions in the navigation client/server.cpp files
  • and gave the std::vector <-> RepeatedPointer conversions their own header

now the invocation looks like:

// when destination type has a `from_proto` method, use it:
repeatedPtrToVec(response.obstacles(), *ret);

// otherwise, pass in the conversion function:
repeatedPtrToVec(response.waypoints(), *ret, waypoint_from_proto);

Copy link
Member

@acmorrow acmorrow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just taking a look because I'm interested. The client/server parts all look great, and I'm glad to see that the helpers are making it much easier to add these implementations. I think the proto_utils stuff could be simplified and generalized.

/// in cpp implementation files, but not in wrapper headers consumed by third party code.
#pragma once

#include <viam/api/common/v1/common.pb.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A semi-near-term goal for the C++ SDK is to move all the proto stuff out of public headers. Could you push this down into viam/sdk/common/private/proto_utils.hpp? That's how we indicate that files shouldn't be consumed except by files that form part of the SDK or published as part of the install step.


namespace viam {
namespace sdk {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add namespace impl here, so viam::sdk::impl. That's the namespace we use for any private things that shouldn't escape.

/// @brief Copies elements from a protobuf repeated pointer array into a std::vector. Src type
/// must be implicitly convertible to Dst (probably via operator on Src).
template <typename Src, typename Dst>
void vecToRepeatedPtr(const std::vector<Src>& vec, google::protobuf::RepeatedPtrField<Dst>& dest) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a slight preference for out parameters being taken by pointer, as it makes it clearer at the call site that the field is likely being mutated:

vecToRepeatedPtr(my_vec, dest);  // err, who is getting modified here?

vs

vecToRepeatedPtr(my_vec, &dest);  // ah, classic out parameter.

Others dislike this because they worry that it allows dest to be nullptr. I don't find that compelling because requiring it to be a reference doesn't prevent the opposite mistake:

RepeatedPtrField<X>* pdest = ...
vecToRepeatedPtr(my_vec, *pdest);  // oops, what if pdest is nullptr?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So generally speaking I'm very much against output parameters being passed as pointers, it's one of the things I dislike the most about google's C++ coding guidelines. I think with your counterexample above the point is that, in C++ and as high level as this code is, we generally aren't passing around pointer types in the first place, and the point of references is that they must point to a valid object or else it's UB. So as for

oops, what if pdest is nullptr

That's on the caller for dereferencing a null pointer in the first place, and is equally true of if my_vec was *p_my_vec for some reason, and this is kind of the point of C++ having reference types.

Callsite legibility I'm somewhat sympathetic to but would be better done with something like std::ref(arg) or gsl::output_parameter, because then you don't lose the non-null guarantee.

.....All that said, I think this could be a pointer parameter just for ABI insulation reasons


/// @brief Non-member to_proto() version. (necessary for moving generated types out of wrapper
/// headers). Template param F is a function that converts from Src to Dst.
template <typename Src, typename Dst>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More idiomatic would just to be take

template <typename Src, typename Callable>
void vecToRepeatedPtr(const std::vector<Src>& vec,
                      google::protobuf::RepeatedPtrField<Dst>& dest,
                      Callable&& c) {

Because then you can pass any sort of callable (std::function, lambda, function pointer as you have now).

You can then enforce the shape of the callable with a static assert or similar.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explain this one -- this is better because it would select this specialization and return a static assert error, rather than giving a long 'no matching template' error?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, as written, yours will only accept function pointers. I could be wrong, I'd need to test it out on godbolt.

Dst to_proto(const Src&)) {
dest.Clear();
dest.Reserve(vec.size());
for (auto& x : vec) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would work with other containers than just std::vector right? std::array, std::list, etc.?

template <typename Src, typename Dst>
void repeatedPtrToVec(const google::protobuf::RepeatedPtrField<Src>& src,
std::vector<Dst>& vec,
Dst from_proto(const Src&)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could eliminate the distinction between the converting and non-converting by just always taking a transformation function and defaulting it to std::identity (might need to get it from boost for now)

That actually has me wondering whether all of these are really glosses on std::transform and would be better implemented that way?


API api() const override;

virtual Mode get_mode(const std::string name, const ProtoStruct& extra) = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is the public facing header, this is the one that should have doc comments. Also, please add blank lines between the end of the declaration of one method and the beginning of the next.

    virtual LocationResponse get_location(const std::string name, const ProtoStruct& extra) = 0;

    virtual std::unique_ptr<std::vector<Waypoint>> get_waypoints(const std::string name,
                                                                 const ProtoStruct& extra) = 0;

That, moreso, once comments are added:

    /// Some comment
    virtual LocationResponse get_location(const std::string name, const ProtoStruct& extra) = 0;

    /// Another comment
    virtual std::unique_ptr<std::vector<Waypoint>> get_waypoints(const std::string name,
                                                                 const ProtoStruct& extra) = 0;


using namespace viam::service::navigation::v1;

Navigation::Waypoint waypoint_from_proto(const viam::service::navigation::v1::Waypoint& proto) {
Copy link
Contributor

@lia-viam lia-viam Nov 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So just as a general guideline it's kind of a C++ anti-pattern to have the type name in the name of the function--since C doesn't have function overloading you're kind of forced to do things like f_int f_double etc, but in this case function overloading is able to resolve from_proto(some_waypoint) to call the overload which takes a const Waypoint&.

That said, this function and path_from_proto are only used in this .cpp file, so I would probably just define them inline as a lambda passed to your conversion function, ie,

repeatedPtrToVec(src, dest, [](const auto& proto) { return Navigation::Waypoint{proto.id(), geo_point::from_proto(proto.location())}; });

Copy link
Member Author

@abe-winter abe-winter Nov 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed to bare from/to proto in 4e9f32c

(holding off on bigger changes bc it sounds like sdk team will do a design pass on repeatedPtrToVec stuff after this merges)

@abe-winter abe-winter marked this pull request as ready for review November 18, 2024 15:59
@abe-winter abe-winter requested a review from a team as a code owner November 18, 2024 15:59
@abe-winter abe-winter requested review from stuqdog and lia-viam and removed request for a team November 18, 2024 15:59
}

// weak backport of std::transform.
template <typename T, typename U>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just template this as template <typename T, typename U, typename Func> and have the second argument be Func f

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that made it sad

could specify template param I guess? but can I leave as-is?

/host/src/viam/sdk/tests/test_navigation.cpp: In lambda function:
/host/src/viam/sdk/tests/test_navigation.cpp:78:14: error: no matching function for call to 'mapOver(std::vectorviam::sdk::Navigation::Waypoint&, std::__cxx11::basic_string (*)(const viam::sdk::Navigation::Waypoint&))'
78 | });
| ^
/host/src/viam/sdk/tests/test_navigation.cpp:46:16: note: candidate: 'template<class T, class U, class Func> std::vector viam::sdktests::navigation::mapOver(const std::vector&, Func)'
46 | std::vector mapOver(const std::vector& source, Func f) {
| ^~~~~~~
/host/src/viam/sdk/tests/test_navigation.cpp:46:16: note: template argument deduction/substitution failed:
/host/src/viam/sdk/tests/test_navigation.cpp:78:14: note: couldn't deduce template parameter 'T'
78 | });

@lia-viam lia-viam merged commit 8aed0c9 into main Nov 18, 2024
4 checks passed
@lia-viam lia-viam deleted the aw-nav-service branch November 18, 2024 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants