From 5f246aeeb4bc5104384500b4702e7b39a7148d24 Mon Sep 17 00:00:00 2001 From: qzhhhi Date: Thu, 18 Jun 2026 13:31:15 +0000 Subject: [PATCH] feat(host)!: Add thread setup support to advanced options - Introduce `thread_setup` in `agent::AdvancedOptions` so callers can configure the transport event thread before it starts handling USB I/O. - Add `bind_advanced_options()` to bind stateful nothrow callables without introducing `std::function` into the binary interface. The transport now waits until `thread_setup` returns before continuing construction, so any bound temporary state remains alive during the callback. - Also document the lifetime and slicing constraints of bound advanced options, and clarify that `thread_setup` is only for per-thread OS-level setup such as priority or CPU affinity. BREAKING CHANGE: `agent::AdvancedOptions` is now non-copyable and non-movable, and no longer preserves the previous aggregate-style usage. The USB transport connection options alias now reuses `agent::AdvancedOptions`. --- host/include/librmcs/agent/common.hpp | 82 ++++++++++++++++++++++++++- host/src/protocol/handler.cpp | 7 +-- host/src/transport/transport.hpp | 5 +- host/src/transport/usb/usb.cpp | 15 ++++- 4 files changed, 98 insertions(+), 11 deletions(-) diff --git a/host/include/librmcs/agent/common.hpp b/host/include/librmcs/agent/common.hpp index 1c78bde..f8ffb3f 100644 --- a/host/include/librmcs/agent/common.hpp +++ b/host/include/librmcs/agent/common.hpp @@ -1,9 +1,89 @@ #pragma once +#include +#include +#include + namespace librmcs::agent { -struct AdvancedOptions { +/** + * @brief Advanced transport options passed during agent construction. + * + * `bind_advanced_options()` returns an object derived from `AdvancedOptions` whose `thread_setup` + * callback depends on state stored in that derived object. Copying or moving the base type would + * slice away that state while retaining a now-dangling function pointer, so `AdvancedOptions` is + * intentionally non-copyable and non-movable. + * + * Construct `AdvancedOptions` directly during agent construction, or explicitly copy the required + * plain data fields into a fresh `AdvancedOptions` instance instead of copying an existing object. + * + * @warning Never copy, assign, or reuse any function pointer from an object returned by + * `bind_advanced_options()` in any other `AdvancedOptions` instance; doing so is undefined + * behavior. + */ +class AdvancedOptions { +public: + AdvancedOptions() = default; + + AdvancedOptions(const AdvancedOptions&) = delete; + AdvancedOptions& operator=(const AdvancedOptions&) = delete; + AdvancedOptions(AdvancedOptions&&) = delete; + AdvancedOptions& operator=(AdvancedOptions&&) = delete; + + ~AdvancedOptions() = default; + bool dangerously_skip_version_checks = false; + + /** + * @brief Callback invoked on the transport event thread before transport I/O handling begins. + * + * This hook is intended only for per-thread environment setup, such as thread priority, + * CPU affinity, thread naming, or other OS-level thread configuration. + * + * @warning This callback runs during transport construction, before `Handler` and the + * enclosing agent object finish construction. + * @warning The callback must not access the agent object being constructed, any of its + * members, or any transport/protocol APIs. In particular, do not capture and use `this` + * from the constructing agent object. + */ + void (*thread_setup)(const AdvancedOptions&) noexcept = nullptr; + + AdvancedOptions& set_dangerously_skip_version_checks(bool value) { + dangerously_skip_version_checks = value; + return *this; + } + + AdvancedOptions& set_thread_setup(void (*value)(const AdvancedOptions&) noexcept) { + thread_setup = value; + return *this; + } }; +/** + * @brief Binds callable to `AdvancedOptions`. + * + * @warning The returned object must outlive any use of its function pointers. + * @warning Do not store, copy, move, or slice the returned object as `AdvancedOptions`. + */ +template +requires std::is_nothrow_invocable_v&> +auto bind_advanced_options(FunctorT&& thread_setup_impl) { + using Functor = std::decay_t; + + class OptionsImpl : public AdvancedOptions { + public: + explicit OptionsImpl(Functor thread_setup_impl) + : thread_setup_impl_(std::move(thread_setup_impl)) { + thread_setup = [](const AdvancedOptions& self) noexcept { + std::invoke(static_cast(self).thread_setup_impl_); + }; + } + + private: + Functor thread_setup_impl_; + }; + + return OptionsImpl{std::forward(thread_setup_impl)}; +} + } // namespace librmcs::agent diff --git a/host/src/protocol/handler.cpp b/host/src/protocol/handler.cpp index 3af7a65..be9372d 100644 --- a/host/src/protocol/handler.cpp +++ b/host/src/protocol/handler.cpp @@ -209,12 +209,7 @@ Handler::Handler( uint16_t usb_vid, int32_t usb_pid, std::string_view serial_filter, const agent::AdvancedOptions& options, data::DataCallback& callback) : impl_(new Impl( - transport::usb::create_transport( - usb_vid, usb_pid, serial_filter, - transport::usb::ConnectionOptions{ - .dangerously_skip_version_checks = options.dangerously_skip_version_checks, - }), - callback)) {} + transport::usb::create_transport(usb_vid, usb_pid, serial_filter, options), callback)) {} Handler::Handler(Handler&& other) noexcept : impl_(std::exchange(other.impl_, nullptr)) {} diff --git a/host/src/transport/transport.hpp b/host/src/transport/transport.hpp index 6610738..83b56b8 100644 --- a/host/src/transport/transport.hpp +++ b/host/src/transport/transport.hpp @@ -8,6 +8,7 @@ #include #include "core/src/protocol/constant.hpp" +#include "librmcs/agent/common.hpp" namespace librmcs::host::transport { @@ -140,9 +141,7 @@ class Transport { namespace usb { -struct ConnectionOptions { - bool dangerously_skip_version_checks = false; -}; +using ConnectionOptions = agent::AdvancedOptions; std::unique_ptr create_transport( uint16_t usb_vid, int32_t usb_pid, std::string_view serial_filter, diff --git a/host/src/transport/usb/usb.cpp b/host/src/transport/usb/usb.cpp index 9d445c4..ca79d56 100644 --- a/host/src/transport/usb/usb.cpp +++ b/host/src/transport/usb/usb.cpp @@ -45,7 +45,20 @@ class Usb : public Transport { }}; init_transmit_transfers(); - event_thread_ = std::thread{[this]() { handle_events(); }}; + + if (options.thread_setup) { + std::atomic thread_setup_done{false}; + event_thread_ = std::thread{[this, &options, &thread_setup_done]() { + options.thread_setup(options); + thread_setup_done.store(true, std::memory_order_release); + thread_setup_done.notify_one(); + handle_events(); + }}; + // Wait until thread_setup returns, so any bound options state remains alive. + thread_setup_done.wait(false, std::memory_order_acquire); + } else { + event_thread_ = std::thread{[this]() { handle_events(); }}; + } rollback_on_failure.disable(); }