nros C++ API
Lightweight ROS 2 client for embedded real-time systems (C++ headers)
Loading...
Searching...
No Matches
server.hpp
Go to the documentation of this file.
1// nros-cpp: Action server class
2// Freestanding C++ — no exceptions, no STL required
3
9
10#ifndef NROS_RCLCPP_ACTION_SERVER_HPP
11#define NROS_RCLCPP_ACTION_SERVER_HPP
12
13#include <cstdint>
14#include <cstddef>
15#include <string.h>
16
17#include "rclcpp/detail/callback_context.hpp" // phase-456 W3's one-word context carrier
19#include "rclcpp/detail/entity_name.hpp" // phase-444 — the one entity-name copy
20#include "rclcpp/owned.hpp" // issue 2015 — `Server<A>::SharedPtr`
21#include "rclcpp/result.hpp"
22#include "rclcpp_action/detail/upstream.hpp" // issue 2015 — hosted goal-handle plumbing
23// issue 0964 — the feedback/result TX buffers below size through
24// `detail::buffer_bounds`, so this header needs it directly rather than by
25// luck through a transitive include.
26#include "rclcpp/size_bound.hpp"
27
28// Phase 118.D — most action_server FFI symbols come from
29// `nros_cpp_ffi.h`; cbindgen renders Rust `*const [u8; 16]` as
30// pointer-to-array (`const uint8_t (*goal_id)[16]`), so callsites
31// below `reinterpret_cast` from the array-decay shape used in C++
32// member-function signatures.
33//
34// `nros_cpp_action_server_set_callbacks` is excluded from cbindgen
35// output (its Rust signature uses `Option<extern "C" fn(...)>`,
36// which cbindgen renders as opaque structs). Declare locally below
37// with plain function-pointer typedefs that match the actual ABI.
38#include "rclcpp/detail/nros_cpp_ffi.h"
39
40extern "C" {
41typedef int32_t (*nros_cpp_goal_callback_t)(const uint8_t goal_id[16], const uint8_t* data,
42 size_t len, void* ctx);
43typedef int32_t (*nros_cpp_cancel_callback_t)(const uint8_t goal_id[16], void* ctx);
44/* Issue 0796 — post-accept hook. Excluded from cbindgen for the same reason as
45 * the two above (`Option<extern "C" fn>` renders as an opaque struct), so this
46 * declaration is MIRRORED in `rclc/component.h` as well; `just check c`'s
47 * cross-include TU is the gate that catches a half-updated mirror. */
48typedef void (*nros_cpp_accepted_callback_t)(const uint8_t goal_id[16], void* ctx);
49
52 void* ctx);
55 void* ctx);
56} // extern "C"
57
58namespace rclcpp {
59namespace detail {
60
61// Issue 1913 — the hash each of an action's five endpoints is keyed on. An
62// Iron+ peer keys every endpoint on its own type, so the one `Goal::TYPE_HASH`
63// these entry points used to pass matched none of them. Generated action types
64// carry the five as `<Endpoint>_HASH` members; a type generated by an older
65// `nros` does not, and gets all-NULL, which the runtime reads as "use the type
66// hash" (the old behaviour). C++14, so detection is spelled by hand.
67template <class...> struct nros_void_ {
68 typedef void type;
69};
70
71template <class A, class = void> struct action_endpoint_hashes_of {
72 static nros_cpp_action_hashes_t get() {
73 nros_cpp_action_hashes_t h = {nullptr, nullptr, nullptr, nullptr, nullptr};
74 return h;
75 }
76};
77
78template <class A>
80 typename nros_void_<decltype(A::SEND_GOAL_SERVICE_HASH)>::type> {
81 static nros_cpp_action_hashes_t get() {
82 nros_cpp_action_hashes_t h = {A::SEND_GOAL_SERVICE_HASH, A::CANCEL_GOAL_SERVICE_HASH,
83 A::GET_RESULT_SERVICE_HASH, A::FEEDBACK_MESSAGE_HASH,
84 A::STATUS_MESSAGE_HASH};
85 return h;
86 }
87};
88
89template <class A> inline nros_cpp_action_hashes_t action_endpoint_hashes() {
91}
92
93} // namespace detail
94} // namespace rclcpp
95
96// phase-483 W1: the goal vocabulary lives in `rclcpp_action::`, where upstream
97// keeps it. It was `nros::` until then, and `rclcpp_action::` re-exported two
98// of the five names.
99namespace rclcpp_action {
100
126struct GoalUUID {
128 static const size_t SIZE = 16;
129
131 uint8_t uuid[16];
132
134 GoalUUID() : uuid{} {}
135
137 explicit GoalUUID(const uint8_t* src) : uuid{} {
138 for (size_t i = 0; i < SIZE; ++i) {
139 uuid[i] = src[i];
140 }
141 }
142
144 uint8_t* data() { return uuid; }
145 const uint8_t* data() const { return uuid; }
146
148 bool is_zero() const {
149 for (size_t i = 0; i < SIZE; ++i) {
150 if (uuid[i] != 0) return false;
151 }
152 return true;
153 }
154
155 bool operator==(const GoalUUID& other) const {
156 for (size_t i = 0; i < SIZE; ++i) {
157 if (uuid[i] != other.uuid[i]) return false;
158 }
159 return true;
160 }
161
162 bool operator!=(const GoalUUID& other) const { return !(*this == other); }
163
167 bool operator<(const GoalUUID& other) const {
168 for (size_t i = 0; i < SIZE; ++i) {
169 if (uuid[i] != other.uuid[i]) return uuid[i] < other.uuid[i];
170 }
171 return false;
172 }
173};
174
175static_assert(sizeof(GoalUUID) == 16,
176 "rclcpp_action::GoalUUID must be exactly the 16 bytes the FFI passes as uint8_t[16]");
177static_assert(alignof(GoalUUID) == alignof(uint8_t),
178 "rclcpp_action::GoalUUID must not add alignment over its uint8_t[16]");
179
191static const size_t ACTION_NAME_MAX = 256;
192
206enum class GoalResponse : int8_t {
210};
211
226enum class CancelResponse : int8_t {
229};
230
236constexpr int32_t to_ffi(GoalResponse r) {
237 return (r == GoalResponse::ACCEPT_AND_EXECUTE) ? 1
239 : 0;
240}
241
244constexpr int32_t to_ffi(CancelResponse r) {
245 return (r == CancelResponse::ACCEPT) ? 1 : 0;
246}
247
255enum class CancelReturnCode : int8_t {
256 Ok = 0,
260};
261
264enum class GoalStatus : int8_t {
272};
273
274} // namespace rclcpp_action
275
283namespace rclcpp {
284class Node;
285}
286
287// ============================================================================
288// `rclcpp_action::Server<A>` -- DEFINED here (RFC-0089: rclcpp_action:: is the home)
289// ============================================================================
290//
291// phase-428: the definition moved from `nros::ActionServer<A>` to
292// `rclcpp_action::Server<A>` (the `nros::` alias went in phase-483 W1). This one
293// is a RENAME as well as a move -- upstream's namespace already says "action",
294// so the type is `Server`, not `ActionServer`.
295//
296// The vocabulary the signatures use (`GoalUUID`, `GoalResponse`,
297// `CancelResponse`, `GoalStatus`, `CancelReturnCode`) is declared above, in
298// `rclcpp_action::` where upstream keeps it (phase-483 W1; it was `nros::`
299// before). `GoalResponse` and `CancelResponse` carry upstream's values since
300// issue 1637, so a ported `rclcpp_action::GoalResponse::REJECT` compiles AND
301// means what it meant upstream. Before that they were 0-based, and exposing
302// them under the upstream name would have turned a compile error into a silent
303// renumbering.
304namespace rclcpp_action {
305
306template <typename A> class ServerGoalHandle;
307namespace detail {
308template <typename A> struct ServerUpstream;
309} // namespace detail
310
351template <typename A> class Server {
352 public:
353 using GoalType = typename A::Goal;
354 using ResultType = typename A::Result;
355 using FeedbackType = typename A::Feedback;
356
361 using SharedPtr = ::rclcpp::Owned<Server>;
364
366 using TypedGoalFn = ::rclcpp_action::GoalResponse (*)(const uint8_t uuid[16],
367 const GoalType& goal);
369 using TypedGoalFnWithCtx = ::rclcpp_action::GoalResponse (*)(const uint8_t uuid[16],
370 const GoalType& goal, void* ctx);
372 using TypedCancelFn = ::rclcpp_action::CancelResponse (*)(const uint8_t uuid[16]);
374 using TypedCancelFnWithCtx = ::rclcpp_action::CancelResponse (*)(const uint8_t uuid[16],
375 void* ctx);
377 using TypedAcceptedFn = void (*)(const uint8_t uuid[16]);
379 using TypedAcceptedFnWithCtx = void (*)(const uint8_t uuid[16], void* ctx);
381 using TypedVisitorFn = void (*)(const uint8_t uuid[16], ::rclcpp_action::GoalStatus status);
382
389 template <typename F>::rclcpp::Result set_goal_callback(F f) {
390 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
391 user_goal_fn_ = TypedGoalFn(f); // compile error if F is not convertible
392 user_goal_fn_ctx_ = nullptr; // mutually exclusive with _with_ctx
393 user_goal_ctx_ = nullptr;
394 return install_callbacks();
395 }
396
405 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
406 user_goal_fn_ctx_ = f;
407 user_goal_ctx_ = ctx;
408 user_goal_fn_ = nullptr;
409 return install_callbacks();
410 }
411
415 template <typename F>::rclcpp::Result set_cancel_callback(F f) {
416 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
417 user_cancel_fn_ = TypedCancelFn(f); // compile error if F is not convertible
418 user_cancel_fn_ctx_ = nullptr; // mutually exclusive with _with_ctx
419 user_cancel_ctx_ = nullptr;
420 return install_callbacks();
421 }
422
430 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
431 user_cancel_fn_ctx_ = f;
432 user_cancel_ctx_ = ctx;
433 user_cancel_fn_ = nullptr;
434 return install_callbacks();
435 }
436
457 template <typename F>::rclcpp::Result set_accepted_callback(F f) {
458 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
459 user_accepted_fn_ = TypedAcceptedFn(f); // compile error if F is not convertible
460 user_accepted_fn_ctx_ = nullptr; // mutually exclusive with _with_ctx
461 user_accepted_ctx_ = nullptr;
462 return install_callbacks();
463 }
464
470 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
471 user_accepted_fn_ctx_ = f;
472 user_accepted_ctx_ = ctx;
473 user_accepted_fn_ = nullptr;
474 return install_callbacks();
475 }
476
486 ::rclcpp::Result publish_feedback(const uint8_t goal_id[16], const FeedbackType& feedback) {
487 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
488
489 uint8_t buf[::rclcpp::detail::buffer_bounds<FeedbackType>::tx];
490 size_t len = 0;
491 if (FeedbackType::ffi_serialize(&feedback, buf, sizeof(buf), &len) != 0) {
492 return ::rclcpp::Result(::rclcpp::ErrorCode::Error);
493 }
494 return ::rclcpp::Result(nros_cpp_action_server_publish_feedback(
495 storage_, executor_, reinterpret_cast<const uint8_t(*)[16]>(goal_id), buf, len));
496 }
497
512 const ResultType& result) {
513 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
514
515 uint8_t buf[::rclcpp::detail::buffer_bounds<ResultType>::tx];
516 size_t len = 0;
517 if (ResultType::ffi_serialize(&result, buf, sizeof(buf), &len) != 0) {
518 return ::rclcpp::Result(::rclcpp::ErrorCode::Error);
519 }
520 return ::rclcpp::Result(nros_cpp_action_server_complete_goal(
521 storage_, executor_, reinterpret_cast<const uint8_t(*)[16]>(goal_id),
522 static_cast<int32_t>(status), buf, len));
523 }
524
527 ::rclcpp::Result complete_goal(const uint8_t goal_id[16], const ResultType& result) {
529 }
530
532 ::rclcpp::Result publish_feedback(const ::rclcpp_action::GoalUUID& goal_id,
533 const FeedbackType& feedback) {
534 return publish_feedback(goal_id.data(), feedback);
535 }
536
538 ::rclcpp::Result complete_goal(const ::rclcpp_action::GoalUUID& goal_id,
539 ::rclcpp_action::GoalStatus status, const ResultType& result) {
540 return complete_goal(goal_id.data(), status, result);
541 }
542
544 ::rclcpp::Result complete_goal(const ::rclcpp_action::GoalUUID& goal_id,
545 const ResultType& result) {
546 return complete_goal(goal_id.data(), ::rclcpp_action::GoalStatus::Succeeded, result);
547 }
548
563 ::rclcpp::Result execute(const uint8_t goal_id[16]) {
564 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
565 return ::rclcpp::Result(nros_cpp_action_server_execute(
566 storage_, executor_, reinterpret_cast<const uint8_t(*)[16]>(goal_id)));
567 }
568
570 ::rclcpp::Result execute(const ::rclcpp_action::GoalUUID& goal_id) {
571 return execute(goal_id.data());
572 }
573
590 ::rclcpp::Result succeed(const uint8_t goal_id[16], const ResultType& result) {
592 }
593
598 ::rclcpp::Result abort(const uint8_t goal_id[16], const ResultType& result) {
600 }
601
610 ::rclcpp::Result canceled(const uint8_t goal_id[16], const ResultType& result) {
612 }
613
615 ::rclcpp::Result succeed(const ::rclcpp_action::GoalUUID& goal_id, const ResultType& result) {
616 return complete_goal(goal_id.data(), ::rclcpp_action::GoalStatus::Succeeded, result);
617 }
618
620 ::rclcpp::Result abort(const ::rclcpp_action::GoalUUID& goal_id, const ResultType& result) {
621 return complete_goal(goal_id.data(), ::rclcpp_action::GoalStatus::Aborted, result);
622 }
623
625 ::rclcpp::Result canceled(const ::rclcpp_action::GoalUUID& goal_id, const ResultType& result) {
626 return complete_goal(goal_id.data(), ::rclcpp_action::GoalStatus::Canceled, result);
627 }
628
650 template <typename F>::rclcpp::Result for_each_active_goal(F f) {
651 if (!initialized_) return ::rclcpp::Result(::rclcpp::ErrorCode::NotInitialized);
652 TypedVisitorFn visitor = TypedVisitorFn(f); // compile error if F is not convertible
653
654 auto trampoline = [](const uint8_t goal_id[16], int8_t status, void* ctx) {
655 TypedVisitorFn fn = ::rclcpp::detail::fn_from_context<TypedVisitorFn>(ctx);
656 if (fn == nullptr) return;
657 fn(goal_id, static_cast<::rclcpp_action::GoalStatus>(status));
658 };
659 return ::rclcpp::Result(nros_cpp_action_server_for_each_active_goal(
660 storage_, executor_,
661 reinterpret_cast<void (*)(const uint8_t(*)[16], int8_t, void*)>(+trampoline),
662 ::rclcpp::detail::fn_to_context(visitor)));
663 }
664
687 const char* get_action_name() const { return initialized_ ? action_name_ : ""; }
688
696 bool goal_exists(const uint8_t goal_id[16]) const {
697 if (!initialized_) return false;
698 return nros_cpp_action_server_goal_exists(storage_, executor_,
699 reinterpret_cast<const uint8_t(*)[16]>(goal_id));
700 }
701
703 bool goal_exists(const ::rclcpp_action::GoalUUID& goal_id) const {
704 return goal_exists(goal_id.data());
705 }
706
708 bool is_valid() const { return initialized_; }
709
725 if (initialized_) {
726 nros_cpp_action_server_detach(storage_, executor_);
727 nros_cpp_action_server_destroy(storage_);
728 initialized_ = false;
729 }
730 }
731
732 // Move semantics (non-copyable). Relocation goes through the
733 // `nros_cpp_action_server_relocate` runtime call (Phase 84.C1); then
734 // `nros_cpp_action_server_retarget` re-points the executor's arena entry
735 // (whose callback context is the STORAGE address) at the new storage —
736 // issue 2015: without it a moved server dispatched through the moved-from
737 // bytes — and `install_callbacks()` re-registers the goal/cancel
738 // trampolines with the new `this` as THEIR context.
739 Server(Server&& other)
740 : executor_(other.executor_), user_goal_fn_(other.user_goal_fn_),
741 user_goal_fn_ctx_(other.user_goal_fn_ctx_), user_goal_ctx_(other.user_goal_ctx_),
742 user_cancel_fn_(other.user_cancel_fn_), user_cancel_fn_ctx_(other.user_cancel_fn_ctx_),
743 user_cancel_ctx_(other.user_cancel_ctx_), user_accepted_fn_(other.user_accepted_fn_),
744 user_accepted_fn_ctx_(other.user_accepted_fn_ctx_),
745 user_accepted_ctx_(other.user_accepted_ctx_), action_name_{},
746 initialized_(other.initialized_),
747 upstream_(static_cast<detail::HostedBox&&>(other.upstream_)) {
748 ::memcpy(action_name_, other.action_name_, sizeof(action_name_));
749 if (other.initialized_) {
750 nros_cpp_action_server_relocate(other.storage_, storage_);
751 // issue 2015 — the arena entry follows the storage.
752 (void)nros_cpp_action_server_retarget(storage_, executor_);
753 other.initialized_ = false;
754 (void)install_callbacks();
755 }
757 }
758
760 if (this != &other) {
761 if (initialized_) {
762 // issue 1496 — same pairing as `~Server()`: release the arena
763 // entry this storage owns before the storage is overwritten by
764 // the relocate below.
765 nros_cpp_action_server_detach(storage_, executor_);
766 nros_cpp_action_server_destroy(storage_);
767 }
768 executor_ = other.executor_;
769 user_goal_fn_ = other.user_goal_fn_;
770 user_goal_fn_ctx_ = other.user_goal_fn_ctx_;
771 user_goal_ctx_ = other.user_goal_ctx_;
772 user_cancel_fn_ = other.user_cancel_fn_;
773 user_cancel_fn_ctx_ = other.user_cancel_fn_ctx_;
774 user_cancel_ctx_ = other.user_cancel_ctx_;
775 user_accepted_fn_ = other.user_accepted_fn_;
776 user_accepted_fn_ctx_ = other.user_accepted_fn_ctx_;
777 user_accepted_ctx_ = other.user_accepted_ctx_;
778 ::memcpy(action_name_, other.action_name_, sizeof(action_name_));
779 initialized_ = other.initialized_;
780 upstream_ = static_cast<detail::HostedBox&&>(other.upstream_);
781 if (other.initialized_) {
782 nros_cpp_action_server_relocate(other.storage_, storage_);
783 // issue 2015 — the arena entry follows the storage.
784 (void)nros_cpp_action_server_retarget(storage_, executor_);
785 other.initialized_ = false;
786 (void)install_callbacks();
787 }
789 }
790 return *this;
791 }
792
796 : executor_(nullptr), user_goal_fn_(nullptr), user_goal_fn_ctx_(nullptr),
797 user_goal_ctx_(nullptr), user_cancel_fn_(nullptr), user_cancel_fn_ctx_(nullptr),
798 user_cancel_ctx_(nullptr), user_accepted_fn_(nullptr), user_accepted_fn_ctx_(nullptr),
799 user_accepted_ctx_(nullptr), action_name_{}, initialized_(false) {}
800
801 private:
802 Server(const Server&) = delete;
803 Server& operator=(const Server&) = delete;
804
805 friend class ::rclcpp::Node;
806 // Issue 2015 — the hosted goal handle asks this server for its goal's
807 // status, and `create_server` binds upstream's callbacks onto it.
808 friend class ::rclcpp_action::ServerGoalHandle<A>;
809 friend struct detail::ServerUpstream<A>;
810
812 bool goal_status(const uint8_t goal_id[16], int8_t& out) {
813 if (!initialized_) return false;
814 struct Query {
815 const uint8_t* id;
816 int8_t status;
817 bool found;
818 } q = {goal_id, 0, false};
819 auto visit = [](const uint8_t(*id)[16], int8_t status, void* ctx) {
820 Query* qq = static_cast<Query*>(ctx);
821 if (::memcmp(*id, qq->id, 16) == 0) {
822 qq->status = status;
823 qq->found = true;
824 }
825 };
826 if (nros_cpp_action_server_for_each_active_goal(storage_, executor_, +visit, &q) != 0)
827 return false;
828 out = q.status;
829 return q.found;
830 }
831
832 // ── C trampolines ───────────────────────────────────────────────
833 //
834 // `ctx` is a pointer to this `ActionServer<A>` instance, so the
835 // trampoline reads the user's stored function pointer via the
836 // instance's own fields — no shared mutable statics.
837
838 static int32_t goal_trampoline(const uint8_t goal_id[16], const uint8_t* data, size_t len,
839 void* ctx) {
840 auto* self = static_cast<Server*>(ctx);
841 if (!self) return ::rclcpp_action::to_ffi(::rclcpp_action::GoalResponse::REJECT);
842 GoalType g;
843 if (GoalType::ffi_deserialize(data, len, &g) != 0) {
844 return ::rclcpp_action::to_ffi(::rclcpp_action::GoalResponse::REJECT);
845 }
846 if (self->user_goal_fn_ctx_ != nullptr) {
847 return ::rclcpp_action::to_ffi(
848 self->user_goal_fn_ctx_(goal_id, g, self->user_goal_ctx_));
849 }
850 if (self->user_goal_fn_ != nullptr) {
851 return ::rclcpp_action::to_ffi(self->user_goal_fn_(goal_id, g));
852 }
853 return ::rclcpp_action::to_ffi(::rclcpp_action::GoalResponse::REJECT);
854 }
855
856 static int32_t cancel_trampoline(const uint8_t goal_id[16], void* ctx) {
857 auto* self = static_cast<Server*>(ctx);
858 if (!self) return ::rclcpp_action::to_ffi(::rclcpp_action::CancelResponse::ACCEPT);
859 if (self->user_cancel_fn_ctx_ != nullptr) {
860 return ::rclcpp_action::to_ffi(
861 self->user_cancel_fn_ctx_(goal_id, self->user_cancel_ctx_));
862 }
863 if (self->user_cancel_fn_ != nullptr) {
864 return ::rclcpp_action::to_ffi(self->user_cancel_fn_(goal_id));
865 }
866 return ::rclcpp_action::to_ffi(::rclcpp_action::CancelResponse::ACCEPT);
867 }
868
869 static void accepted_trampoline(const uint8_t goal_id[16], void* ctx) {
870 auto* self = static_cast<Server*>(ctx);
871 if (!self) return;
872 if (self->user_accepted_fn_ctx_ != nullptr) {
873 self->user_accepted_fn_ctx_(goal_id, self->user_accepted_ctx_);
874 return;
875 }
876 if (self->user_accepted_fn_ != nullptr) {
877 self->user_accepted_fn_(goal_id);
878 }
879 }
880
881 ::rclcpp::Result install_callbacks() {
882 bool goal_set = (user_goal_fn_ != nullptr) || (user_goal_fn_ctx_ != nullptr);
883 bool cancel_set = (user_cancel_fn_ != nullptr) || (user_cancel_fn_ctx_ != nullptr);
884 bool accepted_set = (user_accepted_fn_ != nullptr) || (user_accepted_fn_ctx_ != nullptr);
885 nros_cpp_goal_callback_t gcb = goal_set ? &goal_trampoline : nullptr;
886 nros_cpp_cancel_callback_t ccb = cancel_set ? &cancel_trampoline : nullptr;
887 nros_cpp_ret_t ret = nros_cpp_action_server_set_callbacks(storage_, gcb, ccb, this);
888 if (ret != 0) return ::rclcpp::Result(ret);
889 // Issue 0796 — a SECOND install call rather than a fourth parameter on
890 // the first: `nros_cpp_action_server_set_callbacks` is declared in
891 // three places and called directly by C components, so growing it
892 // would have broken every one of them. Both calls pass the same
893 // `this`, which is the shared context slot the runtime keeps.
894 nros_cpp_accepted_callback_t acb = accepted_set ? &accepted_trampoline : nullptr;
895 return ::rclcpp::Result(nros_cpp_action_server_set_accepted_callback(storage_, acb, this));
896 }
897
898 alignas(8) uint8_t storage_[NROS_CPP_ACTION_SERVER_STORAGE_SIZE];
899 void* executor_; // Executor context needed for feedback/result operations
900 TypedGoalFn user_goal_fn_;
901 TypedGoalFnWithCtx user_goal_fn_ctx_;
902 void* user_goal_ctx_;
903 TypedCancelFn user_cancel_fn_;
904 TypedCancelFnWithCtx user_cancel_fn_ctx_;
905 void* user_cancel_ctx_;
906 TypedAcceptedFn user_accepted_fn_;
907 TypedAcceptedFnWithCtx user_accepted_fn_ctx_;
908 void* user_accepted_ctx_;
909 // phase-456 W3b — a `TypedVisitorFn user_visitor_fn_` sat here, written at
910 // the head of `for_each_active_goal` and cleared at its tail. The visitor
911 // travels in the FFI's own `void* ctx` now; see that function.
912 // Phase 87.6 put a `char action_name_[256]` here for a `get_action_name()`
913 // that was never written, and an earlier phase-417 W4.b wave deleted it as
914 // dead state. W4.b's second half writes the accessor, so it is back and it
915 // is read — see `get_action_name()` for why the FFI cannot answer instead.
916 char action_name_[::rclcpp_action::ACTION_NAME_MAX];
917 bool initialized_;
921 detail::HostedBox upstream_;
922};
923
940template <typename A> class ServerGoalHandle {
941 public:
942 using Goal = typename A::Goal;
943 using Feedback = typename A::Feedback;
944 using Result = typename A::Result;
945#ifdef NROS_CPP_HAS_SHARED_PTR // hosted-family: action-interop
946 using SharedPtr = ::std::shared_ptr<ServerGoalHandle>;
947 using ConstSharedPtr = ::std::shared_ptr<const ServerGoalHandle>;
948#else
949 using SharedPtr = detail::Refused<ServerGoalHandle>;
950 using ConstSharedPtr = detail::Refused<ServerGoalHandle>;
951#endif
952
953 ServerGoalHandle(Server<A>* server, const GoalUUID& uuid)
954 : server_(server), uuid_(uuid), goal_() {}
955
957 const GoalUUID& get_goal_id() const { return uuid_; }
959 bool is_canceling() const { return status() == GoalStatus::Canceling; }
961 bool is_executing() const { return status() == GoalStatus::Executing; }
968 void execute() {
969 if (server_ != nullptr) (void)server_->execute(uuid_.data());
970 }
971
972 bool is_active() const {
973 const GoalStatus s = status();
974 return s == GoalStatus::Accepted || s == GoalStatus::Executing ||
976 }
977
978#ifdef NROS_CPP_HAS_SHARED_PTR // hosted-family: action-interop
980 ::std::shared_ptr<const Goal> get_goal() const {
981 auto* g = goal_.template as<::std::shared_ptr<const Goal>>();
982 return g != nullptr ? *g : ::std::shared_ptr<const Goal>();
983 }
985 void publish_feedback(::std::shared_ptr<Feedback> feedback) {
986 if (feedback && server_ != nullptr)
987 (void)server_->publish_feedback(uuid_.data(), *feedback);
988 }
990 void succeed(::std::shared_ptr<Result> result) { terminate(GoalStatus::Succeeded, result); }
992 void abort(::std::shared_ptr<Result> result) { terminate(GoalStatus::Aborted, result); }
994 void canceled(::std::shared_ptr<Result> result) { terminate(GoalStatus::Canceled, result); }
995#endif
996
997 private:
998 friend struct detail::ServerUpstream<A>;
999
1000 GoalStatus status() const {
1001 int8_t s = 0;
1002 if (server_ == nullptr || !server_->goal_status(uuid_.data(), s))
1003 return GoalStatus::Unknown;
1004 return static_cast<GoalStatus>(s);
1005 }
1006
1007#ifdef NROS_CPP_HAS_SHARED_PTR // hosted-family: action-interop
1008 void terminate(GoalStatus status, const ::std::shared_ptr<Result>& result) {
1009 if (server_ == nullptr) return;
1010 const Result empty{};
1011 (void)server_->complete_goal(uuid_.data(), status, result ? *result : empty);
1012 detail::ServerUpstream<A>::forget(*server_, uuid_);
1013 }
1014#endif
1015
1016 Server<A>* server_;
1017 GoalUUID uuid_;
1019 detail::HostedBox goal_;
1020};
1021
1022namespace detail {
1023
1029template <typename A> struct ServerUpstream {
1030 using Goal = typename A::Goal;
1032
1034 static void rehome(Server<A>& s) {
1035#ifdef NROS_CPP_HAS_SHARED_PTR // hosted-family: action-interop
1036 if (State* st = s.upstream_.template as<State>()) st->server = &s;
1037#else
1038 (void)s;
1039#endif
1040 }
1041
1042#ifdef NROS_CPP_HAS_SHARED_PTR // hosted-family: action-interop
1043 using GoalFn = ::std::function<GoalResponse(const GoalUUID&, ::std::shared_ptr<const Goal>)>;
1044 using CancelFn = ::std::function<CancelResponse(::std::shared_ptr<Handle>)>;
1045 using AcceptedFn = ::std::function<void(::std::shared_ptr<Handle>)>;
1046
1047 struct Entry {
1048 GoalUUID id;
1049 ::std::shared_ptr<const Goal> goal;
1052 bool execute;
1053 };
1054 struct State {
1055 GoalFn goal;
1056 CancelFn cancel;
1057 AcceptedFn accepted;
1058 Server<A>* server;
1059 ::std::vector<Entry> goals;
1060 };
1061
1062 static void destroy(void* p) { delete static_cast<State*>(p); }
1063
1064 template <typename G, typename C, typename Acc>
1065 static ::rclcpp::Result bind(Server<A>& s, G&& goal, C&& cancel, Acc&& accepted) {
1066 State* st = new State{GoalFn(static_cast<G&&>(goal)),
1067 CancelFn(static_cast<C&&>(cancel)),
1068 AcceptedFn(static_cast<Acc&&>(accepted)),
1069 &s,
1070 {}};
1071 s.upstream_.reset(st, &destroy);
1072 ::rclcpp::Result r = s.set_goal_callback_with_ctx(&on_goal, st);
1073 if (r.ok()) r = s.set_cancel_callback_with_ctx(&on_cancel, st);
1074 if (r.ok()) r = s.set_accepted_callback_with_ctx(&on_accepted, st);
1075 return r;
1076 }
1077
1079 static void forget(Server<A>& s, const GoalUUID& id) {
1080 State* st = s.upstream_.template as<State>();
1081 if (st == nullptr) return;
1082 for (size_t i = 0; i < st->goals.size(); ++i) {
1083 if (st->goals[i].id == id) {
1084 st->goals.erase(st->goals.begin() + static_cast<long>(i));
1085 return;
1086 }
1087 }
1088 }
1089
1090 private:
1091 static ::std::shared_ptr<Handle> make_handle(State* st, const GoalUUID& id) {
1092 ::std::shared_ptr<Handle> h = ::std::make_shared<Handle>(st->server, id);
1093 for (const Entry& e : st->goals) {
1094 if (e.id == id) {
1095 h->goal_.reset(new ::std::shared_ptr<const Goal>(e.goal), [](void* p) {
1096 delete static_cast<::std::shared_ptr<const Goal>*>(p);
1097 });
1098 break;
1099 }
1100 }
1101 return h;
1102 }
1103
1104 static GoalResponse on_goal(const uint8_t uuid[16], const Goal& goal, void* ctx) {
1105 State* st = static_cast<State*>(ctx);
1106 if (!st->goal) return GoalResponse::REJECT;
1107 const GoalUUID id(uuid);
1108 ::std::shared_ptr<const Goal> msg = ::std::make_shared<const Goal>(goal);
1109 const GoalResponse r = st->goal(id, msg);
1110 if (r != GoalResponse::REJECT) {
1111 forget(*st->server, id);
1112 st->goals.push_back(Entry{id, msg, r == GoalResponse::ACCEPT_AND_EXECUTE});
1113 }
1114 return r;
1115 }
1116
1117 static CancelResponse on_cancel(const uint8_t uuid[16], void* ctx) {
1118 State* st = static_cast<State*>(ctx);
1119 if (!st->cancel) return CancelResponse::REJECT;
1120 return st->cancel(make_handle(st, GoalUUID(uuid)));
1121 }
1122
1123 static void on_accepted(const uint8_t uuid[16], void* ctx) {
1124 State* st = static_cast<State*>(ctx);
1125 const GoalUUID id(uuid);
1126 // Issue 2027 — upstream's server calls `execute()` itself for
1127 // ACCEPT_AND_EXECUTE, so the goal is EXECUTING (and the status array
1128 // says so) by the time `handle_accepted` sees it; ACCEPT_AND_DEFER
1129 // stays ACCEPTED until the handle's `execute()`.
1130 for (const Entry& e : st->goals) {
1131 if (e.id == id) {
1132 if (e.execute) (void)st->server->execute(uuid);
1133 break;
1134 }
1135 }
1136 if (st->accepted) st->accepted(make_handle(st, id));
1137 }
1138#endif
1139};
1140
1141} // namespace detail
1142
1143} // namespace rclcpp_action
1144
1145// Phase 84.G8: out-of-line definition of Node::create_action_server<A>().
1146#include "rclcpp/node.hpp"
1147
1148namespace rclcpp {} // namespace rclcpp
1149
1150namespace rclcpp {
1151template <typename A>
1153 const ::rclcpp::QoS& qos,
1154 const ::rclcpp::ActionServerOptions& options) {
1155 if (!initialized_) return Result(::rclcpp::ErrorCode::NotInitialized);
1157 nros_cpp_ret_t ret = nros_cpp_action_server_create(&handle_, action_name, A::TYPE_NAME,
1158 A::Goal::TYPE_HASH, ffi_qos, out.storage_);
1159 if (ret != 0) return Result(ret);
1160 // Register with executor — creates transport handles (3 queryables + 2 publishers).
1161 // Deferred from create to avoid FreeRTOS QEMU deadlocks. Phase 87.6:
1162 // names are passed at register-time (buffers live on the C++
1163 // `rclcpp_action::Server<A>` class, not in the runtime struct).
1164 // Phase 189.M3.3.c — `sched_context` binds the action's (arena-registered)
1165 // goal-service handle to a scheduling context. UNSET ⇒ 0 (inherit, no-op).
1166 uint8_t sched = (options.sched_context == ::rclcpp::SCHED_CONTEXT_UNSET)
1167 ? 0u
1168 : static_cast<uint8_t>(options.sched_context);
1169 const nros_cpp_action_hashes_t hashes = ::rclcpp::detail::action_endpoint_hashes<A>();
1170 ret = nros_cpp_action_server_register(out.storage_, executor_handle_, action_name, A::TYPE_NAME,
1171 A::Goal::TYPE_HASH, &hashes, sched);
1172 if (ret == 0) {
1173 out.executor_ = executor_handle_;
1174 // phase-417 W4.b — remember the name for `get_action_name()`. Truncating
1175 // copy, as `Subscription`'s and the two polling tiers' do: a name longer
1176 // than `rclcpp_action::ACTION_NAME_MAX` is not a reason to fail a create that the
1177 // runtime already accepted.
1178 ::rclcpp::detail::assign_entity_name(out.action_name_, action_name);
1179 out.initialized_ = true;
1180 }
1181 return Result(ret);
1182}
1183} // namespace rclcpp
1184
1185namespace rclcpp {} // namespace rclcpp
1186
1187#endif // NROS_RCLCPP_ACTION_SERVER_HPP
Result create_action_server(::rclcpp_action::Server< A > &out, const char *action_name, const ::rclcpp::QoS &qos=::rclcpp::QoS::services(), const ::rclcpp::ActionServerOptions &options={})
Definition server.hpp:1152
bool ok() const
Returns true if the operation succeeded.
Definition result.hpp:227
Definition server.hpp:940
detail::Refused< ServerGoalHandle > SharedPtr
Definition server.hpp:949
ServerGoalHandle(Server< A > *server, const GoalUUID &uuid)
Definition server.hpp:953
typename A::Feedback Feedback
Definition server.hpp:943
typename A::Goal Goal
Definition server.hpp:942
typename A::Result Result
Definition server.hpp:944
detail::Refused< ServerGoalHandle > ConstSharedPtr
Definition server.hpp:950
void execute()
Definition server.hpp:968
bool is_active() const
The goal has not reached a terminal state.
Definition server.hpp:972
bool is_canceling() const
The goal is CANCELING: a cancel request for it was accepted.
Definition server.hpp:959
bool is_executing() const
The goal is EXECUTING.
Definition server.hpp:961
const GoalUUID & get_goal_id() const
The goal's id.
Definition server.hpp:957
Definition server.hpp:351
void(*)(const uint8_t uuid[16], ::rclcpp_action::GoalStatus status) TypedVisitorFn
User-facing visitor signature for for_each_active_goal.
Definition server.hpp:381
~Server()
Definition server.hpp:724
::rclcpp::Owned< Server > SharedPtr
Definition server.hpp:361
::rclcpp::Result abort(const ::rclcpp_action::GoalUUID &goal_id, const ResultType &result)
abort taking a GoalUUID value.
Definition server.hpp:620
::rclcpp::Result complete_goal(const uint8_t goal_id[16], const ResultType &result)
Definition server.hpp:527
::rclcpp::Result abort(const uint8_t goal_id[16], const ResultType &result)
Definition server.hpp:598
::rclcpp_action::GoalResponse(*)(const uint8_t uuid[16], const GoalType &goal, void *ctx) TypedGoalFnWithCtx
User-facing typed goal callback signature with user context (Phase 84.G9).
Definition server.hpp:369
::rclcpp::Result publish_feedback(const uint8_t goal_id[16], const FeedbackType &feedback)
Definition server.hpp:486
::rclcpp::Result complete_goal(const uint8_t goal_id[16], ::rclcpp_action::GoalStatus status, const ResultType &result)
Definition server.hpp:511
::rclcpp::Result set_goal_callback_with_ctx(TypedGoalFnWithCtx f, void *ctx)
Definition server.hpp:404
bool goal_exists(const ::rclcpp_action::GoalUUID &goal_id) const
goal_exists taking a GoalUUID value.
Definition server.hpp:703
::rclcpp::Result set_accepted_callback_with_ctx(TypedAcceptedFnWithCtx f, void *ctx)
Definition server.hpp:469
::rclcpp::Result set_cancel_callback(F f)
Definition server.hpp:415
typename A::Feedback FeedbackType
Definition server.hpp:355
::rclcpp::Result succeed(const ::rclcpp_action::GoalUUID &goal_id, const ResultType &result)
succeed taking a GoalUUID value.
Definition server.hpp:615
::rclcpp::Result complete_goal(const ::rclcpp_action::GoalUUID &goal_id, ::rclcpp_action::GoalStatus status, const ResultType &result)
complete_goal taking a GoalUUID value (phase-417 W4.b).
Definition server.hpp:538
::rclcpp::Result canceled(const uint8_t goal_id[16], const ResultType &result)
Definition server.hpp:610
const char * get_action_name() const
Definition server.hpp:687
Server(Server &&other)
Definition server.hpp:739
Server()
Definition server.hpp:795
void(*)(const uint8_t uuid[16], void *ctx) TypedAcceptedFnWithCtx
User-facing typed accepted-goal callback signature with user context.
Definition server.hpp:379
::rclcpp::Result canceled(const ::rclcpp_action::GoalUUID &goal_id, const ResultType &result)
canceled taking a GoalUUID value.
Definition server.hpp:625
Server & operator=(Server &&other)
Definition server.hpp:759
void(*)(const uint8_t uuid[16]) TypedAcceptedFn
User-facing typed accepted-goal callback signature (issue 0796).
Definition server.hpp:377
::rclcpp_action::CancelResponse(*)(const uint8_t uuid[16], void *ctx) TypedCancelFnWithCtx
User-facing typed cancel callback signature with user context (Phase 84.G9).
Definition server.hpp:374
::rclcpp::Result set_goal_callback(F f)
Definition server.hpp:389
::rclcpp::Result execute(const ::rclcpp_action::GoalUUID &goal_id)
execute taking a GoalUUID value.
Definition server.hpp:570
::rclcpp::Result set_accepted_callback(F f)
Definition server.hpp:457
::rclcpp::Result publish_feedback(const ::rclcpp_action::GoalUUID &goal_id, const FeedbackType &feedback)
publish_feedback taking a GoalUUID value (phase-417 W4.b).
Definition server.hpp:532
::rclcpp_action::GoalResponse(*)(const uint8_t uuid[16], const GoalType &goal) TypedGoalFn
User-facing typed goal callback signature.
Definition server.hpp:366
bool goal_exists(const uint8_t goal_id[16]) const
Definition server.hpp:696
::rclcpp::Result set_cancel_callback_with_ctx(TypedCancelFnWithCtx f, void *ctx)
Definition server.hpp:429
typename A::Goal GoalType
Definition server.hpp:353
::rclcpp_action::CancelResponse(*)(const uint8_t uuid[16]) TypedCancelFn
User-facing typed cancel callback signature.
Definition server.hpp:372
bool is_valid() const
Check if the action server is initialized and valid.
Definition server.hpp:708
::rclcpp_action::ServerGoalHandle< A > GoalHandle
Upstream's rclcpp_action::ServerGoalHandle<A> for this action.
Definition server.hpp:363
::rclcpp::Result complete_goal(const ::rclcpp_action::GoalUUID &goal_id, const ResultType &result)
complete_goal taking a GoalUUID value (phase-417 W4.b).
Definition server.hpp:544
typename A::Result ResultType
Definition server.hpp:354
::rclcpp::Result for_each_active_goal(F f)
Definition server.hpp:650
::rclcpp::Result succeed(const uint8_t goal_id[16], const ResultType &result)
Definition server.hpp:590
::rclcpp::Result execute(const uint8_t goal_id[16])
Definition server.hpp:563
Inline storage-size macros for opaque entity buffers.
int nros_cpp_ret_t
Definition future.hpp:25
Definition rclcpp.hpp:418
constexpr nros_cpp_qos_t qos_to_ffi(const QoS &qos)
Definition qos.hpp:719
nros_cpp_action_hashes_t action_endpoint_hashes()
Definition server.hpp:89
Definition server.hpp:307
Definition node.hpp:203
constexpr int32_t to_ffi(GoalResponse r)
Definition server.hpp:236
CancelResponse
Definition server.hpp:226
@ ACCEPT
Definition server.hpp:228
GoalStatus
Definition server.hpp:264
@ Canceled
Definition server.hpp:270
@ Accepted
Definition server.hpp:266
@ Executing
Definition server.hpp:267
@ Succeeded
Definition server.hpp:269
@ Aborted
Definition server.hpp:271
@ Canceling
Definition server.hpp:268
@ Unknown
Definition server.hpp:265
static const size_t ACTION_NAME_MAX
Definition server.hpp:191
Client & operator=(Client &&other)
Definition client.hpp:1002
GoalResponse
Definition server.hpp:206
@ ACCEPT_AND_DEFER
Definition server.hpp:209
@ REJECT
Definition server.hpp:207
@ ACCEPT_AND_EXECUTE
Definition server.hpp:208
CancelReturnCode
Definition server.hpp:255
@ Ok
Definition server.hpp:256
@ Rejected
Definition server.hpp:257
@ GoalTerminated
Definition server.hpp:259
@ UnknownGoal
Definition server.hpp:258
Definition rclcpp.hpp:75
@ Error
Generic failure not covered by a more specific code.
Definition result.hpp:118
@ NotInitialized
Definition result.hpp:131
ResultOf< void > Result
Definition result.hpp:247
rclcpp::Node and global session helpers.
**auto handle
Definition client.hpp:127
typename A::Goal GoalType
Definition client.hpp:480
detail::Refused< Client > CancelResponse
Definition client.hpp:499
rclcpp::Result, rclcpp::ErrorCode, and the RCLCPP_TRY macro.
int32_t(* nros_cpp_goal_callback_t)(const uint8_t goal_id[16], const uint8_t *data, size_t len, void *ctx)
Definition server.hpp:41
nros_cpp_ret_t nros_cpp_action_server_set_callbacks(void *handle, nros_cpp_goal_callback_t goal_cb, nros_cpp_cancel_callback_t cancel_cb, void *ctx)
int32_t(* nros_cpp_cancel_callback_t)(const uint8_t goal_id[16], void *ctx)
Definition server.hpp:43
nros_cpp_ret_t nros_cpp_action_server_set_accepted_callback(void *handle, nros_cpp_accepted_callback_t accepted_cb, void *ctx)
void(* nros_cpp_accepted_callback_t)(const uint8_t goal_id[16], void *ctx)
Definition server.hpp:48
Definition qos.hpp:84
static nros_cpp_action_hashes_t get()
Definition server.hpp:72
Definition server.hpp:67
void type
Definition server.hpp:68
Definition server.hpp:126
bool operator!=(const GoalUUID &other) const
Definition server.hpp:162
static const size_t SIZE
Byte count — unique_identifier_msgs/UUID is a fixed uint8[16].
Definition server.hpp:128
bool operator==(const GoalUUID &other) const
Definition server.hpp:155
const uint8_t * data() const
Definition server.hpp:145
bool is_zero() const
True when every byte is zero — mirrors nros_core::GoalId::is_zero().
Definition server.hpp:148
uint8_t uuid[16]
UUID bytes, in wire order.
Definition server.hpp:131
GoalUUID()
The zero ("null") goal id — mirrors nros_core::GoalId::zero().
Definition server.hpp:134
uint8_t * data()
Pointer to the 16 bytes — what the raw-array overloads and the FFI take.
Definition server.hpp:144
GoalUUID(const uint8_t *src)
Copy the 16 bytes at src, which must point to at least that many.
Definition server.hpp:137
bool operator<(const GoalUUID &other) const
Definition server.hpp:167
Definition server.hpp:1029
static void rehome(Server< A > &s)
Point the bound state at s after a move.
Definition server.hpp:1034
typename A::Goal Goal
Definition server.hpp:1030
ServerGoalHandle< A > Handle
Definition server.hpp:1031
**timer cancel()