-
Notifications
You must be signed in to change notification settings - Fork 22
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-9337: Introduce proto API bridge and insulate various geometry and math types from API #332
Conversation
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.
Nice, this looks great! I love how much cleaner all this looks, thanks for the work both on the broader change and on particular type refactors.
There's a lot going on here so I might want to give it a second pass, but right now this all looks great for me beyond some confusion around Orientation
's to_proto
method.
void to_proto<Orientation>::operator()(const Orientation& self, app::v1::Orientation* o) const { | ||
struct Visitor { | ||
void operator()(const axis_angles& a) { | ||
app::v1::Orientation_AxisAngles aa; | ||
aa.set_x(a.x); | ||
aa.set_y(a.y); | ||
aa.set_z(a.z); | ||
aa.set_theta(a.theta); | ||
*orientation.mutable_axis_angles() = std::move(aa); |
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.
(q) I think I must be misunderstanding something, how does this compile? Where is orientation
defined? It looks to me like the proto Orientation
value has been renamed o
, from orientation
. What am I missing?
edit: it looks like orientation
is being defined after the Visitor
struct, is that correct? I'm a little bit surprised that's allowed in general, but also I wonder if I'm interpreting that correctly because it seems to me we should be modifying the passed-in o
value, not the orientation
value created on line 90.
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.
So orientation
is a data member of struct Visitor
, it's defined after all the call operators have already been defined but still within Visitor
. We are in fact modifying the passed-in o
value, that is an Orientation*
and it's being used to initialize Orientation& orientation
because Visitor
is initialized with *o
when we say Visitor{*o}
You can think of this similar to a lambda capture of [&orientation = *o]
which is also how a lambda works under the hood, it's defining an anonymous, function-local struct with a call operator whose data members are initialized to the capture list
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.
Looks great. LGTM. I focused, per suggestion, mostly just on the new code. I did skim through the conversions, but I didn't want to spend a lot of time there as it seems @stuqdog is having a look there.
#include <viam/api/common/v1/common.pb.h> | ||
#include <viam/sdk/common/proto_convert.hpp> | ||
|
||
VIAM_SDK_API_FWD_NAMESPACE_BEGIN(common) |
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 love the macro, but I see why you did it. However, we don't currently have the mechanisms in place to ensure that the macro is no longer defined after consuming our headers. Would a viam/sdk/viam_api_fwd.hpp
header let you avoid the macro? I think we had discussed such an idea at one point. Maybe you tried it and there were undesirable consequences?
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 open to getting rid of the macro, it's more of a convenience than anything, and also makes it easy to grep for where forward declarations are happening.
Putting everything in a single fwd
header is doable, but I think that from a user's perspective I prefer that
- if you do want to escape hatch into the proto conversions, the same header with the SDK types you're working with also shows you the relevant API types
- you don't have to introduce all the other forward decls into scope
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.
OK, those are good enough arguments against the fwd header that I think we can eliminate that.
How about a compromise? We keep the macro for now, but file a ticket that when the SDK goes to C++17, the macro gets removed, because then it becomes a lot less painful to write:
namespace ::viam::api::common::v1 {
class Vector3;
}
So, a ticket for that labeled as C++17? That or just do it explicitly now with C++14 namespaces. I've got a pretty strong aversion to macros in public headers if at all avoidable.
Also, it is probably worth filing a ticket now, if one doesn't exist, to introduce prelude/postlude headers to do macro push/pops so that the tooling for avoiding macro leakage is in place. It will be required in order to support adding ABI visibility annotations and/or calling convention annotations should we ever want to support windows.
template <typename ProtoType> | ||
struct from_proto; | ||
|
||
template <typename Callable> |
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 this type is worth a comment, especially with the tricky
args_t
thing happening. - When we get to C++[17/20] does the standard give us tools to do this rather than needing boost? Mostly, is there a path to a polyfill 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.
It honestly might even be doable in the current standard, just a little horrible maybe. I like callable traits especially for ease of use though especially given the following caveat: https://www.boost.org/doc/libs/1_86_0/libs/callable_traits/doc/html/index.html#callable_traits.introduction.motivation
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.
That's pretty horrific. Maybe throw a TODO on here that it would be nice to eliminate this boost dependency in our headers if possible? Maybe future us finds a nice way to do it in some future standard.
constexpr proto_convert_details::to_proto_impl to_proto{}; | ||
|
||
/// @brief Function object implementing conversion from an API type to an SDK type. | ||
/// This callable works for any type with a proto_convert_details::from_proto specialization as | ||
/// described above. | ||
constexpr proto_convert_details::from_proto_impl from_proto{}; |
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.
These are objects defined in a header (though constexpr). Are there any concerns about linkage? I realize that's vague, but my mental model for how constexpr objects in headers work is pretty weak.
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.
Let me look into that actually because I was modeling this off how range-v3 does it but I see now they have a macro: https://github.com/ericniebler/range-v3/blob/7e6f34b1e820fb8321346888ef0558a0ec842b8e/include/range/v3/detail/config.hpp#L568
#include <boost/callable_traits/return_type.hpp> | ||
#include <boost/mp11/algorithm.hpp> | ||
|
||
#include <type_traits> |
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.
Arguably this should come ahead of the boost headers
struct to_proto<std::vector<SdkType>> { | ||
void operator()( | ||
const std::vector<SdkType>& vec, | ||
::google::protobuf::RepeatedPtrField<EquivalentApiType<SdkType>>* ptr_field) const { | ||
ptr_field->Reserve(vec.size()); | ||
for (const auto& elem : vec) { | ||
to_proto<SdkType>{}(elem, ptr_field->Add()); | ||
} | ||
} | ||
}; | ||
|
||
template <typename ApiType> | ||
struct from_proto<::google::protobuf::RepeatedPtrField<ApiType>> { |
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 totally sold on making these part of our public API. Or, at least not yet. Could these be in common/private
and under viam::sdk::impl
?
I don't doubt the utility, but I'm not sure we need to be in the position of exposing helpers like this for the cases where consumers have opted into dealing with our low-level proto stuff.
It is easier to keep it private now and expose it if it proves worthwhile, than to take it away 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 think that's a better call, I was actually worrying myself about this today because I think there may be cases where it might be RepeatedField
vs RepeatedPtrField
and this would be an annoying API lock-in
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.
OK, I think let's move them to internal then, since it seems we have similar thoughts 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.
apparently the choice of repeated field vs ptr field is actually categorical: https://protobuf.dev/reference/cpp/api-docs/google.protobuf.repeated_field/#
but for now I will still leave it in a private header and we can think of public
izing it if there seems to be a need
#include <viam/api/common/v1/common.pb.h> | ||
#include <viam/sdk/common/proto_convert.hpp> | ||
|
||
VIAM_SDK_API_FWD_NAMESPACE_BEGIN(common) |
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.
OK, those are good enough arguments against the fwd header that I think we can eliminate that.
How about a compromise? We keep the macro for now, but file a ticket that when the SDK goes to C++17, the macro gets removed, because then it becomes a lot less painful to write:
namespace ::viam::api::common::v1 {
class Vector3;
}
So, a ticket for that labeled as C++17? That or just do it explicitly now with C++14 namespaces. I've got a pretty strong aversion to macros in public headers if at all avoidable.
Also, it is probably worth filing a ticket now, if one doesn't exist, to introduce prelude/postlude headers to do macro push/pops so that the tooling for avoiding macro leakage is in place. It will be required in order to support adding ABI visibility annotations and/or calling convention annotations should we ever want to support windows.
src/viam/sdk/CMakeLists.txt
Outdated
@@ -142,6 +142,8 @@ target_sources(viamsdk | |||
../../viam/sdk/common/exception.hpp | |||
../../viam/sdk/common/linear_algebra.hpp | |||
../../viam/sdk/common/pose.hpp | |||
../../viam/sdk/common/proto_convert.hpp | |||
../../viam/sdk/common/proto_convert_vector.hpp |
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.
⬆️ - How close do the changes in this review get things w.r.t. removing this:
# This bit of magic is required so that we get the public build-time
# include path of viamapi in *ahead* of the path anchored to `src` by
# `BASE_DIRS` below, because otherwise we will pull in the static
# versions of the proto files even if we are generating dynamically.
target_include_directories(viamsdk
PUBLIC
"$<BUILD_INTERFACE:$<TARGET_PROPERTY:viam-cpp-sdk::viamapi,INTERFACE_INCLUDE_DIRECTORIES>>"
)
Or at least reducing it to PRIVATE
?
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.
Ooh interesting, I think that will be doable once the epic to get all API imports out of public headers is complete!
template <typename ProtoType> | ||
struct from_proto; | ||
|
||
template <typename Callable> |
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.
That's pretty horrific. Maybe throw a TODO on here that it would be nice to eliminate this boost dependency in our headers if possible? Maybe future us finds a nice way to do it in some future standard.
struct to_proto<std::vector<SdkType>> { | ||
void operator()( | ||
const std::vector<SdkType>& vec, | ||
::google::protobuf::RepeatedPtrField<EquivalentApiType<SdkType>>* ptr_field) const { | ||
ptr_field->Reserve(vec.size()); | ||
for (const auto& elem : vec) { | ||
to_proto<SdkType>{}(elem, ptr_field->Add()); | ||
} | ||
} | ||
}; | ||
|
||
template <typename ApiType> | ||
struct from_proto<::google::protobuf::RepeatedPtrField<ApiType>> { |
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.
OK, I think let's move them to internal then, since it seems we have similar thoughts here.
This PR partially does RSDK-9337 but I figured I would open it now because it is already getting high surface area and there are some API changes. (edit: OK sorry I just went ahead and added the rest of them)
Here we introduce the API bridge approach which will be used for opting in/out of inclusion of the viam API. The magic happens in
sdk/common/proto_convert.hpp
(open to renaming) which defines some template types which can be overridden to provide sizeless proto conversion interfaces, and the omni-callables which will "just work" on everything which specializes the templates. This will need to be documented somewhere, but now the approach for conversions is, for someviam::sdk::SdkType
andviam::common::v1::ApiType
, provide the following specializations:The header
proto_convert.hpp
also contains the definition of two callable objects calledto_proto
andfrom_proto
, and with the specializations above you getv2::to_proto(const SdkType&)
which returnsApiType
by value, andv2::from_proto(const ApiType&)
which returnsSdkType
by value. There are also type traitsEquivalentSdkType<ApiType>
andEquivalentApiType<SdkType>
which will tell you the corresponding types.The
v2
namespace is temporary pending resolution of a name clash with theto_proto
andfrom_proto
inproto_value.hpp
.There is also
proto_convert_vector
which handles conversion betweenstd::vector
andRepeatedPtrField
. Note however that for a givenstd::vector<SdkType>
andgoogle::proto::RepeatedPtrField<ApiType>
, if the specializations above are present then you can dov2::to_proto(const std::vector<SdkType>&)
andv2::from_proto(const google::proto::RepeatedPtrField<ApiType>&)
without any further intervention. This obviates the conversions inproto_utils.hpp
which will be removed in a subsequent PR.In the process of doing these changes I made some more-than-drive-by changes to some of the classes. Namely
Vector3
is now an aggregate with all public members and no user-defined constructorsconst
GeometryType
is now anenum class
, andGeometryType::unknown
is goneGeometryConfig
has some constructors instead of a bunch of trivial settersGeometryConfig
'sGeometryType
cannot vary independently of the storedgeometry_specifics
OrientationConfig
has been replaced withOrientation
, and there is now aget_type(const Orientation&)
rather than letting the type vary independently of theOrientation