Skip to content

Commit

Permalink
RSDK-794 ServiceHelper to simplify service method implementations (#178)
Browse files Browse the repository at this point in the history
  • Loading branch information
acmorrow authored Nov 14, 2023
1 parent 46c1395 commit 229d950
Show file tree
Hide file tree
Showing 7 changed files with 303 additions and 280 deletions.
1 change: 1 addition & 0 deletions src/viam/sdk/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ target_sources(viamsdk
common/linear_algebra.cpp
common/pose.cpp
common/proto_type.cpp
common/service_helper.cpp
common/utils.cpp
common/world_state.cpp
components/base/base.cpp
Expand Down
42 changes: 42 additions & 0 deletions src/viam/sdk/common/service_helper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <viam/sdk/common/service_helper.hpp>

#include <sstream>

namespace viam {
namespace sdk {

::grpc::Status ServiceHelperBase::fail(::grpc::StatusCode code, const char* message) const noexcept
try {
std::ostringstream stream;
stream << '[' << method_ << "]: " << message;
return {code, stream.str()};
} catch (...) {
return {code, message};
}

::grpc::Status ServiceHelperBase::failNoRequest() const noexcept {
return fail(::grpc::INVALID_ARGUMENT, "Called without a `request` object");
}

::grpc::Status ServiceHelperBase::failNoResource(const std::string& name) const noexcept try {
std::ostringstream stream;
stream << "Failed to find resource `" << name << "`";
return fail(::grpc::NOT_FOUND, stream.str().c_str());
} catch (...) {
return fail(::grpc::NOT_FOUND, "Failed to find resource");
}

::grpc::Status ServiceHelperBase::failStdException(const std::exception& xcp) const noexcept try {
std::ostringstream stream;
stream << "Failed with a std::exception: " << xcp.what();
return fail(::grpc::INTERNAL, stream.str().c_str());
} catch (...) {
return fail(::grpc::INTERNAL, "Failed with a std::exception: <unknown>");
}

::grpc::Status ServiceHelperBase::failUnknownException() const noexcept {
return fail(::grpc::INTERNAL, "Failed with an unknown exception");
}

} // namespace sdk
} // namespace viam
92 changes: 92 additions & 0 deletions src/viam/sdk/common/service_helper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#pragma once

#include <type_traits>

#include <viam/sdk/resource/resource_server_base.hpp>

namespace viam {
namespace sdk {

class ServiceHelperBase {
public:
::grpc::Status fail(::grpc::StatusCode code, const char* message) const noexcept;

::grpc::Status failNoRequest() const noexcept;

::grpc::Status failNoResource(const std::string& name) const noexcept;

::grpc::Status failStdException(const std::exception& xcp) const noexcept;

::grpc::Status failUnknownException() const noexcept;

protected:
explicit ServiceHelperBase(const char* method) noexcept : method_{method} {}

private:
const char* method_;
};

template <typename ServiceType, typename RequestType>
class ServiceHelper : public ServiceHelperBase {
public:
ServiceHelper(const char* method, ResourceServer* rs, RequestType* request) noexcept
: ServiceHelperBase{method}, rs_{rs}, request_{request} {};

template <typename Callable>
::grpc::Status operator()(Callable&& callable) const noexcept try {
if (!request_) {
return failNoRequest();
}
const auto resource = rs_->resource_manager()->resource<ServiceType>(request_->name());
if (!resource) {
return failNoResource(request_->name());
}
return invoke_(std::forward<Callable>(callable), std::move(resource));
} catch (const std::exception& xcp) {
return failStdException(xcp);
} catch (...) {
return failUnknownException();
}

auto getExtra() const {
return request_->has_extra() ? struct_to_map(request_->extra()) : AttributeMap{};
}

private:
template <typename Callable, typename... Args>
using is_void_result = std::is_void<std::result_of_t<Callable(Args...)>>;

// Implementation of `invoke_` for a Callable returning non-void,
// presumably an error return, which we return as a
// ::grpc::Status.
template <typename Callable,
typename ResourcePtrType,
std::enable_if_t<!is_void_result<Callable, ServiceHelper&, ResourcePtrType&&>::value,
bool> = true>
::grpc::Status invoke_(Callable&& callable, ResourcePtrType&& resource) const {
return std::forward<Callable>(callable)(*this, std::forward<ResourcePtrType>(resource));
}

// Implementation of `invoke_` for a Callable returning void,
// which is therefore either non-failing or communicates errors by
// throwing exceptions. We return an OK status automatically.
template <typename Callable,
typename ResourcePtrType,
std::enable_if_t<is_void_result<Callable, ServiceHelper&, ResourcePtrType&&>::value,
bool> = true>
::grpc::Status invoke_(Callable&& callable, ResourcePtrType&& resource) const {
std::forward<Callable>(callable)(*this, std::forward<ResourcePtrType>(resource));
return {};
}

ResourceServer* rs_;
RequestType* request_;
};

template <typename ServiceType, typename RequestType>
auto make_service_helper(const char* method, ResourceServer* rs, RequestType* request) {
return ServiceHelper<ServiceType, RequestType>{method, rs, request};
}

} // namespace sdk
} // namespace viam
146 changes: 36 additions & 110 deletions src/viam/sdk/components/encoder/server.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <viam/sdk/components/encoder/server.hpp>

#include <viam/sdk/common/service_helper.hpp>
#include <viam/sdk/common/utils.hpp>
#include <viam/sdk/components/encoder/encoder.hpp>
#include <viam/sdk/config/resource.hpp>
Expand All @@ -14,132 +15,57 @@ EncoderServer::EncoderServer(std::shared_ptr<ResourceManager> manager) : Resourc
::grpc::Status EncoderServer::GetPosition(
::grpc::ServerContext* context,
const ::viam::component::encoder::v1::GetPositionRequest* request,
::viam::component::encoder::v1::GetPositionResponse* response) {
if (!request) {
return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT,
"Called [Encoder::GetPosition] without a request");
};

const std::shared_ptr<Resource> rb = resource_manager()->resource(request->name());
if (!rb) {
return grpc::Status(grpc::UNKNOWN, "resource not found: " + request->name());
}

const std::shared_ptr<Encoder> encoder = std::dynamic_pointer_cast<Encoder>(rb);

AttributeMap extra;
if (request->has_extra()) {
extra = struct_to_map(request->extra());
}

const Encoder::position result =
encoder->get_position(extra, Encoder::from_proto(request->position_type()));
response->set_value(result.value);
response->set_position_type(Encoder::to_proto(result.type));

return ::grpc::Status();
::viam::component::encoder::v1::GetPositionResponse* response) noexcept {
return make_service_helper<Encoder>(
"EncoderServer::GetPosition", this, request)([&](auto& helper, auto& encoder) {
const Encoder::position result =
encoder->get_position(helper.getExtra(), Encoder::from_proto(request->position_type()));
response->set_value(result.value);
response->set_position_type(Encoder::to_proto(result.type));
});
}

::grpc::Status EncoderServer::ResetPosition(
::grpc::ServerContext* context,
const ::viam::component::encoder::v1::ResetPositionRequest* request,
::viam::component::encoder::v1::ResetPositionResponse* response) {
if (!request) {
return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT,
"Called [Encoder::ResetPosition] without a request");
};

const std::shared_ptr<Resource> rb = resource_manager()->resource(request->name());
if (!rb) {
return grpc::Status(grpc::UNKNOWN, "resource not found: " + request->name());
}

const std::shared_ptr<Encoder> encoder = std::dynamic_pointer_cast<Encoder>(rb);

AttributeMap extra;
if (request->has_extra()) {
extra = struct_to_map(request->extra());
}

encoder->reset_position(extra);

return ::grpc::Status();
::viam::component::encoder::v1::ResetPositionResponse* response) noexcept {
return make_service_helper<Encoder>("EncoderServer::ResetPosition", this, request)(
[&](auto& helper, auto& encoder) { encoder->reset_position(helper.getExtra()); });
}

::grpc::Status EncoderServer::GetProperties(
::grpc::ServerContext* context,
const ::viam::component::encoder::v1::GetPropertiesRequest* request,
::viam::component::encoder::v1::GetPropertiesResponse* response) {
if (!request) {
return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT,
"Called [Encoder::GetProperties] without a request");
};

const std::shared_ptr<Resource> rb = resource_manager()->resource(request->name());
if (!rb) {
return grpc::Status(grpc::UNKNOWN, "resource not found: " + request->name());
}

const std::shared_ptr<Encoder> encoder = std::dynamic_pointer_cast<Encoder>(rb);

AttributeMap extra;
if (request->has_extra()) {
extra = struct_to_map(request->extra());
}

const Encoder::properties result = encoder->get_properties(extra);
response->set_ticks_count_supported(result.ticks_count_supported);
response->set_angle_degrees_supported(result.angle_degrees_supported);

return ::grpc::Status();
::viam::component::encoder::v1::GetPropertiesResponse* response) noexcept {
return make_service_helper<Encoder>(
"EncoderServer::GetProperties", this, request)([&](auto& helper, auto& encoder) {
const Encoder::properties result = encoder->get_properties(helper.getExtra());
response->set_ticks_count_supported(result.ticks_count_supported);
response->set_angle_degrees_supported(result.angle_degrees_supported);
});
}

::grpc::Status EncoderServer::GetGeometries(::grpc::ServerContext* context,
const ::viam::common::v1::GetGeometriesRequest* request,
::viam::common::v1::GetGeometriesResponse* response) {
if (!request) {
return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT,
"Called [GetGeometries] without a request");
};

const std::shared_ptr<Resource> rb = resource_manager()->resource(request->name());
if (!rb) {
return grpc::Status(grpc::UNKNOWN, "resource not found: " + request->name());
}

AttributeMap extra;
if (request->has_extra()) {
extra = struct_to_map(request->extra());
}

const std::shared_ptr<Encoder> encoder = std::dynamic_pointer_cast<Encoder>(rb);
const std::vector<GeometryConfig> geometries = encoder->get_geometries(extra);
for (const auto& geometry : geometries) {
*response->mutable_geometries()->Add() = geometry.to_proto();
}

return ::grpc::Status();
::grpc::Status EncoderServer::GetGeometries(
::grpc::ServerContext* context,
const ::viam::common::v1::GetGeometriesRequest* request,
::viam::common::v1::GetGeometriesResponse* response) noexcept {
return make_service_helper<Encoder>(
"EncoderServer::GetGeometries", this, request)([&](auto& helper, auto& encoder) {
const std::vector<GeometryConfig> geometries = encoder->get_geometries(helper.getExtra());
for (const auto& geometry : geometries) {
*response->mutable_geometries()->Add() = geometry.to_proto();
}
});
}

::grpc::Status EncoderServer::DoCommand(grpc::ServerContext* context,
const viam::common::v1::DoCommandRequest* request,
viam::common::v1::DoCommandResponse* response) {
if (!request) {
return ::grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT,
"Called [Encoder::DoCommand] without a request");
};

const std::shared_ptr<Resource> rb = resource_manager()->resource(request->name());
if (!rb) {
return grpc::Status(grpc::UNKNOWN, "resource not found: " + request->name());
}

const std::shared_ptr<Encoder> encoder = std::dynamic_pointer_cast<Encoder>(rb);
const AttributeMap result = encoder->do_command(struct_to_map(request->command()));

*response->mutable_result() = map_to_struct(result);

return ::grpc::Status();
viam::common::v1::DoCommandResponse* response) noexcept {
return make_service_helper<Encoder>(
"EncoderServer::DoCommand", this, request)([&](auto& helper, auto& encoder) {
const AttributeMap result = encoder->do_command(struct_to_map(request->command()));
*response->mutable_result() = map_to_struct(result);
});
}

void EncoderServer::register_server(std::shared_ptr<Server> server) {
Expand Down
15 changes: 8 additions & 7 deletions src/viam/sdk/components/encoder/server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,26 @@ class EncoderServer : public ResourceServer,
::grpc::Status GetPosition(
::grpc::ServerContext* context,
const ::viam::component::encoder::v1::GetPositionRequest* request,
::viam::component::encoder::v1::GetPositionResponse* response) override;
::viam::component::encoder::v1::GetPositionResponse* response) noexcept override;

::grpc::Status ResetPosition(
::grpc::ServerContext* context,
const ::viam::component::encoder::v1::ResetPositionRequest* request,
::viam::component::encoder::v1::ResetPositionResponse* response) override;
::viam::component::encoder::v1::ResetPositionResponse* response) noexcept override;

::grpc::Status GetProperties(
::grpc::ServerContext* context,
const ::viam::component::encoder::v1::GetPropertiesRequest* request,
::viam::component::encoder::v1::GetPropertiesResponse* response) override;
::viam::component::encoder::v1::GetPropertiesResponse* response) noexcept override;

::grpc::Status GetGeometries(::grpc::ServerContext* context,
const ::viam::common::v1::GetGeometriesRequest* request,
::viam::common::v1::GetGeometriesResponse* response) override;
::grpc::Status GetGeometries(
::grpc::ServerContext* context,
const ::viam::common::v1::GetGeometriesRequest* request,
::viam::common::v1::GetGeometriesResponse* response) noexcept override;

::grpc::Status DoCommand(grpc::ServerContext* context,
const viam::common::v1::DoCommandRequest* request,
viam::common::v1::DoCommandResponse* response) override;
viam::common::v1::DoCommandResponse* response) noexcept override;

void register_server(std::shared_ptr<Server> server) override;
};
Expand Down
10 changes: 10 additions & 0 deletions src/viam/sdk/resource/resource_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include <memory>
#include <string>
#include <type_traits>
#include <unordered_map>

#include <boost/optional/optional.hpp>
Expand All @@ -27,6 +28,15 @@ class ResourceManager {
/// @throws `std::runtime_error` if the desired resource does not exist.
std::shared_ptr<Resource> resource(const std::string& name);

/// @brief Returns a resource after dynamically downcasting to `T`.
/// @param name of the desired resource.
/// @throws `std::runtime_error` if the desired resource does not exist.
template <typename T>
std::shared_ptr<T> resource(const std::string& name) {
static_assert(std::is_base_of<Resource, T>::value, "T is not derived from Resource");
return std::dynamic_pointer_cast<T>(resource(name));
}

/// @brief Replaces all resources in the manager.
/// @param resources The resources to replace with.
void replace_all(const std::unordered_map<Name, std::shared_ptr<Resource>>& resources);
Expand Down
Loading

0 comments on commit 229d950

Please sign in to comment.