diff --git a/.clang-tidy b/.clang-tidy index db558504e..b6d162238 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -42,6 +42,7 @@ Checks: - -bugprone-assert-side-effect - -readability-named-parameter - -readability-identifier-length + - -readability-redundant-member-init CheckOptions: performance-move-const-arg.CheckTriviallyCopyableMove: false diff --git a/AGENTS.md b/AGENTS.md index 96cfafca2..ee784bb70 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,29 +45,35 @@ work versus an explicit `Start()` method. `Event` owns its handlers. `EventSubscriber` is a non-owning façade used to subscribe and emit through an owning object. -`Subscribe()` returns `EventHandlerDeleter`. It is lightweight and is **not** an -RAII object: destroying or discarding it does not unsubscribe the handler. Call -Use `Subscription` to control the handler lifetime; do not manage the returned -deleter directly. +`Subscribe()` returns `RegEventHandler`. It is lightweight and is **not** an +RAII object: destroying or discarding it does not unsubscribe the handler. +`RegEventHandler` remains a copyable aggregate, but every returned token must +be transferred to exactly one `Subscription` or `MultiSubscription`; copying or +retaining it for multiple owners is unsupported misuse. Use `Subscription` to +control the handler lifetime; do not manage the returned deleter directly. Use RAII lifetime control when a callback should have an owner lifetime: -- `Subscription` owns one `EventHandlerDeleter`; destruction/reset unsubscribes. +- `Subscription` owns one `RegEventHandler`; destruction/reset unsubscribes. - `MultiSubscription` owns several deleters and unsubscribes them together. - A temporary `Subscription` unsubscribes at the end of its scope. - Retain the subscription for as long as its callback may run. +Unregistering an event rejects new invocations and registrations. Handlers +already captured by an active invocation snapshot continue unless individually +unsubscribed. + Define events with a private `Event` member and expose a subscriber: ```cpp using ChangedEvent = Event; -ChangedEvent::Subscriber changed_event(); +ChangedEvent const& changed_event(); private: ChangedEvent changed_event_; ``` -Return `EventSubscriber{changed_event_}` from the accessor and emit from the +Return `changed_event_ const&` from the accessor and emit from the owner with `changed_event_.Emit(value)`. ## Actions diff --git a/aether/CMakeLists.txt b/aether/CMakeLists.txt index 586300b84..a2919fa78 100644 --- a/aether/CMakeLists.txt +++ b/aether/CMakeLists.txt @@ -26,7 +26,9 @@ list(APPEND aether_srcs "server.cpp" "server_keys.cpp" "socket_initializer.cpp" - "tele_statistics.cpp") + "tele_statistics.cpp" + "env.cpp" + ) list(APPEND aether_srcs "aether_c/c_uid.cpp" @@ -35,12 +37,6 @@ list(APPEND aether_srcs list(APPEND aether_srcs "types/address.cpp") -list(APPEND aether_srcs - "events/event_list.cpp" - "events/event_deleter.cpp" - "events/event_subscription.cpp" - "events/multi_subscription.cpp") - list(APPEND aether_srcs "poller/epoll_poller.cpp" "poller/kqueue_poller.cpp" @@ -60,9 +56,7 @@ list(APPEND aether_srcs "ae_actions/select_client.cpp") list(APPEND aether_srcs - "registration/api/client_reg_api_safe.cpp" "registration/api/client_reg_api_unsafe.cpp" - "registration/api/global_reg_client_api.cpp" "registration/api/global_reg_server_api.cpp" "registration/api/registration_root_api.cpp" "registration/api/server_registration_api.cpp" @@ -143,7 +137,6 @@ list(APPEND aether_srcs list(APPEND aether_srcs "stream_api/sized_packet_gate.cpp" - "stream_api/stream_api.cpp" ) list(APPEND aether_srcs @@ -151,9 +144,10 @@ list(APPEND aether_srcs ) list(APPEND aether_srcs - "api_protocol/protocol_context.cpp" - "api_protocol/api_pack_parser.cpp" - "api_protocol/return_result_api.cpp" + "api_protocol/details/protocol_context.cpp" + "api_protocol/details/api_packer.cpp" + "api_protocol/details/api_parser.cpp" + "api_protocol/details/packet_reader.cpp" ) list(APPEND aether_srcs diff --git a/aether/access_points/lora_module_access_point.h b/aether/access_points/lora_module_access_point.h index aa3440cf2..abfa7c134 100644 --- a/aether/access_points/lora_module_access_point.h +++ b/aether/access_points/lora_module_access_point.h @@ -25,7 +25,7 @@ # include "aether/actions/action.h" # include "aether/actions/action_ptr.h" # include "aether/adapters/lora_module_adapter.h" -# include "aether/events/event_subscription.h" +# include "aether/events/events.h" # include "aether/lora_modules/ilora_module_driver.h" # include "aether/types/state_machine.h" diff --git a/aether/access_points/modem_access_point.cpp b/aether/access_points/modem_access_point.cpp index f68cc93c3..386159f71 100644 --- a/aether/access_points/modem_access_point.cpp +++ b/aether/access_points/modem_access_point.cpp @@ -29,14 +29,16 @@ namespace ae { ModemConnectAction::ModemConnectAction(AeContext const& ae_context, IModemDriver& driver) - : driver_{&driver}, + : Action{ae_context}, + driver_{&driver}, + connection_event_{ae_context}, task_sub_{ae_context.scheduler().Task([&]() noexcept { Start(); })} { AE_TELED_DEBUG("ModemConnectAction created"); } -ModemConnectAction::ConnectionEvent::Subscriber +ModemConnectAction::ConnectionEvent const& ModemConnectAction::connection_event() { - return EventSubscriber{connection_event_}; + return connection_event_; } void ModemConnectAction::Start() { diff --git a/aether/access_points/modem_access_point.h b/aether/access_points/modem_access_point.h index 500b39636..a94819dfc 100644 --- a/aether/access_points/modem_access_point.h +++ b/aether/access_points/modem_access_point.h @@ -25,7 +25,6 @@ # include "aether/actions/action.h" # include "aether/adapters/modem_adapter.h" # include "aether/ae_context.h" -# include "aether/events/event_subscription.h" # include "aether/events/events.h" namespace ae { @@ -38,7 +37,7 @@ class ModemConnectAction final : public Action { explicit ModemConnectAction(AeContext const& ae_context, IModemDriver& driver); - ConnectionEvent::Subscriber connection_event(); + ConnectionEvent const& connection_event(); private: void Start(); diff --git a/aether/access_points/wifi_access_point.cpp b/aether/access_points/wifi_access_point.cpp index 3af71b2aa..8e7595af4 100644 --- a/aether/access_points/wifi_access_point.cpp +++ b/aether/access_points/wifi_access_point.cpp @@ -35,20 +35,22 @@ WifiConnectAction::WifiConnectAction( AeContext const& ae_context, WifiAccessPoint& access_point, WifiDriver& driver, WiFiAp wifi_ap, std::optional psp, std::optional base_station) - : ae_context_{ae_context}, + : Action{ae_context}, + ae_context_{ae_context}, access_point_{&access_point}, driver_{&driver}, wifi_ap_{std::move(wifi_ap)}, psp_{std::move(psp)}, base_station_{std::move(base_station)}, + connection_event_{ae_context_}, scheduler_sub_{ ae_context_.scheduler().Task([this]() { EnsureConnected(); })} { AE_TELED_DEBUG("WifiConnectAction created"); } -WifiConnectAction::ConnectionEvent::Subscriber +WifiConnectAction::ConnectionEvent const& WifiConnectAction::connection_event() { - return EventSubscriber{connection_event_}; + return connection_event_; } void WifiConnectAction::EnsureConnected() { @@ -65,7 +67,7 @@ void WifiConnectAction::EnsureConnected() { void WifiConnectAction::Connect() { connect_sub_ = driver_->connect_res_event().Subscribe( - [&](Result&& res) { + [&](Result& res) { connect_sub_.Reset(); if (res) { AE_TELED_INFO("Wifi connected"); diff --git a/aether/access_points/wifi_access_point.h b/aether/access_points/wifi_access_point.h index f63c60a57..dfb25b579 100644 --- a/aether/access_points/wifi_access_point.h +++ b/aether/access_points/wifi_access_point.h @@ -46,7 +46,7 @@ class WifiConnectAction final : public Action { std::optional psp, std::optional base_station); - ConnectionEvent::Subscriber connection_event(); + ConnectionEvent const& connection_event(); private: void EnsureConnected(); diff --git a/aether/actions/action.h b/aether/actions/action.h index c7710040d..7e4468a35 100644 --- a/aether/actions/action.h +++ b/aether/actions/action.h @@ -25,7 +25,8 @@ class Action { public: using FinishedEvent = Event; - Action() noexcept = default; + template + explicit Action(Ec const& ec) noexcept : finished_event_{ec} {} virtual ~Action() noexcept = default; AE_CLASS_MOVE_ONLY(Action) @@ -35,9 +36,7 @@ class Action { finished_event_.Emit(); } - FinishedEvent::Subscriber finished_event() noexcept { - return EventSubscriber{finished_event_}; - } + FinishedEvent const& finished_event() noexcept { return finished_event_; } bool is_finished() const noexcept { return is_finished_; } diff --git a/aether/actions/action_context.h b/aether/actions/action_context.h index b3f796123..e53dc4e7d 100644 --- a/aether/actions/action_context.h +++ b/aether/actions/action_context.h @@ -19,6 +19,7 @@ #include +#include "aether/events/events.h" #include "aether/tasks/details/task_subsctiption.h" namespace ae { @@ -34,6 +35,7 @@ concept AsyncScheduler = requires(T& t) { template concept ActionContext = requires(T t) { { t.scheduler() } -> AsyncScheduler; + { t.event_system() } -> events::EventSystemConcept; }; } // namespace ae diff --git a/aether/actions/actions_queue.h b/aether/actions/actions_queue.h index 74e712b02..dbe6f45ab 100644 --- a/aether/actions/actions_queue.h +++ b/aether/actions/actions_queue.h @@ -17,13 +17,13 @@ #ifndef AETHER_ACTIONS_ACTIONS_QUEUE_H_ #define AETHER_ACTIONS_ACTIONS_QUEUE_H_ -#include +#include #include +#include #include -#include #include "aether-miscpp/types/small_function.h" -#include "aether/events/event_subscription.h" +#include "aether/events/events.h" namespace ae { namespace actions_queue_internal { diff --git a/aether/actions/repeatable_task.h b/aether/actions/repeatable_task.h index 210b53512..fbabf1491 100644 --- a/aether/actions/repeatable_task.h +++ b/aether/actions/repeatable_task.h @@ -20,10 +20,12 @@ #include #include -#include "aether/actions/action.h" #include "aether-miscpp/meta/time_traits.h" #include "aether-miscpp/types/small_function.h" + +#include "aether/actions/action.h" #include "aether/actions/action_context.h" +#include "aether/events/events.h" namespace ae { /** @@ -39,11 +41,13 @@ class RepeatableTask : public Action { requires(IsDuration_v) RepeatableTask(AC const& ac, Task&& task, D interval, int max_repeat_count = kRepeatCountInfinite) - : ac_{ac}, + : Action{ac}, + ac_{ac}, task_{std::move(task)}, interval_{ std::chrono::duration_cast(interval)}, - max_repeat_count_{max_repeat_count} { + max_repeat_count_{max_repeat_count}, + repeat_count_exceeded_event_{ac_} { Run(); } @@ -51,8 +55,8 @@ class RepeatableTask : public Action { void Stop() { stopped_ = true; } - Event::Subscriber repeat_count_exceeded() noexcept { - return EventSubscriber{repeat_count_exceeded_event_}; + Event const& repeat_count_exceeded() noexcept { + return repeat_count_exceeded_event_; } private: diff --git a/aether/adapters/adapter.cpp b/aether/adapters/adapter.cpp index d86fb50ae..f7e62ba08 100644 --- a/aether/adapters/adapter.cpp +++ b/aether/adapters/adapter.cpp @@ -16,12 +16,21 @@ #include "aether/adapters/adapter.h" +#include + +#include "aether/ae_context.h" + namespace ae { #ifdef AE_DISTILLATION -Adapter::Adapter(ObjProp prop) : Obj{prop} {} +Adapter::Adapter(ObjProp prop) + : Obj{prop}, new_access_point_event_{AeContext{*this}} {} #endif // AE_DISTILLATION -Adapter::NewAccessPoint::Subscriber Adapter::new_access_point() { - return EventSubscriber{new_access_point_event_}; +Adapter::NewAccessPoint const& Adapter::new_access_point() { + assert(!!new_access_point_event_); + return *new_access_point_event_; } + +void Adapter::Loaded() { new_access_point_event_.emplace(AeContext{*this}); } + } // namespace ae diff --git a/aether/adapters/adapter.h b/aether/adapters/adapter.h index fa892352f..b64a59fea 100644 --- a/aether/adapters/adapter.h +++ b/aether/adapters/adapter.h @@ -46,10 +46,11 @@ class Adapter : public Obj { virtual std::vector access_points() = 0; - virtual NewAccessPoint::Subscriber new_access_point(); + virtual NewAccessPoint const& new_access_point(); protected: - NewAccessPoint new_access_point_event_; + void Loaded(); + std::optional new_access_point_event_; }; } // namespace ae diff --git a/aether/ae_actions/check_access_for_send_message.cpp b/aether/ae_actions/check_access_for_send_message.cpp index 6fbf0aa35..6710637aa 100644 --- a/aether/ae_actions/check_access_for_send_message.cpp +++ b/aether/ae_actions/check_access_for_send_message.cpp @@ -23,30 +23,31 @@ CheckAccessForSendMessage::CheckAccessForSendMessage( AeContext const& ae_context, Uid destination, CloudServerConnections& cloud_connection, RequestPolicy::Variant request_policy) - : destination_{destination}, + : Action{ae_context}, + destination_{destination}, cloud_request_{ ae_context, ApiRequestHandler{[this](ApiContext& auth_api, auto*, - auto* request) { - wait_check_sub_ = - auth_api->check_access_for_send_message(destination_) - .Subscribe([&](auto const& res) { - if (res) { - ResponseReceived(); - request->Succeeded(); - } else { - ErrorReceived(); - request->Failed(); - } - }); + auto* request) { + wait_check_sub_ = auth_api->CheckAccessForSendMessage(destination_) + .Subscribe([&](auto const& res) { + if (res) { + ResponseReceived(); + request->Succeeded(); + } else { + ErrorReceived(); + request->Failed(); + } + }); }}, cloud_connection, request_policy, - } {} + }, + result_event_{ae_context} {} -CheckAccessForSendMessage::ResultEvent::Subscriber -CheckAccessForSendMessage::result_event() noexcept { - return EventSubscriber{result_event_}; +auto CheckAccessForSendMessage::result_event() const noexcept + -> ResultEvent const& { + return result_event_; } void CheckAccessForSendMessage::ResponseReceived() { diff --git a/aether/ae_actions/check_access_for_send_message.h b/aether/ae_actions/check_access_for_send_message.h index 433989ce7..b29c63d84 100644 --- a/aether/ae_actions/check_access_for_send_message.h +++ b/aether/ae_actions/check_access_for_send_message.h @@ -17,14 +17,15 @@ #ifndef AETHER_AE_ACTIONS_CHECK_ACCESS_FOR_SEND_MESSAGE_H_ #define AETHER_AE_ACTIONS_CHECK_ACCESS_FOR_SEND_MESSAGE_H_ -#include "aether/common.h" -#include "aether/types/uid.h" #include "aether-miscpp/types/result.h" -#include "aether/events/events.h" + #include "aether/actions/action.h" #include "aether/cloud_connections/cloud_request.h" -#include "aether/cloud_connections/request_policy.h" #include "aether/cloud_connections/cloud_server_connections.h" +#include "aether/cloud_connections/request_policy.h" +#include "aether/common.h" +#include "aether/events/events.h" +#include "aether/types/uid.h" namespace ae { class CloudServerConnections; @@ -39,7 +40,7 @@ class CheckAccessForSendMessage final : public Action { AE_CLASS_NO_COPY_MOVE(CheckAccessForSendMessage) - ResultEvent::Subscriber result_event() noexcept; + ResultEvent const& result_event() const noexcept; private: void ResponseReceived(); diff --git a/aether/ae_actions/get_servers.cpp b/aether/ae_actions/get_servers.cpp index df7549bf9..3fdc53522 100644 --- a/aether/ae_actions/get_servers.cpp +++ b/aether/ae_actions/get_servers.cpp @@ -25,39 +25,42 @@ GetServersAction::GetServersAction(AeContext const& ae_context, std::vector server_ids, CloudServerConnections& cloud_connection, RequestPolicy::Variant request_policy) - : server_ids_{std::move(server_ids)}, + : Action{ae_context}, + server_ids_{std::move(server_ids)}, + result_event_{ae_context}, cloud_request_{ ae_context, ApiCallWithListener{ ApiCall{[this](ApiContext& auth_api, auto*) { AE_TELED_DEBUG("Resolve servers {}", server_ids_); - auth_api->resolver_servers(server_ids_); + auth_api->ResolveServer(server_ids_); }}, - ResponseSubscriber{[this](ClientApiSafe& client_api, auto*, - auto* request) { - return client_api.send_server_descriptor_event().Subscribe( - [this, request](auto const& sd) { - GetResponse(sd, request); - }); - }}}, + ResponseSubscriber{ + [this](ClientApiSafe& client_api, auto*, auto* request) { + return client_api.send_server_descriptor_event().Subscribe( + [this, request](auto const& sd) { + GetResponse(sd, request); + }); + }}}, cloud_connection, request_policy, } { - request_subs_ += cloud_request_.result_event().Subscribe([this](bool success) { - if (success) { - AE_TELED_INFO("GetServersAction succeeded"); - result_event_.Emit( - Ok const&>{server_descriptors_}); - } else { - AE_TELED_ERROR("GetServersAction failed"); - result_event_.Emit(Error{1}); - } - Finish(); - }); + request_subs_ += + cloud_request_.result_event().Subscribe([this](bool success) { + if (success) { + AE_TELED_INFO("GetServersAction succeeded"); + result_event_.Emit( + Ok const&>{server_descriptors_}); + } else { + AE_TELED_ERROR("GetServersAction failed"); + result_event_.Emit(Error{1}); + } + Finish(); + }); } -GetServersAction::ResultEvent::Subscriber GetServersAction::result_event() { - return EventSubscriber{result_event_}; +GetServersAction::ResultEvent const& GetServersAction::result_event() const { + return result_event_; } void GetServersAction::GetResponse(ServerDescriptor const& server_descriptor, diff --git a/aether/ae_actions/get_servers.h b/aether/ae_actions/get_servers.h index ef7d34ff0..9e23ce1f7 100644 --- a/aether/ae_actions/get_servers.h +++ b/aether/ae_actions/get_servers.h @@ -19,12 +19,12 @@ #include -#include "aether/ae_context.h" #include "aether-miscpp/types/result.h" +#include "aether/actions/action.h" +#include "aether/ae_context.h" #include "aether/events/events.h" #include "aether/types/server_id.h" -#include "aether/actions/action.h" -#include "aether/events/multi_subscription.h" + #include "aether/cloud_connections/cloud_request.h" #include "aether/cloud_connections/cloud_server_connections.h" @@ -39,7 +39,7 @@ class GetServersAction : public Action { CloudServerConnections& cloud_connection, RequestPolicy::Variant request_policy); - ResultEvent::Subscriber result_event(); + ResultEvent const& result_event() const; private: void GetResponse(ServerDescriptor const& server_descriptor, diff --git a/aether/ae_actions/ping.cpp b/aether/ae_actions/ping.cpp index bef220080..e9eb0785c 100644 --- a/aether/ae_actions/ping.cpp +++ b/aether/ae_actions/ping.cpp @@ -22,8 +22,6 @@ # include # include -# include "aether/server.h" - # include "aether/cloud_connections/cloud_server_connection.h" # include "aether/work_cloud_api/work_server_api/authorized_api.h" @@ -67,7 +65,8 @@ Ping::Ping(AeContext const& ae_context, next_ping_hint_{next_ping_hint}, rx_window_{rx_window}, timeout_{timeout}, - server_id_{cloud_server_connection_->server_id()} { + server_id_{cloud_server_connection_->server_id()}, + result_event_{ae_context_} { AE_TELE_INFO( kPing, "Ping action created to server id: {}, interval: {:%S}s, rx_window: " @@ -75,7 +74,7 @@ Ping::Ping(AeContext const& ae_context, server_id_, next_ping_hint_, rx_window_, timeout_); } -Ping::ResultEvent::Subscriber Ping::result_event() { return result_event_; } +Ping::ResultEvent const& Ping::result_event() const { return result_event_; } void Ping::Start(TimePoint current_time) { auto* cc = cloud_server_connection_->client_connection(); @@ -90,7 +89,7 @@ void Ping::Start(TimePoint current_time) { state_ = RequestState::kPending; auto& write_action = cc->AuthorizedApiCall( - SubApi{[this, current_time](ApiContext& auth_api) { + [this, current_time](ApiContext& auth_api) { auto next_ping_hint_ms = static_cast( std::chrono::duration_cast( next_ping_hint_) @@ -99,7 +98,7 @@ void Ping::Start(TimePoint current_time) { std::chrono::duration_cast(rx_window_) .count()); - auto pong_promise = auth_api->ping(next_ping_hint_ms, rx_window_ms); + auto pong_promise = auth_api->Ping(next_ping_hint_ms, rx_window_ms); auto req_id = pong_promise.request_id(); AE_TELE_DEBUG(kPingSend, @@ -130,7 +129,7 @@ void Ping::Start(TimePoint current_time) { ResetRequestSubscriptions(); result_event_.Emit(PingResult{Error{5}}); } - }}); + }); write_sub_ = write_action.status_event().Subscribe([this](auto status) { if (status == WriteAction::Status::kFail) { @@ -148,8 +147,8 @@ void Ping::Start(TimePoint current_time) { } # if DEBUG - cc->LoginApiCall(SubApi{[&](ApiContext& api_call) { - api_call->get_my_ip().Subscribe([&](auto&& res) noexcept { + cc->LoginApiCall([&](ApiContext& api_call) { + api_call->GetMyIp().Subscribe([&](auto&& res) noexcept { if (res) { auto&& ip = std::forward(res).value(); AE_TELED_DEBUG("Server id: {}, our public ip: {}:{}, coords: {},{}", @@ -159,7 +158,7 @@ void Ping::Start(TimePoint current_time) { std::forward(res).error()); } }); - }}); + }); # endif } diff --git a/aether/ae_actions/ping.h b/aether/ae_actions/ping.h index 411deec66..fbd29f8f0 100644 --- a/aether/ae_actions/ping.h +++ b/aether/ae_actions/ping.h @@ -26,9 +26,9 @@ # include "aether-miscpp/types/result.h" # include "aether/ae_context.h" -# include "aether/api_protocol/request_id.h" +# include "aether/api_protocol/api_protocol.h" # include "aether/clock.h" -# include "aether/events/event_subscription.h" +# include "aether/common.h" # include "aether/events/events.h" # include "aether/tasks/details/task_subsctiption.h" # include "aether/types/server_id.h" @@ -51,7 +51,7 @@ class Ping { AE_CLASS_NO_COPY_MOVE(Ping); - ResultEvent::Subscriber result_event(); + ResultEvent const& result_event() const; void Start(TimePoint current_time); diff --git a/aether/ae_actions/select_client.cpp b/aether/ae_actions/select_client.cpp index 4cd7c8989..69e5986c2 100644 --- a/aether/ae_actions/select_client.cpp +++ b/aether/ae_actions/select_client.cpp @@ -27,7 +27,7 @@ namespace ae { SelectClientAction::SelectClientAction(AeContext const& ae_context, Client::ptr client) - : ae_context_{ae_context} { + : Action{ae_context}, ae_context_{ae_context}, result_event_{ae_context_} { AE_TELED_DEBUG("Select loaded client"); task_sub_ = ae_context_.scheduler().Task([this, c{std::move(client)}]() { result_event_.Emit(Ok{c}); @@ -40,7 +40,10 @@ SelectClientAction::SelectClientAction(AeContext const& ae_context, Aether& aether, Registration& registration, std::string client_id) - : ae_context_{ae_context}, client_id_{std::move(client_id)} { + : Action{ae_context}, + ae_context_{ae_context}, + result_event_{ae_context_}, + client_id_{std::move(client_id)} { AE_TELED_DEBUG("Waiting for client registration"); registration_sub_ = registration.registration().Subscribe( [this, aeth{&aether}](auto const& res) { @@ -56,7 +59,7 @@ SelectClientAction::SelectClientAction(AeContext const& ae_context, #endif SelectClientAction::SelectClientAction(AeContext const& ae_context) - : ae_context_{ae_context} { + : Action{ae_context}, ae_context_{ae_context}, result_event_{ae_context_} { AE_TELED_DEBUG("No clients to select"); task_sub_ = ae_context_.scheduler().Task([this]() { result_event_.Emit(Error{2}); @@ -64,7 +67,7 @@ SelectClientAction::SelectClientAction(AeContext const& ae_context) }); } -SelectClientAction::ResultEvent::Subscriber SelectClientAction::result_event() { - return EventSubscriber{result_event_}; +SelectClientAction::ResultEvent const& SelectClientAction::result_event() { + return result_event_; } } // namespace ae diff --git a/aether/ae_actions/select_client.h b/aether/ae_actions/select_client.h index 65c3827b5..2f0c73b82 100644 --- a/aether/ae_actions/select_client.h +++ b/aether/ae_actions/select_client.h @@ -23,7 +23,6 @@ #include "aether/actions/action.h" #include "aether/ae_context.h" #include "aether/config.h" -#include "aether/events/event_subscription.h" #include "aether/events/events.h" namespace ae { @@ -53,7 +52,7 @@ class SelectClientAction final : public Action { */ explicit SelectClientAction(AeContext const& ae_context); - ResultEvent::Subscriber result_event(); + ResultEvent const& result_event(); private: AeContext ae_context_; diff --git a/aether/ae_actions/telemetry.cpp b/aether/ae_actions/telemetry.cpp index 879c577a9..5e501adff 100644 --- a/aether/ae_actions/telemetry.cpp +++ b/aether/ae_actions/telemetry.cpp @@ -65,7 +65,7 @@ void Telemetry::SendTelemetry() { cloud_connection_->CallApi( ApiCall{[&](ApiContext& auth_api, auto*) { - auth_api->send_telemetry(std::move(*telemetry)); + auth_api->SendTelemetry(*telemetry); }}, RequestPolicy::Priority{server_num}); diff --git a/aether/ae_context.h b/aether/ae_context.h index 8d691b6de..c82f370f3 100644 --- a/aether/ae_context.h +++ b/aether/ae_context.h @@ -19,54 +19,97 @@ #include -#include "aether/config.h" +#include "aether-objects/env/env.h" +#include "aether-objects/obj/domain.h" -// IWYU pragma: begin_exports -#include "aether/tasks/manual_task_scheduler.h" -// IWYU pragma: end_exports +#include "aether/env.h" namespace ae { -class Aether; -using TaskScheduler = ManualTaskScheduler< - TaskManagerConf>; +template +concept AeObject = requires(T const& t) { + requires std::same_as, Domain*>; +}; struct AeCtxTable { - Aether& (*aether_getter)(void* obj); - TaskScheduler& (*scheduler_getter)(void* obj); + Aether& (*aether_getter)(void const* obj); + TaskScheduler& (*scheduler_getter)(void const* obj); + EventSystem& (*event_system_getter)(void const* obj); }; -struct AeCtx { - bool operator==(AeCtx const&) const = default; +template +struct AeContextMaker; - void const* obj; - AeCtxTable const* vtable; +template T> +struct AeContextMaker { + static constexpr auto value = AeCtxTable{ + .aether_getter = [](void const* obj) -> decltype(auto) { + return static_cast(const_cast(obj)) // NOLINT(*const-cast*) + ->template get(); + }, + .scheduler_getter = [](void const* obj) -> decltype(auto) { + return static_cast(const_cast(obj)) // NOLINT(*const-cast*) + ->template get(); + }, + .event_system_getter = [](void const* obj) -> decltype(auto) { + return static_cast(const_cast(obj)) // NOLINT(*const-cast*) + ->template get(); + }, + }; }; -template -concept AeContextual = requires(T const& t) { - { t.ToAeContext() } -> std::same_as; +template +struct AeContextMaker { + static constexpr auto value = AeCtxTable{ + .aether_getter = [](void const* obj) -> decltype(auto) { + return get_env(*static_cast(obj)); + }, + .scheduler_getter = [](void const* obj) -> decltype(auto) { + return get_env(*static_cast(obj)); + }, + .event_system_getter = [](void const* obj) -> decltype(auto) { + return get_env(*static_cast(obj)); + }, + }; +}; + +template +struct AeContextMaker { + static constexpr auto value = AeCtxTable{ + .aether_getter = [](void const* obj) -> decltype(auto) { + return get_env(*static_cast(obj)->domain); + }, + .scheduler_getter = [](void const* obj) -> decltype(auto) { + return get_env(*static_cast(obj)->domain); + }, + .event_system_getter = [](void const* obj) -> decltype(auto) { + return get_env(*static_cast(obj)->domain); + }, + }; }; +template +concept AeContextual = + std::same_as::value), AeCtxTable const*>; + class AeContext { public: template constexpr AeContext(T const& obj) // NOLINT(*explicit-constructor) - : ctx_{obj.ToAeContext()} {} + : obj_{&obj}, vtable_{&AeContextMaker::value} {} - Aether& aether() const { - return ctx_.vtable->aether_getter( - const_cast(ctx_.obj)); // NOLINT(*const-cast) - } - TaskScheduler& scheduler() const { - return ctx_.vtable->scheduler_getter( - const_cast(ctx_.obj)); // NOLINT(*const-cast) + Aether& aether() const { return vtable_->aether_getter(obj_); } + TaskScheduler& scheduler() const { return vtable_->scheduler_getter(obj_); } + EventSystem& event_system() const { + return vtable_->event_system_getter(obj_); } bool operator==(AeContext const&) const = default; private: - AeCtx ctx_; + void const* obj_; + AeCtxTable const* vtable_; }; + } // namespace ae #endif // AETHER_AE_CONTEXT_H_ diff --git a/aether/aether.cpp b/aether/aether.cpp index 98f11bd53..a934518dd 100644 --- a/aether/aether.cpp +++ b/aether/aether.cpp @@ -26,28 +26,16 @@ #include "aether/registration/registration.h" #include "aether/work_cloud.h" -#include "aether/aether_tele.h" +#include "aether/tele.h" namespace ae { -Aether::Aether() : task_scheduler{make_unique()} {} +Aether::Aether() = default; -Aether::Aether(ObjProp prop) - : Obj{prop}, task_scheduler{make_unique()} { - AE_TELE_DEBUG(AetherCreated); -} +Aether::Aether(ObjProp prop) : Obj{prop} { AE_TELE_DEBUG(AetherCreated); } Aether::~Aether() { AE_TELE_DEBUG(AetherDestroyed); } -AeCtx Aether::ToAeContext() const { - static constexpr AeCtxTable ae_table{ - [](void* obj) -> Aether& { return *static_cast(obj); }, - [](void* obj) -> TaskScheduler& { - return *static_cast(obj)->task_scheduler; - }}; - return AeCtx{const_cast(this), &ae_table}; // NOLINT(*const-cast) -} - Client::ptr Aether::CreateClient(ClientConfig const& config, std::string const& client_id) { auto client = FindClient(client_id); diff --git a/aether/aether.h b/aether/aether.h index f5d72b9ab..3cc5286b0 100644 --- a/aether/aether.h +++ b/aether/aether.h @@ -21,12 +21,10 @@ #include #include "aether-objects/obj/obj.h" -#include "aether/clock.h" -#include "aether/memory.h" -#include "aether/types/client_config.h" #include "aether/ae_actions/select_client.h" -#include "aether/ae_context.h" +#include "aether/memory.h" +#include "aether/types/client_config.h" namespace ae { class Server; @@ -71,9 +69,6 @@ class Aether : public Obj { tele_statistics, poller, dns_resolver, adapter_registry); } - // AeContext protocol - AeCtx ToAeContext() const; - ObjPtr CreateClient(ClientConfig const& config, std::string const& client_id); SelectClientAction& SelectClient(Uid parent_uid, @@ -93,8 +88,6 @@ class Aether : public Obj { ObjPtr tele_statistics; - std::unique_ptr task_scheduler; - private: ObjPtr FindClient(std::string const& client_id); void StoreClient(ObjPtr client); diff --git a/aether/aether_app.cpp b/aether/aether_app.cpp index 1d5bd2884..35de67902 100644 --- a/aether/aether_app.cpp +++ b/aether/aether_app.cpp @@ -42,7 +42,6 @@ // IWYU pragma: end_keeps namespace ae { - void AetherAppContext::TelemetryInit() { #if AE_TELE_ENABLED // Init telemetry sink @@ -114,8 +113,7 @@ static Aether::ptr AetherFactory(AetherAppContext const& context) { #if !AE_DISTILLATION || AE_FILTRATION auto a = Aether::ptr::Declare( CreateWith{context.domain()}.with_id(GlobalId::kAether)); - a.Load(); - if (a.is_loaded()) { + if (auto aptr = a.Load(); !!aptr) { return a; } #endif @@ -298,8 +296,9 @@ void AetherAppContext::InitComponentContext() { // clean old state domain_storage.CleanUp(); #endif // AE_DISTILLATION + auto const& env = context.env_.Resolve(); - return std::make_unique(domain_storage); + return std::make_unique(domain_storage, env.get()); }); if (!aether_) { @@ -350,6 +349,10 @@ std::unique_ptr AetherApp::Construct(AetherAppContext context) { auto app = std::unique_ptr{new AetherApp()}; app->aether_ = context.aether(); + // set aether to environment + // The problems might be if any aether's members will try to use env's aether + // before it set + context.set_aether_to_env_(context.env_.Resolve().get(), app->aether_); #if AE_DISTILLATION app->aether_->tele_statistics = context.tele_statistics_.Resolve(context); app->aether_->client_prefab = context.client_prefab_.Resolve(context); @@ -376,6 +379,7 @@ std::unique_ptr AetherApp::Construct(AetherAppContext context) { // save domain from context to the app app->domain_facility_ = std::move(std::move(context).domain_storage_.Resolve()); + app->env_ = std::move(std::move(context).env_.Resolve()); app->domain_ = std::move(std::move(context).domain_.Resolve(context)); return app; } diff --git a/aether/aether_app.h b/aether/aether_app.h index 4f509313c..add1a3789 100644 --- a/aether/aether_app.h +++ b/aether/aether_app.h @@ -19,11 +19,15 @@ #include #include +#include +#include #include #include +#include "aether-objects/env/env.h" #include "aether-objects/obj/domain.h" #include "aether-objects/ptr/ptr.h" + #include "aether/common.h" #include "aether/config.h" #include "aether/memory.h" @@ -32,12 +36,12 @@ #include "aether/events/events.h" // IWYU pragma: keep #include "aether/adapter_registry.h" -#include "aether/ae_context.h" #include "aether/aether.h" #include "aether/client.h" #include "aether/cloud.h" #include "aether/crypto.h" #include "aether/dns/dns_resolve.h" +#include "aether/env.h" #include "aether/obj/component_factory.h" #include "aether/poller/poller.h" #include "aether/tele_statistics.h" @@ -45,25 +49,79 @@ #include "aether/domain_storage/domain_storage_factory.h" namespace ae { -using TeleStatisticsTrap = ae::tele::StatisticsTrap; +/** + * \brief Provided Env must meet specified requirenments + */ +template +concept AeEnvConcept = requires(T& t) { + requires(std::derived_from); + { t.scheduler() } -> std::same_as; + { t.event_system() } -> std::same_as; + { t.SetAether(std::declval const&>()) }; +}; + +/** + * \brief AetherApp construction context. + * Provide here all factories required for different objects. + * Factories could call each other via context.() this atomaticaly + * creates dependency graph for them. Avoid cycle dependencies. + * To set factory use AetherAppContext.Factory(...)&& method. Notice it + * must be called on rvalue context and returns rvalue, so it's easier to chain + * all set factory together. Setup factories available only in DESTILLATION + * mode. + * To use factory use AetherAppContext.() const method. + */ class AetherAppContext { friend class AetherApp; public: + /** + * \brief Create aether with default factories for domain storage and env + * For domain storage DomainStorageFactory::Create is used \see + * domain_storage/domain_storage_factory.h + * For Env AeEnv is created \see env.h + */ explicit AetherAppContext() - : AetherAppContext(DomainStorageFactory::Create) {} + : AetherAppContext(DomainStorageFactory::Create, + []() { return std::make_unique(); }) {} + + template ds = + typename std::invoke_result_t::element_type> + explicit AetherAppContext(DomainFactory&& domain_storage_factory) + : AetherAppContext(std::forward(domain_storage_factory), + []() { return std::make_unique(); }) {} - template - requires(std::is_invocable_r_v, Func>) - explicit AetherAppContext(Func&& domain_storage_factory) { + /** + * \brief Provide the most basic factories for aether app. + * Factory for domain storage and env + */ + template ds = + typename std::invoke_result_t::element_type, + // must create ae env concept type + AeEnvConcept env = + typename std::invoke_result_t::element_type> + explicit AetherAppContext(DomainFactory&& domain_storage_factory, + EnvFactory&& env_factory) + : // Save set aether function + set_aether_to_env_{[](Env* e, Aether::ptr const& a) { + static_cast(e)->SetAether(a); + }} { TelemetryInit(); - domain_storage_.Factory(std::forward(domain_storage_factory)); + domain_storage_.Factory( + std::forward(domain_storage_factory)); + env_.Factory(std::forward(env_factory)); } AE_CLASS_MOVE_ONLY(AetherAppContext) + /// Access to domain objcet Domain& domain() const { return *domain_.Resolve(*this); } + /// The main root Aether object Aether::ptr& aether() const { return aether_.Resolve(*this); } AdapterRegistry::ptr& adapter_registry() const { @@ -133,6 +191,7 @@ class AetherAppContext { void InitComponentContext(); ComponentFactory> domain_storage_; + ComponentFactory> env_; ComponentFactory> domain_; ComponentFactory aether_; ComponentFactory adapter_registry_; @@ -145,6 +204,7 @@ class AetherAppContext { ComponentFactory tele_statistics_; bool tele_statistics_trap_is_set{false}; + void (*set_aether_to_env_)(Env* env, Aether::ptr const&); }; /** @@ -162,7 +222,7 @@ class AetherApp { void Exit(int code = 0) { exit_code_ = code; // wake up the task thread - aether_->task_scheduler->Task([]() {}); + try_get_env(*this)->Task([]() {}); } bool IsExited() const { return exit_code_.has_value(); } @@ -175,7 +235,7 @@ class AetherApp { * \brief Run one iteration of application update loop. */ TimePoint Update(TimePoint current_time) { - return aether_->task_scheduler->Update(current_time); + return try_get_env(*this)->Update(current_time); } /** @@ -183,7 +243,7 @@ class AetherApp { */ void WaitUntil(TimePoint wakeup_time) { if (!IsExited()) { - aether_->task_scheduler->WaitUntil(wakeup_time); + try_get_env(*this)->WaitUntil(wakeup_time); } } @@ -214,15 +274,14 @@ class AetherApp { } Domain& domain() const { return *domain_; } + Env* get_env() const { return env_.get(); } Aether::ptr const& aether() const { return aether_; } - // AeContext protocol - AeCtx ToAeContext() const { return aether_->ToAeContext(); } - private: AetherApp() = default; std::unique_ptr domain_facility_; + std::unique_ptr env_; std::unique_ptr domain_; Aether::ptr aether_; diff --git a/aether/aether_c/aether_capi.cpp b/aether/aether_c/aether_capi.cpp index 79f4001da..66a36319f 100644 --- a/aether/aether_c/aether_capi.cpp +++ b/aether/aether_c/aether_capi.cpp @@ -176,7 +176,7 @@ void Listen(AetherClient* client, void* user_data) { } client->client->message_stream_manager().new_port_event().Subscribe( - [client, user_data](ae::P2pPortHandle handle) { + [client, user_data](ae::P2pPortHandle& handle) { auto dest = handle.destination(); auto stream = std::make_shared( ae::AeContext{*aether_app}, client->client.Load(), dest, diff --git a/aether/aether_tele.h b/aether/aether_tele.h index ebabde73b..19db24902 100644 --- a/aether/aether_tele.h +++ b/aether/aether_tele.h @@ -22,6 +22,8 @@ #include "aether-tele/levels.h" #include "aether-tele/sink.h" #include "aether-tele/tags.h" +#include "aether-tele/traps/statistics_trap.h" + #include "aether/config.h" namespace ae { @@ -128,6 +130,11 @@ struct AetherTeleConfig { }; }; }; + +static constexpr auto kStatisticsMaxSize = + static_cast(AE_STATISTICS_MAX_SIZE); +using TeleStatisticsTrap = ae::tele::StatisticsTrap; + } // namespace ae // Main module for aether and tags diff --git a/aether/all.h b/aether/all.h index 582c99379..156a523d6 100644 --- a/aether/all.h +++ b/aether/all.h @@ -27,10 +27,7 @@ #include "aether/actions/action_context.h" #include "aether/actions/repeatable_task.h" -#include "aether/events/cumulative_event.h" -#include "aether/events/event_subscription.h" #include "aether/events/events.h" -#include "aether/events/multi_subscription.h" #include "aether/executors/executors.h" #include "aether-miscpp/format/format.h" diff --git a/aether/api_protocol/api_class_impl.h b/aether/api_protocol/api_class_impl.h deleted file mode 100644 index 3ff4a54ee..000000000 --- a/aether/api_protocol/api_class_impl.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_API_CLASS_IMPL_H_ -#define AETHER_API_PROTOCOL_API_CLASS_IMPL_H_ - -#include "aether-miscpp/reflect/reflect.h" -#include "aether/api_protocol/api_class.h" -#include "aether/api_protocol/api_pack_parser.h" -#include "aether/tele.h" - -namespace ae { -/** - * \brief Use as tag for method with return value. - */ -template -struct PromiseResult { - AE_REFLECT_MEMBERS(request_id); - RequestId request_id; -}; - -template -struct IsPromiseResult : std::false_type {}; -template -struct IsPromiseResult> : std::true_type {}; - -/** - * \brief Use as helper to continue parsing with subapi. - */ -template -struct SubContextImpl { - SubContextImpl(ProtocolContext& protocol_context, - std::vector&& data) - : sub_data{std::move(data)}, parser{protocol_context, sub_data} {} - - void Parse(Api& api) { parser.Parse(api); } - - std::vector sub_data; - ApiParser parser; -}; - -template -struct IsSubContextImpl : std::false_type {}; -template -struct IsSubContextImpl> : std::true_type {}; - -/** - * \brief Helper struct to invoke method implementations with received message - */ -template -struct MethodInvoke; - -/** - * \brief Specialization for empty args. - */ -template -struct MethodInvoke { - public: - static constexpr auto kMessageCode = message_id; - using Message = GenericMessage<>; - - static void Invoke(TApi* obj, Message&& /* message */, ApiParser&) { - (obj->*method_ptr)(); - } -}; - -/** - * \brief Specialization for method with plain args. - */ -template -struct MethodInvoke::value && - !IsSubContextImpl::value>> { - public: - static constexpr auto kMessageCode = message_id; - using Message = GenericMessage, std::decay_t...>; - - static void Invoke(TApi* obj, Message&& message, ApiParser&) { - std::apply( - [&](auto&& first, auto&&... args) { - (obj->*method_ptr)(std::forward(first), - std::forward(args)...); - }, - std::move(message).fields); - } -}; - -/** - * \brief Specialization for method with return value. - * RequestId passed through PromiseResult, R is a type should be returned. - */ -template , Args...)> -struct MethodInvoke { - public: - static constexpr auto kMessageCode = message_id; - using Message = GenericMessage, std::decay_t...>; - - static void Invoke(TApi* obj, Message&& message, ApiParser&) { - std::apply( - [&](PromiseResult promise, auto&&... args) { - (obj->*method_ptr)(promise, std::forward(args)...); - }, - std::move(message).fields); - } -}; - -/** - * \brief Specialization for method with subapi. - * SubApi used for continue to parse subapi. - */ -template , Args...)> -struct MethodInvoke { - public: - static constexpr auto kMessageCode = message_id; - using Message = GenericMessage...>; - - static void Invoke(TApi* obj, Message&& message, ApiParser& parser) { - std::apply( - [&](auto&&... args) { - auto data = parser.Extract>(); - - (obj->*method_ptr)( - SubContextImpl{parser.Context(), std::move(data)}, - std::forward(args)...); - }, - std::move(message).fields); - } -}; - -/** - * \brief A helper for registration method implementation in ApiClassImpl. - */ -template -struct RegMethod; - -template -struct RegMethod { - using Method = MethodInvoke; -}; - -/** - * \brief A helper for registration api extension. - */ -template -struct ExtApi; - -template -struct ExtApi { - using type = T; - using Field = reflect::MetaMember; - - static constexpr auto field = Field{"ext", ptr}; -}; - -/** - * \brief A registered list of method implementation. - * Each Api class should provide ApiMethods alias to instance of ImplList. - * Use RegMethod or ExtApi for each element. - */ -template -struct ImplList { - using Impls = TypeList; -}; - -template -struct HasApiMethods : std::false_type {}; - -template -struct HasApiMethods> - : std::true_type {}; - -template -struct LoadSelector; - -template -static bool ApiLoadFactory(Api* api, MessageId message_id, ApiParser& parser, - std::index_sequence) { - return (LoadSelector>::Load(api, message_id, - parser) || - ...); -} - -template -struct LoadSelector> { - static bool Load(Api* api, MessageId message_id, ApiParser& parser) { - if (Id != message_id) { - return false; - } - using Method = typename RegMethod::Method; - auto message = parser.Extract(); - Method::Invoke(api, std::move(message), parser); - return true; - } -}; - -template -struct LoadSelector> { - static bool Load(Api* api, MessageId message_id, ApiParser& parser) { - using ExtApi = ExtApi; - auto&& ext_api = ExtApi::field.get(*api); - return LoadFactoryImpl(&ext_api, message_id, parser); - } -}; - -template -static bool LoadFactoryImpl(Api* api, MessageId message_id, ApiParser& parser) { - static_assert(HasApiMethods::value, "Api should provide ApiMethods"); - using List = decltype(Api::ApiMethods()); - constexpr auto list_size = TypeListSize_v; - - return ApiLoadFactory( - api, message_id, parser, std::make_index_sequence()); -} - -/** - * \brief Api Class Implementation. - * Implements Api class with LoadFactory in generic way configured with Api. - * Each api class should provide a list of implemented methods through - * ApiMethods getter method returns \see ImplList. - * Method invoke performed by pointer to method and *this* casted to selected - * api class. Also Api class could provide api extensions through \see ExtList. - */ -template -class ApiClassImpl : public ApiClass { - public: - using ApiClass::ApiClass; - - void LoadFactory(MessageId message_id, ApiParser& parser) { - auto res = LoadFactoryImpl(static_cast(this), message_id, parser); - if (!res) { - AE_TELED_WARNING("Dropped packet: unknown API message id {}", - static_cast(message_id)); - parser.Cancel(); - return; - } - } -}; -} // namespace ae - -#define AE_METHODS(...) \ - static constexpr auto ApiMethods() { return ::ae::ImplList<__VA_ARGS__>{}; } - -#endif // AETHER_API_PROTOCOL_API_CLASS_IMPL_H_ diff --git a/aether/api_protocol/api_context.h b/aether/api_protocol/api_context.h deleted file mode 100644 index cf862d368..000000000 --- a/aether/api_protocol/api_context.h +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_API_CONTEXT_H_ -#define AETHER_API_PROTOCOL_API_CONTEXT_H_ - -#include -#include - -#include "aether/common.h" -#include "aether/memory.h" - -#include "aether/api_protocol/api_pack_parser.h" - -namespace ae { -class IPackMessage { - public: - virtual ~IPackMessage() = default; - - virtual void Pack(ApiPacker& packer) && = 0; -}; - -template -class PackMessage final : public IPackMessage { - public: - template - explicit PackMessage(UApiClass&& api_class, U&& api_message) - : api_class_{ - std::forward(api_class), - }, api_message_{std::forward(api_message)} {} - - void Pack(ApiPacker& packer) && override { - api_class_.Pack(std::move(api_message_), packer); - } - - private: - TApiClass api_class_; - TApiMessage api_message_; -}; - -template -PackMessage(TApiClass&& api_class, TApiMessages&&... api_messages) - -> PackMessage...>; - -/** - * \brief Stack of packed messages to generate one packet - */ -class PacketStack { - public: - PacketStack() = default; - - AE_CLASS_MOVE_ONLY(PacketStack) - - template - void Push(TApi&& api, TMessage&& message) & { - packets_.emplace_back(make_unique( - std::forward(api), std::forward(message))); - } - - void Pack(ApiPacker& packer) && { - for (auto& packet : packets_) { - std::move(*packet).Pack(packer); - } - packets_.clear(); - } - - private: - std::vector> packets_; -}; - -/** - * \brief Api method call context. - * Wrapper for api class. Pointer to TApi return through proxy object which - * setting the context. - * Methods called in one context form one packet. - */ -template -class ApiContext { - template - friend struct Method; - - public: - // push and pop packet stack for api method call - struct ApiCallProxy { - ApiCallProxy(TApi& api, PacketStack& packet_stack) : api_{&api} { - api_->protocol_context().PushPacketStack(packet_stack); - } - ~ApiCallProxy() { api_->protocol_context().PopPacketStack(); } - - [[nodiscard]] TApi* operator->() { return api_; } - - private: - TApi* api_; - }; - - explicit ApiContext(TApi& api) : api_{&api} {} - - AE_CLASS_MOVE_ONLY(ApiContext) - - // return the object setting the context of the api method call - [[nodiscard]] ApiCallProxy operator->() { - return ApiCallProxy{*api_, packet_stack_}; - } - - [[nodiscard]] std::vector Pack() && { - auto data = std::vector{}; - - ApiPacker packer{api_->protocol_context(), data}; - - std::move(packet_stack_).Pack(packer); - return data; - } - - operator std::vector() && { return std::move(*this).Pack(); } - - private: - TApi* api_; - PacketStack packet_stack_; -}; - -/** - * \brief Sub API method call context. - * Used for form sub child data packet from sub api method calls. - */ -template -class SubContext { - template - friend struct Method; - - public: - SubContext(TApi& api, PacketStack& packet_stack) - : api_{&api}, packet_stack_{&packet_stack} {} - - AE_CLASS_MOVE_ONLY(SubContext) - - // return the object setting the context of the api method call - [[nodiscard]] typename ApiContext::ApiCallProxy operator->() { - return typename ApiContext::ApiCallProxy{*api_, *packet_stack_}; - } - - private: - TApi* api_; - PacketStack* packet_stack_; -}; - -} // namespace ae - -#endif // AETHER_API_PROTOCOL_API_CONTEXT_H_ diff --git a/aether/api_protocol/api_method.h b/aether/api_protocol/api_method.h deleted file mode 100644 index 4e186969a..000000000 --- a/aether/api_protocol/api_method.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_API_METHOD_H_ -#define AETHER_API_PROTOCOL_API_METHOD_H_ - -#include "aether/api_protocol/api_context.h" -#include "aether/api_protocol/api_message.h" -#include "aether/api_protocol/api_pack_parser.h" -#include "aether/api_protocol/api_promise.h" -#include "aether/api_protocol/protocol_context.h" - -namespace ae { -class DefaultArgProc { - public: - template - auto operator()(Args&&... args) { - return GenericMessage{std::forward(args)...}; - } -}; - -/** - * \brief Method call implementation. - * It's a client side for generating request packets. - * Each Method provide operator() so it fells like a real method call. - * Method call should be performed in ApiContext \see ApiContext. - */ -template -struct Method; - -/** - * \brief Specialization for method with plain arguments. - * A GenericMessage generated directly from list of args. - */ -template -struct Method { - explicit Method(ProtocolContext& protocol_context, ArgProc arg_proc = {}) - : protocol_context_{&protocol_context}, arg_proc_{std::move(arg_proc)} {} - - void operator()(Args... args) { - auto* packet_stack = protocol_context_->packet_stack(); - assert(packet_stack != nullptr && - "Method::operator() requires active ApiContext"); - packet_stack->Push(*this, arg_proc_(std::forward(args)...)); - } - - template - void Pack(GenericMessage&& message, ApiPacker& packer) { - packer.Pack(MessageCode, std::move(message)); - } - - private: - ProtocolContext* protocol_context_; - ArgProc arg_proc_; -}; - -/** - * \brief Specialization for method with return value. - * A GenericMessage generated with request id and list of args. - * return ApiPromise for waiting the result or error. - */ -template -struct Method(Args...), ArgProc> { - explicit Method(ProtocolContext& protocol_context, ArgProc arg_proc = {}) - : protocol_context_{&protocol_context}, arg_proc_{std::move(arg_proc)} {} - - ApiPromise operator()(Args... args) { - auto request_id = RequestId::GenRequestId(); - auto* packet_stack = protocol_context_->packet_stack(); - assert(packet_stack != nullptr && - "Method::operator() requires active ApiContext"); - packet_stack->Push(*this, - arg_proc_(request_id, std::forward(args)...)); - return ApiPromise{*protocol_context_, request_id}; - } - - template - void Pack(GenericMessage&& message, ApiPacker& packer) { - packer.Pack(MessageCode, std::move(message)); - } - - private: - ProtocolContext* protocol_context_; - ArgProc arg_proc_; -}; -} // namespace ae - -#endif // AETHER_API_PROTOCOL_API_METHOD_H_ diff --git a/aether/api_protocol/api_pack_parser.cpp b/aether/api_protocol/api_pack_parser.cpp deleted file mode 100644 index 895a33e82..000000000 --- a/aether/api_protocol/api_pack_parser.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/api_protocol/api_pack_parser.h" - -namespace ae { -ApiParser::ApiParser(ProtocolContext& protocol_context, - std::vector const& data) - : protocol_context_{protocol_context}, - archive{MessageBuffer{ - const_cast&>(data), // NOLINT(*const-cast) - }} { - protocol_context_.PushParser(*this); -} - -ApiParser::~ApiParser() { protocol_context_.PopParser(); } - -ProtocolContext& ApiParser::Context() { return protocol_context_; } - -void ApiParser::Cancel() { - archive.buffer().read_offset = archive.buffer().buff.size(); -} - -ApiPacker::ApiPacker(ProtocolContext& protocol_context_, - std::vector& data) - : protocol_context_{protocol_context_}, archive{MessageBuffer{data}} { - protocol_context_.PushPacker(*this); -} - -ApiPacker::~ApiPacker() { protocol_context_.PopPacker(); } - -ProtocolContext& ApiPacker::Context() { return protocol_context_; } -} // namespace ae diff --git a/aether/api_protocol/api_pack_parser.h b/aether/api_protocol/api_pack_parser.h deleted file mode 100644 index a2b95c7c9..000000000 --- a/aether/api_protocol/api_pack_parser.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_API_PACK_PARSER_H_ -#define AETHER_API_PROTOCOL_API_PACK_PARSER_H_ - -#include -#include -#include -#include -#include - -#include "aether-miscpp/serialization/binary_archive.h" - -#include "aether/api_protocol/api_message.h" -#include "aether/api_protocol/protocol_context.h" - -namespace ae { -class ApiParser; -class ApiPacker; - -// Parsing raw data buffer to API messages -class ApiParser { - public: - ApiParser(ProtocolContext& protocol_context_, - std::vector const& data); - ~ApiParser(); - - template - void Parse(TApiClass& api_class) { - while (archive.buffer().read_offset < archive.buffer().buff.size()) { - MessageId message_id{std::numeric_limits::max()}; - if (auto res = archive.Load(message_id); res.IsErr()) { - // message_id didn't loaded - assert(false && "message_id didn't loaded"); - return; - } - api_class.LoadFactory(message_id, *this); - } - } - - template - void Load(TApiClass& api_class) { - Message msg{}; - if (auto res = archive.Load(msg); res.IsErr()) { - // message_id didn't loaded - assert(false && "Message didn't loaded"); - return; - } - api_class.Execute(std::move(msg), *this); - } - - template - T Extract() { - T result{}; - if (auto res = archive.Load(result); res.IsErr()) { - // message_id didn't loaded - assert(false && "result didn't extracted"); - } - return result; - } - - // cancel parsing - void Cancel(); - ProtocolContext& Context(); - - private: - ProtocolContext& protocol_context_; - seri::BinaryArchive archive; -}; - -// Packing API messages to raw data buffer -class ApiPacker { - public: - ApiPacker(ProtocolContext& protocol_context_, - std::vector& data); - ~ApiPacker(); - - template - void Pack(MessageId message_id, Message const& msg) { - auto res = archive.Save(message_id); - if (!res) { - assert(false && "Message id didn't saved"); - return; - } - res = archive.Save(msg); - if (!res) { - assert(false && "Message didn't saved"); - return; - } - } - - ProtocolContext& Context(); - - private: - ProtocolContext& protocol_context_; - seri::BinaryArchive archive; -}; - -} // namespace ae - -#endif // AETHER_API_PROTOCOL_API_PACK_PARSER_H_ diff --git a/aether/api_protocol/api_promise.h b/aether/api_protocol/api_promise.h deleted file mode 100644 index 0a7a81e84..000000000 --- a/aether/api_protocol/api_promise.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_API_PROMISE_H_ -#define AETHER_API_PROTOCOL_API_PROMISE_H_ - -#include -#include -#include - -#include "aether-miscpp/meta/ignore_t.h" -#include "aether-miscpp/types/result.h" - -#include "aether/events/events.h" - -#include "aether/api_protocol/api_pack_parser.h" -#include "aether/api_protocol/protocol_context.h" -#include "aether/api_protocol/request_id.h" - -namespace ae { -namespace api_promise_internal { -template -class PendingResponseEntry final : public ProtocolContext::PendingResponse { - public: - using value_type = std::conditional_t, Ignore, Value>; - using result_type = Result; - - void OnResult(ApiParser& parser) override { - if constexpr (std::is_void_v) { - event.Emit(result_type{Ok{kIgnore}}); - } else { - event.Emit(result_type{Ok{parser.template Extract()}}); - } - } - - void OnError(std::uint8_t, std::int32_t error_code) override { - event.Emit(result_type{Error{static_cast(error_code)}}); - } - - void OnEvicted() override { - event.Emit(result_type{Error{static_cast(-1)}}); - } - - Event event; -}; -} // namespace api_promise_internal - -template -class ApiPromise { - public: - using error_type = Error; - using value_type = std::conditional_t, Ignore, Value>; - using result_type = Result; - - ApiPromise(ProtocolContext& protocol_context, RequestId req_id) noexcept - : protocol_context_{&protocol_context}, request_id_{req_id} {} - - constexpr RequestId request_id() const { return request_id_; } - - template - constexpr decltype(auto) Subscribe(Fn&& fn) { - using Entry = api_promise_internal::PendingResponseEntry; - auto& entry = - protocol_context_->template CreatePendingResponse(request_id_); - return EventSubscriber{entry.event}.Subscribe(std::forward(fn)); - } - - private: - ProtocolContext* protocol_context_; - RequestId request_id_; -}; - -} // namespace ae -#endif // AETHER_API_PROTOCOL_API_PROMISE_H_ diff --git a/aether/api_protocol/api_protocol.h b/aether/api_protocol/api_protocol.h index c091c43e9..ee65d0884 100644 --- a/aether/api_protocol/api_protocol.h +++ b/aether/api_protocol/api_protocol.h @@ -18,17 +18,15 @@ #define AETHER_API_PROTOCOL_API_PROTOCOL_H_ // IWYU pragma: begin_exports -#include "aether/api_protocol/api_class.h" -#include "aether/api_protocol/api_class_impl.h" -#include "aether/api_protocol/api_context.h" -#include "aether/api_protocol/api_message.h" -#include "aether/api_protocol/api_method.h" -#include "aether/api_protocol/api_pack_parser.h" -#include "aether/api_protocol/api_promise.h" -#include "aether/api_protocol/protocol_context.h" -#include "aether/api_protocol/request_id.h" -#include "aether/api_protocol/return_result_api.h" -#include "aether/api_protocol/sub_api.h" +#include "aether/api_protocol/details/api_class_declare.h" +#include "aether/api_protocol/details/api_context.h" +#include "aether/api_protocol/details/api_message.h" +#include "aether/api_protocol/details/api_packer.h" +#include "aether/api_protocol/details/api_parser.h" +#include "aether/api_protocol/details/api_promise.h" +#include "aether/api_protocol/details/protocol_context.h" +#include "aether/api_protocol/details/request_id.h" +#include "aether/api_protocol/details/sub_api.h" // IWYU pragma: end_exports #endif // AETHER_API_PROTOCOL_API_PROTOCOL_H_ */ diff --git a/aether/api_protocol/details/api_class_declare.h b/aether/api_protocol/details/api_class_declare.h new file mode 100644 index 000000000..fd52332fc --- /dev/null +++ b/aether/api_protocol/details/api_class_declare.h @@ -0,0 +1,430 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_API_CLASS_DECLARE_H_ +#define AETHER_API_PROTOCOL_DETAILS_API_CLASS_DECLARE_H_ + +#include +#include +#include + +#include "aether-miscpp/meta/type_list.h" + +#include "aether/types/data_buffer.h" + +#include "aether/api_protocol/details/api_internal_context.h" +#include "aether/api_protocol/details/api_message.h" +#include "aether/api_protocol/details/api_parser.h" +#include "aether/api_protocol/details/api_promise.h" +#include "aether/api_protocol/details/packet_list.h" +#include "aether/api_protocol/details/request_id.h" +#include "aether/api_protocol/details/return_result_api.h" + +namespace ae { +template +class SubApi; + +namespace api_class_declate_internal { +template +struct AreMethodsSame : std::false_type {}; +template +struct AreMethodsSame : std::true_type {}; + +template +static constexpr inline auto AreMethodsSame_v = AreMethodsSame::value; + +struct NullRegMethod {}; + +template +struct RegMethod { + static constexpr auto kMessageId = message_id; + static constexpr auto kMethod = method; +}; + +template +struct IsRegMethod : std::false_type {}; + +template +struct IsRegMethod> : std::true_type {}; + +template +struct ApiMessageIdsValid; + +template <> +struct ApiMessageIdsValid> : std::true_type {}; + +template +struct ApiMessageIdsValid> + : std::bool_constant< + ((RegMethod::kMessageId != Rest::kMessageId) && ...) && + (RegMethod::kMessageId != ReturnResultApi::kSendResult) && + (RegMethod::kMessageId != ReturnResultApi::kSendError) && + ApiMessageIdsValid>::value> {}; + +template +consteval bool AreApiMessageIdsValid(TypeList) { + return ApiMessageIdsValid>::value; +} + +/** + * Replace actual argument type to types usable for generating a message. + * const refs a rvalues replace to plain type. + * SubApi calls replaces to DataBuffer. + */ +template +struct ArgsReplace { + using type = T; +}; + +template +struct ArgsReplace : ArgsReplace {}; + +template +struct ArgsReplace : ArgsReplace {}; + +template +struct ArgsReplace> { + using type = DataBuffer; +}; + +template +struct MessageTrait; + +template +struct MessageTrait { + using args_list = TypeList::type...>; + using message_type = GenericMessage::type...>; + static constexpr bool kReturnValue = false; +}; + +template (C::*method_prt)(Args...)> +struct MessageTrait { + using args_list = TypeList::type...>; + using message_type = + GenericMessage::type...>; + using return_type = Ret; + static constexpr bool kReturnValue = true; +}; + +template + requires(IsRegMethod::value) +struct ApiMessageTrait { + using message_type = + ApiMessage::message_type>; +}; + +/** + * \brief Find the registered method by method only. + * Cmpare all registered methods from type list is method same as kMethod. + * If not found return NullRegMethod. + */ +template +static consteval auto FindRegByMethod(TypeList<>) { + return NullRegMethod{}; +} + +template +static consteval auto FindRegByMethod(TypeList) { + if constexpr (AreMethodsSame::value) { + return Reg{}; + } else { + return FindRegByMethod(TypeList{}); + } +} + +/** + * \brief Makes type list for api methods list. + * It obtains the biggest size and alignment from all api messages from + * RegMethods. + */ +template +static consteval auto GenPacketListForApi(TypeList) { + auto max = [](auto... v) { + std::size_t res{}; + ((res = res > v ? res : v), ...); + return res; + }; + + constexpr auto max_size = max( + sizeof(void*), + sizeof( + PackMessage::message_type>)...); + constexpr auto max_align = max( + alignof(void*), + alignof( + PackMessage::message_type>)...); + + return PacketList{}; +} +} // namespace api_class_declate_internal + +using api_class_declate_internal::RegMethod; + +#define METHOD(id, method) ::ae::RegMethod + +#define API_LIST(...) \ + template \ + static consteval auto ApiList() -> ::ae::TypeList<__VA_ARGS__> { \ + static_assert( \ + ::ae::api_class_declate_internal::AreApiMessageIdsValid( \ + ::ae::TypeList<__VA_ARGS__>{}), \ + "API message IDs must be unique and cannot use reserved IDs 0 or 1"); \ + return {}; \ + } + +template +class DeclareApi { + friend Api; + + private: + DeclareApi() = default; + ~DeclareApi() = default; + + template + requires(api_class_declate_internal::IsRegMethod::value) + static void InvokeReturnResultApi(ServerApiContext& server_context); + + template + requires(api_class_declate_internal::IsRegMethod::value) + static void InvokeApi(DeclareApi& self, ServerApiContext& server_context); + + /** + * \brief Invoke registered method + */ + template + requires(api_class_declate_internal::IsRegMethod::value) + static void ServerInvokeMethod(DeclareApi& self, + ServerApiContext& server_context); + + static bool ServerDispatchMethods(DeclareApi& self, + ServerApiContext& server_context, + MessageId id); + + public: + using api_concept = Api; + /** + * \brief Make a packet list type suitable for current Api class method + * list + */ + template + static consteval auto MakePacketList() { + return api_class_declate_internal::GenPacketListForApi( + Api::template ApiList()); + } + + /** + * \brief Call client's api meothod. + * The method must be one of registered api methods in ApiList \see API_LIST + */ + template + static decltype(auto) CallClientMethod(ClientApiContext& client_api_context, + Args&&... args); + + /** + * \brief Call client's api method. + * Returns the same as client's method returns + */ + template + decltype(auto) ClientMethod(Args&&... args); + + /** + * \brief Load server's api method. Invoke the method with MessageId id and + * return true if invoked, false if not. + */ + bool LoadMethod(MessageId id); + + // Expose ReturnResultApi's methods + + template + void SendResult(RequestId id, T&& data); + + void SendError(RequestId id, std::uint8_t error_type, + std::int32_t error_code); + + void set_client_context(ClientApiContext& context) { + client_context_ = &context; + } + void reset_client_context() { client_context_ = nullptr; } + ClientApiContext& client_context() { + assert(client_context_ != nullptr && "Context is null"); + return *client_context_; + } + + void set_server_context(ServerApiContext& context) { + server_context_ = &context; + } + void reset_server_context() { server_context_ = nullptr; } + ServerApiContext& server_context() { + assert(server_context_ != nullptr && "Context is null"); + return *server_context_; + } + + private: + ClientApiContext* client_context_ = nullptr; + ServerApiContext* server_context_ = nullptr; +}; + +template +concept DeclaredApi = + requires { std::is_void_v>; }; + +template +template + requires(api_class_declate_internal::IsRegMethod::value) +void DeclareApi::InvokeReturnResultApi(ServerApiContext& server_context) { + if constexpr (std::is_same_v) { + ReturnResultApi::SendResultImpl(server_context); + } else { + ReturnResultApi::SendErrorImpl(server_context); + } +} + +template +template + requires(api_class_declate_internal::IsRegMethod::value) +void DeclareApi::InvokeApi(DeclareApi& self, + ServerApiContext& server_context) { + using Trait = api_class_declate_internal::MessageTrait; + // message to read + using M = TypeListToTemplate_t; + auto& reader = server_context.reader(); + auto invoker = [&](Args&&... args) { + // Invoke the api class method with extracted arguments + return (static_cast(self).* + RegMethod::kMethod)(std::forward(args)...); + }; + if constexpr (Trait::kReturnValue) { + // setup request id into server method invoke context + auto request_id = reader.TryExtract(); + if (!request_id) { + reader.Cancel(); + return; + } + server_context.SetRequestId(*request_id); + auto m = reader.TryExtract(); + if (!m) { + reader.Cancel(); + return; + } + ApiPromise promise = + std::apply(invoker, m->fields); + // Currently the server api implementation class responsible for invoke + // SendResult/SendError to actually return vale + // Here it looks impossible + // We need to switch from server method invoke context to client method + // invoke context + (void)promise; + } else { + auto m = reader.TryExtract(); + if (!m) { + reader.Cancel(); + return; + } + std::apply(invoker, m->fields); + } +} + +/** + * \brief Invoke registered method + */ +template +template + requires(api_class_declate_internal::IsRegMethod::value) +void DeclareApi::ServerInvokeMethod(DeclareApi& self, + ServerApiContext& server_context) { + if constexpr (std::is_same_v || + std::is_same_v) { + InvokeReturnResultApi(server_context); + } else { + InvokeApi(self, server_context); + } +} + +template +bool DeclareApi::ServerDispatchMethods(DeclareApi& self, + ServerApiContext& server_context, + MessageId id) { + auto dispatcher = [&](TypeList) { + bool res = (((id == Regs::kMessageId) + ? (ServerInvokeMethod(self, server_context)), + true : false) || + ...); + return res; + }; + return dispatcher(JoinedTypeList_t())>{}); +} + +template +template +decltype(auto) DeclareApi::CallClientMethod( + ClientApiContext& client_api_context, Args&&... args) { + using Reg = decltype(api_class_declate_internal::FindRegByMethod( + Api::template ApiList())); + static_assert( + !std::is_same_v); + + using Trait = api_class_declate_internal::MessageTrait; + using AeTrait = api_class_declate_internal::ApiMessageTrait; + auto& packet_list = client_api_context.packet_list(); + + if constexpr (Trait::kReturnValue) { + auto& protocol_context = client_api_context.protocol_context(); + auto id = protocol_context.NextRequestId(); + packet_list.Push(typename AeTrait::message_type{ + typename Trait::message_type{id, std::forward(args)...}}); + auto& future = + protocol_context.template CreateFuture(id); + return ApiPromise{future}; + } else { + packet_list.Push(typename AeTrait::message_type{ + typename Trait::message_type{std::forward(args)...}}); + } +} + +template +template +decltype(auto) DeclareApi::ClientMethod(Args&&... args) { + assert(client_context_ != nullptr && "Call client api without context"); + return CallClientMethod(*client_context_, + std::forward(args)...); +} + +template +bool DeclareApi::LoadMethod(MessageId id) { + assert(server_context_ != nullptr && "Load method without context"); + return ServerDispatchMethods(*this, *server_context_, id); +} + +template +template +void DeclareApi::SendResult(RequestId id, T&& data) { + assert(client_context_ != nullptr && "Call client api without context"); + ReturnResultApi::SendResult(*client_context_, id, std::forward(data)); +} + +template +void DeclareApi::SendError(RequestId id, std::uint8_t error_type, + std::int32_t error_code) { + assert(client_context_ != nullptr && "Call client api without context"); + ReturnResultApi::SendError(*client_context_, id, error_type, error_code); +} + +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_API_CLASS_DECLARE_H_ diff --git a/aether/api_protocol/details/api_context.h b/aether/api_protocol/details/api_context.h new file mode 100644 index 000000000..a42cab130 --- /dev/null +++ b/aether/api_protocol/details/api_context.h @@ -0,0 +1,112 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_API_CONTEXT_H_ +#define AETHER_API_PROTOCOL_DETAILS_API_CONTEXT_H_ + +#include + +#include "aether/common.h" +#include "aether/config.h" + +#include "aether/api_protocol/details/api_class_declare.h" +#include "aether/api_protocol/details/packet_list.h" + +namespace ae { + +static inline constexpr std::size_t kMaxApiCallPerApiContext = + AE_API_PROTOCOL_MAX_API_CALL_PER_CONTEXT; + +/** + * \brief Api method call context. + * Wrapper for api class. Pointer to TApi return through proxy object which + * setting the context. + * Methods called in one context form one packet. + * The referenced API and ProtocolContext, when provided, must outlive this + * context, its active call proxy, and every synchronous call or Pack(). + */ +template +class ApiContext { + public: + struct ClientApiContextImpl : public ClientApiContext { + public: + ClientApiContextImpl() = default; + explicit ClientApiContextImpl(ProtocolContext& pc) + : protocol_context_{&pc} {} + + IPacketList& packet_list() override { return packet_list_; } + ProtocolContext& protocol_context() override { + assert(protocol_context_ != nullptr && + "Protocol context requested but it's null"); + return *protocol_context_; + } + + using PacketList = + decltype(Api::template MakePacketList()); + PacketList packet_list_; + ProtocolContext* protocol_context_{nullptr}; + }; + + // Push and pop client api context for an API method call. The context and + // referenced API must outlive the returned proxy. + struct ApiCallProxy { + ApiCallProxy(Api& api, ClientApiContextImpl& context_impl) : api_{&api} { + api_->set_client_context(context_impl); + } + ~ApiCallProxy() { api_->reset_client_context(); } + + [[nodiscard]] Api* operator->() { return api_; } + + private: + Api* api_; + }; + + /** + * \brief Api context for simple method calls + */ + explicit ApiContext(Api& api) : api_{&api}, context_impl_{} {} + /** + * \brief Api context with shared protocol context + */ + ApiContext(Api& api, ProtocolContext& pc) : api_{&api}, context_impl_{pc} {} + + ApiContext(ApiContext const&) = delete; + ApiContext& operator=(ApiContext const&) = delete; + ApiContext(ApiContext&&) = delete; + ApiContext& operator=(ApiContext&&) = delete; + + // return the object setting the context of the api method call + [[nodiscard]] ApiCallProxy operator->() { + return ApiCallProxy{*api_, context_impl_}; + } + + // Pack is terminal. Reusing or repacking this context is unsupported. + [[nodiscard]] DataBuffer Pack() && { + return std::move(context_impl_.packet_list_).Pack(); + } + + operator DataBuffer() && // NOLINT (*explicit*) + { + return std::move(*this).Pack(); + } + + private: + Api* api_; + ClientApiContextImpl context_impl_; +}; +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_API_CONTEXT_H_ diff --git a/aether/api_protocol/details/api_internal_context.h b/aether/api_protocol/details/api_internal_context.h new file mode 100644 index 000000000..0600fc36f --- /dev/null +++ b/aether/api_protocol/details/api_internal_context.h @@ -0,0 +1,51 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_INTERNAL_DETAILS_CONTEXT_H_ +#define AETHER_API_INTERNAL_DETAILS_CONTEXT_H_ + +#include "aether/api_protocol/details/request_id.h" + +namespace ae { +class IPacketList; +class PacketReader; +class ProtocolContext; + +class ClientApiContext { + public: + virtual IPacketList& packet_list() = 0; + virtual ProtocolContext& protocol_context() = 0; + + protected: + ~ClientApiContext() = default; +}; + +class ServerApiContext { + public: + virtual PacketReader& reader() = 0; + virtual ProtocolContext& protocol_context() = 0; + + /// Request Id installed in context of every method with return value + void SetRequestId(RequestId id) { id_ = id; } + RequestId request_id() const { return id_; } + + protected: + ~ServerApiContext() = default; + RequestId id_{}; +}; +} // namespace ae + +#endif // AETHER_API_INTERNAL_DETAILS_CONTEXT_H_ diff --git a/aether/api_protocol/api_message.h b/aether/api_protocol/details/api_message.h similarity index 83% rename from aether/api_protocol/api_message.h rename to aether/api_protocol/details/api_message.h index 05251322a..66a8db903 100644 --- a/aether/api_protocol/api_message.h +++ b/aether/api_protocol/details/api_message.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AETHER_API_PROTOCOL_API_MESSAGE_H_ -#define AETHER_API_PROTOCOL_API_MESSAGE_H_ +#ifndef AETHER_API_PROTOCOL_DETAILS_API_MESSAGE_H_ +#define AETHER_API_PROTOCOL_DETAILS_API_MESSAGE_H_ #include #include @@ -30,27 +30,29 @@ namespace ae { using MessageId = std::uint8_t; -class ApiParser; -class ApiPacker; - using MessageBuffer = VectorBuffer; +template +struct ApiMessage { + static constexpr MessageId kMessageId = id; + using message_type = Message; + + message_type message; +}; + /** * \brief A message formed from template parameters */ template struct GenericMessage { explicit GenericMessage() = default; - explicit GenericMessage(Ts... args) : fields{std::forward(args)...} {} + explicit GenericMessage(Ts... args) + requires(sizeof...(Ts) > 0) + : fields{std::forward(args)...} {} [[no_unique_address]] std::tuple fields; }; -template <> -struct GenericMessage<> { - explicit GenericMessage() = default; -}; - namespace seri { template struct Serializer> { @@ -90,4 +92,4 @@ struct Serializer> { } // namespace ae -#endif // AETHER_API_PROTOCOL_API_MESSAGE_H_ +#endif // AETHER_API_PROTOCOL_DETAILS_API_MESSAGE_H_ diff --git a/aether/registration/api/global_reg_client_api.cpp b/aether/api_protocol/details/api_packer.cpp similarity index 68% rename from aether/registration/api/global_reg_client_api.cpp rename to aether/api_protocol/details/api_packer.cpp index 87a9eb6ac..d93f09403 100644 --- a/aether/registration/api/global_reg_client_api.cpp +++ b/aether/api_protocol/details/api_packer.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024 Aethernet Inc. + * Copyright 2026 Aethernet Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,8 @@ * limitations under the License. */ -#include "aether/registration/api/global_reg_client_api.h" +#include "aether/api_protocol/details/api_packer.h" -#if AE_SUPPORT_REGISTRATION namespace ae { -GlobalRegClientApi::GlobalRegClientApi(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, return_result{protocol_context} {} +ApiPacker::ApiPacker(DataBuffer& data) : archive{MessageBuffer{data}} {} } // namespace ae -#endif diff --git a/aether/api_protocol/details/api_packer.h b/aether/api_protocol/details/api_packer.h new file mode 100644 index 000000000..f8124f609 --- /dev/null +++ b/aether/api_protocol/details/api_packer.h @@ -0,0 +1,53 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_API_PACKER_H_ +#define AETHER_API_PROTOCOL_DETAILS_API_PACKER_H_ + +#include + +#include "aether-miscpp/serialization/binary_archive.h" + +#include "aether/types/data_buffer.h" + +#include "aether/api_protocol/details/api_message.h" + +namespace ae { +// Packing API messages to raw data buffer +class ApiPacker { + public: + explicit ApiPacker(DataBuffer& data); + + template + void Pack(MessageId message_id, Message const& msg) { + auto res = archive.Save(message_id); + if (!res) { + assert(false && "Message id didn't saved"); + return; + } + res = archive.Save(msg); + if (!res) { + assert(false && "Message didn't saved"); + return; + } + } + + private: + seri::BinaryArchive archive; +}; +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_API_PACKER_H_ diff --git a/aether/events/event_deleter.cpp b/aether/api_protocol/details/api_parser.cpp similarity index 50% rename from aether/events/event_deleter.cpp rename to aether/api_protocol/details/api_parser.cpp index ba82aad80..05ec31a5c 100644 --- a/aether/events/event_deleter.cpp +++ b/aether/api_protocol/details/api_parser.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2025 Aethernet Inc. + * Copyright 2024 Aethernet Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,24 +14,18 @@ * limitations under the License. */ -#include "aether/events/event_deleter.h" +#include "aether/api_protocol/details/api_parser.h" namespace ae { -EventHandlerDeleter::EventHandlerDeleter( - std::shared_ptr const& event_handlers, - EventHandlersList::Index index) - : event_handlers_{event_handlers}, index_{index} {} +ApiParser::ServerApiContextImpl::ServerApiContextImpl(ProtocolContext& pc, + DataBuffer const& buffer) + : protocol_context_{pc}, reader_{buffer} {} -void EventHandlerDeleter::Delete() { - if (auto handlers = event_handlers_.lock(); handlers) { - handlers->Remove(index_); - } +PacketReader& ApiParser::ServerApiContextImpl::reader() { return reader_; } +ProtocolContext& ApiParser::ServerApiContextImpl::protocol_context() { + return protocol_context_; } -bool EventHandlerDeleter::alive() const { - if (auto handlers = event_handlers_.lock(); handlers) { - return handlers->Alive(index_); - } - return false; -} +ApiParser::ApiParser(ProtocolContext& protocol_context, DataBuffer const& data) + : server_context_{protocol_context, data} {} } // namespace ae diff --git a/aether/api_protocol/details/api_parser.h b/aether/api_protocol/details/api_parser.h new file mode 100644 index 000000000..d7d91cf32 --- /dev/null +++ b/aether/api_protocol/details/api_parser.h @@ -0,0 +1,78 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_API_PARSER_H_ +#define AETHER_API_PROTOCOL_DETAILS_API_PARSER_H_ + +#include +#include + +#include "aether-miscpp/misc/defer.h" + +#include "aether/types/data_buffer.h" + +#include "aether/api_protocol/details/api_internal_context.h" +#include "aether/api_protocol/details/api_message.h" +#include "aether/api_protocol/details/packet_reader.h" + +namespace ae { +class ProtocolContext; +// Parsing raw data buffer to API messages +class ApiParser { + public: + class ServerApiContextImpl final : public ServerApiContext { + public: + ServerApiContextImpl(ProtocolContext& pc, DataBuffer const& buffer); + PacketReader& reader() override; + ProtocolContext& protocol_context() override; + + private: + ProtocolContext& protocol_context_; + PacketReader reader_; + }; + + ApiParser(ProtocolContext& protocol_context_, DataBuffer const& data); + + /** + * \brief Parse api class + */ + template + bool Parse(Api& api_class) { + api_class.set_server_context(server_context_); + ae_defer[&] { api_class.reset_server_context(); }; + auto& reader = server_context_.reader(); + + while (!reader.canceled() && !reader.eof()) { + auto msg_id = reader.TryExtract(); + if (!msg_id) { + reader.Cancel(); + assert(false && "message_id didn't loaded"); + return false; + } + if (!api_class.LoadMethod(*msg_id)) { + return false; + } + } + return reader.eof() && !reader.canceled(); + } + + private: + ServerApiContextImpl server_context_; +}; + +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_API_PARSER_H_ diff --git a/aether/api_protocol/details/api_promise.h b/aether/api_protocol/details/api_promise.h new file mode 100644 index 000000000..ac5db7c8b --- /dev/null +++ b/aether/api_protocol/details/api_promise.h @@ -0,0 +1,154 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_API_PROMISE_H_ +#define AETHER_API_PROTOCOL_DETAILS_API_PROMISE_H_ + +#include +#include +#include + +#include "aether-miscpp/meta/ignore_t.h" +#include "aether-miscpp/types/result.h" + +#include "aether/events/events.h" + +#include "aether/api_protocol/details/packet_reader.h" +#include "aether/api_protocol/details/request_id.h" + +namespace ae { + +/** + * \brief Base future class. + * This could be stored in protocol context and OnResult, OnError, OnEvicted + * invoked on different protocol events. + */ +class IApiFuture { + public: + virtual ~IApiFuture() = default; + + virtual void OnResult(PacketReader& reader) = 0; + virtual void OnError(std::uint8_t, std::int32_t error_code) = 0; + virtual void OnEvicted() = 0; +}; + +/** + * \brief Actual future class, it knows the actual value type. + * Also it's possible to set value directly via OnResult(V&& v). + * This allows to use it also on server side. + */ +template +class ApiFuture final : public IApiFuture { + public: + using result_type = Result; + using ResultEvent = Event; + + explicit ApiFuture(EventContext auto const& context, RequestId req_id) + : request_id{req_id}, result_event{context} {} + + void OnResult(PacketReader& reader) override { + auto value = reader.template TryExtract(); + if (!value) { + reader.Cancel(); + result_event.Emit(Error{std::int32_t{-2}}); + return; + } + OnResult(std::move(*value)); + } + + void OnResult(V&& v) { result_event.Emit(Ok{std::move(v)}); } + + void OnError(std::uint8_t, std::int32_t error_code) override { + result_event.Emit(Error{error_code}); + } + + void OnEvicted() override { + result_event.Emit(Error{static_cast(-1)}); + } + + RequestId request_id; + ResultEvent result_event; +}; + +/** + * \brief Sepcialization for void type. + * Void type does not require to use parser to extract any value. + */ +template <> +class ApiFuture final : public IApiFuture { + public: + using result_type = Result; + using ResultEvent = Event; + + explicit ApiFuture(EventContext auto const& context, RequestId req_id) + : request_id{req_id}, result_event{context} {} + + void OnResult(PacketReader&) override { OnResult(); } + + void OnResult() { result_event.Emit(result_type{Ok{kIgnore}}); } + + void OnError(std::uint8_t, std::int32_t error_code) override { + result_event.Emit(Error{error_code}); + } + + void OnEvicted() override { result_event.Emit(Error{std::int32_t{-1}}); } + + RequestId request_id; + ResultEvent result_event; +}; + +/** + * \brief Transient, non-owning request handle for subscribing during setup. + * + * ApiPromise is intended only for the request setup path, primarily to call + * Subscribe(). Do not store it in objects or otherwise retain it. Before the + * promise, future, or ProtocolContext lifetime can end, callers must create + * and retain Subscription or MultiSubscription RAII owners for any callbacks. + * + * ApiPromise does not cancel its request when discarded. Do not use or retain + * it after the associated operation completes, is evicted, or its + * ProtocolContext is destroyed. Subscription tokens returned by Subscribe() + * follow the Event ownership rules: transfer each token to exactly one + * Subscription or MultiSubscription. + */ +template +class ApiPromise { + public: + ApiPromise() = default; + ApiPromise(ApiFuture& future) noexcept // NOLINT(*explicit*) + : future_{&future} {} + + [[nodiscard]] constexpr explicit operator bool() const noexcept { + return future_ != nullptr; + } + + constexpr RequestId request_id() const { + assert(future_ != nullptr && "Get request id from empty future"); + return future_->request_id; + } + + template + constexpr decltype(auto) Subscribe(F&& f) { + assert(future_ != nullptr && "Subscribe to empty future"); + return future_->result_event.Subscribe(std::forward(f)); + } + + private: + ApiFuture* future_{}; +}; + +} // namespace ae +#endif // AETHER_API_PROTOCOL_DETAILS_API_PROMISE_H_ diff --git a/aether/api_protocol/details/packet_list.h b/aether/api_protocol/details/packet_list.h new file mode 100644 index 000000000..a3b52836a --- /dev/null +++ b/aether/api_protocol/details/packet_list.h @@ -0,0 +1,123 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_PACKET_LIST_H_ +#define AETHER_API_PROTOCOL_DETAILS_PACKET_LIST_H_ + +#include +#include + +#include "aether/types/data_buffer.h" + +#include "aether/api_protocol/details/api_message.h" +#include "aether/api_protocol/details/api_packer.h" + +namespace ae { +class IPackMessage { + public: + virtual ~IPackMessage() = default; + virtual void Pack(ApiPacker& packer) && = 0; +}; + +template +class PackMessage; + +template +class PackMessage> final : public IPackMessage { + public: + using Message = ApiMessage; + template + requires(std::same_as, Message>) + explicit PackMessage(U&& message) : message_{std::forward(message)} {} + + void Pack(ApiPacker& packer) && override { + packer.Pack(Message::kMessageId, std::move(message_.message)); + } + + private: + [[no_unique_address]] Message message_; +}; + +class IPacketList { + public: + /** + * \brief Push new message to the packet list. + */ + template + bool Push(M&& message) & { + if (full()) { + return false; + } + + // add new message into the pull + auto* elem = Allocate(); + assert(elem != nullptr); + new (elem) PackMessage>(std::forward(message)); + return true; + } + + virtual DataBuffer Pack() && = 0; + virtual bool full() const = 0; + + protected: + IPacketList() = default; + ~IPacketList() = default; + + virtual IPackMessage* Allocate() = 0; +}; + +/** + * \brief Stack of packed messages to generate one packet + */ +template +class PacketList final : public IPacketList { + public: + PacketList() = default; + + PacketList(PacketList const&) = delete; + PacketList& operator=(PacketList const&) = delete; + PacketList(PacketList&&) = delete; + PacketList& operator=(PacketList&&) = delete; + ~PacketList() { + for (auto const& e : list_) { + std::destroy_at(e.template get_address()); + } + } + + DataBuffer Pack() && override { + DataBuffer buffer; + ApiPacker packer{buffer}; + for (auto& p : list_) { + auto& packet = p.template get_reference(); + std::move(packet).Pack(packer); + } + return buffer; + } + bool full() const override { return list_.full(); } + + protected: + IPackMessage* Allocate() override { + auto& ref = list_.emplace_back(); + return ref.template get_address(); + } + + private: + using element_type = etl::aligned_storage_t; + etl::vector list_; +}; +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_PACKET_LIST_H_ diff --git a/aether/serial_ports/at_support/at_operation.h b/aether/api_protocol/details/packet_reader.cpp similarity index 59% rename from aether/serial_ports/at_support/at_operation.h rename to aether/api_protocol/details/packet_reader.cpp index d5d49f813..4bfeb3704 100644 --- a/aether/serial_ports/at_support/at_operation.h +++ b/aether/api_protocol/details/packet_reader.cpp @@ -14,22 +14,20 @@ * limitations under the License. */ -#ifndef AETHER_SERIAL_PORTS_AT_OPERATION_H_ -#define AETHER_SERIAL_PORTS_AT_OPERATION_H_ - -#include "aether/ae_context.h" -#include "aether/actions/action.h" -#include "aether/executors/executors.h" -#include "aether/actions/actions_queue.h" +#include "aether/api_protocol/details/packet_reader.h" namespace ae { -template -class SenderOperation final : public Action { - public: - explicit SenderOperation(AeContext const& ae_context, S&& s) {} +PacketReader::PacketReader(DataBuffer const& buffer) + : archive{MessageBuffer{ + const_cast(buffer) // NOLINT(*const-cast*) + }} {} - private: -}; -} // namespace ae +void PacketReader::Cancel() { canceled_ = true; } + +bool PacketReader::canceled() const { return canceled_; } -#endif // AETHER_SERIAL_PORTS_AT_OPERATION_H_ +bool PacketReader::eof() const { + return archive.buffer().read_offset == archive.buffer().buff.size(); +} + +} // namespace ae diff --git a/aether/api_protocol/details/packet_reader.h b/aether/api_protocol/details/packet_reader.h new file mode 100644 index 000000000..bdc250ebf --- /dev/null +++ b/aether/api_protocol/details/packet_reader.h @@ -0,0 +1,77 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_PACKET_READER_H_ +#define AETHER_API_PROTOCOL_DETAILS_PACKET_READER_H_ + +#include +#include +#include + +#include "aether-miscpp/serialization/binary_archive.h" + +#include "aether/types/data_buffer.h" + +#include "aether/api_protocol/details/api_message.h" + +namespace ae { +class PacketReader { + public: + explicit PacketReader(DataBuffer const& buffer); + + /** + * \brief Tries to extract a message from the reader. + * + * Returns nullopt on read failure without canceling or rolling back the + * reader. + */ + template + std::optional TryExtract() { + std::optional result{std::in_place}; + if (auto res = archive.Load(result.value()); res.IsErr()) { + return std::nullopt; + } + return result; + } + /** + * \brief Extracts a message or part of one from the reader. + * + * The caller must know that a complete, correctly encoded T is present in + * the buffer. If this precondition is violated, the reader is canceled and + * no usable value is guaranteed. + */ + template + T Extract() { + T value{}; + if (auto res = archive.Load(value); res.IsErr()) { + Cancel(); + assert(false && "value didn't extracted"); + } + return value; + } + + // cancel parsing + void Cancel(); + bool canceled() const; + bool eof() const; + + private: + seri::BinaryArchive archive; + bool canceled_{false}; +}; +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_PACKET_READER_H_ diff --git a/aether/api_protocol/details/protocol_context.cpp b/aether/api_protocol/details/protocol_context.cpp new file mode 100644 index 000000000..86257d30c --- /dev/null +++ b/aether/api_protocol/details/protocol_context.cpp @@ -0,0 +1,119 @@ +/* + * Copyright 2024 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "aether/api_protocol/details/protocol_context.h" + +#include +#include + +#include "aether/tele.h" + +namespace ae { +ProtocolContext::ProtocolContext(TaskScheduler& scheduler, + EventSystem& event_system) + : scheduler_{&scheduler}, event_system_{&event_system} {} + +ProtocolContext::~ProtocolContext() { + while (!pending_list_.empty()) { + auto entry = TakeOldestPending(); + DestroyPending(entry); + } +} + +TaskScheduler& ProtocolContext::scheduler() const { return *scheduler_; } +EventSystem& ProtocolContext::event_system() const { return *event_system_; } + +RequestId ProtocolContext::NextRequestId() { + return RequestId{next_request_id_++}; +} + +bool ProtocolContext::SetSendResultResponse(PacketReader& reader, + RequestId request_id) { + auto entry = TakePending(request_id); + if (entry.response == nullptr) { + AE_TELED_DEBUG("No callback for request id {} cancel parse", request_id); + return false; + } + + entry.response->OnResult(reader); + DestroyPending(entry); + return true; +} + +bool ProtocolContext::SetSendErrorResponse(RequestId req_id, + std::uint8_t error_type, + std::int32_t error_code) { + auto entry = TakePending(req_id); + if (entry.response == nullptr) { + AE_TELED_DEBUG("No callback for error with request id {}", req_id); + return false; + } + + entry.response->OnError(error_type, error_code); + DestroyPending(entry); + return true; +} + +void ProtocolContext::EvictPending(PendingEntry const& entry) { + assert(entry.response != nullptr && + "EvictPending requires a pending response entry"); + entry.response->OnEvicted(); + DestroyPending(entry); +} + +void ProtocolContext::DestroyPending(PendingEntry const& entry) { + assert(entry.response != nullptr && + "DestroyPending requires a pending response entry"); + futures_pool_.destroy(entry.response); +} + +void ProtocolContext::PreparePendingResponseSlot() { + // ensure there is enough in pool for new pending response + // oldest pending should be evicted + // If during eviction somebody take free space, evict again + while (pending_list_.full()) { + auto oldest_entry = TakeOldestPending(); + EvictPending(oldest_entry); + } +} + +ProtocolContext::PendingEntry ProtocolContext::TakePending( + RequestId request_id) { + auto* it = std::find_if(std::begin(pending_list_), std::end(pending_list_), + [request_id](auto const& pe) noexcept { + return pe.request_id == request_id; + }); + if (it == std::end(pending_list_)) { + return PendingEntry{RequestId{}, nullptr}; + } + + auto found = *it; + pending_list_.erase(it); + return found; +} + +ProtocolContext::PendingEntry ProtocolContext::TakeOldestPending() { + assert(!pending_list_.empty() && + "TakeOldestPending requires a pending response"); + + // oldest is the first + auto* it = pending_list_.begin(); + auto found = *it; + pending_list_.erase(it); + + return found; +} +} // namespace ae diff --git a/aether/api_protocol/details/protocol_context.h b/aether/api_protocol/details/protocol_context.h new file mode 100644 index 000000000..a78e61057 --- /dev/null +++ b/aether/api_protocol/details/protocol_context.h @@ -0,0 +1,120 @@ +/* + * Copyright 2024 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_PROTOCOL_CONTEXT_H_ +#define AETHER_API_PROTOCOL_DETAILS_PROTOCOL_CONTEXT_H_ + +#include +#include + +#include + +#include "aether/warning_disable.h" +DISABLE_WARNING_PUSH() +IGNORE_IMPLICIT_CONVERSION() +#include +DISABLE_WARNING_POP() + +#include "aether/config.h" + +#include "aether/actions/action_context.h" +#include "aether/events/events.h" +#include "aether/tasks/manual_task_scheduler.h" + +#include "aether/api_protocol/details/api_promise.h" +#include "aether/api_protocol/details/request_id.h" + +namespace ae { +class PacketReader; + +class ProtocolContext { + public: + static constexpr auto kMaxPendingResponses = + AE_API_PROTOCOL_MAX_PENDING_RESPONSES; + static constexpr auto kFutureMaxSize = sizeof(ApiFuture); + static constexpr auto kFutureMaxAlign = alignof(ApiFuture); + + static_assert(kMaxPendingResponses > 0, + "AE_API_PROTOCOL_MAX_PENDING_RESPONSES must be greater than 0"); + + explicit ProtocolContext(ActionContext auto const& context) + : ProtocolContext{ + context.scheduler(), + context.event_system(), + } {} + ProtocolContext(TaskScheduler& scheduler, EventSystem& event_system); + ~ProtocolContext(); + + TaskScheduler& scheduler() const; + EventSystem& event_system() const; + + RequestId NextRequestId(); + + // Create api future for specified value type with request id + template + ApiFuture& CreateFuture(RequestId request_id) { + using Entry = ApiFuture; + static_assert(sizeof(Entry) <= kFutureMaxSize, + "Pending response entry exceeds kFutureMaxSize"); + static_assert(alignof(Entry) <= kFutureMaxAlign, + "Pending response entry alignment exceeds kFutureMaxAlign"); + + PreparePendingResponseSlot(); + + auto* entry_ptr = futures_pool_.template create(*this, request_id); + assert(entry_ptr != nullptr && + "Pending response pool allocation failed after slot preparation"); + pending_list_.emplace_back(PendingEntry{request_id, entry_ptr}); + return *entry_ptr; + } + + bool SetSendResultResponse(PacketReader& reader, RequestId request_id); + bool SetSendErrorResponse(RequestId req_id, std::uint8_t error_type, + std::int32_t error_code); + + private: + using FuturesPool = + etl::generic_pool; + + struct PendingEntry { + RequestId request_id; + IApiFuture* response; + }; + + using PendingList = etl::vector; + + // Call the eviction event on pending response + void EvictPending(PendingEntry const& entry); + // Call the destructor on pending response + void DestroyPending(PendingEntry const& entry); + // Ensure we could add at least one pending response, the oldest would be + // evicted + void PreparePendingResponseSlot(); + // Find pending entry with request id and pop it from pending list + PendingEntry TakePending(RequestId request_id); + // Pop the oldest pending response from the pending list + PendingEntry TakeOldestPending(); + + TaskScheduler* scheduler_; + EventSystem* event_system_; + std::uint32_t next_request_id_{}; + + FuturesPool futures_pool_; + PendingList pending_list_; +}; +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_PROTOCOL_CONTEXT_H_ diff --git a/aether/api_protocol/request_id.h b/aether/api_protocol/details/request_id.h similarity index 85% rename from aether/api_protocol/request_id.h rename to aether/api_protocol/details/request_id.h index f53c81dc0..07a5eba5c 100644 --- a/aether/api_protocol/request_id.h +++ b/aether/api_protocol/details/request_id.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AETHER_API_PROTOCOL_REQUEST_ID_H_ -#define AETHER_API_PROTOCOL_REQUEST_ID_H_ +#ifndef AETHER_API_PROTOCOL_DETAILS_REQUEST_ID_H_ +#define AETHER_API_PROTOCOL_DETAILS_REQUEST_ID_H_ #include @@ -24,11 +24,6 @@ namespace ae { struct RequestId { - static auto GenRequestId() { - static RequestId request_id{1}; - return request_id.id++; - } - RequestId() = default; RequestId(std::uint32_t id) : id(id) {} @@ -51,4 +46,4 @@ struct Formatter { }; } // namespace ae -#endif // AETHER_API_PROTOCOL_REQUEST_ID_H_ +#endif // AETHER_API_PROTOCOL_DETAILS_REQUEST_ID_H_ diff --git a/aether/api_protocol/details/return_result_api.h b/aether/api_protocol/details/return_result_api.h new file mode 100644 index 000000000..6509ed96f --- /dev/null +++ b/aether/api_protocol/details/return_result_api.h @@ -0,0 +1,143 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_RETURN_RESULT_API_H_ +#define AETHER_API_PROTOCOL_DETAILS_RETURN_RESULT_API_H_ + +#include + +#include "aether-miscpp/reflect/reflect.h" + +#include "aether/api_protocol/details/api_internal_context.h" +// IWYU pragma: begin_keeps +#include "aether/api_protocol/details/packet_list.h" +#include "aether/api_protocol/details/protocol_context.h" +#include "aether/api_protocol/details/request_id.h" +// IWYU pragma: end_keeps + +namespace ae { +namespace return_result_api_internal { +struct SendResultTag {}; +struct SendErrorTag {}; +static inline constexpr auto send_result_tag = SendResultTag{}; +static inline constexpr auto send_error_tag = SendErrorTag{}; +} // namespace return_result_api_internal + +namespace api_class_declate_internal { +template +struct RegMethod; + +template +struct RegMethod { + static constexpr auto kMessageId = id; + static constexpr auto kMethod = return_result_api_internal::send_result_tag; +}; + +template +struct RegMethod { + static constexpr auto kMessageId = id; + static constexpr auto kMethod = return_result_api_internal::send_error_tag; +}; + +} // namespace api_class_declate_internal + +class ReturnResultApi { + template + struct SendResultMessage { + AE_REFLECT_MEMBERS(req_id, data); + RequestId req_id; + T data; + }; + + struct SendErrorMessage { + AE_REFLECT_MEMBERS(req_id, error_type, error_code); + RequestId req_id; + std::uint8_t error_type; + std::int32_t error_code; + }; + + public: + static constexpr MessageId kSendResult = 0; + static constexpr MessageId kSendError = 1; + + using RegSendResult = api_class_declate_internal::RegMethod< + kSendResult, return_result_api_internal::send_result_tag>; + using RegSendError = api_class_declate_internal::RegMethod< + kSendError, return_result_api_internal::send_error_tag>; + + template + static void SendResult(ClientApiContext& client_context, RequestId req_id, + T&& data) { + using M = SendResultMessage>; + client_context.packet_list().Push(ApiMessage{ + M{.req_id = req_id, .data = std::forward(data)}}); + } + + static void SendError(ClientApiContext& client_context, RequestId req_id, + std::uint8_t error_type, std::int32_t error_code) { + client_context.packet_list().Push( + ApiMessage{SendErrorMessage{ + .req_id = req_id, + .error_type = error_type, + .error_code = error_code, + }}); + } + + static void SendResultImpl(ServerApiContext& server_context) { + auto& reader = server_context.reader(); + auto request_id = reader.TryExtract(); + if (!request_id) { + reader.Cancel(); + return; + } + if (!server_context.protocol_context().SetSendResultResponse(reader, + *request_id)) { + reader.Cancel(); + } + } + + static void SendErrorImpl(ServerApiContext& server_context) { + auto& reader = server_context.reader(); + auto request_id = reader.TryExtract(); + if (!request_id) { + reader.Cancel(); + return; + } + auto error_type = reader.TryExtract(); + if (!error_type) { + reader.Cancel(); + return; + } + auto error_code = reader.TryExtract(); + if (!error_code) { + reader.Cancel(); + return; + } + + if (!server_context.protocol_context().SetSendErrorResponse( + *request_id, *error_type, *error_code)) { + server_context.reader().Cancel(); + } + } + + static consteval auto ServeApiList() { + return TypeList{}; + } +}; + +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_RETURN_RESULT_API_H_ diff --git a/aether/api_protocol/details/sub_api.h b/aether/api_protocol/details/sub_api.h new file mode 100644 index 000000000..adb0e9c73 --- /dev/null +++ b/aether/api_protocol/details/sub_api.h @@ -0,0 +1,123 @@ +/* + * Copyright 2025 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_API_PROTOCOL_DETAILS_SUB_API_H_ +#define AETHER_API_PROTOCOL_DETAILS_SUB_API_H_ + +#include + +#include "aether-miscpp/meta/function_signature.h" +#include "aether-miscpp/meta/type_list.h" +#include "aether-miscpp/misc/override.h" +#include "aether-miscpp/types/small_function.h" + +#include "aether/types/data_buffer.h" + +#include "aether/api_protocol/details/api_class_declare.h" +#include "aether/api_protocol/details/api_context.h" +#include "aether/api_protocol/details/api_internal_context.h" + +namespace ae { +namespace sub_api_internal { +template +struct ApiContextApiType; + +template +struct ApiContextApiType> { + using type = T; +}; +} // namespace sub_api_internal + +/** + * \brief Sub api caller + * For client it invokes Api methods in api context. + */ +template +class SubApi { + public: + static_assert(DeclaredApi, "Api must be declared api"); + using Method = SmallFunction& api)>; + + // client variant + // init with ApiContext caller or Method + template + requires(!std::same_as>) + SubApi(TFunc&& caller) // NOLINT(*explicit*) + : var_{std::in_place_index<1>, std::forward(caller)} {} + SubApi(Method caller) // NOLINT(*explicit*) + : var_{std::in_place_index<1>, std::move(caller)} {} + // init with filled api context + SubApi(ApiContext&& api_context) // NOLINT(*explicit*) + : var_{std::in_place_index<0>, std::move(api_context).Pack()} {} + + // server variant + // init with just data buffer + SubApi(DataBuffer buffer) // NOLINT(*explicit*) + : var_{std::in_place_index<0>, std::move(buffer)} {} + + // client variant with api and external client context + // invoke caller with api class or return stored buffer + template + requires(std::same_as || std::derived_from) + DataBuffer operator()(A& api, ClientApiContext& client_context) && { + // protocol context must be used only for method invoke + return std::move(*this).Visit([&](Method&& method) { + auto ac = ApiContext{api, client_context.protocol_context()}; + std::move(method)(ac); + return std::move(ac).Pack(); + }); + } + // client variant with external maded api context + // invoke caller with api class or return stored buffer + DataBuffer operator()(ApiContext&& api_context) && { + return std::move(*this).Visit([&](Method&& method) { + std::move(method)(api_context); + return std::move(api_context).Pack(); + }); + } + + // server variant + // return the stored buffer + DataBuffer&& buffer() && { + assert(var_.index() == 0 && "Server variant must contain a data buffer"); + return std::move(std::get(var_)); + } + + private: + template + DataBuffer Visit(MethodInvoker&& method_invoker) && { + return std::visit( + Override{ + std::forward(method_invoker), + [](DataBuffer&& data_buffer) noexcept { + return std::move(data_buffer); + }, + }, + std::move(var_)); + } + + std::variant var_; +}; + +template +SubApi(ApiContext&&) -> SubApi; + +template +SubApi(TFunc&&) -> SubApi::args>>>::type>; +} // namespace ae + +#endif // AETHER_API_PROTOCOL_DETAILS_SUB_API_H_ diff --git a/aether/api_protocol/make_api_call_sender.h b/aether/api_protocol/make_api_call_sender.h index 7eff5c432..b7058a142 100644 --- a/aether/api_protocol/make_api_call_sender.h +++ b/aether/api_protocol/make_api_call_sender.h @@ -17,168 +17,218 @@ #ifndef AETHER_API_PROTOCOL_MAKE_API_CALL_SENDER_H_ #define AETHER_API_PROTOCOL_MAKE_API_CALL_SENDER_H_ +#include +#include +#include #include -#include +#include "aether/events/events.h" #include "aether/executors/executors.h" -#include "aether/api_protocol/api_context.h" -#include "aether/events/event_subscription.h" #include "aether/write_action/write_action.h" +#include "aether/api_protocol/details/api_context.h" +#include "aether/api_protocol/details/api_promise.h" + namespace ae { struct WriteFailed {}; -namespace make_api_call_internal { -template -struct OpBase { - constexpr explicit OpBase(R&& r) noexcept : recv{std::move(r)} {} +namespace make_api_call_sender_internal { + +template +inline constexpr bool kIsApiPromise = false; - virtual void Reset() noexcept {} - virtual bool is_reset() const noexcept { return false; } +template +inline constexpr bool kIsApiPromise> = true; - R recv; +template +concept ApiPromiseReturn = kIsApiPromise>; - protected: - ~OpBase() = default; +template +concept ApiCall = requires(Fn& fn, ApiContext& api_context) { + { std::invoke(fn, api_context) } -> ApiPromiseReturn; }; -template -struct Receiver { - using receiver_concept = ex::receiver_t; +template +struct CallTraits; - constexpr void set_value(auto&&... v) && noexcept { - if (!op->is_reset()) { - op->Reset(); - ex::set_value(std::move(op->recv), std::forward(v)...); - } - } - constexpr void set_error(auto&& e) && noexcept { - if (!op->is_reset()) { - op->Reset(); - ex::set_error(std::move(op->recv), std::forward(e)); - } - } - constexpr void set_stopped() && noexcept { - if (!op->is_reset()) { - op->Reset(); - ex::set_stopped(std::move(op->recv)); - } - } - constexpr auto get_env() noexcept { - assert(!op->is_reset()); - return ex::get_env(op->recv); - } +template +struct CallTraits> { + using CompletionSignatures = ex::completion_signatures< + ex::set_value_t(T), ex::set_error_t(std::int32_t), + ex::set_error_t(WriteFailed), ex::set_stopped_t()>; + static constexpr bool kHasValue = true; +}; - OpBase* op; +template <> +struct CallTraits> { + using CompletionSignatures = ex::completion_signatures< + ex::set_value_t(), ex::set_error_t(std::int32_t), + ex::set_error_t(WriteFailed), ex::set_stopped_t()>; + static constexpr bool kHasValue = false; }; -template -class Operation final : public OpBase { - public: - using receiver = Receiver; +template +class Operation final { + using Traits = CallTraits; - constexpr Operation(R&& recv, Api* api, Stream* stream, Fn&& fn) noexcept - : OpBase{std::move(recv)}, + public: + template + Operation(R&& receiver, Api* api, ProtocolContext* protocol_context, + Stream* stream, Fn&& fn) noexcept + : receiver_{std::forward(receiver)}, api_{api}, + protocol_context_{protocol_context}, stream_{stream}, fn_{std::move(fn)} {} - constexpr void start() noexcept { - // prepare api call by user's function - auto api_context = ApiContext{*api_}; - // though recv_ moved inside the function, it's possible that the function - // does not move it inside so we keep store recv_ as a member to ensure it - // live enough - recv_.emplace(this); - if constexpr (std::is_invocable_v&, receiver&&>) { - std::invoke(fn_, api_context, std::move(recv_.value())); - } else { - static_assert(std::is_invocable_v&, receiver&>, - "Fn must be invocable with ApiContext& and receiver&"); - std::invoke(fn_, api_context, recv_.value()); + /** + * Starts this lvalue-qualified stdexec operation state. + * + * stdexec calls start() to synchronously build the API context and invoke + * the callable. An empty promise completes the receiver with set_stopped + * synchronously and does not write. Otherwise, start() subscribes to the + * promise, packs and writes the request, subscribes to write status, and + * returns. + * + * The caller/system must ensure that this operation is started once and + * remains alive until completion. For a non-empty promise, promise and write + * callbacks must be delivered asynchronously and serialized; terminal + * callbacks must not be concurrent or reentrant. Under this contract, + * surrounding event/write integration must deliver at most one terminal + * promise or write callback for the operation; exactly-once terminal + * delivery is a precondition because this simplified implementation + * intentionally has no completed_ guard. + * + * Under this contract, + * terminal callbacks reset subscriptions before completing the receiver, and + * no operation member may be accessed afterward. + */ + void start() & noexcept { + auto api_context = protocol_context_ == nullptr + ? ApiContext{*api_} + : ApiContext{*api_, *protocol_context_}; + auto promise = std::invoke(fn_, api_context); + if (!promise) { + ex::set_stopped(std::move(receiver_)); + return; } - // write the result to the stream - auto& stream_action = stream_->Write(std::move(api_context).Pack()); - // if stream is failed, the api call is also failed - sub_ = stream_action.status_event().Subscribe( - [&](WriteAction::Status status) noexcept { - switch (status) { - case WriteAction::Status::kFail: - Reset(); - ex::set_error(std::move(OpBase::recv), WriteFailed{}); - break; - case WriteAction::Status::kStop: - Reset(); - ex::set_stopped(std::move(OpBase::recv)); - break; - default: - break; - } + + result_subscription_ = promise.Subscribe([this](auto&& result) noexcept { + this->OnPromiseResult(std::forward(result)); + }); + auto& write_action = stream_->Write(std::move(api_context).Pack()); + write_subscription_ = write_action.status_event().Subscribe( + [this](WriteAction::Status status) noexcept { + this->OnWriteStatus(status); }); } - void Reset() noexcept override { sub_.Reset(); is_reset_ = true; } - bool is_reset() const noexcept override { return is_reset_; } - private: + template + void OnPromiseResult(Result&& result) noexcept { + ResetSubscriptions(); + if (result) { + if constexpr (Traits::kHasValue) { + ex::set_value(std::move(receiver_), + std::forward(result).value()); + } else { + ex::set_value(std::move(receiver_)); + } + return; + } + + ex::set_error(std::move(receiver_), std::forward(result).error()); + } + + void OnWriteStatus(WriteAction::Status status) noexcept { + switch (status) { + case WriteAction::Status::kSuccess: + return; + case WriteAction::Status::kFail: + ResetSubscriptions(); + ex::set_error(std::move(receiver_), WriteFailed{}); + return; + case WriteAction::Status::kStop: + ResetSubscriptions(); + ex::set_stopped(std::move(receiver_)); + return; + } + } + + void ResetSubscriptions() noexcept { + result_subscription_.Reset(); + write_subscription_.Reset(); + } + + Receiver receiver_; Api* api_; + ProtocolContext* protocol_context_; Stream* stream_; Fn fn_; - Subscription sub_; - bool is_reset_ = false; - std::optional recv_; + Subscription result_subscription_; + Subscription write_subscription_; }; -template +template + requires ApiCall class Sender { public: using sender_concept = ex::sender_t; + using Return = + std::remove_cvref_t&>>; template static consteval auto get_completion_signatures() noexcept { - return ex::__transform_completion_signatures( - Completions{}, ex::__keep_completion{}, - ex::__keep_completion{}, - ex::__keep_completion{}, - ex::completion_signatures{}); + return typename CallTraits::CompletionSignatures{}; } - constexpr Sender(Api& api, Stream& stream, Fn&& fn) noexcept - : api_{&api}, stream_{&stream}, fn_{std::move(fn)} {} + Sender(Api& api, Stream& stream, Fn&& fn) noexcept + : api_{&api}, + protocol_context_{nullptr}, + stream_{&stream}, + fn_{std::move(fn)} {} + Sender(Api& api, ProtocolContext& protocol_context, Stream& stream, + Fn&& fn) noexcept + : api_{&api}, + protocol_context_{&protocol_context}, + stream_{&stream}, + fn_{std::move(fn)} {} - constexpr auto connect(ex::receiver auto&& recv) noexcept { - return Operation>{ - std::forward(recv), - api_, - stream_, - std::move(fn_), - }; + template + auto connect(Receiver&& receiver) && noexcept { + return Operation>{ + std::forward(receiver), api_, protocol_context_, stream_, + std::move(fn_)}; } private: Api* api_; + ProtocolContext* protocol_context_; Stream* stream_; Fn fn_; }; -template struct MakeApiCall { template - constexpr auto operator()(Api& api, Stream& stream, Fn&& fn) const noexcept { - return Sender, Api, Stream, - std::decay_t>{ - api, - stream, - std::forward(fn), - }; + auto operator()(Api& api, Stream& stream, Fn&& fn) const noexcept + -> Sender> { + return Sender>{api, stream, + std::forward(fn)}; + } + + template + auto operator()(Api& api, ProtocolContext& protocol_context, Stream& stream, + Fn&& fn) const noexcept + -> Sender> { + return Sender>{api, protocol_context, stream, + std::forward(fn)}; } }; -} // namespace make_api_call_internal +} // namespace make_api_call_sender_internal -template -static constexpr inline auto make_api_call = - make_api_call_internal::MakeApiCall{}; +inline constexpr make_api_call_sender_internal::MakeApiCall make_api_call{}; } // namespace ae diff --git a/aether/api_protocol/protocol_context.cpp b/aether/api_protocol/protocol_context.cpp deleted file mode 100644 index 37d2f9b5c..000000000 --- a/aether/api_protocol/protocol_context.cpp +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/api_protocol/protocol_context.h" - -#include -#include -#include - -#include "aether/api_protocol/api_protocol.h" - -#include "aether/tele.h" - -namespace ae { -ProtocolContext::ProtocolContext() = default; -ProtocolContext::~ProtocolContext() { - while (!pending_responses_.empty()) { - auto entry = TakeOldestPending(); - DestroyPending(entry); - } -} - -void ProtocolContext::SetSendResultResponse(RequestId request_id) { - auto entry = TakePending(request_id); - if (entry.response == nullptr) { - AE_TELED_DEBUG("No callback for request id {} cancel parse", request_id); - parser()->Cancel(); - } - - auto* p = parser(); - assert(p != nullptr && "Parser shouldn't be null"); - entry.response->OnResult(*p); - DestroyPending(entry); -} - -void ProtocolContext::SetSendErrorResponse(RequestId req_id, - std::uint8_t error_type, - std::uint32_t error_code) { - auto entry = TakePending(req_id); - if (entry.response == nullptr) { - AE_TELED_DEBUG("No callback for error with request id {}", req_id); - parser()->Cancel(); - } - - entry.response->OnError(error_type, static_cast(error_code)); - DestroyPending(entry); -} - -void ProtocolContext::EvictPending(PendingEntry const& entry) { - assert(entry.response != nullptr && - "EvictPending requires a pending response entry"); - entry.response->OnEvicted(); - DestroyPending(entry); -} - -void ProtocolContext::DestroyPending(PendingEntry const& entry) { - assert(entry.response != nullptr && - "DestroyPending requires a pending response entry"); - pending_response_pool_.destroy(entry.response); -} - -void ProtocolContext::PreparePendingResponseSlot(RequestId request_id) { - // ensure there is one pending response for request_id - auto existing_entry = TakePending(request_id); - if (existing_entry.response != nullptr) { - EvictPending(existing_entry); - return; - } - - // ensure there is enough in pool for new pending response - // oldest pending should be evicted - if (pending_responses_.full()) { - auto oldest_entry = TakeOldestPending(); - EvictPending(oldest_entry); - } - - assert(!pending_responses_.full() && - "Pending response registry must have a free slot"); - assert(pending_response_pool_.available() != 0U && - "Pending response pool must have a free slot"); -} - -ProtocolContext::PendingEntry ProtocolContext::TakePending( - RequestId request_id) { - auto* it = - std::find_if(std::begin(pending_responses_), std::end(pending_responses_), - [request_id](auto const& pe) noexcept { - return pe.request_id == request_id; - }); - if (it == std::end(pending_responses_)) { - return PendingEntry{RequestId{}, nullptr}; - } - - auto found = *it; - pending_responses_.erase(it); - return found; -} - -ProtocolContext::PendingEntry ProtocolContext::TakeOldestPending() { - assert(!pending_responses_.empty() && - "TakeOldestPending requires a pending response"); - - // oldest is the first - auto* it = pending_responses_.begin(); - auto found = *it; - pending_responses_.erase(it); - - return found; -} - -void ProtocolContext::PushPacketStack(PacketStack& packet_stack) { - assert(!packet_stacks_.full() && - "AE_API_PROTOCOL_MAX_PACKET_STACK_DEPTH exceeded"); - packet_stacks_.push(&packet_stack); -} - -void ProtocolContext::PopPacketStack() { - assert(!packet_stacks_.empty() && "Packet stack context is empty"); - packet_stacks_.pop(); -} - -PacketStack* ProtocolContext::packet_stack() { - if (packet_stacks_.empty()) { - return nullptr; - } - auto* packet_stack = packet_stacks_.top(); - assert(packet_stack != nullptr && "Packet stack context contains nullptr"); - return packet_stack; -} - -void ProtocolContext::PushParser(ApiParser& parser) { - assert(!parsers_.full() && - "AE_API_PROTOCOL_MAX_PARSER_PACKER_DEPTH exceeded for parser"); - parsers_.push(&parser); -} - -void ProtocolContext::PopParser() { - assert(!parsers_.empty() && "Parser context is empty"); - parsers_.pop(); -} - -ApiParser* ProtocolContext::parser() { - if (parsers_.empty()) { - return nullptr; - } - auto* parser = parsers_.top(); - assert(parser != nullptr && "Parser context contains nullptr"); - return parser; -} - -void ProtocolContext::PushPacker(ApiPacker& packer) { - assert(!packers_.full() && - "AE_API_PROTOCOL_MAX_PARSER_PACKER_DEPTH exceeded for packer"); - packers_.push(&packer); -} - -void ProtocolContext::PopPacker() { - assert(!packers_.empty() && "Packer context is empty"); - packers_.pop(); -} - -ApiPacker* ProtocolContext::packer() { - if (packers_.empty()) { - return nullptr; - } - auto* packer = packers_.top(); - assert(packer != nullptr && "Packer context contains nullptr"); - return packer; -} - -} // namespace ae diff --git a/aether/api_protocol/protocol_context.h b/aether/api_protocol/protocol_context.h deleted file mode 100644 index e4d5741e6..000000000 --- a/aether/api_protocol/protocol_context.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_PROTOCOL_CONTEXT_H_ -#define AETHER_API_PROTOCOL_PROTOCOL_CONTEXT_H_ - -#include -#include - -#include - -#include "aether/warning_disable.h" -DISABLE_WARNING_PUSH() -IGNORE_IMPLICIT_CONVERSION() -#include -#include -DISABLE_WARNING_POP() - -#include "aether/api_protocol/request_id.h" -#include "aether/config.h" - -namespace ae { -class PacketStack; -class ApiParser; -class ApiPacker; - -class ProtocolContext { - public: - class PendingResponse { - public: - virtual ~PendingResponse() = default; - - virtual void OnResult(ApiParser& parser) = 0; - virtual void OnError(std::uint8_t error_type, std::int32_t error_code) = 0; - virtual void OnEvicted() = 0; - }; - - static constexpr auto kMaxPendingResponses = - AE_API_PROTOCOL_MAX_PENDING_RESPONSES; - static constexpr auto kPendingResponseMaxSize = - AE_API_PROTOCOL_PENDING_RESPONSE_MAX_SIZE; - static constexpr auto kPendingResponseAlign = - AE_API_PROTOCOL_PENDING_RESPONSE_ALIGN; - static constexpr auto kMaxPacketStackDepth = - AE_API_PROTOCOL_MAX_PACKET_STACK_DEPTH; - static constexpr auto kMaxParserPackerDepth = - AE_API_PROTOCOL_MAX_PARSER_PACKER_DEPTH; - static_assert(kMaxPendingResponses > 0, - "AE_API_PROTOCOL_MAX_PENDING_RESPONSES must be greater than 0"); - static_assert( - kMaxPacketStackDepth > 0, - "AE_API_PROTOCOL_MAX_PACKET_STACK_DEPTH must be greater than 0"); - static_assert( - kMaxParserPackerDepth > 0, - "AE_API_PROTOCOL_MAX_PARSER_PACKER_DEPTH must be greater than 0"); - - ProtocolContext(); - ~ProtocolContext(); - - template - Entry& CreatePendingResponse(RequestId request_id) { - static_assert(sizeof(Entry) <= kPendingResponseMaxSize, - "Pending response entry exceeds " - "AE_API_PROTOCOL_PENDING_RESPONSE_MAX_SIZE"); - static_assert(alignof(Entry) <= kPendingResponseAlign, - "Pending response entry alignment exceeds " - "AE_API_PROTOCOL_PENDING_RESPONSE_ALIGN"); - - PreparePendingResponseSlot(request_id); - - auto* entry_ptr = pending_response_pool_.template create(); - assert(entry_ptr != nullptr && - "Pending response pool allocation failed after slot preparation"); - pending_responses_.emplace_back(PendingEntry{request_id, entry_ptr}); - return *entry_ptr; - } - - void SetSendResultResponse(RequestId request_id); - void SetSendErrorResponse(RequestId req_id, std::uint8_t error_type, - std::uint32_t error_code); - - void PushPacketStack(PacketStack& packet_stack); - void PopPacketStack(); - PacketStack* packet_stack(); - - void PushParser(ApiParser& parser); - void PopParser(); - ApiParser* parser(); - - void PushPacker(ApiPacker& packer); - void PopPacker(); - ApiPacker* packer(); - - private: - using PendingResponsePool = - etl::generic_pool; - - struct PendingEntry { - RequestId request_id; - PendingResponse* response; - }; - - using PendingList = etl::vector; - using PacketStackStack = etl::stack; - using ParserStack = etl::stack; - using PackerStack = etl::stack; - - void EvictPending(PendingEntry const& entry); - void DestroyPending(PendingEntry const& entry); - void PreparePendingResponseSlot(RequestId request_id); - PendingEntry TakePending(RequestId request_id); - PendingEntry TakeOldestPending(); - - PendingResponsePool pending_response_pool_; - PendingList pending_responses_; - - PacketStackStack packet_stacks_; - ParserStack parsers_; - PackerStack packers_; -}; -} // namespace ae - -#endif // AETHER_API_PROTOCOL_PROTOCOL_CONTEXT_H_ diff --git a/aether/api_protocol/return_result_api.cpp b/aether/api_protocol/return_result_api.cpp deleted file mode 100644 index da48662e0..000000000 --- a/aether/api_protocol/return_result_api.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/api_protocol/return_result_api.h" - -namespace ae { -ReturnResultApi::ReturnResultApi(ProtocolContext& protocol_context) - : ApiClass{protocol_context}, send_error_{protocol_context} {} - -void ReturnResultApi::SendResultImpl(RequestId request_id) { - protocol_context().SetSendResultResponse(request_id); -} - -void ReturnResultApi::SendErrorImpl(RequestId request_id, - std::uint8_t error_type, - std::uint32_t error_code) { - protocol_context().SetSendErrorResponse(request_id, error_type, error_code); -} -} // namespace ae diff --git a/aether/api_protocol/return_result_api.h b/aether/api_protocol/return_result_api.h deleted file mode 100644 index b4820960c..000000000 --- a/aether/api_protocol/return_result_api.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_RETURN_RESULT_API_H_ -#define AETHER_API_PROTOCOL_RETURN_RESULT_API_H_ - -#include - -#include "aether-miscpp/reflect/reflect.h" - -#include "aether/api_protocol/api_class_impl.h" -#include "aether/api_protocol/api_method.h" - -namespace ae { -class ReturnResultApi : public ApiClass { - template - struct SendResultMessage { - AE_REFLECT_MEMBERS(req_id, data); - RequestId req_id; - T data; - }; - - public: - static constexpr MessageId kSendResult = 0; - static constexpr MessageId kSendError = 1; - - explicit ReturnResultApi(ProtocolContext& protocol_context); - virtual ~ReturnResultApi() = default; - - void SendResultImpl(RequestId request_id); - void SendErrorImpl(RequestId request_id, std::uint8_t error_type, - std::uint32_t error_code); - - template - void SendResult(RequestId req_id, T&& data) { - auto* packet_stack = protocol_context().packet_stack(); - assert(packet_stack); - packet_stack->Push(*this, SendResultMessage{req_id, std::forward(data)}); - } - - void SendError(RequestId req_id, std::uint8_t error_type, - std::uint32_t error_code) { - send_error_(req_id, error_type, error_code); - } - - AE_METHODS(RegMethod, - RegMethod); - - template - void Pack(SendResultMessage&& result, ApiPacker& packer) const { - packer.Pack(kSendResult, std::move(result)); - } - - private: - Method - send_error_; -}; - -} // namespace ae - -#endif // AETHER_API_PROTOCOL_RETURN_RESULT_API_H_ diff --git a/aether/api_protocol/sub_api.h b/aether/api_protocol/sub_api.h deleted file mode 100644 index 183c528c2..000000000 --- a/aether/api_protocol/sub_api.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_API_PROTOCOL_SUB_API_H_ -#define AETHER_API_PROTOCOL_SUB_API_H_ - -#include - -#include "aether-miscpp/meta/type_list.h" -#include "aether-miscpp/reflect/reflect.h" -#include "aether-miscpp/types/small_function.h" -#include "aether-miscpp/meta/function_signature.h" -#include "aether/api_protocol/api_context.h" -#include "aether/api_protocol/api_pack_parser.h" - -namespace ae { -namespace sub_api_internal { -template -struct ApiContextApiType; - -template -struct ApiContextApiType> { - using type = T; -}; -} // namespace sub_api_internal - -template -class SubApi { - public: - using Method = SmallFunction& api)>; - - template - requires(!std::same_as>) - explicit SubApi(TFunc&& caller) : caller_{std::forward(caller)} {} - explicit SubApi(Method caller) : caller_{std::move(caller)} {} - - std::vector operator()(TApi& api) const { - auto api_context = ApiContext{api}; - caller_(api_context); - return std::move(api_context).Pack(); - } - - private: - Method caller_; -}; - -template -SubApi(TFunc&&) -> SubApi::args>>>::type>; - -template -class SubApiImpl { - public: - struct PassThrough { - std::vector const& operator()( - std::vector const& d) { - return d; - } - }; - - SubApiImpl() = default; - - template - void Parse(TApi& api, TFunc pre_parse = {}) { - auto&& mod_data = pre_parse(data_); - if (mod_data.empty()) { - return; - } - ApiParser parser{api.protocol_context(), mod_data}; - parser.Parse(api); - } - - AE_REFLECT_MEMBERS(data_); - - private: - std::vector data_; -}; - -} // namespace ae - -#endif // AETHER_API_PROTOCOL_SUB_API_H_ diff --git a/aether/channels/ethernet_channel.cpp b/aether/channels/ethernet_channel.cpp index f5dd24602..20eb78bf6 100644 --- a/aether/channels/ethernet_channel.cpp +++ b/aether/channels/ethernet_channel.cpp @@ -19,7 +19,7 @@ #include #include -#include "aether/events/event_subscription.h" +#include "aether/events/events.h" #include "aether/memory.h" #include "aether/aether.h" @@ -60,33 +60,51 @@ auto CreateTransport(AeContext const& context, PtrView const& poller, return EthernetTransportFactory::Create(context.aether(), poller_ptr, e); } +struct TestAlive { + TestAlive() { printf("CREATED\n"); } + ~TestAlive() { + if (!moved) { + printf("DESTROYED\n"); + } + } + TestAlive(TestAlive&& o) noexcept { + printf("MOVED\n"); + o.moved = true; + } + bool moved = false; +}; + ex::sender auto TransportConnect(std::unique_ptr&& stream) { return ex::create), ex::set_error_t(int)>( - [&, s{std::move(stream)}, + [s{std::move(stream)}, link_sub{Subscription{}}](auto& ctx) mutable noexcept { - auto handle_link_state = [&]() noexcept { + // if already linked return stream + switch (s->stream_info().link_state) { + case LinkState::kLinked: { + return ex::set_value(std::move(ctx.receiver), std::move(s)); + } + case LinkState::kLinkError: { + return ex::set_error(std::move(ctx.receiver), 1); + } + default: + break; + } + + // wait till linked + link_sub = s->stream_update_event().Subscribe([&]() noexcept { link_sub.Reset(); switch (s->stream_info().link_state) { case LinkState::kLinked: { - ex::set_value(std::move(ctx.receiver), std::move(s)); - return true; + return ex::set_value(std::move(ctx.receiver), std::move(s)); } case LinkState::kLinkError: { - ex::set_error(std::move(ctx.receiver), 1); - return true; + return ex::set_error(std::move(ctx.receiver), 1); } default: - return false; + break; } - }; - - // if already linked return stream - if (handle_link_state()) { - return; - } - // wait till linked - link_sub = s->stream_update_event().Subscribe(handle_link_state); + }); }); } @@ -111,6 +129,18 @@ ex::sender auto MakeTransportBuilder(AeContext ae_context, ex::then([&](auto const& e) noexcept { return CreateTransport(c, p, e); }) | + // check if transport created + ex::let_value( + [](auto& s) noexcept + -> ex::variant_sender< + decltype(ex::just_error(1)), + decltype(ex::just(std::unique_ptr< + ByteIStream>{}))> { + if (!s) { + return ex::just_error(1); + } + return ex::just(std::move(s)); + }) | ex::let_value([](auto& s) noexcept { return TransportConnect(std::move(s)); }) | diff --git a/aether/channels/wifi_channel.cpp b/aether/channels/wifi_channel.cpp index 5421253c8..e5e38c976 100644 --- a/aether/channels/wifi_channel.cpp +++ b/aether/channels/wifi_channel.cpp @@ -22,7 +22,7 @@ # include # include "aether-objects/ptr/ptr_view.h" -# include "aether/events/event_subscription.h" +# include "aether/events/events.h" # include "aether/executors/executors.h" # include "aether/aether.h" @@ -42,7 +42,7 @@ ex::sender auto WifiConnect(Ptr const& access_point) { auto access_point = ap.Lock(); assert(access_point && "Wifi access point is not loaded"); connect_sub_ = access_point->Connect().connection_event().Subscribe( - [&](bool is_connected) mutable noexcept { + [&](bool is_connected) noexcept { if (is_connected) { ex::set_value(std::move(ctx.receiver)); } else { diff --git a/aether/client_connectivity_policy.cpp b/aether/client_connectivity_policy.cpp index 7b0ce8757..285f34c8f 100644 --- a/aether/client_connectivity_policy.cpp +++ b/aether/client_connectivity_policy.cpp @@ -18,6 +18,8 @@ #include +#include "aether/ae_context.h" + namespace ae { namespace { @@ -84,9 +86,15 @@ ClientConnectivityPolicy::ClientConnectivityPolicy() ClientConnectivityPolicy::ClientConnectivityPolicy(ObjProp prop) : Base{prop}, rx_targets_{RequestPolicy::All{}}, - rx_timings_{MakeDefaultRxTimings()} {} + rx_timings_{MakeDefaultRxTimings()}, + suspend_allowed_event_{std::in_place, AeContext{*this}} {} #endif +void ClientConnectivityPolicy::Loaded() { + suspend_allowed_event_.emplace(AeContext{*this}); + ResetRuntimeState(); +} + auto ClientConnectivityPolicy::ConfigureRxTimings( RequestPolicy::Variant targets) -> RxTimingConfig { return RxTimingConfig{*this, std::move(targets)}; @@ -150,7 +158,7 @@ void ClientConnectivityPolicy::DecrementSuspendBlock() { --suspend_block_count_; can_suspend_ = suspend_block_count_ == 0; if (can_suspend_) { - suspend_allowed_event_.Emit(); + suspend_allowed_event_->Emit(); } } diff --git a/aether/client_connectivity_policy.h b/aether/client_connectivity_policy.h index 53c063b7e..bb4974434 100644 --- a/aether/client_connectivity_policy.h +++ b/aether/client_connectivity_policy.h @@ -21,10 +21,12 @@ #include #include #include +#include #include "aether-objects/obj/obj.h" #include "aether/clock.h" +#include "aether/common.h" #include "aether/config.h" #include "aether/events/events.h" @@ -111,11 +113,6 @@ class ClientConnectivityPolicy : public Obj { AE_CLASS_NO_COPY_MOVE(ClientConnectivityPolicy); AE_OBJECT_REFLECT(AE_MMBRS(rx_targets_, rx_timings_)) - template - void Load(CurrentVersion, Dnv& dnv) { - dnv(base_, rx_targets_, rx_timings_); - ResetRuntimeState(); - } RxTimingConfig ConfigureRxTimings( RequestPolicy::Variant targets = RequestPolicy::All{}); @@ -127,8 +124,8 @@ class ClientConnectivityPolicy : public Obj { const noexcept { return rx_timings_; } - Event::Subscriber suspend_allowed_event() noexcept { - return EventSubscriber{suspend_allowed_event_}; + Event const& suspend_allowed_event() noexcept { + return *suspend_allowed_event_; } ConnectivityStatus GetStatus() const noexcept; @@ -138,6 +135,7 @@ class ClientConnectivityPolicy : public Obj { void ReportNextServiceTime(std::size_t priority, TimePoint next_service_time); private: + void Loaded(); void ResetRuntimeState(); void IncrementSuspendBlock(); void DecrementSuspendBlock(); @@ -148,7 +146,7 @@ class ClientConnectivityPolicy : public Obj { bool can_suspend_{true}; std::uint8_t suspend_block_count_{}; - Event suspend_allowed_event_; + std::optional> suspend_allowed_event_; }; } // namespace ae diff --git a/aether/client_messages/p2p_message_stream.cpp b/aether/client_messages/p2p_message_stream.cpp index 12dcef53d..7ae0132e1 100644 --- a/aether/client_messages/p2p_message_stream.cpp +++ b/aether/client_messages/p2p_message_stream.cpp @@ -33,10 +33,13 @@ namespace ae { namespace p2p_stream_internal { class MessageSendStream final : public IStream { public: - explicit MessageSendStream(CloudServerConnections& cloud_connection, + explicit MessageSendStream(EventContext auto const& context, + CloudServerConnections& cloud_connection, RequestPolicy::Variant request_policy) : cloud_connection_{&cloud_connection}, request_policy_{request_policy}, + out_data_event_{context}, + stream_update_event_{context}, servers_update_sub_{cloud_connection_->servers_update_event().Subscribe( MethodPtr<&MessageSendStream::UpdateServers>{this})} { UpdateServers(); @@ -44,15 +47,15 @@ class MessageSendStream final : public IStream { WriteAction& Write(AeMessage&& message) override { return cloud_connection_->CallApi( - ApiCall{[&message](ApiContext& auth_api, auto*) { - auth_api->send_message(std::move(message)); + ApiCall{[&](ApiContext& auth_api, auto*) { + auth_api->SendMessage(message); }}, request_policy_); } StreamInfo stream_info() const override { return stream_info_; } - OutDataEvent::Subscriber out_data_event() override { return out_data_event_; } - StreamUpdateEvent::Subscriber stream_update_event() override { + OutDataEvent const& out_data_event() override { return out_data_event_; } + StreamUpdateEvent const& stream_update_event() override { return stream_update_event_; } @@ -154,7 +157,9 @@ P2pStream::P2pStream(AeContext const& ae_context, Ptr const& client, client_{client}, destination_{std::move(destination)}, handle_{std::move(handle)}, - buffer_write_{ae_context_, MethodPtr<&P2pStream::OnWrite>{this}} { + buffer_write_{ae_context_, MethodPtr<&P2pStream::OnWrite>{this}}, + out_data_event_{ae_context_}, + stream_update_event_{ae_context_} { AE_TELE_DEBUG(kP2pMessageStreamNew, "P2pStream created for {}", destination_); assert(!destination_.empty()); @@ -173,7 +178,7 @@ WriteAction& P2pStream::Write(DataBuffer&& data) { return buffer_write_.Write(std::move(message_data)); } -P2pStream::StreamUpdateEvent::Subscriber P2pStream::stream_update_event() { +P2pStream::StreamUpdateEvent const& P2pStream::stream_update_event() { return stream_update_event_; } @@ -184,7 +189,7 @@ StreamInfo P2pStream::stream_info() const { return {}; } -P2pStream::OutDataEvent::Subscriber P2pStream::out_data_event() { +P2pStream::OutDataEvent const& P2pStream::out_data_event() { return out_data_event_; } @@ -225,9 +230,9 @@ void P2pStream::ConnectSend() { .GetServerConnectionFactory()); message_send_stream_ = std::make_unique( - *dest_cloud_conn_, RequestPolicy::MainServer{}); + ae_context_, *dest_cloud_conn_, RequestPolicy::MainServer{}); message_send_stream_->stream_update_event().Subscribe( - stream_update_event_); + MethodPtr<&StreamUpdateEvent::Emit>{&stream_update_event_}); AE_TELED_DEBUG("Send connected"); buffer_write_.buffer_off(); stream_update_event_.Emit(); diff --git a/aether/client_messages/p2p_message_stream.h b/aether/client_messages/p2p_message_stream.h index 600bb5a25..519ae1d05 100644 --- a/aether/client_messages/p2p_message_stream.h +++ b/aether/client_messages/p2p_message_stream.h @@ -52,9 +52,9 @@ class P2pStream final : public ByteIStream { AE_CLASS_NO_COPY_MOVE(P2pStream); WriteAction& Write(DataBuffer&& data) override; - StreamUpdateEvent::Subscriber stream_update_event() override; + StreamUpdateEvent const& stream_update_event() override; StreamInfo stream_info() const override; - OutDataEvent::Subscriber out_data_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; void WriteOut(DataBuffer const& data); diff --git a/aether/client_messages/p2p_message_stream_manager.cpp b/aether/client_messages/p2p_message_stream_manager.cpp index caf7f15c3..026d1a5c4 100644 --- a/aether/client_messages/p2p_message_stream_manager.cpp +++ b/aether/client_messages/p2p_message_stream_manager.cpp @@ -17,14 +17,14 @@ #include "aether/client_messages/p2p_message_stream_manager.h" #include -#include #include #include "aether/client.h" -#include "aether/client_messages/client_messages_tele.h" #include "aether/cloud_connections/cloud_server_connection.h" #include "aether/work_cloud_api/client_api/client_api_safe.h" +#include "aether/client_messages/client_messages_tele.h" + namespace ae { P2pMessageStreamManager::P2pMessageStreamManager(AeContext const& ae_context, @@ -32,10 +32,11 @@ P2pMessageStreamManager::P2pMessageStreamManager(AeContext const& ae_context, : ae_context_{ae_context}, client_{client}, cloud_connection_{&client->cloud_connection()}, + new_port_event_{ae_context_}, on_message_received_sub_{CloudEventListener{ ApiEventSubscriber{[this](ClientApiSafe& client_api, - [[maybe_unused]] CloudServerConnection* server_connection) - -> EventHandlerDeleter { + [[maybe_unused]] CloudServerConnection* + server_connection) -> RegEventHandler { return client_api.send_message_event().Subscribe( MethodPtr<&P2pMessageStreamManager::NewMessageReceived>{this}); }}, @@ -46,9 +47,9 @@ P2pPortHandle P2pMessageStreamManager::CreatePort(Uid const& destination) { return P2pPortHandle{std::move(port)}; } -P2pMessageStreamManager::NewPortEvent::Subscriber +P2pMessageStreamManager::NewPortEvent const& P2pMessageStreamManager::new_port_event() { - return EventSubscriber{new_port_event_}; + return new_port_event_; } std::pair, bool> @@ -64,7 +65,8 @@ P2pMessageStreamManager::GetOrCreatePort(Uid const& destination) { // -Warray-bounds warning while destroying P2pReceivePort from // _Sp_counted_ptr_inplace because P2pReceivePort owns Event/RcPtr storage. auto port = std::shared_ptr{ - std::make_unique(destination)}; + std::make_unique(ae_context_, + destination)}; ports_.emplace(destination, port); return {port, true}; } @@ -75,7 +77,8 @@ void P2pMessageStreamManager::NewMessageReceived(AeMessage const& message) { assert(port != nullptr); if (is_new) { - new_port_event_.Emit(P2pPortHandle{port}); + auto handle = P2pPortHandle{port}; + new_port_event_.Emit(handle); } port->Deliver(message.data); diff --git a/aether/client_messages/p2p_message_stream_manager.h b/aether/client_messages/p2p_message_stream_manager.h index 9dc1a1a1f..dabcfc6b3 100644 --- a/aether/client_messages/p2p_message_stream_manager.h +++ b/aether/client_messages/p2p_message_stream_manager.h @@ -38,13 +38,17 @@ class P2pReceivePort; class P2pMessageStreamManager { public: - using NewPortEvent = Event; + // A port handle transfers its receive-port ownership to exactly one + // subscriber. Ownership transfer is synchronous: the subscriber must move + // the handle during dispatch. This usage contract is not enforced at + // runtime. + using NewPortEvent = Event; P2pMessageStreamManager(AeContext const& ae_context, Ptr const& client); P2pPortHandle CreatePort(Uid const& destination); - NewPortEvent::Subscriber new_port_event(); + NewPortEvent const& new_port_event(); private: void NewMessageReceived(AeMessage const& message); diff --git a/aether/client_messages/p2p_port_handle.cpp b/aether/client_messages/p2p_port_handle.cpp index c3be51df5..d393738bc 100644 --- a/aether/client_messages/p2p_port_handle.cpp +++ b/aether/client_messages/p2p_port_handle.cpp @@ -4,14 +4,10 @@ namespace ae { namespace p2p_stream_internal { - -P2pReceivePort::P2pReceivePort(Uid destination) - : destination_{std::move(destination)} {} - Uid const& P2pReceivePort::destination() const { return destination_; } -P2pReceivePort::OutDataEvent::Subscriber P2pReceivePort::out_data_event() { - return EventSubscriber{out_data_event_}; +P2pReceivePort::OutDataEvent const& P2pReceivePort::out_data_event() { + return out_data_event_; } void P2pReceivePort::Deliver(DataBuffer const& data) { diff --git a/aether/client_messages/p2p_port_handle.h b/aether/client_messages/p2p_port_handle.h index 2e88cf810..c5c6420e3 100644 --- a/aether/client_messages/p2p_port_handle.h +++ b/aether/client_messages/p2p_port_handle.h @@ -5,9 +5,9 @@ #include #include "aether/common.h" -#include "aether/types/uid.h" -#include "aether/types/data_buffer.h" #include "aether/events/events.h" +#include "aether/types/data_buffer.h" +#include "aether/types/uid.h" namespace ae { @@ -17,13 +17,14 @@ class P2pReceivePort { public: using OutDataEvent = Event; - explicit P2pReceivePort(Uid destination); + P2pReceivePort(EventContext auto const& context, Uid destination) + : destination_{destination}, out_data_event_{context} {} ~P2pReceivePort() = default; AE_CLASS_NO_COPY_MOVE(P2pReceivePort) Uid const& destination() const; - OutDataEvent::Subscriber out_data_event(); + OutDataEvent const& out_data_event(); void Deliver(DataBuffer const& data); private: @@ -52,7 +53,7 @@ class P2pPortHandle { return port_->destination(); } - OutDataEvent::Subscriber out_data_event() { + OutDataEvent const& out_data_event() { assert(port_ != nullptr && "P2pPortHandle is empty — was it moved from?"); return port_->out_data_event(); } diff --git a/aether/client_messages/p2p_safe_message_stream.cpp b/aether/client_messages/p2p_safe_message_stream.cpp index ad74164af..69870ba09 100644 --- a/aether/client_messages/p2p_safe_message_stream.cpp +++ b/aether/client_messages/p2p_safe_message_stream.cpp @@ -18,16 +18,17 @@ #include -#include "aether/stream_api/tied_gates.h" #include "aether/safe_stream/safe_stream.h" +#include "aether/stream_api/tied_gates.h" namespace ae { P2pSafeStream::P2pSafeStream(AeContext const& ae_context, SafeStreamConfig const& config, std::shared_ptr p2p_stream) - : sized_packet_gate_{}, + : sized_packet_gate_{ae_context}, safe_stream_{std::make_unique(ae_context, config)}, p2p_stream_{std::move(p2p_stream)}, + out_data_event_{ae_context}, out_data_sub_{TiedEventOutData( [this](auto const& data) { out_data_event_.Emit(data); }, sized_packet_gate_, *safe_stream_)} { @@ -52,13 +53,12 @@ StreamInfo P2pSafeStream::stream_info() const { return info; } -P2pSafeStream::StreamUpdateEvent::Subscriber -P2pSafeStream::stream_update_event() { +P2pSafeStream::StreamUpdateEvent const& P2pSafeStream::stream_update_event() { return safe_stream_->stream_update_event(); } -P2pSafeStream::OutDataEvent::Subscriber P2pSafeStream::out_data_event() { - return EventSubscriber{out_data_event_}; +P2pSafeStream::OutDataEvent const& P2pSafeStream::out_data_event() { + return out_data_event_; } void P2pSafeStream::Restream() { safe_stream_->Restream(); } diff --git a/aether/client_messages/p2p_safe_message_stream.h b/aether/client_messages/p2p_safe_message_stream.h index e32a16ce6..cf1939ec9 100644 --- a/aether/client_messages/p2p_safe_message_stream.h +++ b/aether/client_messages/p2p_safe_message_stream.h @@ -44,8 +44,8 @@ class P2pSafeStream final : public ByteIStream { WriteAction& Write(DataBuffer&& data) override; StreamInfo stream_info() const override; - StreamUpdateEvent::Subscriber stream_update_event() override; - OutDataEvent::Subscriber out_data_event() override; + StreamUpdateEvent const& stream_update_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; private: diff --git a/aether/cloud.cpp b/aether/cloud.cpp index b73997ec2..1247871a9 100644 --- a/aether/cloud.cpp +++ b/aether/cloud.cpp @@ -22,9 +22,12 @@ #include #include +#include "aether/ae_context.h" + namespace ae { -Cloud::Cloud(ObjProp prop) : Obj{prop} {} +Cloud::Cloud(ObjProp prop) + : Obj{prop}, cloud_updated_{std::in_place, AeContext{*this}} {} void Cloud::AddServer(Server::ptr server) { [[maybe_unused]] auto const server_loaded = @@ -55,7 +58,7 @@ void Cloud::AddServer(Server::ptr server) { servers_.emplace(server_id, CloudServer{priority, std::move(server)}); }); assert(server_loaded && "cloud server must load"); - cloud_updated_.Emit(); + cloud_updated_->Emit(); } void Cloud::SetServers(std::vector const& servers) { @@ -79,7 +82,7 @@ void Cloud::SetServers(std::vector const& servers) { .second; assert(inserted && "cloud server must not duplicate"); } - cloud_updated_.Emit(); + cloud_updated_->Emit(); } std::map& Cloud::servers() { return servers_; } @@ -88,8 +91,14 @@ std::map const& Cloud::servers() const { return servers_; } -EventSubscriber Cloud::cloud_updated() { - return EventSubscriber{cloud_updated_}; +Event const& Cloud::cloud_updated() { + assert(!!cloud_updated_); + return *cloud_updated_; +} + +void Cloud::Loaded() { + // init cloud_updated_ event after loaded + cloud_updated_.emplace(AeContext{*this}); } } // namespace ae diff --git a/aether/cloud.h b/aether/cloud.h index 06fe059ba..680f8cfab 100644 --- a/aether/cloud.h +++ b/aether/cloud.h @@ -19,11 +19,12 @@ #include #include +#include #include -#include "aether/events/events.h" - #include "aether-objects/obj/obj.h" + +#include "aether/events/events.h" #include "aether/server.h" namespace ae { @@ -52,11 +53,12 @@ class Cloud : public Obj { std::map& servers(); std::map const& servers() const; - EventSubscriber cloud_updated(); + Event const& cloud_updated(); private: + void Loaded(); std::map servers_; - Event cloud_updated_; + std::optional> cloud_updated_; }; } // namespace ae diff --git a/aether/cloud_connections/cloud_callbacks.h b/aether/cloud_connections/cloud_callbacks.h index 8e830869b..f5dea7095 100644 --- a/aether/cloud_connections/cloud_callbacks.h +++ b/aether/cloud_connections/cloud_callbacks.h @@ -19,8 +19,8 @@ #include "aether-miscpp/types/small_function.h" -#include "aether/events/event_deleter.h" #include "aether/api_protocol/api_protocol.h" +#include "aether/events/events.h" #include "aether/work_cloud_api/client_api/client_api_safe.h" #include "aether/work_cloud_api/work_server_api/authorized_api.h" @@ -30,20 +30,21 @@ class CloudServerConnections; class CloudRequest; // subscribe to client's api events -struct ApiEventSubscriber : SmallFunction {}; +struct ApiEventSubscriber + : SmallFunction { +}; // call authorized api -struct ApiCall - : SmallFunction& auth_api, - CloudServerConnection* server_connection)> {}; +struct ApiCall : SmallFunction& auth_api, + CloudServerConnection* server_connection)> { +}; // listen to client's api response struct ResponseSubscriber - : SmallFunction {}; + : SmallFunction {}; // ApiCall combined with its ResponseSubscriber struct ApiCallWithListener { @@ -59,4 +60,4 @@ struct ApiRequestHandler } // namespace ae -#endif // AETHER_CLOUD_CONNECTIONS_CLOUD_CALLBACKS_H_ \ No newline at end of file +#endif // AETHER_CLOUD_CONNECTIONS_CLOUD_CALLBACKS_H_ diff --git a/aether/cloud_connections/cloud_request.cpp b/aether/cloud_connections/cloud_request.cpp index 0a972cc7e..aa5cdc3b8 100644 --- a/aether/cloud_connections/cloud_request.cpp +++ b/aether/cloud_connections/cloud_request.cpp @@ -32,14 +32,16 @@ CloudRequest::CloudRequest(AeContext const& ae_context, CloudServerConnections& cloud_server_connections, RequestPolicy::Variant policy, std::size_t max_retries, Duration request_timeout) - : ae_context_{ae_context}, + : Action{ae_context}, + ae_context_{ae_context}, request_{std::move(api_call)}, cloud_scs_{&cloud_server_connections}, policy_{policy}, max_retries_{max_retries}, request_timeout_{request_timeout}, server_changed_sub_{cloud_scs_->servers_update_event().Subscribe( - MethodPtr<&CloudRequest::ServersUpdated>{this})} { + MethodPtr<&CloudRequest::ServersUpdated>{this})}, + result_event_{ae_context_} { PrefillServerRequests(); EnqueueMakeRequest(); } @@ -49,14 +51,16 @@ CloudRequest::CloudRequest(AeContext const& ae_context, CloudServerConnections& cloud_server_connections, RequestPolicy::Variant policy, std::size_t max_retries, Duration request_timeout) - : ae_context_{ae_context}, + : Action{ae_context}, + ae_context_{ae_context}, request_{std::move(api_request)}, cloud_scs_{&cloud_server_connections}, policy_{policy}, max_retries_{max_retries}, request_timeout_{request_timeout}, server_changed_sub_{cloud_scs_->servers_update_event().Subscribe( - MethodPtr<&CloudRequest::ServersUpdated>{this})} { + MethodPtr<&CloudRequest::ServersUpdated>{this})}, + result_event_{ae_context_} { PrefillServerRequests(); EnqueueMakeRequest(); } @@ -71,8 +75,8 @@ void CloudRequest::Failed() { result_event_.Emit(false); } -CloudRequest::ResultEvent::Subscriber CloudRequest::result_event() { - return EventSubscriber{result_event_}; +CloudRequest::ResultEvent const& CloudRequest::result_event() { + return result_event_; } void CloudRequest::PrefillServerRequests() { diff --git a/aether/cloud_connections/cloud_request.h b/aether/cloud_connections/cloud_request.h index ceb6d6f59..0517d9749 100644 --- a/aether/cloud_connections/cloud_request.h +++ b/aether/cloud_connections/cloud_request.h @@ -18,12 +18,12 @@ #include -#include "aether/common.h" -#include "aether/ae_context.h" #include "aether/actions/action.h" -#include "aether/cloud_connections/request_policy.h" +#include "aether/ae_context.h" #include "aether/cloud_connections/cloud_callbacks.h" #include "aether/cloud_connections/cloud_server_connections.h" +#include "aether/cloud_connections/request_policy.h" +#include "aether/common.h" namespace ae { /** @@ -67,7 +67,7 @@ class CloudRequest final : public Action { void Succeeded(); void Failed(); - ResultEvent::Subscriber result_event(); + ResultEvent const& result_event(); private: void MakeRequest(); diff --git a/aether/cloud_connections/cloud_server_connections.cpp b/aether/cloud_connections/cloud_server_connections.cpp index 993eb8988..e6ff53d4a 100644 --- a/aether/cloud_connections/cloud_server_connections.cpp +++ b/aether/cloud_connections/cloud_server_connections.cpp @@ -46,14 +46,15 @@ static constexpr auto kCloudServerQuarantineTime = std::chrono::milliseconds{AE_CLOUD_SERVER_QUARANTINE_TIME_MS}; cloud_server_connections_internal::EmptyConnectionsWA::EmptyConnectionsWA( - AeContext const& ae_context) { + AeContext const& ae_context) + : WriteAction{ae_context} { ae_context.scheduler().Task( [&]() { WriteAction::SetStatus(WriteAction::Status::kFail); }); } cloud_server_connections_internal::ReplicaWA::ReplicaWA( - std::vector&& swas) noexcept - : swas_{std::move(swas)} { + AeContext const& ae_context, std::vector&& swas) noexcept + : WriteAction{ae_context}, swas_{std::move(swas)} { assert(!swas_.empty()); for (auto* action : swas_) { subs_ += @@ -88,22 +89,25 @@ CloudServerConnections::CloudServerConnections( : ae_context_{ae_context}, cloud_{cloud}, connection_factory_{std::move(connection_factory)}, - max_connections_{max_connections} { + max_connections_{max_connections}, + servers_update_event_{ae_context_}, + server_quarantined_event_{ae_context_}, + server_quarantine_release_event_{ae_context_} { InitServerConnections(); if (max_connections_ != 0) { ReconcileServers(); } } -CloudServerConnections::ServersUpdate::Subscriber +CloudServerConnections::ServersUpdate const& CloudServerConnections::servers_update_event() { return servers_update_event_; } -CloudServerConnections::ServerQuarantineEvent::Subscriber +CloudServerConnections::ServerQuarantineEvent const& CloudServerConnections::server_quarantined_event() { return server_quarantined_event_; } -CloudServerConnections::ServerQuarantineEvent::Subscriber +CloudServerConnections::ServerQuarantineEvent const& CloudServerConnections::server_quarantine_release_event() { return server_quarantine_release_event_; } @@ -274,9 +278,10 @@ void CloudServerConnections::ReconcileServers() { if (selected_servers_.size() >= max_connections_) { return; } - // Vacancy-fill model: keep the current selected list stable and only append - // usable servers to fill available slots. The selected prefix is skipped; - // failed candidates move to the quarantined suffix, so retry the same index. + // Vacancy-fill model: keep the current selected list stable and only + // append usable servers to fill available slots. The selected prefix is + // skipped; failed candidates move to the quarantined suffix, so retry the + // same index. AE_TELED_DEBUG("Reconcile servers vacancy={} selected_count={}", max_connections_ - selected_servers_.size(), selected_servers_.size()); @@ -361,7 +366,7 @@ WriteAction& CloudServerConnections::EmptyWriteAction() { } WriteAction& CloudServerConnections::ReplicaWriteAction( std::vector&& swas) { - return replica_was_.emplace_back(std::move(swas)); + return replica_was_.emplace_back(ae_context_, std::move(swas)); } } // namespace ae diff --git a/aether/cloud_connections/cloud_server_connections.h b/aether/cloud_connections/cloud_server_connections.h index c0bf1acf7..7e0974513 100644 --- a/aether/cloud_connections/cloud_server_connections.h +++ b/aether/cloud_connections/cloud_server_connections.h @@ -25,7 +25,6 @@ #include "aether-objects/ptr/ptr_view.h" #include "aether/ae_context.h" #include "aether/events/events.h" -#include "aether/events/multi_subscription.h" #include "aether/write_action/write_action.h" #include "aether/cloud.h" @@ -44,7 +43,8 @@ class EmptyConnectionsWA final : public WriteAction { class ReplicaWA final : public WriteAction { public: - explicit ReplicaWA(std::vector&& swas) noexcept; + explicit ReplicaWA(AeContext const& ae_context, + std::vector&& swas) noexcept; void Stop() noexcept override; private: @@ -69,9 +69,9 @@ class CloudServerConnections { /** * \brief The event then top list of the servers were updated. */ - ServersUpdate::Subscriber servers_update_event(); - ServerQuarantineEvent::Subscriber server_quarantined_event(); - ServerQuarantineEvent::Subscriber server_quarantine_release_event(); + ServersUpdate const& servers_update_event(); + ServerQuarantineEvent const& server_quarantined_event(); + ServerQuarantineEvent const& server_quarantine_release_event(); /** * \brief List of currently selected servers in priority order */ diff --git a/aether/cloud_connections/ping_cloud_servers.h b/aether/cloud_connections/ping_cloud_servers.h index dd55ef659..3f28e0f94 100644 --- a/aether/cloud_connections/ping_cloud_servers.h +++ b/aether/cloud_connections/ping_cloud_servers.h @@ -25,7 +25,6 @@ # include # include "aether/ae_context.h" -# include "aether/events/event_subscription.h" # include "aether/executors/executors.h" # include "aether/tasks/manual_task_scheduler.h" # include "aether/types/server_id.h" diff --git a/aether/common.h b/aether/common.h index 4a6b0e9ed..81101cfaa 100644 --- a/aether/common.h +++ b/aether/common.h @@ -19,9 +19,11 @@ #include -#include "aether/config.h" +#if ESP_PLATFORM +# include "esp_attr.h" +#endif -namespace ae { +#include "aether/config.h" #define _QUOTE(x) #x #define STR(x) _QUOTE(x) @@ -38,18 +40,6 @@ namespace ae { #define AE_ESC_(...) AE_VAN_##__VA_ARGS__ #define AE_VAN_AE_ISH -enum class CompressionMethod : std::uint8_t { - kNone = AE_NONE, -#if AE_COMPRESSION_ZLIB == 1 - kZlib = 1, -#endif // AE_COMPRESSION_ZLIB == 1 -#if AE_COMPRESSION_LZMA == 1 - kLzma = 2, -#endif // AE_COMPRESSION_LZMA == 1 -}; - -} // namespace ae - // default copy move constructors #define AE_CLASS_MOVE_(class_name, impl) \ class_name(class_name&& other) noexcept = impl; \ @@ -76,4 +66,65 @@ enum class CompressionMethod : std::uint8_t { #define AE_CLASS_NO_COPY_MOVE(class_name) \ AE_CLASS_COPY_(class_name, delete) \ AE_CLASS_MOVE_(class_name, delete) + +#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \ + defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || \ + defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || \ + defined(__MIBSEB__) || \ + defined(Q_BYTE_ORDER) && Q_BYTE_ORDER == Q_BIG_ENDIAN +# define AE_ENDIANNESS AE_BIG_ENDIAN +#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \ + defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \ + defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(__i386__) || \ + defined(__amd64) || defined(__amd64__) || defined(_MIPSEL) || \ + defined(__MIPSEL) || defined(__MIPSEL__) || defined(ESP_PLATFORM) || \ + defined(Q_BYTE_ORDER) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN +# define AE_ENDIANNESS AE_LITTLE_ENDIAN +#else +# define AE_ENDIANNESS AE_LITTLE_ENDIAN +// #error "Undefined endianness for the architecture" +#endif + +namespace ae { +template +T SwapToInet(const T& t) { +#if AE_ENDIANNESS == AE_LITTLE_ENDIAN + union { + T t; + std::uint8_t t8[sizeof(T)]; + } src, dest; + src.t = t; + for (size_t e = 0; e < sizeof(T); e++) { + dest.t8[e] = src.t8[sizeof(T) - e - 1]; + } + return dest.t; +#else + return t; +#endif +} + +template +T SwapToLittleEndian(const T& t) { +#if AE_ENDIANNESS == AE_BIG_ENDIAN + union { + T t; + std::uint8_t t8[sizeof(T)]; + } src, dest; + src.t = t; + for (size_t e = 0; e < sizeof(T); e++) { + dest.t8[e] = src.t8[sizeof(T) - e - 1]; + } + return dest.t; +#else + return t; +#endif +} +} // namespace ae + +#if ESP_PLATFORM +# define RTC_STORAGE_ATTR RTC_DATA_ATTR +#else +# define RTC_STORAGE_ATTR +#endif + #endif // AETHER_COMMON_H_ diff --git a/aether/config.h b/aether/config.h index eacd198e9..70b757fd0 100644 --- a/aether/config.h +++ b/aether/config.h @@ -46,25 +46,25 @@ # define AE_TASK_ALIGN alignof(std::max_align_t) #endif -#ifndef AE_API_PROTOCOL_MAX_PENDING_RESPONSES -# define AE_API_PROTOCOL_MAX_PENDING_RESPONSES 10 +// Configuration for event system +#ifndef AE_EVENTS_MAX_COUNT +# define AE_EVENTS_MAX_COUNT 256 #endif - -#ifndef AE_API_PROTOCOL_MAX_PACKET_STACK_DEPTH -# define AE_API_PROTOCOL_MAX_PACKET_STACK_DEPTH 4 +#ifndef AE_EVENT_HANDLER_MAX_SIZE +# define AE_EVENT_HANDLER_MAX_SIZE 48 #endif - -#ifndef AE_API_PROTOCOL_MAX_PARSER_PACKER_DEPTH -# define AE_API_PROTOCOL_MAX_PARSER_PACKER_DEPTH 4 +#ifndef AE_EVENT_HANDLER_ALIGN +# define AE_EVENT_HANDLER_ALIGN (alignof(std::max_align_t)) #endif -// Maximum size in bytes for a single pending response pool element. -#ifndef AE_API_PROTOCOL_PENDING_RESPONSE_MAX_SIZE -# define AE_API_PROTOCOL_PENDING_RESPONSE_MAX_SIZE 4 * sizeof(void*) +// Maximum amount of pending responses for each server +#ifndef AE_API_PROTOCOL_MAX_PENDING_RESPONSES +# define AE_API_PROTOCOL_MAX_PENDING_RESPONSES 10 #endif -#ifndef AE_API_PROTOCOL_PENDING_RESPONSE_ALIGN -# define AE_API_PROTOCOL_PENDING_RESPONSE_ALIGN alignof(std::max_align_t) +// Maximum amount of method calls for one api layer at a time +#ifndef AE_API_PROTOCOL_MAX_API_CALL_PER_CONTEXT +# define AE_API_PROTOCOL_MAX_API_CALL_PER_CONTEXT 3 #endif #ifndef AE_SUPPORT_IPV4 @@ -528,13 +528,6 @@ # define AE_STATISTICS_MAX_SIZE (10 * 1024) // 10 KB #endif -#ifndef AE_EVENT_HANDLER_MAX_SIZE -# define AE_EVENT_HANDLER_MAX_SIZE 48 -#endif -#ifndef AE_EVENT_HANDLER_ALIGN -# define AE_EVENT_HANDLER_ALIGN (alignof(std::max_align_t)) -#endif - #ifndef NDEBUG # define DEBUG 1 #endif diff --git a/aether/connection_manager/client_cloud_manager.cpp b/aether/connection_manager/client_cloud_manager.cpp index f9df3dd54..75c15980e 100644 --- a/aether/connection_manager/client_cloud_manager.cpp +++ b/aether/connection_manager/client_cloud_manager.cpp @@ -20,6 +20,7 @@ #include #include +#include "aether/ae_context.h" #include "aether/aether.h" #include "aether/client.h" #include "aether/work_cloud.h" @@ -32,16 +33,18 @@ constexpr int kGetServersRequestError = 1; GetCloudFromCache::GetCloudFromCache(AeContext const& ae_context, Cloud::ptr cloud) - : cloud_{std::move(cloud)} { + : GetCloudAction{ae_context}, + cloud_{std::move(cloud)}, + result_event_{ae_context} { ae_context.scheduler().Task([this]() { result_event_.Emit(Ok{std::move(cloud_)}); Finish(); }); } -GetCloudFromCache::ResultEvent::Subscriber +GetCloudFromCache::ResultEvent const& GetCloudFromCache::result_event() noexcept { - return EventSubscriber{result_event_}; + return result_event_; } void BuildNewServers(Aether::ptr const& aether, @@ -149,7 +152,10 @@ auto SortNewServers(std::vector const& sids) { ClientCloudManager::ClientCloudManager(ObjProp prop, ObjPtr aether, ObjPtr client) - : Obj{prop}, aether_{std::move(aether)}, client_{std::move(client)} { + : Obj{prop}, + aether_{std::move(aether)}, + client_{std::move(client)}, + cloud_update_event_{std::in_place, AeContext{*this}} { // save cloud cache for current client [[maybe_unused]] auto const cache_initialized = client_.WithLoaded([&](auto const& obj) { @@ -164,9 +170,10 @@ ClientCloudManager::ClientCloudManager(ObjProp prop, ObjPtr aether, assert(cache_initialized && "Client did not load"); } -ClientCloudManager::CloudUpdateEvent::Subscriber +ClientCloudManager::CloudUpdateEvent const& ClientCloudManager::cloud_update_event() { - return EventSubscriber{cloud_update_event_}; + assert(!!cloud_update_event_); + return *cloud_update_event_; } GetCloudAction& ClientCloudManager::GetCloud(Uid client_uid) { @@ -234,6 +241,10 @@ auto ClientCloudManager::MakeServersSender(std::vector const& sids) { SortNewServers(sids); } +void ClientCloudManager::Loaded() { + cloud_update_event_.emplace(AeContext{*this}); +} + void ClientCloudManager::CloudConfigs(std::vector const& configs) { for (auto const& conf : configs) { AE_TELED_DEBUG("Got cloud config update subject:{}, ver:{}, cloud:[{}]", @@ -288,12 +299,12 @@ void ClientCloudManager::FinalizeCloudConfig(CloudConfig const& conf) { if (res->IsOk()) { auto cloud = RegisterCloud(conf.subject_uid, std::move(*res).value()); - cloud_update_event_.Emit(conf.subject_uid, - Ok{cloud}); + cloud_update_event_->Emit(conf.subject_uid, + Ok{cloud}); } else { AE_TELED_ERROR("Cloud resolve failed!, Error {}", res->error()); // TODO: how to handle such error? - cloud_update_event_.Emit(conf.subject_uid, Error{res->error()}); + cloud_update_event_->Emit(conf.subject_uid, Error{res->error()}); } })); } diff --git a/aether/connection_manager/client_cloud_manager.h b/aether/connection_manager/client_cloud_manager.h index 2cb412888..806217357 100644 --- a/aether/connection_manager/client_cloud_manager.h +++ b/aether/connection_manager/client_cloud_manager.h @@ -60,7 +60,7 @@ class GetCloudFromCache final : public GetCloudAction { public: GetCloudFromCache(AeContext const& ae_context, Cloud::ptr cloud); - ResultEvent::Subscriber result_event() noexcept override; + ResultEvent const& result_event() noexcept override; private: Cloud::ptr cloud_; @@ -95,7 +95,7 @@ class ClientCloudManager : public Obj { AE_CLASS_NO_COPY_MOVE(ClientCloudManager) - CloudUpdateEvent::Subscriber cloud_update_event(); + CloudUpdateEvent const& cloud_update_event(); /** * \brief Make request for cloud for client_uid. @@ -113,6 +113,7 @@ class ClientCloudManager : public Obj { void StartListenForCloudUpdate(); private: + void Loaded(); void CloudConfigs(std::vector const& configs); void FinalizeCloudConfig(CloudConfig const& conf); auto MakeServersSender(std::vector const& sids); @@ -124,7 +125,7 @@ class ClientCloudManager : public Obj { ObjPtr client_; std::map cloud_cache_; - CloudUpdateEvent cloud_update_event_; + std::optional cloud_update_event_; CloudEventListener cloud_update_sub_; std::optional cloud_actions_; std::optional get_servers_pool_; diff --git a/aether/connection_manager/get_cloud_action.h b/aether/connection_manager/get_cloud_action.h index 96391f86d..85b7eedc1 100644 --- a/aether/connection_manager/get_cloud_action.h +++ b/aether/connection_manager/get_cloud_action.h @@ -19,8 +19,8 @@ #include "aether-miscpp/types/result.h" -#include "aether/events/events.h" #include "aether/actions/action.h" +#include "aether/events/events.h" #include "aether/cloud.h" @@ -30,9 +30,11 @@ namespace ae { */ class GetCloudAction : public Action { public: + explicit GetCloudAction(EventContext auto const& context) : Action{context} {} + using ResultEvent = Event)>; - virtual ResultEvent::Subscriber result_event() noexcept = 0; + virtual ResultEvent const& result_event() noexcept = 0; }; } // namespace ae diff --git a/aether/connection_manager/get_cloud_aether.cpp b/aether/connection_manager/get_cloud_aether.cpp index 1ab88a25a..9842089c0 100644 --- a/aether/connection_manager/get_cloud_aether.cpp +++ b/aether/connection_manager/get_cloud_aether.cpp @@ -25,7 +25,8 @@ GetCloudFromAether::GetCloudFromAether(AeContext const& ae_context, ClientCloudManager& client_cloud_manager, CloudServerConnections& cloud_connection, Uid const& client_uid) - : ae_context_{ae_context}, + : GetCloudAction{ae_context}, + ae_context_{ae_context}, client_uid_{client_uid}, cloud_connection_{cloud_connection}, cloud_request_{ @@ -35,7 +36,7 @@ GetCloudFromAether::GetCloudFromAether(AeContext const& ae_context, CloudServerConnection* server_connection) { AE_TELED_DEBUG("Send cloud request for uid:{} at server:{}", client_uid_, server_connection->server_id()); - auth_api->report_applied_config(std::vector{AppliedConfig{ + auth_api->ReportAppliedConfigs(std::vector{AppliedConfig{ .subject_uid = client_uid_, .config_version = -1, }}); @@ -46,7 +47,8 @@ GetCloudFromAether::GetCloudFromAether(AeContext const& ae_context, RequestPolicy::All{}, }, cloud_update_sub_{client_cloud_manager.cloud_update_event().Subscribe( - MethodPtr<&GetCloudFromAether::CloudUpdate>{this})} { + MethodPtr<&GetCloudFromAether::CloudUpdate>{this})}, + result_event_{ae_context_} { cloud_request_result_sub_ = cloud_request_.result_event().Subscribe([this](bool success) { if (!success) { @@ -57,9 +59,9 @@ GetCloudFromAether::GetCloudFromAether(AeContext const& ae_context, }); } -GetCloudFromAether::ResultEvent::Subscriber +GetCloudFromAether::ResultEvent const& GetCloudFromAether::result_event() noexcept { - return EventSubscriber{result_event_}; + return result_event_; } void GetCloudFromAether::CloudUpdate( diff --git a/aether/connection_manager/get_cloud_aether.h b/aether/connection_manager/get_cloud_aether.h index 6b6223d3f..368e13afa 100644 --- a/aether/connection_manager/get_cloud_aether.h +++ b/aether/connection_manager/get_cloud_aether.h @@ -17,13 +17,13 @@ #ifndef AETHET_CONNECTION_MANAGER_CONNECTION_MANAGHER_H_ #define AETHET_CONNECTION_MANAGER_CONNECTION_MANAGHER_H_ -#include "aether/types/uid.h" #include "aether/ae_context.h" #include "aether/events/events.h" +#include "aether/types/uid.h" -#include "aether/connection_manager/get_cloud_action.h" #include "aether/cloud_connections/cloud_request.h" #include "aether/cloud_connections/cloud_server_connections.h" +#include "aether/connection_manager/get_cloud_action.h" namespace ae { class ClientCloudManager; @@ -36,7 +36,7 @@ class GetCloudFromAether final : public GetCloudAction { CloudServerConnections& cloud_connection, Uid const& client_uid); - ResultEvent::Subscriber result_event() noexcept override; + ResultEvent const& result_event() noexcept override; private: void CloudUpdate(Uid const& uid, Result const& res); diff --git a/aether/dns/dns_c_ares.cpp b/aether/dns/dns_c_ares.cpp index aa2d6fc7c..003d902b2 100644 --- a/aether/dns/dns_c_ares.cpp +++ b/aether/dns/dns_c_ares.cpp @@ -30,7 +30,6 @@ # include "aether/warning_disable.h" # include "aether/aether.h" -# include "aether/events/multi_subscription.h" # include "aether/socket_initializer.h" # include "aether/executors/executors.h" @@ -169,7 +168,8 @@ class AresImpl { private: ares_channel_t* channel_; - MultiSubscription multi_subscription_; + // this used to initialize sockets used in ares + // this is a RAII object AE_MAY_UNUSED_MEMBER SocketInitializer socket_initializer_; }; diff --git a/aether/dns/esp32_dns_resolve.h b/aether/dns/esp32_dns_resolve.h index 4d38d1579..b9ec0c573 100644 --- a/aether/dns/esp32_dns_resolve.h +++ b/aether/dns/esp32_dns_resolve.h @@ -20,7 +20,7 @@ #include "aether/config.h" #if AE_SUPPORT_CLOUD_DNS -# include "aether/env.h" +# include "aether/common.h" # if (defined(ESP_PLATFORM)) # define ESP32_DNS_RESOLVER_ENABLED 1 diff --git a/aether/env.cpp b/aether/env.cpp new file mode 100644 index 000000000..16bd467a6 --- /dev/null +++ b/aether/env.cpp @@ -0,0 +1,46 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "aether/env.h" + +#include "aether/aether.h" + +namespace ae { +AeEnv::AeEnv() : scheduler_{}, event_system_{} {} + +void AeEnv::SetAether(ObjPtr const& aether) noexcept { + Ptr p = aether.Load(); + aether_ = p; +} +TaskScheduler& AeEnv::scheduler() const noexcept { return scheduler_; } +EventSystem& AeEnv::event_system() const noexcept { return event_system_; } + +void* AeEnv::find_component(EnvId id) noexcept { + if (id == EnvTypeId::value) { + auto ptr = aether_.Lock(); + assert(!!ptr && "Aether must be set and loaded"); + return ptr.get(); + } + if (id == EnvTypeId::value) { + return &scheduler_; + } + if (id == EnvTypeId::value) { + return &event_system_; + } + + return nullptr; +} +} // namespace ae diff --git a/aether/env.h b/aether/env.h index c7bf0283c..51e46123d 100644 --- a/aether/env.h +++ b/aether/env.h @@ -17,91 +17,37 @@ #ifndef AETHER_ENV_H_ #define AETHER_ENV_H_ -#include -#include -#include +#include "aether-objects/env/env.h" +#include "aether-objects/obj/obj_ptr.h" +#include "aether-objects/ptr/ptr_view.h" -#if ESP_PLATFORM -# include "esp_attr.h" -#endif - -#include "aether/config.h" - -#ifdef __GNUC__ -# define AE_PACK(T, DECL) T __attribute__((__packed__)) DECL -#elif defined(_MSC_VER) -# define AE_PACK(T, DECL) __pragma(pack(push, 1)) T DECL __pragma(pack(pop)) -#else -# error "Packing macro must be specified" -#endif - -#define ALIGNED_TYPE(t) t ALIGNED_(1) +#include "aether/events/events.h" +#include "aether/tasks/manual_task_scheduler.h" namespace ae { +class Aether; -#if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \ - defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) || \ - defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || \ - defined(__MIBSEB__) || \ - defined(Q_BYTE_ORDER) && Q_BYTE_ORDER == Q_BIG_ENDIAN -# define AE_ENDIANNESS AE_BIG_ENDIAN -#elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \ - defined(__LITTLE_ENDIAN__) || defined(__ARMEL__) || \ - defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(__i386__) || \ - defined(__amd64) || defined(__amd64__) || defined(_MIPSEL) || \ - defined(__MIPSEL) || defined(__MIPSEL__) || defined(ESP_PLATFORM) || \ - defined(Q_BYTE_ORDER) && Q_BYTE_ORDER == Q_LITTLE_ENDIAN -# define AE_ENDIANNESS AE_LITTLE_ENDIAN -#else -# define AE_ENDIANNESS AE_LITTLE_ENDIAN -// #error "Undefined endianness for the architecture" -#endif - -template -T SwapToInet(const T& t) { -#if AE_ENDIANNESS == AE_LITTLE_ENDIAN - union { - T t; - std::uint8_t t8[sizeof(T)]; - } src, dest; - src.t = t; - for (size_t e = 0; e < sizeof(T); e++) { - dest.t8[e] = src.t8[sizeof(T) - e - 1]; - } - return dest.t; -#else - return t; -#endif -} +/** + * \brief Aether environment object + * It's possible to extend this type and provide your components through it. + * Or provide your own type \see aether_app + */ +class AeEnv : public Env { + public: + AeEnv(); -template -T SwapToLittleEndian(const T& t) { -#if AE_ENDIANNESS == AE_BIG_ENDIAN - union { - T t; - std::uint8_t t8[sizeof(T)]; - } src, dest; - src.t = t; - for (size_t e = 0; e < sizeof(T); e++) { - dest.t8[e] = src.t8[sizeof(T) - e - 1]; - } - return dest.t; -#else - return t; -#endif -} + void SetAether(ObjPtr const& aether) noexcept; -// To avoid compiler warning "Unused variable". Helps in Log(...) macros. -template -inline void Unused(Args&&...) {} + TaskScheduler& scheduler() const noexcept; + EventSystem& event_system() const noexcept; -#define AE_MSVC_BUG_FIX(x) x + protected: + void* find_component(EnvId id) noexcept override; -#if ESP_PLATFORM -# define RTC_STORAGE_ATTR RTC_DATA_ATTR -#else -# define RTC_STORAGE_ATTR -#endif + mutable TaskScheduler scheduler_; + mutable EventSystem event_system_; + PtrView aether_; +}; } // namespace ae diff --git a/aether/events/cumulative_event.h b/aether/events/cumulative_event.h deleted file mode 100644 index f2b26ff06..000000000 --- a/aether/events/cumulative_event.h +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_EVENTS_CUMULATIVE_EVENT_H_ -#define AETHER_EVENTS_CUMULATIVE_EVENT_H_ - -#include -#include -#include -#include -#include - -#include "aether/events/events.h" - -// TODO: add tests -namespace ae { -/** - * \brief Accumulates data from other events and emit self after all of - * them. - */ -template -class CumulativeEvent { - template - class ValueSetter { - public: - explicit ValueSetter(CumulativeEvent* event) : event_(event) {} - - ValueSetter& operator=(T const& value) { - event_->values_[I] = value; - return *this; - } - - ValueSetter& operator=(T&& value) { - event_->values_[I] = std::move(value); - return *this; - } - - private: - CumulativeEvent* event_; - }; - - public: - CumulativeEvent() = default; - - template - explicit CumulativeEvent( - TFunc&& func, EventSubscriber&&... event_subscribers) { - Connect(std::forward(func), std::move(event_subscribers)...); - } - - template - auto Subscribe(TFunc&& func) { - return EventSubscriber{res_event_}.Subscribe(std::forward(func)); - } - - template - void Connect(TFunc&& func, EventSubscribers&&... event_subscribers) { - static_assert(sizeof...(EventSubscribers) == Count, - "Count of event subscribers should be same as Count"); - - ConnectImpl(std::forward(func), std::make_index_sequence(), - std::forward(event_subscribers)...); - } - - T const& operator[](std::size_t i) const { - assert(i < Count); - return values_[i]; - } - - [[nodiscard]] auto begin() const { return std::begin(values_); } - [[nodiscard]] auto end() const { return std::end(values_); } - - constexpr auto size() const { return values_.size(); } - - private: - template - void ConnectImpl(TFunc&& func, std::index_sequence, - EventSubscribers&&... event_subscribers) { - ((subscriptions_[Is] = - std::forward(event_subscribers) - .Subscribe([this, f{std::forward(func)}](auto&&... args) { - set_map_[Is] = true; - ValueSetter vs{this}; - f(vs, std::forward(args)...); - if (IsFull()) { - res_event_.Emit(*this); - } - })), - ...); - } - - bool IsFull() { - return std::all_of(std::begin(set_map_), std::end(set_map_), - [](auto v) { return v; }); - } - - Event res_event_; - std::array subscriptions_; - std::array set_map_{}; - std::array values_; -}; - -/** - * \brief Specialization for void - */ -template -class CumulativeEvent { - public: - CumulativeEvent() = default; - - template - explicit CumulativeEvent( - EventSubscriber&&... event_subscribers) { - Connect(std::move(event_subscribers)...); - } - - template - auto Subscribe(TFunc&& func) { - return EventSubscriber{res_event_}.Subscribe(std::forward(func)); - } - - template - void Connect(EventSubscribers&&... event_subscribers) { - static_assert(sizeof...(EventSubscribers) == Count, - "Count of event subscribers should be same as Count"); - - ConnectImpl(std::make_index_sequence(), - std::forward(event_subscribers)...); - } - - private: - template - void ConnectImpl(std::index_sequence, - EventSubscribers&&... event_subscribers) { - ((subscriptions_[Is] = std::forward(event_subscribers) - .Subscribe([this](auto&&...) { - set_map_[Is] = true; - if (IsFull()) { - res_event_.Emit(); - } - })), - ...); - } - - bool IsFull() { - return std::all_of(std::begin(set_map_), std::end(set_map_), - [](auto v) { return v; }); - } - - Event res_event_; - std::array subscriptions_; - std::array set_map_{}; -}; - -namespace events_internal { -template -struct HandlerResult; - -template -struct HandlerResult, - std::enable_if_t>> { - using Ret = std::invoke_result_t; -}; -} // namespace events_internal - -template -CumulativeEvent(EventSubscriber&&... event_subscribers) - -> CumulativeEvent; - -template -CumulativeEvent(TFunc&& func, - EventSubscriber&&... event_subscribers) - -> CumulativeEvent< - std::common_type_t>::Ret...>, - sizeof...(TSignatures)>; - -} // namespace ae - -#endif // AETHER_EVENTS_CUMULATIVE_EVENT_H_ diff --git a/aether/events/details/event_handler.h b/aether/events/details/event_handler.h new file mode 100644 index 000000000..f06ffcb6e --- /dev/null +++ b/aether/events/details/event_handler.h @@ -0,0 +1,61 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENT_HANDLER_H_ +#define AETHER_EVENTS_DETAILS_EVENT_HANDLER_H_ + +#include +#include + +namespace ae::events { +/** + * \brief The basic interface for event handlers to store pointers. + */ +class IHandler { + public: + IHandler() = default; + virtual ~IHandler() = default; + + IHandler(IHandler const& other) = delete; + IHandler& operator=(IHandler const& other) = delete; + + IHandler(IHandler&& other) noexcept + : active{other.active.load()}, ref_cntr{other.ref_cntr.load()} {} + IHandler& operator=(IHandler&& other) noexcept { + if (this != &other) { + active = other.active.load(); + ref_cntr = other.ref_cntr.load(); + } + return *this; + } + + std::atomic active{true}; + std::atomic ref_cntr{0}; +}; + +template +class Handler; + +template +class Handler : public IHandler { + public: + // Implementations must not throw. Under the project's no-exceptions + // configuration, a throwing callback terminates. + virtual void Invoke(Args... args) noexcept = 0; +}; +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENT_HANDLER_H_ diff --git a/aether/events/details/event_handler_map.h b/aether/events/details/event_handler_map.h new file mode 100644 index 000000000..57ead22bf --- /dev/null +++ b/aether/events/details/event_handler_map.h @@ -0,0 +1,156 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENTS_MAP_H_ +#define AETHER_EVENTS_DETAILS_EVENTS_MAP_H_ + +#include +#include +#include + +#include +#include + +#include "aether/events/details/event_handler.h" +#include "aether/events/details/event_key_registry.h" + +namespace ae::events { +/** + * \brief Owns fixed-capacity storage and the key-to-handler relationships. + */ +template +class EventHandlerMap { + public: + using Key = ::ae::events::Key; + using Type = IHandler*; + using Map = etl::flat_multimap; + using Iterator = typename Map::iterator; + using Pool = etl::generic_pool; + + EventHandlerMap() = default; + ~EventHandlerMap(); + + template Handler> + [[nodiscard]] auto Add(Key key, Handler&& handler) -> std::decay_t*; + // Deactivate one exact key-to-handler relationship. A referenced handler is + // retained for outstanding snapshots; otherwise its storage is reclaimed. + void Deactivate(Key key, IHandler const* handler) noexcept; + // Reclaim unreferenced handlers for key. This intentionally leaves active + // state unchanged for retained snapshot handlers. + void Prune(Key key) noexcept; + // Reclaim deactivated, unreferenced handlers for a still-registered key. + void PruneDeactivated(Key key) noexcept; + // Get the [begin, end) range of elements with key. + std::pair Get(Key key); + + private: + Iterator Destroy(Iterator iterator) noexcept; + + Pool pool_; + Map events_map_; +}; + +template +EventHandlerMap::~EventHandlerMap() { + while (!events_map_.empty()) { + Destroy(events_map_.begin()); + } +} + +template +template Handler> +auto EventHandlerMap::Add(Key key, + Handler&& handler) + -> std::decay_t* { + using RawType = std::decay_t; + static_assert(sizeof(RawType) <= ElemSize, + "An event handler must fit into map storage"); + static_assert(alignof(RawType) <= ElemAlign, + "An event handler must fit into map storage alignment"); + + if (events_map_.full() || pool_.full()) { + return nullptr; + } + auto* element = + pool_.template create(std::forward(handler)); + if (element == nullptr) { + return nullptr; + } + auto [_, inserted] = events_map_.emplace(key, element); + if (!inserted) { + pool_.destroy(static_cast(element)); + return nullptr; + } + return element; +} + +template +void EventHandlerMap::Deactivate( + Key key, IHandler const* handler) noexcept { + auto [begin, end] = Get(key); + for (auto iterator = begin; iterator != end; ++iterator) { + if (iterator->second == handler) { + iterator->second->active = false; + if (iterator->second->ref_cntr == 0) { + Destroy(iterator); + } + return; + } + } +} + +template +void EventHandlerMap::Prune(Key key) noexcept { + auto iterator = Get(key).first; + while (iterator != events_map_.end() && iterator->first == key) { + if (iterator->second->ref_cntr == 0) { + iterator = Destroy(iterator); + } else { + ++iterator; + } + } +} + +template +void EventHandlerMap::PruneDeactivated( + Key key) noexcept { + auto iterator = Get(key).first; + while (iterator != events_map_.end() && iterator->first == key) { + auto const* handler = iterator->second; + if (!handler->active && handler->ref_cntr == 0) { + iterator = Destroy(iterator); + } else { + ++iterator; + } + } +} + +template +auto EventHandlerMap::Get(Key key) + -> std::pair { + return events_map_.equal_range(key); +} + +template +auto EventHandlerMap::Destroy( + Iterator iterator) noexcept -> Iterator { + pool_.destroy(iterator->second); + return events_map_.erase(iterator); +} + +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENTS_MAP_H_ diff --git a/aether/events/details/event_key_registry.h b/aether/events/details/event_key_registry.h new file mode 100644 index 000000000..524a41d20 --- /dev/null +++ b/aether/events/details/event_key_registry.h @@ -0,0 +1,107 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENT_KEY_REGISTRY_H_ +#define AETHER_EVENTS_DETAILS_EVENT_KEY_REGISTRY_H_ + +#include +#include +#include +#include + +#include + +namespace ae::events { +// Event identifiers have one fixed wire-independent representation throughout +// the event system. +using Key = std::uint32_t; + +/** + * \brief Events key registry + */ +template +class KeyRegistry { + public: + using Key = ::ae::events::Key; + std::optional Register(); + void Retire(Key key); + void Release(Key key); + bool IsRegistered(Key key) const; + bool IsRetiring(Key key) const; + + private: + enum class State : std::uint8_t { kRegistered, kRetiring }; + + Key next_key_{}; + etl::flat_map key_map_; +}; + +template +auto KeyRegistry::Register() -> std::optional { + if (key_map_.full()) { + return std::nullopt; + } + // Try every value in the unsigned key range, including Key::max(). + // Unsigned increment is defined to wrap to zero. + auto const first_key = next_key_; + while (true) { + auto const k = next_key_; + ++next_key_; + auto [_, inserted] = key_map_.emplace(k, State::kRegistered); + if (inserted) { + return k; + } + if (next_key_ == first_key) { + break; + } + } + + return std::nullopt; +} + +template +void KeyRegistry::Retire(Key key) { + auto const i = key_map_.find(key); + if (i != key_map_.end() && i->second == State::kRegistered) { + i->second = State::kRetiring; + } +} + +template +void KeyRegistry::Release(Key key) { + auto const i = key_map_.find(key); + assert(i != key_map_.end() && i->second == State::kRetiring && + "Only retiring keys may be released"); + if (i != key_map_.end() && i->second == State::kRetiring) { + key_map_.erase(i); + } +} + +template +bool KeyRegistry::IsRegistered(Key key) const { + auto const i = key_map_.find(key); + return i != key_map_.end() && i->second == State::kRegistered; +} + +template +bool KeyRegistry::IsRetiring(Key key) const { + auto const i = key_map_.find(key); + return i != key_map_.end() && i->second == State::kRetiring; +} + +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENT_KEY_REGISTRY_H_ diff --git a/aether/events/details/event_multi_subscription.h b/aether/events/details/event_multi_subscription.h new file mode 100644 index 000000000..8c9a3992c --- /dev/null +++ b/aether/events/details/event_multi_subscription.h @@ -0,0 +1,176 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENT_MUILTI_SUBSCRIPTION_H_ +#define AETHER_EVENTS_DETAILS_EVENT_MUILTI_SUBSCRIPTION_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "aether/events/details/event_registered_handler.h" +#include "aether/events/details/event_system.h" + +namespace ae::events { +/** + * \brief A RAII object to handle multiple event subscriptions at the same time. + * + * Reset() and destruction remove all registrations. The event system and the + * events that produced the registrations must outlive this object. The dynamic + * public specialization uses std::vector and can allocate while growing; the + * fixed specialization preflights its capacity and fails fast on overflow. + * Push() requires valid registrations from one event system, each transferred + * to this as its sole owner; using copied RegHandler tokens in multiple owners + * is unsupported misuse. Moving transfers that ownership and leaves the source + * empty. + */ +template typename Vector> +class MultiSubscriptionObject { + struct ReducedRegHandler { + Key key; + IHandler* h; + }; + + public: + MultiSubscriptionObject() = default; + MultiSubscriptionObject(MultiSubscriptionObject const&) = delete; + MultiSubscriptionObject(MultiSubscriptionObject&& other) noexcept + : es_{other.es_}, reg_handlers_{std::move(other.reg_handlers_)} { + other.es_ = nullptr; + other.reg_handlers_.clear(); + } + + MultiSubscriptionObject& operator=(MultiSubscriptionObject const&) = delete; + MultiSubscriptionObject& operator=(MultiSubscriptionObject&& other) noexcept { + if (this != &other) { + Reset(); + es_ = other.es_; + reg_handlers_ = std::move(other.reg_handlers_); + other.es_ = nullptr; + other.reg_handlers_.clear(); + } + return *this; + } + + ~MultiSubscriptionObject() { Reset(); } + + template + requires(std::is_same_v> && ...) + void Push(RegHandler const& re, REs const&... res) { + assert(re.es != nullptr && "Registration event system is null"); + assert(re.h != nullptr && "Registration handler is null"); + assert(((res.es != nullptr && res.h != nullptr) && ...) && + "Registration is invalid"); + CleanUp(); + SetEventSystem(re, res...); + auto const count = 1 + sizeof...(res); + PreflightCapacity(count); + reg_handlers_.reserve(reg_handlers_.size() + count); + reg_handlers_.emplace_back(ReducedRegHandler{.key = re.key, .h = re.h}); + // push the rest + (reg_handlers_.emplace_back(ReducedRegHandler{.key = res.key, .h = res.h}), + ...); + } + + MultiSubscriptionObject& operator+=(RegHandler const& re) { + Push(re); + return *this; + } + + void Reset() { + if (reg_handlers_.empty()) { + return; + } + assert(es_ != nullptr && "Registration event system is null"); + auto to_reset = std::move(reg_handlers_); + for (auto const& rrh : to_reset) { + assert(rrh.h != nullptr && "Registration handler is null"); + es_->RemoveHandler(rrh.key, rrh.h); + } + reg_handlers_.clear(); + } + + explicit operator bool() const { return !reg_handlers_.empty(); } + + private: + template + void SetEventSystem(RegHandler const& re, REs const&... res) { + if (es_ == nullptr) { + es_ = re.es; + } + assert(es_ == re.es && "Registration event systems differ"); + assert(((es_ == res.es) && ...) && "Registration event systems differ"); + } + + void PreflightCapacity(std::size_t count) const { + if (count > reg_handlers_.max_size() - reg_handlers_.size()) { + std::abort(); + } + } + + void CleanUp() { + if (reg_handlers_.empty()) { + return; + } + assert(es_ != nullptr && "Registration event system is null"); + // remove all handler for events, that already unregistered + reg_handlers_.erase( + std::remove_if(std::begin(reg_handlers_), std::end(reg_handlers_), + [this](auto const& rrh) noexcept { + return !es_->IsRegistered(rrh.key); + }), + std::end(reg_handlers_)); + }; + + Es* es_{nullptr}; + Vector reg_handlers_; +}; + +/** + * \brief Multi-subscription with dynamic std::vector storage. + * + * This compatibility specialization may allocate when registrations are added. + */ +template +using MultiSubscriptionObjectDyn = MultiSubscriptionObject; + +namespace events_multi_subscription_internal { +template +struct EtlVector { + template + using type = etl::vector; +}; +} // namespace events_multi_subscription_internal + +/** + * \brief Multi-subscription with fixed etl::vector storage. + * + * Push() fails fast before changing storage when the requested registrations do + * not fit within Capacity. + */ +template +using MultiSubscriptionObjectFix = MultiSubscriptionObject< + Es, events_multi_subscription_internal::EtlVector::template type>; + +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENT_MUILTI_SUBSCRIPTION_H_ diff --git a/aether/events/details/event_object.h b/aether/events/details/event_object.h new file mode 100644 index 000000000..dac0862c8 --- /dev/null +++ b/aether/events/details/event_object.h @@ -0,0 +1,151 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENT_OBJECT_H_ +#define AETHER_EVENTS_DETAILS_EVENT_OBJECT_H_ + +#include +#include +#include +#include +#include + +#include "aether/events/details/event_registered_handler.h" +#include "aether/events/details/event_system.h" +#include "aether/events/details/generic_event_handler.h" + +namespace ae::events { +template +concept EventContext = requires(T const& t) { + { t.event_system() } -> EventSystemConcept; +}; + +/** + * \brief The event object stored in class. + * The event emitted using this object. + * The subscription added also to this object. + * Event object must be initialized with suitable event context or reference to + * event system. The event system must outlive this object and all of its + * subscriptions. Construction and subscription fail fast if their fixed + * capacities are exhausted. Multicast payloads passed by value must be + * copyable because every handler receives a separate copy. Reference payloads + * are shared synchronously by all handlers; handlers must not retain them. + * Move-only by-value payloads and rvalue-reference payloads are unsupported. + */ +template +class EventObject; + +template +class EventObject { + public: + static_assert((!std::is_rvalue_reference_v && ...), + "Multicast events do not support T&& parameters"); + static_assert(((std::is_reference_v || + std::copy_constructible>) && + ...), + "By-value multicast event parameters must be copyable"); + + template + explicit EventObject(EC const& ec) : EventObject{ec.event_system()} {} + + explicit EventObject(Es& es) : es_{&es} { + auto k = es_->RegEvent(); + if (!k) { + assert(false && "Event should be registered on construction"); + std::abort(); + } + key_ = *k; + } + + ~EventObject() { + if (es_ != nullptr) { + es_->UnregEvent(key_); + } + } + + // no copy, but move + EventObject(EventObject const&) = delete; + EventObject& operator=(EventObject const&) = delete; + + // Moving transfers the event registration. The source becomes invalid and + // must not be used except for destruction, assignment, or a validity check. + EventObject(EventObject&& other) noexcept : es_{other.es_}, key_{other.key_} { + other.es_ = nullptr; + } + EventObject& operator=(EventObject&& other) noexcept { + if (this != &other) { + if (es_ != nullptr) { + es_->UnregEvent(key_); + } + es_ = other.es_; + key_ = other.key_; + other.es_ = nullptr; + } + return *this; + } + + /** + * \brief Emit this event synchronously with the supplied arguments. + * + * Dispatch uses an event-system snapshot and does not hold its mutex while + * calling handlers. Calling this on a moved-from EventObject violates its + * contract. + */ + void Emit(Args... args) { + assert(es_ != nullptr && "EventObject is invalid"); + es_->template Invoke(key_, args...); + } + + /** + * \brief Subscribe a handler and return its non-owning registration token. + * + * The returned RegHandler does not own the subscription. Store it in a + * Subscription or MultiSubscription when the handler must be removed. + * Calling this on a moved-from EventObject violates its contract. + */ + template > T> + RegHandler Subscribe(T&& handler) const { + assert(es_ != nullptr && "EventObject is invalid"); + auto* h = es_->AddHandler(key_, std::forward(handler)); + if (h == nullptr) { + std::abort(); + } + return RegHandler{ + .key = key_, + .h = h, + .es = es_, + }; + } + + /** + * \brief Make subscription using generic handler from any invocable functor + */ + template Func> + RegHandler Subscribe(Func&& func) const { + return Subscribe( + GenericHandler{std::forward(func)}); + } + + explicit operator bool() const { return es_ != nullptr; } + + private: + Es* es_{nullptr}; + Key key_{}; +}; + +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENT_OBJECT_H_ diff --git a/aether/events/details/event_registered_handler.h b/aether/events/details/event_registered_handler.h new file mode 100644 index 000000000..a1cc816b8 --- /dev/null +++ b/aether/events/details/event_registered_handler.h @@ -0,0 +1,47 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENT_REGISTERED_HANDLER_H_ +#define AETHER_EVENTS_DETAILS_EVENT_REGISTERED_HANDLER_H_ + +#include "aether/events/details/event_handler.h" +#include "aether/events/details/event_system.h" + +namespace ae::events { +/** + * \brief Registered event handler. + * Stores an event key, a handler pointer, and an event-system pointer. + * + * This aggregate is a lightweight, non-owning registration token. A token + * returned by EventObject::Subscribe is valid and has non-null handler and + * event-system pointers. Destroying it does not unsubscribe the handler; use + * SubscriptionObject or MultiSubscriptionObject to manage registration + * lifetime. + * + * RegHandler remains copyable as an aggregate, but that copyability does not + * permit shared ownership. Transfer every returned token to exactly one + * SubscriptionObject or MultiSubscriptionObject. Copying or retaining a token + * for multiple such owners is unsupported misuse. + */ +template +struct RegHandler { + Key key; + IHandler* h; + Es* es; +}; +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENT_REGISTERED_HANDLER_H_ diff --git a/aether/events/details/event_subscription.h b/aether/events/details/event_subscription.h new file mode 100644 index 000000000..e2427bd26 --- /dev/null +++ b/aether/events/details/event_subscription.h @@ -0,0 +1,91 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENT_SUBSCRIPTION_H_ +#define AETHER_EVENTS_DETAILS_EVENT_SUBSCRIPTION_H_ + +#include +#include +#include + +#include "aether/events/details/event_registered_handler.h" +#include "aether/events/details/event_system.h" + +namespace ae::events { +/** + * \brief A RAII object to handle event subscription. + * Removes the event handler on Reset() or destruction. + * + * The event system and event that produced the registration must outlive this + * object. Construction and assignment require a valid RegHandler produced by + * EventObject::Subscribe and transferred to this as its sole owner. Although + * RegHandler is copyable, using copies to give a registration multiple owners + * is unsupported misuse. Reset() does not wait for a callback already + * selected for dispatch. + */ +template +class SubscriptionObject { + public: + SubscriptionObject() = default; + SubscriptionObject(RegHandler const& re) // NOLINT(*explicit*) + : reg_handler_{re} { + assert(re.es != nullptr && "Registration event system is null"); + assert(re.h != nullptr && "Registration handler is null"); + } + + SubscriptionObject& operator=(RegHandler const& re) { + assert(re.es != nullptr && "Registration event system is null"); + assert(re.h != nullptr && "Registration handler is null"); + Reset(); + reg_handler_.emplace(re); + return *this; + } + + SubscriptionObject(SubscriptionObject const& other) = delete; + SubscriptionObject(SubscriptionObject&& other) noexcept + : reg_handler_{std::move(other.reg_handler_)} { + other.reg_handler_.reset(); + } + + SubscriptionObject& operator=(SubscriptionObject const& other) = delete; + SubscriptionObject& operator=(SubscriptionObject&& other) noexcept { + if (this != &other) { + Reset(); + std::swap(reg_handler_, other.reg_handler_); + } + return *this; + } + + ~SubscriptionObject() { Reset(); } + + void Reset() { + if (reg_handler_) { + assert(reg_handler_->es != nullptr && + "Registration event system is null"); + assert(reg_handler_->h != nullptr && "Registration handler is null"); + reg_handler_->es->RemoveHandler(reg_handler_->key, reg_handler_->h); + reg_handler_.reset(); + } + } + + explicit operator bool() const { return reg_handler_.has_value(); } + + private: + std::optional> reg_handler_; +}; +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENT_SUBSCRIPTION_H_ diff --git a/aether/events/details/event_system.h b/aether/events/details/event_system.h new file mode 100644 index 000000000..9977b417e --- /dev/null +++ b/aether/events/details/event_system.h @@ -0,0 +1,215 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_EVENT_SYSTEM_H_ +#define AETHER_EVENTS_DETAILS_EVENT_SYSTEM_H_ + +#include +#include +#include +#include +#include +#include + +#include + +#include "aether/events/details/event_handler.h" +#include "aether/events/details/event_handler_map.h" +#include "aether/events/details/event_key_registry.h" + +namespace ae::events { +template +concept EventSystemConcept = requires(T& t) { + // for event system required + // be able to reg event + { t.RegEvent() } -> std::same_as>; + // be able to unreg event + { t.UnregEvent(Key{0}) }; + // be able to add handler + { t.AddHandler(Key{0}, std::declval&&>()) }; + // be able to remove handlers by the stored handler pointer + { t.RemoveHandler(Key{}, std::declval()) }; + // be able to invoke event with added handlers + { t.Invoke(Key{0}, 1, true) }; +}; + +/** + * \brief Thread-safe, fixed-capacity central event management system. + * + * An event owns one registered key and handlers are allocated from this + * system's fixed pool. Invoke() snapshots handlers while holding the mutex, + * then invokes them synchronously without the mutex. Consequently callbacks + * may subscribe, unsubscribe, emit recursively, and register events. + * + * RegEvent() returns std::nullopt and AddHandler() returns nullptr when their + * respective capacities are exhausted. A handler must fit within kElemSize and + * kElemAlign; the effective element size includes the IHandler base object. + * EventObject terminates on these resource failures. + * + * Unsubscribing makes a handler ineligible for later snapshot checks but does + * not wait for a callback already in a snapshot; a handler removed after its + * eligibility check may still run. Unregistering an event rejects new + * invocations and registrations, but does not deactivate handlers already + * captured by an active invocation snapshot. Those handlers continue unless + * individually unsubscribed. Handler references keep snapshotted handlers + * alive during recursive dispatch. + */ +template +class EventSystem { + public: + // must fit at least an IHandler + static constexpr std::size_t kElemSize = MaxElemSize + sizeof(IHandler); + // must align at least as IHandler + static constexpr std::size_t kElemAlign = + std::max(MaxElemAlign, alignof(IHandler)); + using Registry = KeyRegistry; + using Map = EventHandlerMap; + using Key = ::ae::events::Key; + + // Registers a new event and returns its key, or std::nullopt on exhaustion. + [[nodiscard]] std::optional RegEvent(); + // Unregisters an event. Active invocation snapshots still run their handlers. + void UnregEvent(Key key); + // check if event registered + bool IsRegistered(Key key) const; + // Adds a handler for a registered key, or returns nullptr on exhaustion. + template Handler> + [[nodiscard]] auto AddHandler(Key key, Handler&& handler) + -> std::decay_t*; + // Remove a handler by its non-owning registration pointer. This form lets + // RAII subscriptions avoid dereferencing a token whose handler is gone, + // including one retained by a retiring event snapshot. + void RemoveHandler(Key key, IHandler const* handler); + // Invoke the handler for event under a key + template + void Invoke(Key key, Args... args); + + private: + void ReleaseRetiringKey(Key key); + + mutable std::mutex mutex_; + Registry key_registry_; + Map event_handlers_map_; +}; + +template +auto EventSystem::RegEvent() -> std::optional { + auto lock = std::scoped_lock{mutex_}; + return key_registry_.Register(); +} + +template +void EventSystem::UnregEvent(Key key) { + auto lock = std::scoped_lock{mutex_}; + if (!key_registry_.IsRegistered(key)) { + return; + } + key_registry_.Retire(key); + event_handlers_map_.Prune(key); + ReleaseRetiringKey(key); +} + +template +bool EventSystem::IsRegistered(Key key) const { + auto lock = std::scoped_lock{mutex_}; + return key_registry_.IsRegistered(key); +} + +template +template T> +auto EventSystem::AddHandler(Key key, T&& handler) + -> std::decay_t* { + auto lock = std::scoped_lock{mutex_}; + if (!key_registry_.IsRegistered(key)) { + return nullptr; + } + + return event_handlers_map_.Add(key, std::forward(handler)); +} + +template +void EventSystem::RemoveHandler(Key key, + IHandler const* handler) { + auto lock = std::scoped_lock{mutex_}; + event_handlers_map_.Deactivate(key, handler); +} + +template +template +void EventSystem::Invoke(Key key, Args... args) { + // all handlers must be derived from Handler + using HandlerType = Handler; + // first collect handlers in one vector + // increase ref counters, so somebody will not destroy them + etl::vector handlers; + { + auto lock = std::scoped_lock{mutex_}; + if (!key_registry_.IsRegistered(key)) { + return; + } + auto [begin, end] = event_handlers_map_.Get(key); + for (auto i = begin; i != end; ++i) { + if (i->second->active) { + i->second->ref_cntr++; + handlers.emplace_back(static_cast(i->second)); + } + } + } + + // this could be unpredictable long operation + // so invoke handlers without mutex + // this allow to add new events and subscribe new handlers from the invoked + // handler or from other threads + for (auto* h : handlers) { + // handlers could be removed during invoke of previous one + // Check eligibility immediately before invoking. A handler removed after + // this check may still run; unsubscription never waits for callbacks. + if (h->active) { + // args are named lvalues here. By-value signature arguments are copied + // for every handler rather than moved from by an earlier callback. + h->Invoke(args...); + } + } + + // Decrease used ref counters and cleanup orphan handlers + // by orphan we meant either this key was unregistered + // or handler become inactive + { + auto lock = std::scoped_lock{mutex_}; + for (auto* h : handlers) { + h->ref_cntr--; + } + if (key_registry_.IsRetiring(key)) { + event_handlers_map_.Prune(key); + ReleaseRetiringKey(key); + } else if (key_registry_.IsRegistered(key)) { + event_handlers_map_.PruneDeactivated(key); + } + } +} + +template +void EventSystem::ReleaseRetiringKey(Key key) { + auto const [begin, end] = event_handlers_map_.Get(key); + if (key_registry_.IsRetiring(key) && begin == end) { + key_registry_.Release(key); + } +} + +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_EVENT_SYSTEM_H_ diff --git a/aether/events/details/generic_event_handler.h b/aether/events/details/generic_event_handler.h new file mode 100644 index 000000000..b3dab5634 --- /dev/null +++ b/aether/events/details/generic_event_handler.h @@ -0,0 +1,58 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EVENTS_DETAILS_GENERIC_EVENT_HANDLER_H_ +#define AETHER_EVENTS_DETAILS_GENERIC_EVENT_HANDLER_H_ + +#include +#include +#include + +#include "aether/events/details/event_handler.h" + +namespace ae::events { +template +class GenericHandler; + +template Func> +class GenericHandler final + : public Handler { + public: + template + requires(std::same_as, std::decay_t>) + explicit GenericHandler(U&& f) : func_{std::forward(f)} {} + + GenericHandler(GenericHandler const&) = delete; + GenericHandler& operator=(GenericHandler const&) = delete; + + GenericHandler(GenericHandler&& other) noexcept + : func_{std::forward(other.func_)} {} + GenericHandler& operator=(GenericHandler&&) noexcept = default; + ~GenericHandler() override = default; + + // Handlers are required to be noexcept for the project's no-exceptions + // target. + void Invoke(Args... args) noexcept override { + std::invoke(func_, std::forward(args)...); + } + + private: + Func func_; +}; + +} // namespace ae::events + +#endif // AETHER_EVENTS_DETAILS_GENERIC_EVENT_HANDLER_H_ diff --git a/aether/events/event_deleter.h b/aether/events/event_deleter.h deleted file mode 100644 index f799a74e9..000000000 --- a/aether/events/event_deleter.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_EVENTS_EVENT_DELETER_H_ -#define AETHER_EVENTS_EVENT_DELETER_H_ - -#include - -#include "aether/common.h" -#include "aether/events/event_list.h" - -namespace ae { -/** - * \brief Class for remove event handlers that are not needed anymore. - * \see Subscription for RAII wrapper. - */ -class EventHandlerDeleter { - public: - EventHandlerDeleter(std::shared_ptr const& event_handlers, - EventHandlersList::Index index); - AE_CLASS_COPY_MOVE(EventHandlerDeleter) - - void Delete(); - - bool alive() const; - - private: - std::weak_ptr event_handlers_; - typename EventHandlersList::Index index_; -}; - -} // namespace ae - -#endif // AETHER_EVENTS_EVENT_DELETER_H_ diff --git a/aether/events/event_handler.h b/aether/events/event_handler.h deleted file mode 100644 index 22c5c186e..000000000 --- a/aether/events/event_handler.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_EVENTS_EVENT_HANDLER_H_ -#define AETHER_EVENTS_EVENT_HANDLER_H_ - -#include -#include - -#include "aether-miscpp/types/small_function.h" -#include "aether/common.h" -#include "aether/config.h" - -namespace ae { - -static constexpr std::size_t kFunctionSize = AE_EVENT_HANDLER_MAX_SIZE; -static constexpr std::size_t kFunctionAlign = AE_EVENT_HANDLER_ALIGN; - -template -class EventHandler; - -/** - * \brief Object to store event handler callback - */ -template -class EventHandler final { - public: - using Fun = SmallFunction; - - template - requires(!std::is_same_v, TFunc>) - constexpr explicit EventHandler(TFunc&& func) - : callback_{std::forward(func)} {} - - template - constexpr explicit EventHandler(MethodPtr func_ptr) - : callback_{func_ptr} {} - - constexpr explicit EventHandler(void (*func_ptr)(TArgs...)) - : callback_{func_ptr} {} - - AE_CLASS_COPY_MOVE(EventHandler); - - constexpr void Invoke(TArgs&&... args) const { - callback_(std::forward(args)...); - } - - private: - Fun callback_; -}; -} // namespace ae -#endif // AETHER_EVENTS_EVENT_HANDLER_H_ diff --git a/aether/events/event_list.cpp b/aether/events/event_list.cpp deleted file mode 100644 index 59ea8e9c4..000000000 --- a/aether/events/event_list.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/events/event_list.h" - -namespace ae { -EventHandlersList::IteratorAdapter::Iterator::Iterator( - typename ListType::iterator it, std::uint16_t index, std::uint16_t size) - : size_{size} { - auto counter = index; - for (auto i = it; counter < size_; ++i, ++counter) { - if (i->is_alive) { - it_ = i; - index_ = counter; - return; - } - } - index_ = size_; -} -EventHandlersList::IteratorAdapter::Iterator& -EventHandlersList::IteratorAdapter::Iterator::operator++() { - ++it_; - ++index_; - if (index_ == size_) { - return *this; - } - if (!it_->is_alive) { - return operator++(); - } - return *this; -} - -EventHandlersList::IteratorAdapter::Iterator -EventHandlersList::IteratorAdapter::Iterator::operator++(int) { - Iterator tmp(*this); - ++(*this); - return tmp; -} - -bool EventHandlersList::IteratorAdapter::Iterator::operator==( - const Iterator& other) const { - return index_ == other.index_; -} - -bool EventHandlersList::IteratorAdapter::Iterator::operator!=( - const Iterator& other) const { - return !(*this == other); -} - -EventHandlersList::IteratorAdapter::IteratorAdapter(EventHandlersList* self) - : self_{self}, - begin_{std::begin(self_->handlers_), 0, - static_cast(self_->handlers_.size())}, - end_{std::end(self_->handlers_), - static_cast(self_->handlers_.size()), - static_cast(self_->handlers_.size())} { - ++self_->use_counter_; -} - -EventHandlersList::IteratorAdapter::~IteratorAdapter() { - if ((--self_->use_counter_) == 0) { - self_->CleanUp(); - } -} - -EventHandlersList::IteratorAdapter EventHandlersList::Iterator() { - auto lock = std::scoped_lock{lock_handlers_}; - return IteratorAdapter{this}; -} - -void EventHandlersList::Remove(Index index) { - auto lock = std::scoped_lock{lock_handlers_}; - index.it->is_alive = false; -} - -bool EventHandlersList::Alive(Index index) const { - auto lock = std::scoped_lock{lock_handlers_}; - return index.it->is_alive; -} - -void EventHandlersList::CleanUp() { - auto lock = std::scoped_lock{lock_handlers_}; - if (use_counter_ != 0) { - return; - } - // remove only from the end to prevent held indexes invalidation - auto remove_begin = std::end(handlers_); - auto it = std::find_if(std::rbegin(handlers_), std::rend(handlers_), - [](auto const& h) { return h.is_alive; }); - if (it != std::rend(handlers_)) { - remove_begin = it.base(); - } - handlers_.erase(remove_begin, std::end(handlers_)); -} -} // namespace ae diff --git a/aether/events/event_list.h b/aether/events/event_list.h deleted file mode 100644 index a67e997d3..000000000 --- a/aether/events/event_list.h +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_EVENTS_EVENT_LIST_H_ -#define AETHER_EVENTS_EVENT_LIST_H_ - -#include -#include -#include -#include -#include - -#include "aether-miscpp/types/aligned_storage.h" -#include "aether/events/event_handler.h" - -namespace ae { -class EventHandlersList { - using TemplateHandler = EventHandler; - // all event handlers has the same size - static constexpr std::size_t kElementSize = sizeof(TemplateHandler); - static constexpr std::size_t kElementAlign = alignof(TemplateHandler); - using ValueType = ManagedStorage; - struct StoreType { - template - requires(!std::is_same_v>) - explicit StoreType(U&& value) : value{std::forward(value)} {} - - ValueType value; - bool is_alive = true; - }; - - using ListType = std::list; - - public: - struct Index { - typename ListType::iterator it; - }; - - /** - * \brief The iterator adapter provide interface for 'for range loop' and lock - * list from actual removing any elements. - * Removed elements will be deleted if no other IteratorAdapter to that list - * exists - */ - class IteratorAdapter { - public: - /** - * \brief Actual Iterator to iterate through the list of event handlers. - * Return only is_alive handlers. - */ - class Iterator { - public: - Iterator() = default; - - Iterator(typename ListType::iterator it, std::uint16_t index, - std::uint16_t size); - - AE_CLASS_COPY_MOVE(Iterator) - - Iterator& operator++(); - - Iterator operator++(int); - - bool operator==(const Iterator& other) const; - - bool operator!=(const Iterator& other) const; - template - auto* get() const { - return it_->value.template ptr>(); - } - - private: - typename ListType::iterator it_{}; - /** - * index and size used to control the end of the list. - * using end iterator is not appropriate here because it's grow with new - * elements added. - */ - std::uint16_t index_{}; - std::uint16_t size_{}; - }; - - using iterator = Iterator; - - explicit IteratorAdapter(EventHandlersList* self); - ~IteratorAdapter(); - - AE_CLASS_COPY_MOVE(IteratorAdapter) - - iterator begin() { return begin_; } - iterator end() { return end_; } - - private: - EventHandlersList* self_; - iterator begin_; - iterator end_; - }; - - EventHandlersList() = default; - - /** - * \brief Insert a new event handler into the list. - * The TSignature must be always be the same. We can't control it on this - * level, so it's user's responsibility to ensure it. - */ - template - Index Insert(EventHandler&& handler) { - auto lock = std::scoped_lock{lock_handlers_}; - auto it = handlers_.emplace(handlers_.end(), std::move(handler)); - return Index{it}; - } - - /** - * \brief Get access to iterator on handlers. - * The TSignature must be always be the same. We can't control it on this - * level, so it's user's responsibility to ensure it. - */ - IteratorAdapter Iterator(); - - /** - * \brief Remove element by index. - * It only marks the handler as dead. - */ - void Remove(Index index); - - /** - * \brief Check if element is alive by index. - */ - bool Alive(Index index) const; - - private: - /** - * \brief Remove last unalived handlers. - */ - void CleanUp(); - - std::atomic_int16_t use_counter_{0}; - mutable std::mutex lock_handlers_; - ListType handlers_; -}; -} // namespace ae - -#endif // AETHER_EVENTS_EVENT_LIST_H_ diff --git a/aether/events/event_subscription.cpp b/aether/events/event_subscription.cpp deleted file mode 100644 index 9f5e72b07..000000000 --- a/aether/events/event_subscription.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/events/event_subscription.h" - -namespace ae { -Subscription::Subscription(EventHandlerDeleter&& event_handler_deleter) - : event_handler_deleter_{std::move(event_handler_deleter)} {} - -Subscription::Subscription(Subscription&& other) noexcept - : event_handler_deleter_{std::move(other.event_handler_deleter_)} { - other.event_handler_deleter_.reset(); -} -Subscription::~Subscription() { Reset(); } - -Subscription& Subscription::operator=(Subscription&& other) noexcept { - if (this != &other) { - Reset(); - event_handler_deleter_ = std::move(other.event_handler_deleter_); - other.event_handler_deleter_.reset(); - } - return *this; -} - -Subscription& Subscription::operator=( - EventHandlerDeleter&& event_handler_deleter) noexcept { - Reset(); - event_handler_deleter_ = std::move(event_handler_deleter); - return *this; -} - -void Subscription::Reset() { - if (event_handler_deleter_) { - event_handler_deleter_->Delete(); - event_handler_deleter_.reset(); - } -} -Subscription::operator bool() const { - return event_handler_deleter_ && event_handler_deleter_->alive(); -} -} // namespace ae diff --git a/aether/events/event_subscription.h b/aether/events/event_subscription.h deleted file mode 100644 index df3e69757..000000000 --- a/aether/events/event_subscription.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_EVENTS_EVENT_SUBSCRIPTION_H_ -#define AETHER_EVENTS_EVENT_SUBSCRIPTION_H_ - -#include - -#include "aether/events/event_deleter.h" - -namespace ae { -/** - * \brief RAII object to manage event subscription - * It stores event handler deleter and call delete on destructor - */ -class Subscription { - public: - using EventHandler = EventHandlerDeleter; - - Subscription() = default; - - Subscription(EventHandlerDeleter&& event_handler_deleter); // NOLINT - Subscription(Subscription const&) = delete; - Subscription(Subscription&& other) noexcept; - ~Subscription(); - - Subscription& operator=(Subscription const&) = delete; - Subscription& operator=(Subscription&& other) noexcept; - Subscription& operator=(EventHandlerDeleter&& event_handler_deleter) noexcept; - - void Reset(); - - explicit operator bool() const; - - private: - std::optional event_handler_deleter_; -}; - -} // namespace ae - -#endif // AETHER_EVENTS_EVENT_SUBSCRIPTION_H_ diff --git a/aether/events/events.h b/aether/events/events.h index 0abe00604..3008ad08e 100644 --- a/aether/events/events.h +++ b/aether/events/events.h @@ -17,156 +17,76 @@ #ifndef AETHER_EVENTS_EVENTS_H_ #define AETHER_EVENTS_EVENTS_H_ -#include -#include -#include -#include -#include +#include "aether/config.h" -#include "aether-miscpp/types/small_function.h" -#include "aether/events/event_deleter.h" -#include "aether/events/event_handler.h" -#include "aether/events/event_list.h" -#include "aether/events/event_subscription.h" +// IWYU pragma: begin_exports +#include "aether/events/details/event_handler.h" +#include "aether/events/details/event_multi_subscription.h" +#include "aether/events/details/event_object.h" +#include "aether/events/details/event_subscription.h" +#include "aether/events/details/event_system.h" +#include "aether/events/details/generic_event_handler.h" +// IWYU pragma: end_exports namespace ae { -template -class Event; - -template -class EventSubscriber; - /** - * \brief Storage for handlers to some event + * \brief Maximum number of simultaneously registered events. + * + * The default EventSystem has independent fixed capacities for event keys and + * handlers. Event construction fails fast when this capacity is exhausted. */ -template -class Event { - public: - using CallbackSignature = void(TArgs...); - using Subscriber = EventSubscriber; - using List = EventHandlersList; - - explicit Event() : events_list_{std::make_shared()} {} - - ~Event() = default; - - AE_CLASS_MOVE_ONLY(Event) - - /** - * \brief Invoke all handlers to this event. - * Some handlers may call this recursively and either unsubscribe or make new - * subscriptions. - */ - void Emit(TArgs... args) { - std::shared_ptr list_life_extander = events_list_; - EmitImpl(list_life_extander, std::forward(args)...); - } - - /** - * \brief Add new subscription to this event - * Users should use EventSubscriber. - * returns event deleter - */ - auto Add(EventHandler&& handler) { - auto index = events_list_->Insert(std::move(handler)); - return EventHandlerDeleter{events_list_, index}; - } - - private: - /** - * \brief Invoke handler to this event. - * Separate static emitter is used to handle self destruction while handler - * invoking. - */ - static void EmitImpl(std::shared_ptr const& events_list, - TArgs&&... args) { - auto iterate_list = events_list->Iterator(); - for (auto it = std::begin(iterate_list); it != std::end(iterate_list); - ++it) { - auto* handler = it.get(); - assert((handler != nullptr) && "Handler is null"); - handler->Invoke(std::forward(args)...); - } - } - - std::shared_ptr events_list_; -}; +static constexpr inline std::size_t kEventsCapacity = + static_cast(AE_EVENTS_MAX_COUNT); +/** \brief Maximum number of handlers in the default EventSystem pool. */ +static constexpr inline std::size_t kEventsHandlerCapacity = + static_cast(AE_EVENTS_MAX_COUNT * 1.2); +/** \brief Maximum user-provided handler size in the default handler pool. */ +static constexpr inline std::size_t kEventHandlerMaxSize = + static_cast(AE_EVENT_HANDLER_MAX_SIZE); +/** \brief Required alignment for user-provided handlers in the default pool. */ +static constexpr inline std::size_t kEventHandlerAlign = + static_cast(AE_EVENT_HANDLER_ALIGN); + +/** \brief The fixed-capacity event system used by the public event types. */ +using EventSystem = + events::EventSystem; /** - * \brief Helper class to make event subscriptions. - * It is designed to be constructed implicitly from Event and to be returned - * from getter to Event in classes + * \brief A multicast event backed by the default EventSystem. + * + * Emit() synchronously invokes a snapshot of eligible handlers. A by-value + * argument must be copyable; each handler receives its own equivalent copy. + * Reference arguments remain references during synchronous dispatch, so all + * handlers observe the same object and must not retain its reference. Rvalue + * reference arguments and move-only by-value arguments are unsupported. */ -template -class EventSubscriber { - public: - using Signature = void(TArgs...); - using Subscription = ae::Subscription; - using EventType = Event; +template +using Event = events::EventObject; - template - static constexpr bool kIsInvocable = - std::is_invocable_r_v, TArgs...>; - - EventSubscriber(EventType& event) : event_{&event} {} - - /** - * \brief Create new subscription to event with callback to Event's signature. - * - * \return EventHandlerDeleter to remove handler after it does not needed - * anymore \see Subscription for RAII wrapper - */ - template - auto Subscribe(TCallback&& cb) { - static_assert(std::is_invocable_v, TArgs...>, - "TCallable must have same signature"); - return event_->Add(EventHandler{std::forward(cb)}); - } - - /** - * \brief Create new subscription to event with pointer to member function. - * - * \return EventHandlerDeleter to remove handler after it does not needed - * anymore \see Subscription for RAII wrapper - */ - template - auto Subscribe(MethodPtr method) { - static_assert(std::is_invocable_v, - "Method must have same signature"); - return event_->Add(EventHandler{method}); - } - - /** - * \brief Create new subscription to event with pointer to free function. - * - * \return EventHandlerDeleter to remove handler after it does not needed - * anymore \see Subscription for RAII wrapper - */ - auto Subscribe(void (*method)(TArgs...)) { - static_assert(std::is_invocable_v, - "method must have same signature"); - return event_->Add(EventHandler{method}); - } - - /** - * \brief Create new subscription to event with different event with same - * signature. - * - * \return EventHandlerDeleter to remove handler after it does not needed - * anymore \see Subscription for RAII wrapper - */ - auto Subscribe(Event& event) { - return event_->Add(EventHandler{ - MethodPtr<&Event::Emit>{&event}}); - } +/** + * \brief A lightweight, non-RAII handler registration. + * + * Discarding this token does not unsubscribe the handler. Transfer each token + * returned by Subscribe() to exactly one Subscription or MultiSubscription to + * give the registration an owner. RegEventHandler remains copyable as an + * aggregate, but copies must not be retained by multiple owners; doing so is + * unsupported misuse. + */ +using RegEventHandler = events::RegHandler; - private: - EventType* event_; -}; +/** \brief RAII owner for one event registration. */ +using Subscription = events::SubscriptionObject; +/** + * \brief RAII owner for several registrations using dynamic std::vector + * storage. + * + * This compatibility type can allocate while growing. Use + * events::MultiSubscriptionObjectFix when fixed-capacity storage is required. + */ +using MultiSubscription = events::MultiSubscriptionObjectDyn; -template -EventSubscriber(Event&) -> EventSubscriber; +using events::EventContext; } // namespace ae diff --git a/aether/events/multi_subscription.cpp b/aether/events/multi_subscription.cpp deleted file mode 100644 index 0c1ef78f8..000000000 --- a/aether/events/multi_subscription.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/events/multi_subscription.h" - -namespace ae { -MultiSubscription::~MultiSubscription() { Reset(); } - -void MultiSubscription::Reset() { - for (auto& del : deleters_) { - if (del.alive()) { - del.Delete(); - } - } - deleters_.clear(); -} - -void MultiSubscription::CleanUp() { - deleters_.erase(std::remove_if(std::begin(deleters_), std::end(deleters_), - [](auto const& del) { return !del.alive(); }), - std::end(deleters_)); -} - -MultiSubscription& MultiSubscription::operator+=( - EventHandlerDeleter&& deleter) { - CleanUp(); - PushToVector(std::move(deleter)); - return *this; -} - -void MultiSubscription::PushToVector(EventHandlerDeleter&& deleter) { - deleters_.emplace_back(std::move(deleter)); -} - -std::size_t MultiSubscription::count() const { return deleters_.size(); } - -} // namespace ae diff --git a/aether/events/multi_subscription.h b/aether/events/multi_subscription.h deleted file mode 100644 index e25785b6e..000000000 --- a/aether/events/multi_subscription.h +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_EVENTS_MULTI_SUBSCRIPTION_H_ -#define AETHER_EVENTS_MULTI_SUBSCRIPTION_H_ - -#include -#include - -#include "aether/common.h" - -#include "aether/events/event_deleter.h" - -namespace ae { -/** - * \brief RAII object to manage set of subscriptions - */ -class MultiSubscription { - public: - using EventHandler = EventHandlerDeleter; - - MultiSubscription() = default; - ~MultiSubscription(); - - AE_CLASS_MOVE_ONLY(MultiSubscription) - - /** - * \brief Push as many as you need subscriptions to the list. - * All dead subscriptions will be cleaned up before add new. - */ - template - void Push(TDeleters&&... deleters) { - CleanUp(); - (PushToVector(std::forward(deleters)), ...); - } - - MultiSubscription& operator+=(EventHandlerDeleter&& deleter); - - /** - * \brief Reset all subscriptions. - */ - void Reset(); - - std::size_t count() const; - - private: - void CleanUp(); - - void PushToVector(EventHandlerDeleter&& deleter); - - std::vector deleters_; -}; - -} // namespace ae - -#endif // AETHER_EVENTS_MULTI_SUBSCRIPTION_H_ diff --git a/aether/executors/action_wait_sender.h b/aether/executors/action_wait_sender.h index c15bc1e3e..13b47b026 100644 --- a/aether/executors/action_wait_sender.h +++ b/aether/executors/action_wait_sender.h @@ -14,8 +14,8 @@ * limitations under the License. */ -#ifndef AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ -#define AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ +#ifndef AETHER_EXECUTORS_ACTION_WAIT_SENDER_H_ +#define AETHER_EXECUTORS_ACTION_WAIT_SENDER_H_ #include #include @@ -33,7 +33,6 @@ IGNORE_IMPLICIT_CONVERSION() DISABLE_WARNING_POP() #include "aether/actions/action.h" -#include "aether/events/event_subscription.h" #include "aether/events/events.h" namespace ae::ex { @@ -41,14 +40,12 @@ namespace action_wait_sender_internal { template struct IsResultEvent : std::false_type {}; -template -struct IsResultEvent)>> : std::true_type {}; -template -struct IsResultEvent)>> : std::true_type { -}; +template +struct IsResultEvent)>> + : std::true_type {}; template -concept ResultEvent = IsResultEvent::value; +concept ResultEvent = IsResultEvent>::value; template concept ActionResultEvent = requires(A& a) { @@ -58,16 +55,15 @@ concept ActionResultEvent = requires(A& a) { template struct ResultEventTrait {}; -template -struct ResultEventTrait)>> { +template +struct ResultEventTrait)>> { using type = Ok; using error = Err; }; template class OpState { - using ResultEvent = - typename decltype(std::declval().result_event())::EventType; + using ResultEvent = std::decay_t().result_event())>; using EventTrait = ResultEventTrait; using ResultType = Result; @@ -147,9 +143,7 @@ class OpState { template class Sender { - using ResultEvent = - typename decltype(std::declval().result_event())::EventType; - + using ResultEvent = std::decay_t().result_event())>; using EventTrait = ResultEventTrait; public: @@ -189,4 +183,4 @@ static constexpr inline auto action_wait = } // namespace ae::ex -#endif // AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ +#endif // AETHER_EXECUTORS_ACTION_WAIT_SENDER_H_ diff --git a/aether/executors/event_wait_sender.h b/aether/executors/event_wait_sender.h new file mode 100644 index 000000000..5899f6f8b --- /dev/null +++ b/aether/executors/event_wait_sender.h @@ -0,0 +1,190 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ +#define AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ + +#include +#include + +#include "aether-miscpp/meta/ignore_t.h" +#include "aether-miscpp/meta/type_list.h" +#include "aether-miscpp/types/result.h" + +#include "aether/warning_disable.h" + +// IWYU pragma: begin_exports +DISABLE_WARNING_PUSH() +IGNORE_IMPLICIT_CONVERSION() +#include +DISABLE_WARNING_POP() + +#include "aether/events/events.h" + +namespace ae::ex { +namespace event_wait_sender_internal { +template +struct IsEventType : std::false_type {}; + +template +struct IsEventType> : std::true_type {}; + +template +concept EventType = IsEventType::value; + +template +struct EventObjectTrait {}; + +template +struct EventObjectTrait> { + using event_system = Es; + using arg_list = TypeList; +}; + +struct EventHandler { + decltype(auto) operator()(auto&&... args) const noexcept { + static_assert(sizeof...(args) <= 1, + "Default handler could handle only one or zero arguments"); + return HandleArg(std::forward(args)...); + } + + template + static decltype(auto) HandleArg(T&& first) noexcept { + return std::forward(first); + } + + static void HandleArg() noexcept { + ; // nothing + } +}; + +template +class OpState { + public: + using event_system = typename EventObjectTrait::event_system; + using Subscription = events::SubscriptionObject; + using result_type = Res; + + constexpr OpState(R&& receiver, E const& event, Handler&& handler) + : receiver_{std::move(receiver)}, + event_{&event}, + handler_{std::move(handler)} {} + + constexpr void start() noexcept { + sub_ = event_->Subscribe([this](auto&&... args) noexcept { + if constexpr (std::is_void_v) { + handler_(std::forward(args)...); + stdexec::set_value(std::move(receiver_)); + } else { + auto&& res = handler_(std::forward(args)...); + if constexpr (IsResultType_v) { + if (res) { + if constexpr (IsIgnore_v) { + stdexec::set_value(std::move(receiver_)); + } else { + stdexec::set_value(std::move(receiver_), std::move(res).value()); + } + } else { + stdexec::set_error(std::move(receiver_), std::move(res).error()); + } + } else { + stdexec::set_value(std::move(receiver_), std::move(res)); + } + } + sub_.Reset(); + }); + } + + private: + R receiver_; + E const* event_; + Handler handler_; + Subscription sub_; +}; + +template +class Sender { + template + static consteval auto GetInvokeResult(TypeList) + -> std::invoke_result_t; + + public: + using invoke_result = std::decay_t::arg_list{}))>; + using sender_concept = stdexec::sender_t; + + /** + * Extract completion signatures from handler's invoke_result + * If invoke_result is void, return just set_value_t() + * If invoke_result some type return set_value_t(invoke_result) + * If invoke_result is Result type: + * if value_type is Ignore type return set_value_t(), set_error_t(error_type) + * if value_type is some type return set_value_t(value_type), + * set_error_t(error_type) + */ + template + static consteval auto get_completion_signatures() { + if constexpr (IsResultType_v) { + using value_type = typename invoke_result::value_type; + using error_type = typename invoke_result::error_type; + if constexpr (IsIgnore_v) { + return stdexec::completion_signatures< + stdexec::set_value_t(), stdexec::set_error_t(error_type)>{}; + } else { + return stdexec::completion_signatures{}; + } + } else if constexpr (!std::is_void_v) { + return stdexec::completion_signatures{}; + } else { + return stdexec::completion_signatures{}; + } + } + + constexpr Sender(E const& event, Handler&& handler) noexcept + : event_{&event}, handler_{std::move(handler)} {} + + template + constexpr auto connect(R&& receiver) && noexcept { + return OpState, E, Handler, invoke_result>{ + std::forward(receiver), *event_, std::move(handler_)}; + } + + private: + E const* event_; + Handler handler_; +}; + +/** + * \brief Creates a sender which after connect and start subscribes to event + * and waits for it's result + */ +struct EventWait { + template + constexpr auto operator()(E const& e, Handler&& handler = {}) const noexcept { + return Sender>{e, std::forward(handler)}; + } +}; +} // namespace event_wait_sender_internal + +static constexpr inline auto event_wait = + event_wait_sender_internal::EventWait{}; + +} // namespace ae::ex + +#endif // AETHER_EXECUTORS_EVENT_WAIT_SENDER_H_ diff --git a/aether/executors/executors.h b/aether/executors/executors.h index d6b8b9f2e..98aecc271 100644 --- a/aether/executors/executors.h +++ b/aether/executors/executors.h @@ -31,6 +31,7 @@ DISABLE_WARNING_POP() #include "aether/executors/any_sender.h" #include "aether/executors/any_waiter.h" #include "aether/executors/async_waiter.h" +#include "aether/executors/event_wait_sender.h" #include "aether/executors/for_range.h" #include "aether/executors/make_sender.h" #include "aether/executors/scheduler_on_tasks.h" diff --git a/aether/executors/for_range.h b/aether/executors/for_range.h index a8e502503..7260325c7 100644 --- a/aether/executors/for_range.h +++ b/aether/executors/for_range.h @@ -17,14 +17,14 @@ #ifndef AETHER_EXECUTORS_FOR_RANGE_H_ #define AETHER_EXECUTORS_FOR_RANGE_H_ -#include #include #include +#include #include -#include "aether/types/iterator.h" #include "aether-miscpp/meta/type_list.h" +#include "aether/types/iterator.h" namespace ae::ex { namespace for_range_internal { @@ -156,29 +156,24 @@ class ForRangeSender { private: static constexpr auto transform_values = []() { - auto convert = []() { - if constexpr (IsContinue_v) { + if constexpr (sizeof...(Args) > 0) { + if constexpr (IsContinue_v>>) { return stdexec::completion_signatures<>{}; } else { - return stdexec::completion_signatures{}; + return stdexec::completion_signatures{}; } - }; - - return stdexec::__concat_completion_signatures( - convert.template operator()()...); + } else { + return stdexec::completion_signatures{}; + } }; static constexpr auto transform_errors = []() { - auto convert = []() { - if constexpr (IsContinue_v) { - return stdexec::completion_signatures<>{}; - } else { - return stdexec::completion_signatures{}; - } - }; - - return stdexec::__concat_completion_signatures( - convert.template operator()()...); + static_assert(sizeof...(Args) > 0 && "Empty set_error is not supported"); + if constexpr (IsContinue_v>>) { + return stdexec::completion_signatures<>{}; + } else { + return stdexec::completion_signatures{}; + } }; Iter iter_; diff --git a/aether/gateway_api/gateway_api.h b/aether/gateway_api/gateway_api.h index afe99a577..ac43ec7fa 100644 --- a/aether/gateway_api/gateway_api.h +++ b/aether/gateway_api/gateway_api.h @@ -23,12 +23,12 @@ # include +# include "aether/api_protocol/api_class_impl.h" +# include "aether/api_protocol/api_method.h" # include "aether/events/events.h" -# include "aether/types/server_id.h" # include "aether/types/client_id.h" # include "aether/types/data_buffer.h" -# include "aether/api_protocol/api_method.h" -# include "aether/api_protocol/api_class_impl.h" +# include "aether/types/server_id.h" # include "aether/gateway_api/server_endpoints.h" @@ -52,7 +52,7 @@ class GatewayClientApi : public ApiClassImpl { AE_METHODS(RegMethod<3, &GatewayClientApi::FromServer>); - auto from_server_event() { return EventSubscriber{from_server_event_}; } + auto from_server_event() { return from_server_event_; } private: Event from_server_event_; diff --git a/aether/lora_modules/dx_smart_lr02_lm.cpp b/aether/lora_modules/dx_smart_lr02_lm.cpp index 0256abe29..b93490631 100644 --- a/aether/lora_modules/dx_smart_lr02_lm.cpp +++ b/aether/lora_modules/dx_smart_lr02_lm.cpp @@ -22,8 +22,8 @@ # include "aether-miscpp/misc/defer.h" # include "aether-miscpp/misc/from_chars.h" -# include "aether/actions/pipeline.h" # include "aether/actions/gen_action.h" +# include "aether/actions/pipeline.h" # include "aether/mstream_buffers.h" # include "aether/serial_ports/serial_port_factory.h" @@ -284,9 +284,8 @@ ActionPtr DxSmartLr02LoraModule::ReadPacket( // return data; // }; -DxSmartLr02LoraModule::DataEvent::Subscriber -DxSmartLr02LoraModule::data_event() { - return EventSubscriber{data_event_}; +DxSmartLr02LoraModule::DataEvent const& DxSmartLr02LoraModule::data_event() { + return data_event_; } ActionPtr diff --git a/aether/lora_modules/dx_smart_lr02_lm.h b/aether/lora_modules/dx_smart_lr02_lm.h index 01ae3aa61..04cee4b63 100644 --- a/aether/lora_modules/dx_smart_lr02_lm.h +++ b/aether/lora_modules/dx_smart_lr02_lm.h @@ -20,15 +20,15 @@ #include "aether/config.h" #if AE_SUPPORT_LORA && AE_ENABLE_DX_SMART_LR02_LM -# include # include +# include -# include "aether/poller/poller.h" -# include "aether/actions/pipeline.h" # include "aether/actions/actions_queue.h" +# include "aether/actions/pipeline.h" # include "aether/actions/repeatable_task.h" -# include "aether/serial_ports/iserial_port.h" +# include "aether/poller/poller.h" # include "aether/serial_ports/at_support/at_support.h" +# include "aether/serial_ports/iserial_port.h" # include "aether/lora_modules/ilora_module_driver.h" @@ -68,7 +68,7 @@ class DxSmartLr02LoraModule final : public ILoraModuleDriver { ActionPtr WritePacket(ConnectionLoraIndex connect_index, ae::DataBuffer const& data) override; - DataEvent::Subscriber data_event() override; + DataEvent const& data_event() override; ActionPtr SetPowerSaveParam( LoraPowerSaveParam const& psp) override; diff --git a/aether/lora_modules/ebyte_e22_lm.cpp b/aether/lora_modules/ebyte_e22_lm.cpp index bce93a534..47128488c 100644 --- a/aether/lora_modules/ebyte_e22_lm.cpp +++ b/aether/lora_modules/ebyte_e22_lm.cpp @@ -22,8 +22,8 @@ # include "aether-miscpp/misc/defer.h" # include "aether-miscpp/misc/from_chars.h" -# include "aether/actions/pipeline.h" # include "aether/actions/gen_action.h" +# include "aether/actions/pipeline.h" # include "aether/mstream_buffers.h" # include "aether/serial_ports/serial_port_factory.h" @@ -73,9 +73,8 @@ EbyteE22LoraModule::CloseNetwork(ae::ConnectionLoraIndex /*connect_index*/) { // void EbyteE22LoraModule::CloseNetwork( // ae::ConnectionLoraIndex /*connect_index*/){}; -ActionPtr -EbyteE22LoraModule::WritePacket(ae::ConnectionLoraIndex /*connect_index*/, - ae::DataBuffer const& /*data*/) { +ActionPtr EbyteE22LoraModule::WritePacket( + ae::ConnectionLoraIndex /*connect_index*/, ae::DataBuffer const& /*data*/) { return {}; } // void EbyteE22LoraModule::WritePacket(ae::ConnectionLoraIndex @@ -93,9 +92,8 @@ ActionPtr EbyteE22LoraModule::ReadPacket( // return data; // }; -EbyteE22LoraModule::DataEvent::Subscriber -EbyteE22LoraModule::data_event() { - return EventSubscriber{data_event_}; +EbyteE22LoraModule::DataEvent const& EbyteE22LoraModule::data_event() { + return data_event_; } ActionPtr diff --git a/aether/lora_modules/ebyte_e22_lm.h b/aether/lora_modules/ebyte_e22_lm.h index 2bcad4fc8..3bd94eab6 100644 --- a/aether/lora_modules/ebyte_e22_lm.h +++ b/aether/lora_modules/ebyte_e22_lm.h @@ -21,15 +21,15 @@ #if AE_SUPPORT_LORA && AE_ENABLE_EBYTE_E22_LM -# include # include +# include -# include "aether/poller/poller.h" -# include "aether/actions/pipeline.h" # include "aether/actions/actions_queue.h" +# include "aether/actions/pipeline.h" # include "aether/actions/repeatable_task.h" -# include "aether/serial_ports/iserial_port.h" +# include "aether/poller/poller.h" # include "aether/serial_ports/at_support/at_support.h" +# include "aether/serial_ports/iserial_port.h" # include "aether/lora_modules/ilora_module_driver.h" @@ -55,7 +55,7 @@ class EbyteE22LoraModule final : public ILoraModuleDriver { ae::ConnectionLoraIndex connect_index, ae::DataBuffer const& data) override; - DataEvent::Subscriber data_event() override; + DataEvent const& data_event() override; ActionPtr SetPowerSaveParam( LoraPowerSaveParam const& psp) override; diff --git a/aether/lora_modules/ilora_module_driver.h b/aether/lora_modules/ilora_module_driver.h index 19ec26c0a..3c9991250 100644 --- a/aether/lora_modules/ilora_module_driver.h +++ b/aether/lora_modules/ilora_module_driver.h @@ -21,11 +21,11 @@ #if AE_SUPPORT_LORA -# include # include +# include -# include "aether/types/address.h" # include "aether/events/events.h" +# include "aether/types/address.h" # include "aether/types/data_buffer.h" # include "aether/actions/notify_action.h" @@ -51,7 +51,7 @@ class ILoraModuleDriver { ConnectionLoraIndex connect_index) = 0; virtual ActionPtr WritePacket( ConnectionLoraIndex connect_index, DataBuffer const& data) = 0; - virtual DataEvent::Subscriber data_event() = 0; + virtual DataEvent const& data_event() = 0; virtual ActionPtr SetPowerSaveParam( LoraPowerSaveParam const& psp) = 0; diff --git a/aether/modems/bg95_at_modem.cpp b/aether/modems/bg95_at_modem.cpp index 1307de119..b5ebe7034 100644 --- a/aether/modems/bg95_at_modem.cpp +++ b/aether/modems/bg95_at_modem.cpp @@ -21,8 +21,8 @@ # include "aether-miscpp/misc/override.h" # include "aether/executors/executors.h" -# include "aether/serial_ports/at_support/at_stage.h" # include "aether/serial_ports/at_support/at_request.h" +# include "aether/serial_ports/at_support/at_stage.h" # include "aether/serial_ports/serial_port_factory.h" # include "aether/modems/modems_tele.h" @@ -50,7 +50,8 @@ OpenNetworkOperationImpl::OpenNetworkOperationImpl(AeContext const& ae_context, auto OpenNetworkOperationImpl::Pipeline() { // BG95 uses QIACT for PDP context activation and QIOPEN for socket connection // AT+QIACT=,, to activate PDP context - // AT+QIOPEN=,,,, to open socket + // AT+QIOPEN=,,,, to open + // socket auto handle = self_->next_connection_index_++; auto protocol_str = [&]() -> std::string_view { @@ -65,27 +66,27 @@ auto OpenNetworkOperationImpl::Pipeline() { // First activate PDP context, then open socket return ex::just() | - at::MakeRequest(at_support_, - "AT+QIACT=1,1,\"0.0.0.0\"", - at::Wait{"+QIACT:", - [](auto&, auto pos) { - std::int32_t context_id{}; - std::int32_t result{}; - if (at_support::ParseResponse(*pos, "+QIACT", - context_id, - result)) { - return result == 1; - } - return false; - }}) | + at::MakeRequest( + at_support_, "AT+QIACT=1,1,\"0.0.0.0\"", + at::Wait{"+QIACT:", + [](auto&, auto pos) { + std::int32_t context_id{}; + std::int32_t result{}; + if (at_support::ParseResponse(*pos, "+QIACT", + context_id, result)) { + return result == 1; + } + return false; + }}) | ex::with_timeout(ae_context_, 30s) | at::MakeRequest(at_support_, - Format(R"(AT+QIOPEN=1,{}, "{}", "{}", {}))", - handle, protocol_str, host_, port_), + Format(R"(AT+QIOPEN=1,{}, "{}", "{}", {}))", handle, + protocol_str, host_, port_), at::Wait{Format("+QIOPEN: {}", handle), [handle](AtBuffer&, auto pos) { std::int32_t result_code{}; - int expected_handle = static_cast(handle); + int expected_handle = + static_cast(handle); if (at_support::ParseResponse( *pos, "+QIOPEN", result_code, expected_handle)) { @@ -221,20 +222,19 @@ ModemStartOperation::ModemStartOperation(AeContext const& ae_context, } auto ModemStartOperation::SetBaudRate(kBaudRate const rate) { - static const std::map - baud_rate_commands_bg95 = { - {kBaudRate::kBaudRate9600, "AT+IPR=9600"}, - {kBaudRate::kBaudRate19200, "AT+IPR=19200"}, - {kBaudRate::kBaudRate38400, "AT+IPR=38400"}, - {kBaudRate::kBaudRate57600, "AT+IPR=57600"}, - {kBaudRate::kBaudRate115200, "AT+IPR=115200"}, - {kBaudRate::kBaudRate230400, "AT+IPR=230400"}, - {kBaudRate::kBaudRate921600, "AT+IPR=921600"}, - {kBaudRate::kBaudRate2900000, "AT+IPR=2900000"}, - {kBaudRate::kBaudRate3000000, "AT+IPR=3000000"}, - {kBaudRate::kBaudRate3200000, "AT+IPR=3200000"}, - {kBaudRate::kBaudRate3684000, "AT+IPR=3684000"}, - {kBaudRate::kBaudRate4000000, "AT+IPR=4000000"}}; + static const std::map baud_rate_commands_bg95 = { + {kBaudRate::kBaudRate9600, "AT+IPR=9600"}, + {kBaudRate::kBaudRate19200, "AT+IPR=19200"}, + {kBaudRate::kBaudRate38400, "AT+IPR=38400"}, + {kBaudRate::kBaudRate57600, "AT+IPR=57600"}, + {kBaudRate::kBaudRate115200, "AT+IPR=115200"}, + {kBaudRate::kBaudRate230400, "AT+IPR=230400"}, + {kBaudRate::kBaudRate921600, "AT+IPR=921600"}, + {kBaudRate::kBaudRate2900000, "AT+IPR=2900000"}, + {kBaudRate::kBaudRate3000000, "AT+IPR=3000000"}, + {kBaudRate::kBaudRate3200000, "AT+IPR=3200000"}, + {kBaudRate::kBaudRate3684000, "AT+IPR=3684000"}, + {kBaudRate::kBaudRate4000000, "AT+IPR=4000000"}}; return ex::let_value([&, rate]() noexcept { auto make_request = [&](std::string&& cmd) noexcept { @@ -256,17 +256,15 @@ auto ModemStartOperation::SetBaudRate(kBaudRate const rate) { auto ModemStartOperation::SetNetMode(kModemMode const modem_mode) { // BG95 specific network mode commands - static const std::map - net_mode_commands_bg95 = { - {kModemMode::kModeAuto, "AT+CNMP=2"}, // Set modem mode Auto - {kModemMode::kModeGSMOnly, "AT+CNMP=13"}, // Set modem mode GSMOnly - {kModemMode::kModeLTEOnly, "AT+CNMP=38"}, // Set modem mode LTEOnly - {kModemMode::kModeGSMLTE, "AT+CNMP=51"}, // Set modem mode GSMLTE - {kModemMode::kModeCatM, "AT+CMNB=1"}, // Set modem mode CatM - {kModemMode::kModeNbIot, "AT+CMNB=2"}, // Set modem mode NbIot - {kModemMode::kModeCatMNbIot, - "AT+CMNB=3"}, // Set modem mode CatMNbIot - }; + static const std::map net_mode_commands_bg95 = { + {kModemMode::kModeAuto, "AT+CNMP=2"}, // Set modem mode Auto + {kModemMode::kModeGSMOnly, "AT+CNMP=13"}, // Set modem mode GSMOnly + {kModemMode::kModeLTEOnly, "AT+CNMP=38"}, // Set modem mode LTEOnly + {kModemMode::kModeGSMLTE, "AT+CNMP=51"}, // Set modem mode GSMLTE + {kModemMode::kModeCatM, "AT+CMNB=1"}, // Set modem mode CatM + {kModemMode::kModeNbIot, "AT+CMNB=2"}, // Set modem mode NbIot + {kModemMode::kModeCatMNbIot, "AT+CMNB=3"}, // Set modem mode CatMNbIot + }; return ex::let_value([&, modem_mode]() noexcept { auto make_request = [&](std::string&& cmd) noexcept { @@ -426,7 +424,7 @@ void ModemStartOperation::RunPipeline() { } ModemStopOperation::ModemStopOperation(AeContext const& ae_context, - Bg95AtModem& self) + Bg95AtModem& self) : ae_context_{ae_context}, self_{&self}, at_support_{self.at_support_} { RunPipeline(); } @@ -536,8 +534,8 @@ void Bg95AtModem::Init() { ModemOperation* Bg95AtModem::Start() { if (!modem_start_operation_ || modem_start_operation_->is_finished()) { if (started_) { - modem_start_operation_ = std::make_unique< - bg95_modem_internal::ModemStartedAlreadyOperation>(); + modem_start_operation_ = + std::make_unique(); } else { modem_start_operation_ = std::make_unique( @@ -551,12 +549,12 @@ ModemOperation* Bg95AtModem::Start() { ModemOperation* Bg95AtModem::Stop() { if (!modem_stop_operation_ || modem_stop_operation_->is_finished()) { if (!started_) { - modem_stop_operation_ = std::make_unique< - bg95_modem_internal::ModemStoppedAlreadyOperation>(); + modem_stop_operation_ = + std::make_unique(); } else { modem_stop_operation_ = - std::make_unique( - ae_context_, *this); + std::make_unique(ae_context_, + *this); } } @@ -581,8 +579,8 @@ ModemOperation* Bg95AtModem::CloseNetwork(ConnectionIndex connect_index) { return op; } -WriteOperation* Bg95AtModem::WritePacket( - ConnectionIndex connect_index, std::span data) { +WriteOperation* Bg95AtModem::WritePacket(ConnectionIndex connect_index, + std::span data) { if (data.size() > kModemMTU) { assert(false); return nullptr; @@ -592,16 +590,13 @@ WriteOperation* Bg95AtModem::WritePacket( return op; } -Bg95AtModem::DataEvent::Subscriber Bg95AtModem::data_event() { - return EventSubscriber{data_event_}; -} +Bg95AtModem::DataEvent const& Bg95AtModem::data_event() { return data_event_; } -ModemOperation* Bg95AtModem::SetPowerSaveParam( - ModemPowerSaveParam const& psp) { +ModemOperation* Bg95AtModem::SetPowerSaveParam(ModemPowerSaveParam const& psp) { if (!modem_set_psp_operation_ || modem_set_psp_operation_->is_finished()) { - modem_set_psp_operation_ = std::make_unique< - bg95_modem_internal::ModemSetPowerSaveParamOperation>(ae_context_, - *this, psp); + modem_set_psp_operation_ = + std::make_unique( + ae_context_, *this, psp); } return modem_set_psp_operation_.get(); @@ -619,17 +614,16 @@ ModemOperation* Bg95AtModem::PowerOff() { // ============================private members=============================== // void Bg95AtModem::SetupPoll() { // BG95 uses +QIURC indication for socket events - poll_listener_.emplace(at_support_.dispatcher(), - "+QIURC: ", [this](auto&, auto pos) { - std::string type; - std::int32_t handle{}; - if (at_support::ParseResponse(*pos, "+QIURC", type, - handle)) { - if (type == "recv") { - PollEvent(handle); - } - } - }); + poll_listener_.emplace( + at_support_.dispatcher(), "+QIURC: ", [this](auto&, auto pos) { + std::string type; + std::int32_t handle{}; + if (at_support::ParseResponse(*pos, "+QIURC", type, handle)) { + if (type == "recv") { + PollEvent(handle); + } + } + }); } void Bg95AtModem::PollEvent(std::int32_t handle) { diff --git a/aether/modems/bg95_at_modem.h b/aether/modems/bg95_at_modem.h index 731f8702b..1ea318463 100644 --- a/aether/modems/bg95_at_modem.h +++ b/aether/modems/bg95_at_modem.h @@ -182,7 +182,7 @@ class Bg95AtModem final : public IModemDriver { ModemOperation* CloseNetwork(ConnectionIndex connect_index) override; WriteOperation* WritePacket(ConnectionIndex connect_index, std::span data) override; - DataEvent::Subscriber data_event() override; + DataEvent const& data_event() override; ModemOperation* SetPowerSaveParam(ModemPowerSaveParam const& psp) override; ModemOperation* PowerOff() override; diff --git a/aether/modems/imodem_driver.h b/aether/modems/imodem_driver.h index e265daee2..a04e3eaf3 100644 --- a/aether/modems/imodem_driver.h +++ b/aether/modems/imodem_driver.h @@ -20,18 +20,18 @@ #include "aether/config.h" #if AE_SUPPORT_MODEMS -# include -# include # include # include +# include +# include +# include "aether-miscpp/meta/ignore_t.h" # include "aether-miscpp/types/result.h" +# include "aether/actions/action.h" # include "aether/events/events.h" +# include "aether/modems/modem_driver_types.h" # include "aether/types/address.h" -# include "aether-miscpp/meta/ignore_t.h" -# include "aether/actions/action.h" # include "aether/types/data_buffer.h" -# include "aether/modems/modem_driver_types.h" namespace ae { enum class ModemError : int {}; @@ -41,9 +41,7 @@ class OpenNetworkOperation : public Action { // return either connection index or error using ResultType = Result; using ResultEvent = Event; - ResultEvent::Subscriber result_event() { - return EventSubscriber{result_event_}; - } + ResultEvent const& result_event() { return result_event_; } std::optional const& result() const { return result_; } protected: @@ -63,9 +61,7 @@ class WriteOperation : public Action { // return size of bytes written, or error code using ResultType = Result; using ResultEvent = Event; - ResultEvent::Subscriber result_event() { - return EventSubscriber{result_event_}; - } + ResultEvent const& result_event() { return result_event_; } std::optional const& result() const { return result_; } protected: @@ -84,9 +80,7 @@ class ModemOperation : public Action { public: using ResultType = Result; using ResultEvent = Event; - ResultEvent::Subscriber result_event() { - return EventSubscriber{result_event_}; - } + ResultEvent const& result_event() { return result_event_; } std::optional const& result() const { return result_; } protected: @@ -118,7 +112,7 @@ class IModemDriver { virtual WriteOperation* WritePacket(ConnectionIndex connect_index, std::span data) = 0; - virtual DataEvent::Subscriber data_event() = 0; + virtual DataEvent const& data_event() = 0; virtual ModemOperation* SetPowerSaveParam(ModemPowerSaveParam const& psp) = 0; virtual ModemOperation* PowerOff() = 0; diff --git a/aether/modems/sim7070_at_modem.cpp b/aether/modems/sim7070_at_modem.cpp index 6721c575d..1d9b30be7 100644 --- a/aether/modems/sim7070_at_modem.cpp +++ b/aether/modems/sim7070_at_modem.cpp @@ -21,8 +21,8 @@ # include "aether-miscpp/misc/override.h" # include "aether/executors/executors.h" -# include "aether/serial_ports/at_support/at_stage.h" # include "aether/serial_ports/at_support/at_request.h" +# include "aether/serial_ports/at_support/at_stage.h" # include "aether/serial_ports/serial_port_factory.h" # include "aether/modems/modems_tele.h" @@ -615,8 +615,8 @@ WriteOperation* Sim7070AtModem::WritePacket( return op; } -Sim7070AtModem::DataEvent::Subscriber Sim7070AtModem::data_event() { - return EventSubscriber{data_event_}; +Sim7070AtModem::DataEvent const& Sim7070AtModem::data_event() { + return data_event_; } ModemOperation* Sim7070AtModem::SetPowerSaveParam( diff --git a/aether/modems/sim7070_at_modem.h b/aether/modems/sim7070_at_modem.h index 82ebbfeb9..7442e9425 100644 --- a/aether/modems/sim7070_at_modem.h +++ b/aether/modems/sim7070_at_modem.h @@ -190,7 +190,7 @@ class Sim7070AtModem final : public IModemDriver { ModemOperation* CloseNetwork(ConnectionIndex connect_index) override; WriteOperation* WritePacket(ConnectionIndex connect_index, std::span data) override; - DataEvent::Subscriber data_event() override; + DataEvent const& data_event() override; ModemOperation* SetPowerSaveParam(ModemPowerSaveParam const& psp) override; ModemOperation* PowerOff() override; diff --git a/aether/modems/thingy91x_at_modem.cpp b/aether/modems/thingy91x_at_modem.cpp index 7027712eb..7ecea97c8 100644 --- a/aether/modems/thingy91x_at_modem.cpp +++ b/aether/modems/thingy91x_at_modem.cpp @@ -20,12 +20,12 @@ # include # include -# include "aether-miscpp/misc/override.h" # include "aether-miscpp/misc/from_chars.h" +# include "aether-miscpp/misc/override.h" # include "aether/executors/executors.h" +# include "aether/serial_ports/at_support/at_request.h" # include "aether/serial_ports/at_support/at_stage.h" # include "aether/serial_ports/serial_port_factory.h" -# include "aether/serial_ports/at_support/at_request.h" # include "aether/modems/modems_tele.h" @@ -793,8 +793,8 @@ WriteOperation* Thingy91xAtModem::WritePacket( return op; } -Thingy91xAtModem::DataEvent::Subscriber Thingy91xAtModem::data_event() { - return EventSubscriber{data_event_}; +Thingy91xAtModem::DataEvent const& Thingy91xAtModem::data_event() { + return data_event_; } ModemOperation* Thingy91xAtModem::SetPowerSaveParam( diff --git a/aether/modems/thingy91x_at_modem.h b/aether/modems/thingy91x_at_modem.h index 46648c68d..147a5f324 100644 --- a/aether/modems/thingy91x_at_modem.h +++ b/aether/modems/thingy91x_at_modem.h @@ -117,7 +117,7 @@ class Thingy91xAtModem final : public IModemDriver { WriteOperation* WritePacket(ConnectionIndex connect_index, std::span data) override; - DataEvent::Subscriber data_event() override; + DataEvent const& data_event() override; ModemOperation* SetPowerSaveParam(ModemPowerSaveParam const& psp) override; ModemOperation* PowerOff() override; diff --git a/aether/prepared_packet/packet_encoder.cpp b/aether/prepared_packet/packet_encoder.cpp index e8374e54b..f0083c005 100644 --- a/aether/prepared_packet/packet_encoder.cpp +++ b/aether/prepared_packet/packet_encoder.cpp @@ -6,8 +6,7 @@ #include "aether/crypto/ikey_provider.h" #include "aether/crypto/sync_crypto_provider.h" -#include "aether/api_protocol/api_context.h" -#include "aether/api_protocol/sub_api.h" +#include "aether/api_protocol/api_protocol.h" #include "aether/work_cloud_api/ae_message.h" #include "aether/work_cloud_api/work_server_api/authorized_api.h" @@ -28,7 +27,6 @@ class PreparedSendMessageKeyProvider final : public ISyncKeyProvider { private: PreparedSendMessage* block_; }; - } // namespace Result EncodePacket( @@ -53,21 +51,18 @@ Result EncodePacket( std::make_unique(*send_message); SyncEncryptProvider encrypt_provider{std::move(key_provider)}; - ProtocolContext protocol_context; - LoginApi login_api{protocol_context, encrypt_provider}; + LoginApi login_api{encrypt_provider}; + + auto login_context = ApiContext{login_api}; - auto api_context = ApiContext{login_api}; + auto auth_context = ApiContext{login_api.authorized_api()}; + auth_context->SendMessage( + AeMessage{send_message->destination_uid, DataBuffer{payload}}); - api_context->login_by_alias( - send_message->sender_ephemeral, - SubApi{ - [&](auto& auth_api) { - auth_api->send_message( - AeMessage{send_message->destination_uid, DataBuffer{payload}}); - }, - }); + login_context->LoginByAlias(send_message->sender_ephemeral, + std::move(auth_context)); - out = std::move(api_context).Pack(); + out = std::move(login_context); return Ok{out.size()}; } diff --git a/aether/registration/api/client_reg_api_safe.h b/aether/registration/api/client_reg_api_safe.h index 94900aac5..bf997916f 100644 --- a/aether/registration/api/client_reg_api_safe.h +++ b/aether/registration/api/client_reg_api_safe.h @@ -24,13 +24,10 @@ # include "aether/api_protocol/api_protocol.h" namespace ae { - -class ClientRegApiSafe final : public ApiClassImpl { +class ClientRegApiSafe final : public DeclareApi { public: - explicit ClientRegApiSafe(ProtocolContext& protocol_context); - - ReturnResultApi return_result; - AE_METHODS(ExtApi<&ClientRegApiSafe::return_result>); + // Here only return result api implemented, which is default + API_LIST() }; } // namespace ae diff --git a/aether/registration/api/client_reg_api_unsafe.cpp b/aether/registration/api/client_reg_api_unsafe.cpp index 5bae7e3c1..f7570d513 100644 --- a/aether/registration/api/client_reg_api_unsafe.cpp +++ b/aether/registration/api/client_reg_api_unsafe.cpp @@ -18,26 +18,23 @@ #if AE_SUPPORT_REGISTRATION namespace ae { -ClientRegRootApi::ClientRegRootApi(ProtocolContext& protocol_context, - IDecryptProvider& root_decrypt_provider, +ClientRegRootApi::ClientRegRootApi(IDecryptProvider& root_decrypt_provider, IDecryptProvider& global_decrypt_provider) - : ApiClassImpl{protocol_context}, - return_result{protocol_context}, - root_decrypt_provider_{&root_decrypt_provider}, - global_decrypt_provider_{&global_decrypt_provider}, - client_reg_api_{protocol_context}, - global_reg_client_api_{protocol_context} {} + : root_decrypt_provider_{&root_decrypt_provider}, + global_decrypt_provider_{&global_decrypt_provider} {} -void ClientRegRootApi::Enter(SubApiImpl sub_api) { - sub_api.Parse(client_reg_api_, [this](auto const& data) { - return root_decrypt_provider_->Decrypt(data); - }); +void ClientRegRootApi::Enter(SubApi sub_api) { + auto unsafe_data = + root_decrypt_provider_->Decrypt(std::move(sub_api).buffer()); + auto parser = ApiParser{server_context().protocol_context(), unsafe_data}; + parser.Parse(client_reg_api_); } -void ClientRegRootApi::EnterGlobal(SubApiImpl sub_api) { - sub_api.Parse(global_reg_client_api_, [this](auto const& data) { - return global_decrypt_provider_->Decrypt(data); - }); +void ClientRegRootApi::EnterGlobal(SubApi sub_api) { + auto unsafe_data = + global_decrypt_provider_->Decrypt(std::move(sub_api).buffer()); + auto parser = ApiParser{server_context().protocol_context(), unsafe_data}; + parser.Parse(global_reg_client_api_); } } // namespace ae diff --git a/aether/registration/api/client_reg_api_unsafe.h b/aether/registration/api/client_reg_api_unsafe.h index be649887d..e2bb3c62e 100644 --- a/aether/registration/api/client_reg_api_unsafe.h +++ b/aether/registration/api/client_reg_api_unsafe.h @@ -20,27 +20,22 @@ #include "aether/config.h" #if AE_SUPPORT_REGISTRATION -# include "aether/crypto/icrypto_provider.h" # include "aether/api_protocol/api_protocol.h" +# include "aether/crypto/icrypto_provider.h" # include "aether/registration/api/client_reg_api_safe.h" # include "aether/registration/api/global_reg_client_api.h" namespace ae { -class ClientRegRootApi final : public ApiClassImpl { +class ClientRegRootApi final : public DeclareApi { public: - explicit ClientRegRootApi(ProtocolContext& protocol_context, - IDecryptProvider& root_decrypt_provider, + explicit ClientRegRootApi(IDecryptProvider& root_decrypt_provider, IDecryptProvider& global_decrypt_provider); - void Enter(SubApiImpl sub_api); - void EnterGlobal(SubApiImpl sub_api); - - ReturnResultApi return_result; + void Enter(SubApi sub_api); + void EnterGlobal(SubApi sub_api); - AE_METHODS(RegMethod<03, &ClientRegRootApi::Enter>, - RegMethod<04, &ClientRegRootApi::EnterGlobal>, - ExtApi<&ClientRegRootApi::return_result>); + API_LIST(METHOD(3, Enter), METHOD(4, EnterGlobal)); private: IDecryptProvider* root_decrypt_provider_; diff --git a/aether/registration/api/global_reg_client_api.h b/aether/registration/api/global_reg_client_api.h index ef8480ca7..c0d20d39d 100644 --- a/aether/registration/api/global_reg_client_api.h +++ b/aether/registration/api/global_reg_client_api.h @@ -23,12 +23,10 @@ # include "aether/api_protocol/api_protocol.h" namespace ae { -class GlobalRegClientApi : public ApiClassImpl { +class GlobalRegClientApi : public DeclareApi { public: - explicit GlobalRegClientApi(ProtocolContext& protocol_context); - - ReturnResultApi return_result; - AE_METHODS(ExtApi<&GlobalRegClientApi::return_result>); + // Here only return result api implemented, which is default + API_LIST() }; } // namespace ae #endif diff --git a/aether/registration/api/global_reg_server_api.cpp b/aether/registration/api/global_reg_server_api.cpp index 79a4859aa..9c313eece 100644 --- a/aether/registration/api/global_reg_server_api.cpp +++ b/aether/registration/api/global_reg_server_api.cpp @@ -18,9 +18,12 @@ #if AE_SUPPORT_REGISTRATION namespace ae { -GlobalRegServerApi::GlobalRegServerApi(ProtocolContext& protocol_context) - : ApiClass{protocol_context}, - set_master_key{protocol_context}, - finish{protocol_context} {} +void GlobalRegServerApi::SetMasterKey(Key const& key) { + ClientMethod<&GlobalRegServerApi::SetMasterKey>(key); +} + +ApiPromise GlobalRegServerApi::Finish() { + return ClientMethod<&GlobalRegServerApi::Finish>(); +} } // namespace ae #endif diff --git a/aether/registration/api/global_reg_server_api.h b/aether/registration/api/global_reg_server_api.h index bff611ff2..4b4e1417b 100644 --- a/aether/registration/api/global_reg_server_api.h +++ b/aether/registration/api/global_reg_server_api.h @@ -22,11 +22,11 @@ #if AE_SUPPORT_REGISTRATION # include -# include "aether/types/uid.h" -# include "aether/crypto/key.h" -# include "aether/types/server_id.h" # include "aether-miscpp/reflect/reflect.h" # include "aether/api_protocol/api_protocol.h" +# include "aether/crypto/key.h" +# include "aether/types/server_id.h" +# include "aether/types/uid.h" namespace ae { @@ -37,12 +37,14 @@ struct RegistrationResponse { std::vector cloud; }; -class GlobalRegServerApi : public ApiClass { +class GlobalRegServerApi : public DeclareApi { public: - explicit GlobalRegServerApi(ProtocolContext& protocol_context); + GlobalRegServerApi() = default; + + void SetMasterKey(Key const& key); + ApiPromise Finish(); - Method<03, void(Key key)> set_master_key; - Method<04, ApiPromise()> finish; + API_LIST(METHOD(3, SetMasterKey), METHOD(4, Finish)); }; } // namespace ae diff --git a/aether/registration/api/registration_root_api.cpp b/aether/registration/api/registration_root_api.cpp index 872dd0ecc..6247b6b4f 100644 --- a/aether/registration/api/registration_root_api.cpp +++ b/aether/registration/api/registration_root_api.cpp @@ -18,18 +18,24 @@ #if AE_SUPPORT_REGISTRATION namespace ae { -RegistrationRootApi::RegistrationRootApi(ProtocolContext& protocol_context, - IEncryptProvider& root_encrypt, +RegistrationRootApi::RegistrationRootApi(IEncryptProvider& root_encrypt, IEncryptProvider& global_encrypt) - : ApiClass{protocol_context}, - get_asymmetric_public_key{protocol_context}, - enter{protocol_context, EnterProc{*this}}, - get_my_ip{protocol_context}, - enc_provider_{&root_encrypt}, - server_registration_api_{protocol_context, global_encrypt} {} + : enc_provider_{&root_encrypt}, server_registration_api_{global_encrypt} {} -DataBuffer RegistrationRootApi::Encrypt(DataBuffer const& data) const { - return enc_provider_->Encrypt(data); +ApiPromise RegistrationRootApi::GetPublicKey(CryptoLib crypto_lib) { + return ClientMethod<&RegistrationRootApi::GetPublicKey>(crypto_lib); +} + +void RegistrationRootApi::Enter(CryptoLib crypto_lib, + SubApi sub_api) { + auto server_api_data = enc_provider_->Encrypt( + std::move(sub_api)(server_registration_api_, client_context())); + ClientMethod<&RegistrationRootApi::Enter>(crypto_lib, + std::move(server_api_data)); +} + +ApiPromise RegistrationRootApi::GetMyIp() { + return ClientMethod<&RegistrationRootApi::GetMyIp>(); } } // namespace ae diff --git a/aether/registration/api/registration_root_api.h b/aether/registration/api/registration_root_api.h index 979e19ac6..26a837772 100644 --- a/aether/registration/api/registration_root_api.h +++ b/aether/registration/api/registration_root_api.h @@ -20,51 +20,29 @@ #include "aether/config.h" #if AE_SUPPORT_REGISTRATION -# include "aether/crypto/signed_key.h" # include "aether/crypto/crypto_definitions.h" - -# include "aether/types/data_buffer.h" # include "aether/crypto/icrypto_provider.h" -# include "aether/api_protocol/api_protocol.h" - -# include "aether/work_cloud_api/info_ip.h" +# include "aether/crypto/signed_key.h" +# include "aether/api_protocol/api_protocol.h" # include "aether/registration/api/server_registration_api.h" +# include "aether/work_cloud_api/info_ip.h" namespace ae { -class RegistrationRootApi : public ApiClass { - class EnterProc { - public: - explicit EnterProc(RegistrationRootApi& api) : api_{&api} {} - - auto operator()(CryptoLib crypto_lib, - SubApi const& sub_api) { - auto def_proc = DefaultArgProc{}; - return def_proc(crypto_lib, - api_->Encrypt(sub_api(api_->server_registration_api_))); - } - - private: - RegistrationRootApi* api_; - }; - +class RegistrationRootApi : public DeclareApi { public: - RegistrationRootApi(ProtocolContext& protocol_context, - IEncryptProvider& root_encrypt, + RegistrationRootApi(IEncryptProvider& root_encrypt, IEncryptProvider& global_encrypt); - Method<03, ApiPromise(CryptoLib crypto_lib)> - get_asymmetric_public_key; - Method<04, void(CryptoLib crypto_lib, SubApi sub_api), - EnterProc> - enter; + ApiPromise GetPublicKey(CryptoLib crypto_lib); + void Enter(CryptoLib crypto_lib, SubApi sub_api); - Method<6, ApiPromise()> get_my_ip; + ApiPromise GetMyIp(); - private: - DataBuffer Encrypt(DataBuffer const& data) const; + API_LIST(METHOD(3, GetPublicKey), METHOD(4, Enter), METHOD(6, GetMyIp)) + private: IEncryptProvider* enc_provider_; ServerRegistrationApi server_registration_api_; }; diff --git a/aether/registration/api/server_registration_api.cpp b/aether/registration/api/server_registration_api.cpp index e494d47d5..a4499c5de 100644 --- a/aether/registration/api/server_registration_api.cpp +++ b/aether/registration/api/server_registration_api.cpp @@ -19,18 +19,32 @@ #if AE_SUPPORT_REGISTRATION namespace ae { -ServerRegistrationApi::ServerRegistrationApi(ProtocolContext& protocol_context, - IEncryptProvider& encrypt_provider) - : ApiClass{protocol_context}, - registration{protocol_context, RegistrationProc{*this}}, - request_proof_of_work_data{protocol_context}, - resolve_servers{protocol_context}, - set_return_key{protocol_context}, - encrypt_provider_{&encrypt_provider}, - global_reg_server_api_{protocol_context} {} - -DataBuffer ServerRegistrationApi::Encrypt(DataBuffer const& data) const { - return encrypt_provider_->Encrypt(data); +ServerRegistrationApi::ServerRegistrationApi(IEncryptProvider& encrypt_provider) + : encrypt_provider_{&encrypt_provider}, global_reg_server_api_{} {} + +void ServerRegistrationApi::Registration( + std::string const& salt, std::string const& password_suffix, + std::vector const& passwords, Uid const& parent_uid_, + SubApi sub_api) { + auto gr_data = encrypt_provider_->Encrypt( + std::move(sub_api)(global_reg_server_api_, client_context())); + ClientMethod<&ServerRegistrationApi::Registration>( + salt, password_suffix, passwords, parent_uid_, std::move(gr_data)); +} + +ApiPromise ServerRegistrationApi::RequestProofOfWorkData( + Uid const& parent_id, PowMethod pow_method) { + return ClientMethod<&ServerRegistrationApi::RequestProofOfWorkData>( + parent_id, pow_method); +} + +ApiPromise> ServerRegistrationApi::ResolveServers( + std::vector const& servers) { + return ClientMethod<&ServerRegistrationApi::ResolveServers>(servers); +} + +void ServerRegistrationApi::SetReturnKey(Key const& key) { + ClientMethod<&ServerRegistrationApi::SetReturnKey>(key); } } // namespace ae diff --git a/aether/registration/api/server_registration_api.h b/aether/registration/api/server_registration_api.h index 18c6f6309..ef67370de 100644 --- a/aether/registration/api/server_registration_api.h +++ b/aether/registration/api/server_registration_api.h @@ -24,19 +24,18 @@ # include # include -# include "aether/types/uid.h" +# include "aether-miscpp/reflect/reflect.h" + +# include "aether/crypto/crypto_definitions.h" +# include "aether/crypto/icrypto_provider.h" # include "aether/crypto/key.h" +# include "aether/crypto/signed_key.h" # include "aether/types/server_id.h" -# include "aether/crypto/crypto_definitions.h" +# include "aether/types/uid.h" -# include "aether-miscpp/reflect/reflect.h" -# include "aether/crypto/signed_key.h" -# include "aether/types/data_buffer.h" -# include "aether/crypto/icrypto_provider.h" # include "aether/api_protocol/api_protocol.h" -# include "aether/work_cloud_api/server_descriptor.h" - # include "aether/registration/api/global_reg_server_api.h" +# include "aether/work_cloud_api/server_descriptor.h" namespace ae { struct PowParams { @@ -49,48 +48,26 @@ struct PowParams { SignedKey global_key; }; -class ServerRegistrationApi : public ApiClass { - class RegistrationProc { - public: - explicit RegistrationProc(ServerRegistrationApi& api) : api_{&api} {} - - template - auto operator()(std::string&& salt, std::string&& password_suffix, - std::vector&& passwords, Uid parent_uid_, - SubApi const& sub_api) { - auto def_proc = DefaultArgProc{}; - return def_proc(std::move(salt), std::move(password_suffix), - std::move(passwords), std::move(parent_uid_), - api_->Encrypt(sub_api(api_->global_reg_server_api_))); - } - - private: - ServerRegistrationApi* api_; - }; - +class ServerRegistrationApi : public DeclareApi { public: - ServerRegistrationApi(ProtocolContext& protocol_context, - IEncryptProvider& encrypt_provider); + explicit ServerRegistrationApi(IEncryptProvider& encrypt_provider); - Method<3, - void(std::string salt, std::string password_suffix, - std::vector passwords, Uid parent_uid_, - SubApi sub_api), - RegistrationProc> - registration; + void Registration(std::string const& salt, std::string const& password_suffix, + std::vector const& passwords, + Uid const& parent_uid_, SubApi sub_api); - Method<4, ApiPromise(Uid parent_id, PowMethod pow_method)> - request_proof_of_work_data; + ApiPromise RequestProofOfWorkData(Uid const& parent_id, + PowMethod pow_method); - Method<5, ApiPromise>( - std::vector servers)> - resolve_servers; + ApiPromise> ResolveServers( + std::vector const& servers); - Method<6, void(Key key)> set_return_key; + void SetReturnKey(Key const& key); - private: - DataBuffer Encrypt(DataBuffer const& data) const; + API_LIST(METHOD(3, Registration), METHOD(4, RequestProofOfWorkData), + METHOD(5, ResolveServers), METHOD(6, SetReturnKey)) + private: IEncryptProvider* encrypt_provider_; GlobalRegServerApi global_reg_server_api_; }; diff --git a/aether/registration/registration.cpp b/aether/registration/registration.cpp index 06130e4a7..5c231c828 100644 --- a/aether/registration/registration.cpp +++ b/aether/registration/registration.cpp @@ -18,11 +18,12 @@ #if AE_SUPPORT_REGISTRATION +# include # include # include "aether-miscpp/misc/override.h" # include "aether/aether.h" -# include "aether/api_protocol/api_context.h" +# include "aether/api_protocol/api_protocol.h" # include "aether/api_protocol/make_api_call_sender.h" # include "aether/crypto.h" # include "aether/crypto/crypto_definitions.h" @@ -39,20 +40,22 @@ namespace ae { Registration::Registration(AeContext const& ae_context, Ptr const& reg_cloud, Uid parent_uid) - : ae_context_{ae_context}, + : Action{ae_context}, + ae_context_{ae_context}, parent_uid_{std::move(parent_uid)}, + protocol_context_{ae_context_}, root_crypto_provider_{}, global_crypto_provider_{}, - client_root_api_{protocol_context_, *root_crypto_provider_.decryptor(), + client_root_api_{*root_crypto_provider_.decryptor(), *global_crypto_provider_.decryptor()}, - server_reg_root_api_{protocol_context_, - *root_crypto_provider_.encryptor(), + server_reg_root_api_{*root_crypto_provider_.encryptor(), *global_crypto_provider_.encryptor()}, root_server_select_stream_{ae_context_, reg_cloud}, // TODO: add configuration response_timeout_{std::chrono::seconds(20)}, sign_pk_{Crypto::ptr{ae_context_.aether().crypto} - ->signs_pk_[kDefaultSignatureMethod]} { + ->signs_pk_[kDefaultSignatureMethod]}, + registration_event_{ae_context_} { AE_TELE_INFO(RegisterStarted); // parent uid must not be empty @@ -64,8 +67,8 @@ Registration::Registration(AeContext const& ae_context, Registration::~Registration() { AE_TELED_DEBUG("~Registration"); } -Registration::RegistrationEvent::Subscriber Registration::registration() { - return EventSubscriber{registration_event_}; +Registration::RegistrationEvent const& Registration::registration() { + return registration_event_; } void Registration::InitConnection() { @@ -93,173 +96,156 @@ void Registration::InitConnection() { void Registration::Restream() { root_server_select_stream_.Restream(); } auto Registration::GetKeys() { - return make_api_call( - server_reg_root_api_, root_server_select_stream_, - [&, s{Subscription{}}](ApiContext& api_call, - auto& r) mutable noexcept { - AE_TELE_INFO(RegisterGetKeys, "GetKeys"); - s = api_call->get_asymmetric_public_key(kDefaultCryptoLibProfile) - .Subscribe([&](auto&& res) mutable noexcept { - if (!res) { - ex::set_error(std::move(r), res.error()); - return; - } - auto signed_key = std::forward(res).value(); - if (!CryptoSignVerify(signed_key.sign, signed_key.key, - sign_pk_)) { - AE_TELE_ERROR(RegisterGetKeysVerificationFailed, - "Sign verification failed"); - ex::set_error(std::move(r), 1); - return; - } - server_pub_key_ = std::move(signed_key.key); - root_crypto_provider_.SetEncryptionKey(server_pub_key_); - AE_TELED_INFO("Key received"); - ex::set_value(std::move(r), Ignore{}); - }); - + return make_api_call( + server_reg_root_api_, protocol_context_, + root_server_select_stream_, + [this](ApiContext& api_call) { + AE_TELE_INFO(RegisterGetKeys, "GetKeys"); + auto promise = api_call->GetPublicKey(kDefaultCryptoLibProfile); # if DEBUG - // For debug, call also for get my ip method to print our public ip - // visible to registration server - api_call->get_my_ip().Subscribe([](auto&& res) { - if (res) { - auto& iip = res.value(); - AE_TELED_INFO("Registration our public ip: {}:{}, coords: {},{}", - iip.ip, iip.port, iip.latitude, iip.longitude); - } else { - AE_TELED_ERROR("Get my ip failed"); - } - }); + // For debug, call also for get my ip method to print our public + // ip visible to registration server + get_my_ip_subscription_ = + api_call->GetMyIp().Subscribe([](auto&& res) { + if (res) { + auto& iip = res.value(); + AE_TELED_INFO( + "Registration our public ip: {}:{}, coords: {},{}", + iip.ip, iip.port, iip.latitude, iip.longitude); + } else { + AE_TELED_ERROR("Get my ip failed"); + } + }); # endif - }); + return promise; + }) | + ex::let_value([this](auto&& signed_key) noexcept { + return ex::create( + [this, signed_key = std::forward( + signed_key)](auto& ctx) mutable noexcept { + if (!CryptoSignVerify(signed_key.sign, signed_key.key, + sign_pk_)) { + AE_TELE_ERROR(RegisterGetKeysVerificationFailed, + "Sign verification failed"); + ex::set_error(std::move(ctx.receiver), std::int32_t{1}); + return; + } + server_pub_key_ = std::move(signed_key.key); + root_crypto_provider_.SetEncryptionKey(server_pub_key_); + AE_TELED_INFO("Key received"); + ex::set_value(std::move(ctx.receiver)); + }); + }); } auto Registration::RequestPowParams() { - return make_api_call( - server_reg_root_api_, root_server_select_stream_, - [&, s{Subscription{}}](ApiContext& api_call, - auto& r) mutable noexcept { - AE_TELE_INFO(RegisterRequestPowParams, "Request proof of work params"); - api_call->enter( - kDefaultCryptoLibProfile, - SubApi{[&](ApiContext& server_api) { - server_api->set_return_key( - root_crypto_provider_.GetDecryptionKey()); - s = server_api - ->request_proof_of_work_data(parent_uid_, - PowMethod::kBCryptCrc32) - .Subscribe([&](auto&& res) mutable noexcept { - if (!res) { - ex::set_error(std::move(r), res.error()); - return; - } - - auto pow_params = - std::forward(res).value(); - if (!CryptoSignVerify(pow_params.global_key.sign, - pow_params.global_key.key, - sign_pk_)) { - AE_TELE_ERROR( - RegisterPowParamsVerificationFailed, - "Proof of work params sign verification failed"); - ex::set_error(std::move(r), 2); - return; - } - - aether_global_key_ = pow_params.global_key.key; - pow_params_.salt = pow_params.salt; - pow_params_.max_hash_value = pow_params.max_hash_value; - pow_params_.password_suffix = - pow_params.password_suffix; - pow_params_.pool_size = pow_params.pool_size; - - global_crypto_provider_.SetEncryptionKey( - aether_global_key_); - master_key_ = - global_crypto_provider_.GetDecryptionKey(); - - ex::set_value(std::move(r), Ignore{}); - }); - }}); - }); + return make_api_call( + server_reg_root_api_, protocol_context_, + root_server_select_stream_, + [this](ApiContext& api_call) { + AE_TELE_INFO(RegisterRequestPowParams, + "Request proof of work params"); + ApiPromise promise; + api_call->Enter( + kDefaultCryptoLibProfile, + [&promise, + this](ApiContext& server_api) { + server_api->SetReturnKey( + root_crypto_provider_.GetDecryptionKey()); + promise = server_api->RequestProofOfWorkData( + parent_uid_, PowMethod::kBCryptCrc32); + }); + return promise; + }) | + ex::let_value([this](auto&& pow_params) noexcept { + return ex::create( + [this, pow_params = std::forward( + pow_params)](auto& ctx) mutable noexcept { + if (!CryptoSignVerify(pow_params.global_key.sign, + pow_params.global_key.key, sign_pk_)) { + AE_TELE_ERROR( + RegisterPowParamsVerificationFailed, + "Proof of work params sign verification failed"); + ex::set_error(std::move(ctx.receiver), std::int32_t{2}); + return; + } + + aether_global_key_ = pow_params.global_key.key; + pow_params_.salt = pow_params.salt; + pow_params_.max_hash_value = pow_params.max_hash_value; + pow_params_.password_suffix = pow_params.password_suffix; + pow_params_.pool_size = pow_params.pool_size; + global_crypto_provider_.SetEncryptionKey(aether_global_key_); + master_key_ = global_crypto_provider_.GetDecryptionKey(); + ex::set_value(std::move(ctx.receiver)); + }); + }); } auto Registration::MakeRegistration() { - return make_api_call( - server_reg_root_api_, root_server_select_stream_, - [&, s{Subscription{}}](ApiContext& api_call, - auto& r) mutable noexcept { - AE_TELE_INFO(RegisterMakeRegistration, "Make registration"); - - // Proof calculation - auto proofs = ProofOfWork::ComputeProofOfWork( - pow_params_.pool_size, pow_params_.salt, - pow_params_.password_suffix, pow_params_.max_hash_value); - - AE_TELE_DEBUG(RegisterKeysGenerated, - "Global Pk: {}:{}, Masterkey: {}:{}", - aether_global_key_.Index(), aether_global_key_, - master_key_.Index(), master_key_); - - api_call->enter( - kDefaultCryptoLibProfile, - SubApi{[&](ApiContext& server_api) { - server_api->registration( - pow_params_.salt, pow_params_.password_suffix, proofs, - parent_uid_, - SubApi{[&](ApiContext& global_api) { - // set master key and wait for confirmation - global_api->set_master_key(master_key_); - s = global_api->finish().Subscribe( - [&, r{std::forward(r)}]( - auto&& res) mutable noexcept { - if (!res) { - ex::set_error(std::move(r), res.error()); - return; - } - AE_TELE_INFO(RegisterRegistrationConfirmed, - "Registration confirmed"); - auto reg_info = - std::forward(res).value(); - client_uid_ = reg_info.uid; - ephemeral_uid_ = reg_info.ephemeral_uid; - // use client cloud_ for cloud resolve request - client_cloud_ = std::move(reg_info.cloud); - assert((client_cloud_.size() > 0) && - "Client's cloud is empty"); - ex::set_value(std::move(r), Ignore{}); - }); - }}); - }}); - }); + return make_api_call( + server_reg_root_api_, protocol_context_, + root_server_select_stream_, + [this](ApiContext& api_call) { + AE_TELE_INFO(RegisterMakeRegistration, "Make registration"); + + // Proof calculation + auto proofs = ProofOfWork::ComputeProofOfWork( + pow_params_.pool_size, pow_params_.salt, + pow_params_.password_suffix, pow_params_.max_hash_value); + + AE_TELE_DEBUG(RegisterKeysGenerated, + "Global Pk: {}:{}, Masterkey: {}:{}", + aether_global_key_.Index(), aether_global_key_, + master_key_.Index(), master_key_); + + ApiPromise promise; + api_call->Enter( + kDefaultCryptoLibProfile, + SubApi{[this, &promise, &proofs]( + ApiContext& server_api) { + server_api->Registration( + pow_params_.salt, pow_params_.password_suffix, proofs, + parent_uid_, + [this, &promise]( + ApiContext& global_api) { + // set master key and wait for confirmation + global_api->SetMasterKey(master_key_); + promise = global_api->Finish(); + }); + }}); + return promise; + }) | + ex::then([this](auto&& reg_info) noexcept { + AE_TELE_INFO(RegisterRegistrationConfirmed, + "Registration confirmed"); + client_uid_ = reg_info.uid; + ephemeral_uid_ = reg_info.ephemeral_uid; + client_cloud_ = std::move(reg_info.cloud); + assert(!client_cloud_.empty() && "Client's cloud is empty"); + }); } auto Registration::ResolveCloud() { - return make_api_call( - server_reg_root_api_, root_server_select_stream_, - [&, s{Subscription{}}](ApiContext& api_call, - auto& r) mutable noexcept { - AE_TELE_INFO(RegisterResolveCloud, "Resolve cloud as [{}]", - client_cloud_); - - api_call->enter( - kDefaultCryptoLibProfile, - SubApi{[&](ApiContext& - server_api) mutable noexcept { - s = server_api->resolve_servers(client_cloud_) - .Subscribe([&](auto&& res) { - if (res) { - ex::set_value( - std::move(r), - OnCloudResolved( - std::forward(res).value())); - } else { - ex::set_error(std::move(r), res.error()); - } - }); - }}); - }); + return make_api_call( + server_reg_root_api_, protocol_context_, + root_server_select_stream_, + [this](ApiContext& api_call) { + AE_TELE_INFO(RegisterResolveCloud, "Resolve cloud as [{}]", + client_cloud_); + + ApiPromise> promise; + api_call->Enter( + kDefaultCryptoLibProfile, + [this, &promise]( + ApiContext& server_api) noexcept { + promise = server_api->ResolveServers(client_cloud_); + }); + return promise; + }) | + ex::then([this](auto&& servers) noexcept { + return OnCloudResolved(servers); + }); } ClientConfig Registration::OnCloudResolved( @@ -326,23 +312,24 @@ void Registration::Run() { return ex::for_continue; }}); }) | - ex::let_stopped([&]() noexcept { return ex::just_error(1); }) | + ex::let_stopped( + [&]() noexcept { return ex::just_error(std::int32_t{1}); }) | // perform the rest of the registration process - ex::let_value([&](auto&&) noexcept { return RequestPowParams(); }) | - ex::let_value([&](auto&&) noexcept { return MakeRegistration(); }) | - ex::let_value([&](auto&&) noexcept { return ResolveCloud(); }) | + ex::let_value([&]() noexcept { return RequestPowParams(); }) | + ex::let_value([&]() noexcept { return MakeRegistration(); }) | + ex::let_value([&]() noexcept { return ResolveCloud(); }) | ex::let_error(Override{ - [&](std::uint32_t e) noexcept { + [&](std::int32_t e) noexcept { AE_TELED_ERROR("Registration failed with error code: {}", e); - return ex::just_error(static_cast(e)); + return ex::just_error(e); }, [&](WriteFailed) noexcept { AE_TELED_ERROR("Registration failed with write error"); - return ex::just_error(-1); + return ex::just_error(std::int32_t{-1}); }, [&](auto&&...) noexcept { AE_TELED_ERROR("Registration failed"); - return ex::just_error(-1); + return ex::just_error(std::int32_t{-1}); }}); waiter_.emplace(ae_context_, std::move(s), diff --git a/aether/registration/registration.h b/aether/registration/registration.h index 6b65fa9ac..115c42eea 100644 --- a/aether/registration/registration.h +++ b/aether/registration/registration.h @@ -21,22 +21,21 @@ #if AE_SUPPORT_REGISTRATION -# include # include +# include -# include "aether/types/uid.h" # include "aether/crypto/key.h" # include "aether/events/events.h" -# include "aether/types/server_id.h" -# include "aether/types/client_config.h" # include "aether/executors/executors.h" -# include "aether-miscpp/types/small_function.h" +# include "aether/types/client_config.h" +# include "aether/types/server_id.h" +# include "aether/types/uid.h" -# include "aether/registration_cloud.h" # include "aether/registration/api/client_reg_api_unsafe.h" # include "aether/registration/api/registration_root_api.h" -# include "aether/registration/root_server_select_stream.h" # include "aether/registration/registration_crypto_provider.h" +# include "aether/registration/root_server_select_stream.h" +# include "aether/registration_cloud.h" namespace ae { class Registration : Action { @@ -47,7 +46,7 @@ class Registration : Action { Ptr const& reg_cloud, Uid parent_uid); ~Registration() override; - RegistrationEvent::Subscriber registration(); + RegistrationEvent const& registration(); private: void InitConnection(); @@ -80,6 +79,10 @@ class Registration : Action { Uid client_uid_; Uid ephemeral_uid_; +# if DEBUG + Subscription get_my_ip_subscription_; +# endif + std::optional> waiter_; diff --git a/aether/registration/root_server_select_stream.cpp b/aether/registration/root_server_select_stream.cpp index 6b520b3d6..ae4dc2ddc 100644 --- a/aether/registration/root_server_select_stream.cpp +++ b/aether/registration/root_server_select_stream.cpp @@ -18,6 +18,8 @@ #if AE_SUPPORT_REGISTRATION +# include "aether-miscpp/types/method_ptr.h" + # include "aether/tele.h" namespace ae { @@ -26,7 +28,11 @@ RootServerSelectStream::RootServerSelectStream( : ae_context_{ae_context}, cloud_{cloud}, buffer_write_{ae_context, - MethodPtr<&RootServerSelectStream::OnWrite>{this}} { + MethodPtr<&RootServerSelectStream::OnWrite>{this}}, + stream_update_event_{ae_context_}, + out_data_event_{ae_context_}, + server_changed_event_{ae_context_}, + cloud_error_event_{ae_context_} { SelectServer(); } @@ -38,12 +44,12 @@ StreamInfo RootServerSelectStream::stream_info() const { return server_connection_ ? server_connection_->stream_info() : StreamInfo{}; } -RootServerSelectStream::StreamUpdateEvent::Subscriber +RootServerSelectStream::StreamUpdateEvent const& RootServerSelectStream::stream_update_event() { return stream_update_event_; } -RootServerSelectStream::OutDataEvent::Subscriber +RootServerSelectStream::OutDataEvent const& RootServerSelectStream::out_data_event() { return out_data_event_; } @@ -57,12 +63,12 @@ void RootServerSelectStream::Restream() { CloudError(); } -RootServerSelectStream::ServerChangedEvent::Subscriber +RootServerSelectStream::ServerChangedEvent const& RootServerSelectStream::server_changed_event() { return server_changed_event_; } -RootServerSelectStream::CloudErrorEvent::Subscriber +RootServerSelectStream::CloudErrorEvent const& RootServerSelectStream::cloud_error_event() { return cloud_error_event_; } @@ -103,7 +109,8 @@ void RootServerSelectStream::SelectServer() { server_connection_.emplace(ae_context_, chosen_server.server.Load()); - server_connection_->out_data_event().Subscribe(out_data_event_); + server_connection_->out_data_event().Subscribe( + MethodPtr<&OutDataEvent::Emit>{&out_data_event_}); server_connection_->server_error_event().Subscribe( MethodPtr<&RootServerSelectStream::ServerError>{this}); diff --git a/aether/registration/root_server_select_stream.h b/aether/registration/root_server_select_stream.h index ad7a0bb6b..94fe67ae5 100644 --- a/aether/registration/root_server_select_stream.h +++ b/aether/registration/root_server_select_stream.h @@ -46,12 +46,12 @@ class RootServerSelectStream final : public ByteIStream { WriteAction& Write(DataBuffer&& data) override; StreamInfo stream_info() const override; - StreamUpdateEvent::Subscriber stream_update_event() override; - OutDataEvent::Subscriber out_data_event() override; + StreamUpdateEvent const& stream_update_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; - ServerChangedEvent::Subscriber server_changed_event(); - CloudErrorEvent::Subscriber cloud_error_event(); + ServerChangedEvent const& server_changed_event(); + CloudErrorEvent const& cloud_error_event(); private: static constexpr auto kBufferCapacity = AE_ROOT_REG_SERVER_BUFFER_CAPACITY; diff --git a/aether/registration_cloud.cpp b/aether/registration_cloud.cpp index 5c539bd0e..d01ec25f7 100644 --- a/aether/registration_cloud.cpp +++ b/aether/registration_cloud.cpp @@ -31,13 +31,21 @@ RegistrationCloud::RegistrationCloud(ObjProp prop, ObjPtr aether) void RegistrationCloud::AddServerSettings(Endpoint address) { // don't care about server id for registration - auto server = aether_.WithLoaded([&](auto const& aether) { - return Server::ptr::Create(domain, ServerId{0}, - std::vector{std::move(address)}, - aether->adapter_registry); + auto sit = servers().find(ServerId{0}); + if (sit == servers().end()) { + auto server = aether_.WithLoaded([&](auto const& aether) { + return Server::ptr::Create(domain, ServerId{0}, + std::vector{std::move(address)}, + aether->adapter_registry); + }); + assert(server.has_value() && "Server must be created"); + AddServer(server.value()); + return; + } + sit->second.server.WithLoaded([&](auto const& s) { + s->endpoints.push_back(std::move(address)); + s->Register(); }); - assert(server.has_value() && "Server must be created"); - AddServer(server.value()); } } // namespace ae diff --git a/aether/safe_stream/details/safe_stream_api.h b/aether/safe_stream/details/safe_stream_api.h index 600cf2167..c59302bd7 100644 --- a/aether/safe_stream/details/safe_stream_api.h +++ b/aether/safe_stream/details/safe_stream_api.h @@ -19,48 +19,23 @@ #include -#include "aether/types/data_buffer.h" #include "aether/api_protocol/api_protocol.h" +#include "aether/types/data_buffer.h" namespace ae { -class SafeStreamApi : public ApiClassImpl { +class ISafeStreamApi : public DeclareApi { public: - explicit SafeStreamApi(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, - ack{protocol_context}, - request_repeat{protocol_context}, - send_reset{protocol_context}, - send{protocol_context} {} - - virtual ~SafeStreamApi() = default; - - Method<3, void(std::uint16_t index)> ack; - Method<4, void(std::uint16_t index)> request_repeat; - Method<5, void(std::uint16_t index, std::uint16_t delta_offset, - std::uint8_t repeat_count, DataBuffer data)> - send_reset; - Method<6, void(std::uint16_t index, std::uint16_t delta_offset, - std::uint8_t repeat_count, DataBuffer data)> - send; + virtual ~ISafeStreamApi() = default; - /** - * \brief Acknowledgment for data buffer up to index - */ - virtual void AckImpl(std::uint16_t index) = 0; - /** - * \brief Request receive data from the index - */ - virtual void RequestRepeatImpl(std::uint16_t index) = 0; - virtual void SendResetImpl(std::uint16_t begin_offset, - std::uint16_t delta_offset, - std::uint8_t repeat_count, DataBuffer data) = 0; - virtual void SendImpl(std::uint16_t begin_offset, std::uint16_t delta_offset, - std::uint8_t repeat_count, DataBuffer data) = 0; + virtual void Ack(std::uint16_t index) = 0; + virtual void RequestRepeat(std::uint16_t index) = 0; + virtual void SendReset(std::uint16_t index, std::uint16_t delta_offset, + std::uint8_t repeat_count, DataBuffer data) = 0; + virtual void Send(std::uint16_t index, std::uint16_t delta_offset, + std::uint8_t repeat_count, DataBuffer data) = 0; - AE_METHODS(RegMethod<3, &SafeStreamApi::AckImpl>, - RegMethod<4, &SafeStreamApi::RequestRepeatImpl>, - RegMethod<5, &SafeStreamApi::SendResetImpl>, - RegMethod<6, &SafeStreamApi::SendImpl>); + API_LIST(METHOD(3, Ack), METHOD(4, RequestRepeat), METHOD(5, SendReset), + METHOD(6, Send)) }; } // namespace ae diff --git a/aether/safe_stream/details/safe_stream_recv_action.h b/aether/safe_stream/details/safe_stream_recv_action.h index 00283ee50..0c0865ce7 100644 --- a/aether/safe_stream/details/safe_stream_recv_action.h +++ b/aether/safe_stream/details/safe_stream_recv_action.h @@ -21,7 +21,6 @@ #include "aether/ae_context.h" #include "aether/common.h" -#include "aether/events/events.h" #include "aether/safe_stream/details/circular_buffer.h" #include "aether/safe_stream/details/receiving_chunk_list.h" #include "aether/safe_stream/details/safe_stream_data_message.h" @@ -30,12 +29,13 @@ #include "aether/tele.h" namespace ae { -class ISendAckRepeat { +class ISafeStreamRecvActionDelegate { public: - virtual ~ISendAckRepeat() = default; + virtual ~ISafeStreamRecvActionDelegate() = default; virtual void SendAck(std::uint16_t index) = 0; virtual void SendRepeatRequest(std::uint16_t index) = 0; + virtual void ReceiveData(DataBuffer& data) = 0; }; template @@ -47,13 +47,11 @@ class SafeStreamRecvAction { using IndexRangeType = CircularBufferImpl::index_range_type; using ReceiveChunkListImpl = ReceiveChunkList; - using ReceiveEvent = Event; - SafeStreamRecvAction(AeContext const& ae_context, - ISendAckRepeat& send_ack_repeat, + ISafeStreamRecvActionDelegate& delegate, SafeStreamConfig const& config) : ae_context_{ae_context}, - send_ack_repeat_{&send_ack_repeat}, + delegate_{&delegate}, send_ack_timeout_{config.send_ack_timeout}, send_repeat_timeout_{config.send_repeat_timeout}, window_size_{config.window_size} { @@ -92,10 +90,6 @@ class SafeStreamRecvAction { HandleData(received_index, data_message.repeat_count, data_message.data); } - ReceiveEvent::Subscriber receive_event() { - return EventSubscriber{receive_event_}; - } - private: void HandleData(IndexType received_index, std::uint8_t repeat_count, DataBuffer const& data) { @@ -202,7 +196,7 @@ class SafeStreamRecvAction { AE_TELED_DEBUG( "Emitted received data range: {}-{} size: {}, last_emitted_: {}", recv_range.left, recv_range.right, data_buffer.size(), last_emitted_); - receive_event_.Emit(std::move(data_buffer)); + delegate_->ReceiveData(data_buffer); chunks_->Acknowledge(recv_range.right); buffer_.Erase(recv_range.right + 1); @@ -218,7 +212,7 @@ class SafeStreamRecvAction { void HandleAcknowledgement() { auto ack_offset = static_cast(last_emitted_); AE_TELED_DEBUG("Send acknowledgement for offset: {}", ack_offset); - send_ack_repeat_->SendAck(static_cast(ack_offset)); + delegate_->SendAck(static_cast(ack_offset)); } void HandleMissing() { @@ -229,8 +223,7 @@ class SafeStreamRecvAction { AE_TELED_DEBUG("Send repeat request for offset range {}-{}", res->left, res->right); auto request_offset = static_cast(res->left); - send_ack_repeat_->SendRepeatRequest( - static_cast(request_offset)); + delegate_->SendRepeatRequest(static_cast(request_offset)); // enqueue again missing_timer_.Reset(); EnqueueMissing(); @@ -238,7 +231,7 @@ class SafeStreamRecvAction { } AeContext ae_context_; - ISendAckRepeat* send_ack_repeat_; + ISafeStreamRecvActionDelegate* delegate_; Duration send_ack_timeout_; Duration send_repeat_timeout_; @@ -252,8 +245,6 @@ class SafeStreamRecvAction { TaskSubscription recv_enqueued_; TaskSubscription ack_timer_; TaskSubscription missing_timer_; - - ReceiveEvent receive_event_; }; } // namespace ae diff --git a/aether/safe_stream/details/safe_stream_send_action.h b/aether/safe_stream/details/safe_stream_send_action.h index 6463f5329..1301ecb9d 100644 --- a/aether/safe_stream/details/safe_stream_send_action.h +++ b/aether/safe_stream/details/safe_stream_send_action.h @@ -23,7 +23,6 @@ #include "aether/common.h" #include "aether/config.h" #include "aether/events/events.h" -#include "aether/events/multi_subscription.h" #include "aether/safe_stream/details/circular_buffer.h" #include "aether/safe_stream/details/safe_stream_data_message.h" #include "aether/safe_stream/details/sending_chunk_list.h" @@ -48,11 +47,18 @@ static inline Num RandomOffset() { } } // namespace safe_stream_send_action_internal -class ISendDataPush { +template +class ISafeStreamSendActionDelegate { public: - virtual ~ISendDataPush() = default; + using IndexType = RingIndex; + + virtual ~ISafeStreamSendActionDelegate() = default; virtual WriteAction& PushData(std::uint16_t index, DataMessage&& data_message) = 0; + virtual void WriteAcknowledged(IndexType buffer_begin, + IndexType ack_index) = 0; + virtual void WriteStopped(IndexType buffer_begin, IndexType stop_index) = 0; + virtual void WriteFailed(IndexType buffer_begin, IndexType failed_index) = 0; }; // TODO: split on templated and not templated parts @@ -68,15 +74,11 @@ class SafeStreamSendAction { using IndexRangeType = RingIndexRange; using SendingChunkListImpl = SendingChunkList; - using AcknowledgedEvent = Event; - using SendFailedEvent = Event; - using StoppedEvent = Event; - SafeStreamSendAction(AeContext const& ae_context, - ISendDataPush& send_data_push, + ISafeStreamSendActionDelegate& delegate, SafeStreamConfig const& config) : ae_context_{ae_context}, - send_data_push_{&send_data_push}, + delegate_{&delegate}, max_repeat_count_{config.max_repeat_count}, window_size_{config.window_size}, sending_buffer_{ @@ -112,7 +114,7 @@ class SafeStreamSendAction { return; } AE_TELED_DEBUG("Stop range {}-{}", range.left, range.right); - stopped_event_.Emit(sending_buffer_.begin(), range.right); + delegate_->WriteStopped(sending_buffer_.begin(), range.right); sending_buffer_.EraseFrom(range.left); } @@ -138,7 +140,7 @@ class SafeStreamSendAction { if (IndexComparable{last_sent_, sending_buffer_.begin()} < confirm_index) { last_sent_ = confirm_index + 1; } - acknowledged_event_.Emit(sending_buffer_.begin(), confirm_index); + delegate_->WriteAcknowledged(sending_buffer_.begin(), confirm_index); sending_buffer_.Erase(confirm_index + 1); sending_chunks_.RemoveUpTo(confirm_index); sending_chunks_.set_buffer_begin(sending_buffer_.begin()); @@ -169,16 +171,6 @@ class SafeStreamSendAction { EnqueueSend(); } - AcknowledgedEvent::Subscriber acknowledged_event() { - return EventSubscriber{acknowledged_event_}; - } - StoppedEvent::Subscriber stopped_event() { - return EventSubscriber{stopped_event_}; - } - SendFailedEvent::Subscriber send_failed_event() { - return EventSubscriber{send_failed_event_}; - } - private: void EnqueueSend() { if (send_enqueued_) { @@ -343,7 +335,7 @@ class SafeStreamSendAction { void RejectSend(IndexType end_index) { // send failed for a chunk - send_failed_event_.Emit(sending_buffer_.begin(), end_index); + delegate_->WriteFailed(sending_buffer_.begin(), end_index); sending_buffer_.Erase(end_index + 1); sending_chunks_.RemoveUpTo(end_index); @@ -367,7 +359,7 @@ class SafeStreamSendAction { data_buffer.begin() + static_cast(dspan.first.size())); - return send_data_push_->PushData( + return delegate_->PushData( static_cast(sending_buffer_.begin()), DataMessage{ init_state_, @@ -379,7 +371,7 @@ class SafeStreamSendAction { } AeContext ae_context_; - ISendDataPush* send_data_push_; + ISafeStreamSendActionDelegate* delegate_; std::uint8_t max_repeat_count_{}; std::size_t window_size_{}; @@ -393,9 +385,6 @@ class SafeStreamSendAction { MultiSubscription sending_data_subs_; MultiSubscription send_subs_; ResponseStatistics response_statistics_; - AcknowledgedEvent acknowledged_event_; - StoppedEvent stopped_event_; - SendFailedEvent send_failed_event_; TaskSubscription repeat_timer_; TaskSubscription send_enqueued_; }; diff --git a/aether/safe_stream/safe_stream.h b/aether/safe_stream/safe_stream.h index 7e9a4dc64..cf0c9921e 100644 --- a/aether/safe_stream/safe_stream.h +++ b/aether/safe_stream/safe_stream.h @@ -35,16 +35,16 @@ namespace ae { template class SafeStream final : public ByteStream, // NOLINT - public SafeStreamApi, - public ISendDataPush, - public ISendAckRepeat { + public ISafeStreamSendActionDelegate, + public ISafeStreamRecvActionDelegate { using IndexType = RingIndex; using IndexRangeType = RingIndexRange; class SSWriteAction final : public WriteAction { public: - explicit SSWriteAction(IndexRangeType index_range, SafeStream& stream) - : stream_{&stream}, index_range_{index_range} {} + explicit SSWriteAction(EventContext auto const& context, + IndexRangeType index_range, SafeStream& stream) + : WriteAction{context}, stream_{&stream}, index_range_{index_range} {} // TODO: add tests for stop void Stop() noexcept override { stream_->StopWrite(index_range_); } @@ -58,25 +58,73 @@ class SafeStream final : public ByteStream, // NOLINT IndexRangeType index_range_; }; + class SafeStreamApiServer : public ISafeStreamApi { + public: + explicit SafeStreamApiServer(SafeStream& self) : self_{&self} {} + // Api impl methods + void Ack(std::uint16_t index) override { + self_->sender_.Acknowledge(index); + } + void RequestRepeat(std::uint16_t index) override { + self_->sender_.RequestRepeat(index); + } + void SendReset(std::uint16_t begin_offset, std::uint16_t delta_offset, + std::uint8_t repeat_count, DataBuffer data) override { + self_->receiver_.PushData(begin_offset, + DataMessage{.reset = true, + .repeat_count = repeat_count, + .delta_offset = delta_offset, + .data = std::move(data)}); + } + void Send(std::uint16_t begin_offset, std::uint16_t delta_offset, + std::uint8_t repeat_count, DataBuffer data) override { + self_->receiver_.PushData(begin_offset, + DataMessage{.reset = false, + .repeat_count = repeat_count, + .delta_offset = delta_offset, + .data = std::move(data)}); + } + + private: + SafeStream* self_; + }; + + class SafeStreamApiClient : public ISafeStreamApi { + public: + // Api impl methods + void Ack(std::uint16_t index) override { + ClientMethod<&ISafeStreamApi::Ack>(index); + } + void RequestRepeat(std::uint16_t index) override { + ClientMethod<&ISafeStreamApi::RequestRepeat>(index); + } + void SendReset(std::uint16_t begin_offset, std::uint16_t delta_offset, + std::uint8_t repeat_count, DataBuffer data) override { + ClientMethod<&ISafeStreamApi::SendReset>(begin_offset, delta_offset, + repeat_count, data); + } + void Send(std::uint16_t begin_offset, std::uint16_t delta_offset, + std::uint8_t repeat_count, DataBuffer data) override { + ClientMethod<&ISafeStreamApi::Send>(begin_offset, delta_offset, + repeat_count, data); + } + }; + public: using SafeStreamSender = SafeStreamSendAction; using SafeStreamReceiver = SafeStreamRecvAction; SafeStream(AeContext const& ae_context, SafeStreamConfig config) - : SafeStreamApi{protocol_context_}, + : ByteStream{ae_context}, ae_context_{ae_context}, config_{config}, + protocol_context_(ae_context_), + api_server_{*this}, + api_client_{}, sender_{ae_context_, *this, config_}, receiver_{ae_context_, *this, config_}, stream_info_{config_.max_packet_size, config_.max_packet_size, false, - LinkState::kUnlinked, false} { - sender_.acknowledged_event().Subscribe( - MethodPtr<&SafeStream::WriteAcknowledged>{this}); - sender_.send_failed_event().Subscribe( - MethodPtr<&SafeStream::WriteFailed>{this}); - - receiver_.receive_event().Subscribe(MethodPtr<&SafeStream::WriteOut>{this}); - } + LinkState::kUnlinked, false} {} AE_CLASS_NO_COPY_MOVE(SafeStream); @@ -89,9 +137,9 @@ class SafeStream final : public ByteStream, // NOLINT return *failed_write_; } // TODO: make without allocation - auto& ref = sswas_.emplace_back( - std::make_pair(res.value().right, - std::make_unique(res.value(), *this))); + auto& ref = sswas_.emplace_back(std::make_pair( + res.value().right, + std::make_unique(ae_context_, res.value(), *this))); return *ref.second; } @@ -107,38 +155,18 @@ class SafeStream final : public ByteStream, // NOLINT OnStreamUpdate(); } - // Api impl methods - void AckImpl(std::uint16_t index) override { sender_.Acknowledge(index); } - void RequestRepeatImpl(std::uint16_t index) override { - sender_.RequestRepeat(index); - } - void SendResetImpl(std::uint16_t begin_offset, std::uint16_t delta_offset, - std::uint8_t repeat_count, DataBuffer data) override { - receiver_.PushData(begin_offset, DataMessage{.reset = true, - .repeat_count = repeat_count, - .delta_offset = delta_offset, - .data = std::move(data)}); - } - void SendImpl(std::uint16_t begin_offset, std::uint16_t delta_offset, - std::uint8_t repeat_count, DataBuffer data) override { - receiver_.PushData(begin_offset, DataMessage{.reset = false, - .repeat_count = repeat_count, - .delta_offset = delta_offset, - .data = std::move(data)}); - } - - // Implement ISendDataPush + // Implement ISafeStreamSendActionDelegate WriteAction& PushData( std::uint16_t begin_offset, DataMessage&& data_message) override { // NOLINT (*param-not-moved) assert(out_); - auto api_adapter = ApiCallAdapter{ApiContext{*this}, *out_}; + auto api_adapter = ApiCallAdapter{api_client_, *out_}; if (data_message.reset) { - api_adapter->send_reset(begin_offset, data_message.delta_offset, - data_message.repeat_count, - std::move(data_message.data)); + api_adapter->SendReset(begin_offset, data_message.delta_offset, + data_message.repeat_count, + std::move(data_message.data)); } else { - api_adapter->send(begin_offset, data_message.delta_offset, + api_adapter->Send(begin_offset, data_message.delta_offset, data_message.repeat_count, std::move(data_message.data)); } @@ -148,21 +176,23 @@ class SafeStream final : public ByteStream, // NOLINT return api_adapter.Flush(); } - // Implement ISendConfirmRepeat + // Implement ISafeStreamRecvActionDelegate void SendAck(std::uint16_t index) override { assert(out_); - auto api_adapter = ApiCallAdapter{ApiContext{*this}, *out_}; - api_adapter->ack(index); + auto api_adapter = ApiCallAdapter{api_client_, *out_}; + api_adapter->Ack(index); api_adapter.Flush(); } void SendRepeatRequest(std::uint16_t index) override { assert(out_); - auto api_adapter = ApiCallAdapter{ApiContext{*this}, *out_}; - api_adapter->request_repeat(index); + auto api_adapter = ApiCallAdapter{api_client_, *out_}; + api_adapter->RequestRepeat(index); api_adapter.Flush(); } + void ReceiveData(DataBuffer& data) override { WriteOut(data); } + void StopWrite(IndexRangeType index_range) { sender_.Stop(index_range); } private: @@ -190,10 +220,10 @@ class SafeStream final : public ByteStream, // NOLINT void OnOutData(DataBuffer const& data) { AE_TELED_DEBUG("Received data {}", data); auto api_parser = ApiParser{protocol_context_, data}; - api_parser.Parse(*this); + api_parser.Parse(api_server_); } - void WriteAcknowledged(IndexType buffer_begin, IndexType ack_index) { + void WriteAcknowledged(IndexType buffer_begin, IndexType ack_index) override { for (auto& [index, sswa] : sswas_) { if (IndexComparable{index, buffer_begin} <= ack_index) { sswa->Acknowledge(); @@ -203,7 +233,7 @@ class SafeStream final : public ByteStream, // NOLINT [](auto const& e) { return e.second->is_finished(); }); } - void WriteStopped(IndexType buffer_begin, IndexType stop_index) { + void WriteStopped(IndexType buffer_begin, IndexType stop_index) override { for (auto& [index, sswa] : sswas_) { if (IndexComparable{index, buffer_begin} <= stop_index) { sswa->Stopped(); @@ -213,7 +243,7 @@ class SafeStream final : public ByteStream, // NOLINT [](auto const& e) { return e.second->is_finished(); }); } - void WriteFailed(IndexType buffer_begin, IndexType failed_index) { + void WriteFailed(IndexType buffer_begin, IndexType failed_index) override { for (auto& [index, sswa] : sswas_) { if (IndexComparable{index, buffer_begin} <= failed_index) { sswa->Rejected(); @@ -226,6 +256,8 @@ class SafeStream final : public ByteStream, // NOLINT AeContext ae_context_; SafeStreamConfig config_; ProtocolContext protocol_context_; + SafeStreamApiServer api_server_; + SafeStreamApiClient api_client_; SafeStreamSender sender_; SafeStreamReceiver receiver_; diff --git a/aether/serial_ports/at_support/at_buffer.cpp b/aether/serial_ports/at_support/at_buffer.cpp index f38168efe..ad02ea02b 100644 --- a/aether/serial_ports/at_support/at_buffer.cpp +++ b/aether/serial_ports/at_support/at_buffer.cpp @@ -16,16 +16,17 @@ #include "aether/serial_ports/at_support/at_buffer.h" +#include "aether-miscpp/types/method_ptr.h" + #include "aether/tele.h" namespace ae { -AtBuffer::AtBuffer(ISerialPort& serial_port) - : data_read_sub_{serial_port.read_event().Subscribe( +AtBuffer::AtBuffer(AeContext const& ae_context, ISerialPort& serial_port) + : update_event_{ae_context}, + data_read_sub_{serial_port.read_event().Subscribe( MethodPtr<&AtBuffer::DataRead>{this})} {} -AtBuffer::UpdateEvent::Subscriber AtBuffer::update_event() { - return EventSubscriber{update_event_}; -} +AtBuffer::UpdateEvent const& AtBuffer::update_event() { return update_event_; } AtBuffer::iterator AtBuffer::FindPattern(std::string_view str) { return FindPattern(str, begin()); diff --git a/aether/serial_ports/at_support/at_buffer.h b/aether/serial_ports/at_support/at_buffer.h index 53395ec76..819fb2f9e 100644 --- a/aether/serial_ports/at_support/at_buffer.h +++ b/aether/serial_ports/at_support/at_buffer.h @@ -17,14 +17,14 @@ #ifndef AETHER_SERIAL_PORTS_AT_SUPPORT_AT_BUFFER_H_ #define AETHER_SERIAL_PORTS_AT_SUPPORT_AT_BUFFER_H_ +#include #include #include -#include #include +#include "aether/ae_context.h" #include "aether/events/events.h" #include "aether/types/data_buffer.h" -#include "aether/events/event_subscription.h" #include "aether/serial_ports/iserial_port.h" @@ -35,9 +35,9 @@ class AtBuffer { using iterator = DataLines::iterator; using UpdateEvent = Event; - explicit AtBuffer(ISerialPort& serial_port); + explicit AtBuffer(AeContext const& ae_context, ISerialPort& serial_port); - UpdateEvent::Subscriber update_event(); + UpdateEvent const& update_event(); iterator FindPattern(std::string_view str); iterator FindPattern(std::string_view str, iterator start); diff --git a/aether/serial_ports/at_support/at_dispatcher.cpp b/aether/serial_ports/at_support/at_dispatcher.cpp index 54d8cd4a9..1100dff1e 100644 --- a/aether/serial_ports/at_support/at_dispatcher.cpp +++ b/aether/serial_ports/at_support/at_dispatcher.cpp @@ -19,6 +19,8 @@ #include #include +#include "aether-miscpp/types/method_ptr.h" + namespace ae { AtDispatcher::AtDispatcher(AtBuffer& buffer) : buffer_{&buffer}, diff --git a/aether/serial_ports/at_support/at_dispatcher.h b/aether/serial_ports/at_support/at_dispatcher.h index c866da63e..b72488463 100644 --- a/aether/serial_ports/at_support/at_dispatcher.h +++ b/aether/serial_ports/at_support/at_dispatcher.h @@ -20,7 +20,7 @@ #include #include -#include "aether/events/event_subscription.h" +#include "aether/events/events.h" #include "aether/serial_ports/at_support/at_buffer.h" namespace ae { diff --git a/aether/serial_ports/at_support/at_stage.h b/aether/serial_ports/at_support/at_stage.h index 0495f3c0c..a9f530942 100644 --- a/aether/serial_ports/at_support/at_stage.h +++ b/aether/serial_ports/at_support/at_stage.h @@ -35,7 +35,8 @@ template class AtStageAction final : public Action { public: AtStageAction(AeContext const& ae_context, RequestSender&& sender) - : waiter_{ae_context, std::move(sender), ActionFinishCb{.self = this}} {} + : Action{ae_context}, + waiter_{ae_context, std::move(sender), ActionFinishCb{.self = this}} {} AE_CLASS_MOVE_ONLY(AtStageAction) @@ -64,7 +65,7 @@ class AtStageActionRunner { : storage_{std::move(std::get(other.storage_))} {} ActionType* operator()() { - auto& generator = std::get(storage_); + auto generator = std::get(std::move(storage_)); storage_.template emplace(generator.ae_context, std::invoke(generator.gen)); return &std::get(storage_); diff --git a/aether/serial_ports/at_support/at_support.cpp b/aether/serial_ports/at_support/at_support.cpp index ffa554733..35077dd59 100644 --- a/aether/serial_ports/at_support/at_support.cpp +++ b/aether/serial_ports/at_support/at_support.cpp @@ -41,8 +41,10 @@ bool ParseArg(std::string_view arg, std::string& value) { } // namespace at_support -AtSupport::AtSupport(ISerialPort& serial) noexcept - : serial_{&serial}, at_buffer_{serial}, dispatcher_{at_buffer_} {}; +AtSupport::AtSupport(AeContext const& ae_context, ISerialPort& serial) noexcept + : serial_{&serial}, + at_buffer_{ae_context, serial}, + dispatcher_{at_buffer_} {}; Result AtSupport::SendATCommand(std::string_view command) { if (!serial_->IsOpen()) { diff --git a/aether/serial_ports/at_support/at_support.h b/aether/serial_ports/at_support/at_support.h index e05b87514..dfc60b22b 100644 --- a/aether/serial_ports/at_support/at_support.h +++ b/aether/serial_ports/at_support/at_support.h @@ -17,20 +17,21 @@ #ifndef AETHER_SERIAL_PORTS_AT_SUPPORT_AT_SUPPORT_H_ #define AETHER_SERIAL_PORTS_AT_SUPPORT_AT_SUPPORT_H_ -#include #include #include +#include #include -#include "aether-miscpp/types/result.h" #include "aether-miscpp/misc/from_chars.h" +#include "aether-miscpp/types/result.h" +#include "aether/ae_context.h" #include "aether/serial_ports/iserial_port.h" // IWYU pragma: begin_exports #include "aether/serial_ports/at_support/at_buffer.h" -#include "aether/serial_ports/at_support/at_listener.h" #include "aether/serial_ports/at_support/at_dispatcher.h" +#include "aether/serial_ports/at_support/at_listener.h" // IWYU pragma: end_exports namespace ae { @@ -87,7 +88,7 @@ static std::optional ParseResponse( class AtSupport { public: - explicit AtSupport(ISerialPort& serial) noexcept; + explicit AtSupport(AeContext const& ae_context, ISerialPort& serial) noexcept; Result SendATCommand(std::string_view command); diff --git a/aether/serial_ports/esp32_serial_port.cpp b/aether/serial_ports/esp32_serial_port.cpp index f74db575b..ff9d4af60 100644 --- a/aether/serial_ports/esp32_serial_port.cpp +++ b/aether/serial_ports/esp32_serial_port.cpp @@ -123,8 +123,8 @@ void Esp32SerialPort::Write(DataBuffer const& data) { } } -Esp32SerialPort::DataReadEvent::Subscriber Esp32SerialPort::read_event() { - return EventSubscriber{read_event_}; +Esp32SerialPort::DataReadEvent const& Esp32SerialPort::read_event() { + return read_event_; } bool Esp32SerialPort::IsOpen() { return uart_num_ != UART_NUM_MAX; } diff --git a/aether/serial_ports/esp32_serial_port.h b/aether/serial_ports/esp32_serial_port.h index 428f26636..494d089f4 100644 --- a/aether/serial_ports/esp32_serial_port.h +++ b/aether/serial_ports/esp32_serial_port.h @@ -60,7 +60,7 @@ class Esp32SerialPort : public ISerialPort { void Write(DataBuffer const& data) override; - DataReadEvent::Subscriber read_event() override; + DataReadEvent const& read_event() override; bool IsOpen() override; diff --git a/aether/serial_ports/iserial_port.h b/aether/serial_ports/iserial_port.h index 90b66841c..1046a624d 100644 --- a/aether/serial_ports/iserial_port.h +++ b/aether/serial_ports/iserial_port.h @@ -17,8 +17,8 @@ #ifndef AETHER_SERIAL_PORTS_ISERIAL_PORT_H_ #define AETHER_SERIAL_PORTS_ISERIAL_PORT_H_ -#include #include +#include #include "aether/events/events.h" @@ -43,7 +43,7 @@ class ISerialPort { /** * \brief Read data event. */ - virtual DataReadEvent::Subscriber read_event() = 0; + virtual DataReadEvent const& read_event() = 0; }; } /* namespace ae */ diff --git a/aether/serial_ports/unix_serial_port.cpp b/aether/serial_ports/unix_serial_port.cpp index 3f4bb9174..392ab07c6 100644 --- a/aether/serial_ports/unix_serial_port.cpp +++ b/aether/serial_ports/unix_serial_port.cpp @@ -19,8 +19,8 @@ #if defined UNIX_SERIAL_PORT_ENABLED # include -# include # include +# include # include "aether-miscpp/misc/defer.h" # include "aether/serial_ports/serial_ports_tele.h" @@ -32,7 +32,8 @@ UnixSerialPort::UnixSerialPort(AeContext const& ae_context, : ae_context_{ae_context}, serial_init_{std::move(serial_init)}, poller_fd_{OpenPort(serial_init_), poller->Native(), - MethodPtr<&UnixSerialPort::PolleEvent>{this}} { + MethodPtr<&UnixSerialPort::PolleEvent>{this}}, + read_event_{ae_context_} { poller_fd_.Events(EventType::kRead | EventType::kError); } @@ -56,8 +57,8 @@ void UnixSerialPort::Write(std::span data) { } } -UnixSerialPort::DataReadEvent::Subscriber UnixSerialPort::read_event() { - return EventSubscriber{read_event_}; +UnixSerialPort::DataReadEvent const& UnixSerialPort::read_event() { + return read_event_; } bool UnixSerialPort::IsOpen() { return *poller_fd_.fd() != kInvalidDescriptor; } diff --git a/aether/serial_ports/unix_serial_port.h b/aether/serial_ports/unix_serial_port.h index 8130f87bc..dba2db91a 100644 --- a/aether/serial_ports/unix_serial_port.h +++ b/aether/serial_ports/unix_serial_port.h @@ -22,16 +22,16 @@ # define UNIX_SERIAL_PORT_ENABLED 1 +# include # include # include -# include # include "aether/ae_context.h" # include "aether/poller/poller.h" -# include "aether/types/data_buffer.h" # include "aether/poller/unix_poller.h" # include "aether/serial_ports/iserial_port.h" # include "aether/serial_ports/serial_port_types.h" +# include "aether/types/data_buffer.h" namespace ae { class UnixSerialPort final : public ISerialPort { @@ -42,7 +42,7 @@ class UnixSerialPort final : public ISerialPort { void Write(std::span data) override; - DataReadEvent::Subscriber read_event() override; + DataReadEvent const& read_event() override; bool IsOpen() override; diff --git a/aether/serial_ports/win_serial_port.cpp b/aether/serial_ports/win_serial_port.cpp index 4c73b0073..98ac2c08e 100644 --- a/aether/serial_ports/win_serial_port.cpp +++ b/aether/serial_ports/win_serial_port.cpp @@ -29,6 +29,7 @@ WinSerialPort::WinSerialPort(AeContext const& ae_context, serial_init_{std::move(serial_init)}, poller_{std::static_pointer_cast(poller->Native())}, fd_{OpenPort(serial_init_)}, + read_event_{ae_context_}, read_buffer_(kReadBufSize) { if (fd_ != INVALID_HANDLE_VALUE) { poller_->Add({fd_}, MethodPtr<&WinSerialPort::PollEvent>{this}); @@ -87,8 +88,8 @@ void WinSerialPort::Write(std::span data) { } } -WinSerialPort::DataReadEvent::Subscriber WinSerialPort::read_event() { - return EventSubscriber{read_event_}; +WinSerialPort::DataReadEvent const& WinSerialPort::read_event() { + return read_event_; } bool WinSerialPort::IsOpen() { return fd_ != INVALID_HANDLE_VALUE; } diff --git a/aether/serial_ports/win_serial_port.h b/aether/serial_ports/win_serial_port.h index 517c015e0..680b0d7ed 100644 --- a/aether/serial_ports/win_serial_port.h +++ b/aether/serial_ports/win_serial_port.h @@ -21,16 +21,16 @@ # define WIN_SERIAL_PORT_ENABLED 1 +# include # include # include -# include # include "aether/ae_context.h" # include "aether/poller/poller.h" -# include "aether/types/data_buffer.h" # include "aether/poller/win_poller.h" # include "aether/serial_ports/iserial_port.h" # include "aether/serial_ports/serial_port_types.h" +# include "aether/types/data_buffer.h" # include @@ -43,7 +43,7 @@ class WinSerialPort final : public ISerialPort { void Write(std::span data) override; - DataReadEvent::Subscriber read_event() override; + DataReadEvent const& read_event() override; bool IsOpen() override; diff --git a/aether/server.cpp b/aether/server.cpp index 2ebcfdeba..ebed25c21 100644 --- a/aether/server.cpp +++ b/aether/server.cpp @@ -18,6 +18,10 @@ #include +#include "aether-miscpp/types/method_ptr.h" + +#include "aether/ae_context.h" + namespace ae { Server::Server(ObjProp prop, ServerId server_id, std::vector endpoints, @@ -26,11 +30,25 @@ Server::Server(ObjProp prop, ServerId server_id, server_id{server_id}, endpoints{std::move(endpoints)}, adapter_registry_{std::move(adapter_registry)}, - subscribed_{} { + channels_changed_{std::in_place, AeContext{*this}} { Register(); } +void Server::Loaded() { + channels_changed_.emplace(AeContext{*this}); + if (!subscribed_) { + subscribed_ = true; + UpdateSubscription(); + } +} + +Server::ChannelsChanged const& Server::channels_changed() { + assert(!!channels_changed_); + return *channels_changed_; +} + void Server::Register() { + channels.clear(); UpdateSubscription(); for (auto const& adapter : adapter_registry_->adapters()) { @@ -41,11 +59,8 @@ void Server::Register() { subscribed_ = true; } -Server::ChannelsChanged::Subscriber Server::channels_changed() { - return EventSubscriber{channels_changed_}; -} - void Server::UpdateSubscription() { + access_point_added_.Reset(); assert(adapter_registry_.is_valid()); adapter_registry_.WithLoaded([&](auto const& ar) { for (auto const& adapter : ar->adapters()) { @@ -53,7 +68,7 @@ void Server::UpdateSubscription() { MethodPtr<&Server::AddChannels>{this}); } }); - channels_changed_.Emit(); + channels_changed_->Emit(); } void Server::AddChannels(AccessPoint::ptr const& access_point) { @@ -61,7 +76,7 @@ void Server::AddChannels(AccessPoint::ptr const& access_point) { auto new_channels = access_point->GenerateChannels(server_ptr); channels.insert(std::end(channels), std::begin(new_channels), std::end(new_channels)); - channels_changed_.Emit(); + channels_changed_->Emit(); } } // namespace ae diff --git a/aether/server.h b/aether/server.h index cf1f3ac38..c2c79052f 100644 --- a/aether/server.h +++ b/aether/server.h @@ -17,14 +17,14 @@ #ifndef AETHER_SERVER_H_ #define AETHER_SERVER_H_ +#include #include -#include "aether/types/address.h" +#include "aether/adapter_registry.h" +#include "aether/channels/channel.h" #include "aether/events/events.h" +#include "aether/types/address.h" #include "aether/types/server_id.h" -#include "aether/channels/channel.h" -#include "aether/adapter_registry.h" -#include "aether/events/multi_subscription.h" namespace ae { class Server : public Obj { @@ -41,31 +41,21 @@ class Server : public Obj { AE_OBJECT_REFLECT(AE_MMBRS(server_id, endpoints, adapter_registry_, channels)) - template - void Load(CurrentVersion, Dnv& dnv){ - dnv(base_); - dnv(server_id, endpoints, adapter_registry_, channels); - if (!subscribed_) { - subscribed_ = true; - UpdateSubscription(); - } - } - - - ChannelsChanged::Subscriber channels_changed(); + ChannelsChanged const& channels_changed(); + void Register(); ServerId server_id{}; std::vector endpoints; std::vector channels; private: - void Register(); + void Loaded(); void UpdateSubscription(); void AddChannels(AccessPoint::ptr const& access_point); AdapterRegistry::ptr adapter_registry_; MultiSubscription access_point_added_; - ChannelsChanged channels_changed_; + std::optional channels_changed_; bool subscribed_{false}; }; } // namespace ae diff --git a/aether/server_connections/channel_select_action.cpp b/aether/server_connections/channel_select_action.cpp index 6294c37bc..b21bf17a2 100644 --- a/aether/server_connections/channel_select_action.cpp +++ b/aether/server_connections/channel_select_action.cpp @@ -37,17 +37,19 @@ void ChannelSelectAction::CbHandle::operator()( channel->channel_statistics().AddConnectionTime(build_time); } - // emit the res, but convert all errors to int + // Emit the result, but convert all errors to int. constexpr int kStopped = -2; - self->result_event_.Emit( + auto result = std::move(res) .value_or(Error>(kStopped)) - .Else([&](auto&& verr) noexcept - -> Result, int> { + // This callback is part of the no-exceptions completion path. + // NOLINTNEXTLINE(bugprone-exception-escape) + .Else([&](auto&& verr) noexcept -> ResultType { return std::visit( [&](auto const& e) noexcept { return Error{HandleError(e)}; }, std::forward(verr)); - })); + }); + self->result_event_.Emit(result); self->Finish(); } @@ -64,7 +66,10 @@ int ChannelSelectAction::CbHandle::HandleError(int e) noexcept { ChannelSelectAction::ChannelSelectAction( AeContext const& ae_context, ChannelEntry& attempted_channel) noexcept - : ae_context_{ae_context}, attempted_channel_{&attempted_channel} {} + : Action{ae_context}, + ae_context_{ae_context}, + attempted_channel_{&attempted_channel}, + result_event_{ae_context_} {} void ChannelSelectAction::Start() { auto channel = attempted_channel_->channel.Lock(); @@ -77,8 +82,8 @@ void ChannelSelectAction::Start() { CbHandle{.self = this, .start_time = Now()}); } -auto ChannelSelectAction::result_event() noexcept -> ResultEvent::Subscriber { - return EventSubscriber{result_event_}; +auto ChannelSelectAction::result_event() noexcept -> ResultEvent const& { + return result_event_; } ChannelEntry& ChannelSelectAction::attempted_channel() noexcept { diff --git a/aether/server_connections/channel_select_action.h b/aether/server_connections/channel_select_action.h index 2bf248682..3fd2361ec 100644 --- a/aether/server_connections/channel_select_action.h +++ b/aether/server_connections/channel_select_action.h @@ -47,13 +47,19 @@ class ChannelSelectAction final : public Action { }; public: - using ResultEvent = Event, int>)>; + using ResultType = Result, int>; + + // Although backed by a multicast Event, this result has exactly one + // consuming subscriber. Dispatch is synchronous; the subscriber may move + // the successful value during the callback and must not retain the reference. + // Do not add another subscriber. + using ResultEvent = Event; ChannelSelectAction(AeContext const& ae_context, ChannelEntry& attempted_channel) noexcept; void Start(); - ResultEvent::Subscriber result_event() noexcept; + ResultEvent const& result_event() noexcept; ChannelEntry& attempted_channel() noexcept; private: diff --git a/aether/server_connections/client_server_connection.cpp b/aether/server_connections/client_server_connection.cpp index ddff0bc24..3ff926ef7 100644 --- a/aether/server_connections/client_server_connection.cpp +++ b/aether/server_connections/client_server_connection.cpp @@ -121,7 +121,7 @@ WriteAction& BufferedServerConnection::Write(DataBuffer&& in_data) { return buffer_write.Write(std::move(in_data)); } -BufferedServerConnection::StreamUpdateEvent::Subscriber +BufferedServerConnection::StreamUpdateEvent const& BufferedServerConnection::stream_update_event() { return server_connection.stream_update_event(); } @@ -130,7 +130,7 @@ StreamInfo BufferedServerConnection::stream_info() const { return server_connection.stream_info(); } -BufferedServerConnection::OutDataEvent::Subscriber +BufferedServerConnection::OutDataEvent const& BufferedServerConnection::out_data_event() { return server_connection.out_data_event(); } @@ -149,8 +149,10 @@ ClientServerConnection::ClientServerConnection(AeContext const& ae_context, crypto_provider_{std::make_unique< client_server_connection_internal::ClientCryptoProvider>( client, server->server_id)}, - client_api_unsafe_{protocol_context_, *crypto_provider_->decryptor()}, - login_api_{protocol_context_, *crypto_provider_->encryptor()}, + protocol_context_{ae_context}, + client_api_unsafe_{ae_context.event_system(), + *crypto_provider_->decryptor()}, + login_api_{*crypto_provider_->encryptor()}, server_connection_{ae_context_, server} { AE_TELED_DEBUG("Client server connection from {}:e-{} to {}", uid_, ephemeral_uid_, server->server_id); @@ -172,23 +174,21 @@ StreamInfo ClientServerConnection::stream_info() const { return server_connection_.stream_info(); } -ByteIStream::StreamUpdateEvent::Subscriber +ByteIStream::StreamUpdateEvent const& ClientServerConnection::stream_update_event() { return server_connection_.stream_update_event(); } WriteAction& ClientServerConnection::LoginApiCall(SubApi login_api) { - auto packet = login_api(login_api_); + auto packet = std::move(login_api)(ApiContext{login_api_, protocol_context_}); return server_connection_.Write(std::move(packet)); } WriteAction& ClientServerConnection::AuthorizedApiCall( SubApi auth_api) { - auto api_call = ApiCallAdapter{ApiContext{login_api_}, server_connection_}; - api_call->login_by_alias(ephemeral_uid_, std::move(auth_api)); - // cppcheck reports false positive - // cppcheck-suppress returnReference - return api_call.Flush(); + auto api_call = ApiContext{login_api_, protocol_context_}; + api_call->LoginByAlias(ephemeral_uid_, std::move(auth_api)); + return server_connection_.Write(std::move(api_call)); } ClientApiSafe& ClientServerConnection::client_safe_api() { @@ -201,7 +201,8 @@ ServerConnection& ClientServerConnection::server_connection() { void ClientServerConnection::OutData(DataBuffer const& data) { auto parser = ApiParser{protocol_context_, data}; - parser.Parse(client_api_unsafe_); + [[maybe_unused]] auto res = parser.Parse(client_api_unsafe_); + assert(res && "Not all data parsed"); } } // namespace ae diff --git a/aether/server_connections/client_server_connection.h b/aether/server_connections/client_server_connection.h index b8e51bd79..1b0264664 100644 --- a/aether/server_connections/client_server_connection.h +++ b/aether/server_connections/client_server_connection.h @@ -47,9 +47,9 @@ class BufferedServerConnection : public ByteIStream { Ptr const& server); WriteAction& Write(DataBuffer&& in_data) override; - StreamUpdateEvent::Subscriber stream_update_event() override; + StreamUpdateEvent const& stream_update_event() override; StreamInfo stream_info() const override; - OutDataEvent::Subscriber out_data_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; Buffer buffer_write; @@ -70,7 +70,7 @@ class ClientServerConnection { void Restream(); StreamInfo stream_info() const; - ByteIStream::StreamUpdateEvent::Subscriber stream_update_event(); + ByteIStream::StreamUpdateEvent const& stream_update_event(); WriteAction& LoginApiCall(SubApi login_api); WriteAction& AuthorizedApiCall(SubApi auth_api); diff --git a/aether/server_connections/server_connection.cpp b/aether/server_connections/server_connection.cpp index 8afc277de..be042d6f7 100644 --- a/aether/server_connections/server_connection.cpp +++ b/aether/server_connections/server_connection.cpp @@ -19,6 +19,8 @@ #include #include +#include "aether-miscpp/types/method_ptr.h" + #include "aether/aether.h" #include "aether/channels/channel.h" #include "aether/server.h" @@ -32,7 +34,11 @@ ServerConnection::ServerConnection(AeContext const& ae_context, : ae_context_{ae_context}, server_{server}, full_connected_{false}, - top_channel_{nullptr} { + top_channel_{nullptr}, + out_data_event_{ae_context_}, + stream_update_event_{ae_context_}, + server_error_{ae_context_}, + channel_changed_{ae_context_} { InitChannels(); SelectChannel(); } @@ -47,15 +53,15 @@ WriteAction& ServerConnection::Write(DataBuffer&& in_data) { return stream_->Write(std::move(in_data)); } -ServerConnection::StreamUpdateEvent::Subscriber +ServerConnection::StreamUpdateEvent const& ServerConnection::stream_update_event() { - return EventSubscriber{stream_update_event_}; + return stream_update_event_; } StreamInfo ServerConnection::stream_info() const { return stream_info_; } -ServerConnection::OutDataEvent::Subscriber ServerConnection::out_data_event() { - return EventSubscriber{out_data_event_}; +ServerConnection::OutDataEvent const& ServerConnection::out_data_event() { + return out_data_event_; } void ServerConnection::Restream() { @@ -63,14 +69,14 @@ void ServerConnection::Restream() { ChannelError(); } -ServerConnection::ServerErrorEvent::Subscriber +ServerConnection::ServerErrorEvent const& ServerConnection::server_error_event() { - return EventSubscriber{server_error_}; + return server_error_; } -ServerConnection::ChannelChangedEvent::Subscriber +ServerConnection::ChannelChangedEvent const& ServerConnection::channel_changed_event() { - return EventSubscriber{channel_changed_}; + return channel_changed_; } Ptr ServerConnection::current_channel() const { @@ -149,16 +155,17 @@ void ServerConnection::SelectChannel() { stream_update_event_.Emit(); channel_select_action_.emplace(ae_context_, *top); - channel_select_action_->result_event().Subscribe([this](auto&& res) noexcept { - if (res) { - ChannelUpdated(channel_select_action_->attempted_channel(), - std::forward(res).value()); - } else { - // Mark the channel that was actually being built. top_channel_ is only - // set after a successful build and must not be used here. - ChannelBuildFailed(channel_select_action_->attempted_channel()); - } - }); + channel_select_action_->result_event().Subscribe( + [this](ChannelSelectAction::ResultType& res) noexcept { + if (res) { + ChannelUpdated(channel_select_action_->attempted_channel(), + std::move(res).value()); + } else { + // Mark the channel that was actually being built. top_channel_ is + // only set after a successful build and must not be used here. + ChannelBuildFailed(channel_select_action_->attempted_channel()); + } + }); // Subscribe before Start so a synchronous TransportBuilder result is not // lost. channel_select_action_->Start(); diff --git a/aether/server_connections/server_connection.h b/aether/server_connections/server_connection.h index 40c27e00e..993723926 100644 --- a/aether/server_connections/server_connection.h +++ b/aether/server_connections/server_connection.h @@ -46,13 +46,13 @@ class ServerConnection final : public ByteIStream { ServerConnection(AeContext const& ae_context, Ptr const& server); WriteAction& Write(DataBuffer&& in_data) override; - StreamUpdateEvent::Subscriber stream_update_event() override; + StreamUpdateEvent const& stream_update_event() override; StreamInfo stream_info() const override; - OutDataEvent::Subscriber out_data_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; - ServerErrorEvent::Subscriber server_error_event(); - ChannelChangedEvent::Subscriber channel_changed_event(); + ServerErrorEvent const& server_error_event(); + ChannelChangedEvent const& channel_changed_event(); Ptr current_channel() const; diff --git a/aether/stream_api/api_call_adapter.h b/aether/stream_api/api_call_adapter.h index 382f7ec11..f32701054 100644 --- a/aether/stream_api/api_call_adapter.h +++ b/aether/stream_api/api_call_adapter.h @@ -20,7 +20,7 @@ #include #include -#include "aether/api_protocol/api_context.h" +#include "aether/api_protocol/api_protocol.h" #include "aether/stream_api/istream.h" #include "aether/types/data_buffer.h" @@ -34,10 +34,20 @@ namespace ae { template class ApiCallAdapter { public: - ApiCallAdapter(ApiContext&& api_context, ByteIStream& byte_stream) - : api_context_{std::move(api_context)}, byte_stream_{&byte_stream} {} + // api and byte_stream must outlive this adapter, active call proxies, and + // synchronous calls or Pack(). + ApiCallAdapter(TApi& api, ByteIStream& byte_stream) + : api_context_{api}, byte_stream_{&byte_stream} {} + // api, protocol_context, and byte_stream must outlive this adapter, active + // call proxies, and synchronous calls or Pack(). + ApiCallAdapter(TApi& api, ProtocolContext& protocol_context, + ByteIStream& byte_stream) + : api_context_{api, protocol_context}, byte_stream_{&byte_stream} {} - AE_CLASS_MOVE_ONLY(ApiCallAdapter) + ApiCallAdapter(ApiCallAdapter const&) = delete; + ApiCallAdapter& operator=(ApiCallAdapter const&) = delete; + ApiCallAdapter(ApiCallAdapter&&) = delete; + ApiCallAdapter& operator=(ApiCallAdapter&&) = delete; WriteAction& Flush() { return byte_stream_->Write(DataBuffer{std::move(api_context_)}); diff --git a/aether/stream_api/gate_trait.h b/aether/stream_api/gate_trait.h index 13c05be34..7f6b80433 100644 --- a/aether/stream_api/gate_trait.h +++ b/aether/stream_api/gate_trait.h @@ -19,7 +19,6 @@ #include -#include "aether/type_traits.h" #include "aether/events/events.h" namespace ae { @@ -83,7 +82,7 @@ template struct EventSubscriberTraits; template -struct EventSubscriberTraits> { +struct EventSubscriberTraits> { using OutType = Arg; }; @@ -94,8 +93,8 @@ struct OutDataEventTraits { template struct OutDataEventTraits::value>> { - using OutType = typename EventSubscriberTraits< - decltype(std::declval().out_data_event())>::OutType; + using OutType = typename EventSubscriberTraits().out_data_event())>>::OutType; }; } // namespace _trait_internal diff --git a/aether/stream_api/gates_stream.h b/aether/stream_api/gates_stream.h deleted file mode 100644 index 0ea6e91be..000000000 --- a/aether/stream_api/gates_stream.h +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_STREAM_API_GATES_STREAM_H_ -#define AETHER_STREAM_API_GATES_STREAM_H_ - -#include -#include -#include - -#include "aether-miscpp/meta/type_list.h" -#include "aether/stream_api/istream.h" -#include "aether/stream_api/gate_trait.h" -#include "aether/stream_api/tied_gates.h" - -namespace ae { -namespace _tied_stream_internal { - -/** - * We need to build a stream \see Stream consistent - * out of many gates like on the scheme: - * - * gate stream - * ------------------------------------------------------------- - * TIn ----->o| write in |o--| |--o| write in |o----> TInOut - * | left gate | |... gates ...| | right gate| - * TOut <-----o| write out |o--| |--o| write out |o<---- TOutIn - * ------------------------------------------------------------- - * To find TIn we need to find left most gate with WriteIn method and take input - * type - * - for TOut we need to find left most gate with WriteOut method and take - * output type, or with out_data_event and take the event argument type - * - for TinOut we need to find riht most gate with WriteIn method and take - * output type - * - for TOutIn we need to find right most gate with WriteOut method and take - * input type - */ -template -struct GateListTrait { - template - struct GetTIn { - using type = void; - }; - template - struct GetTIn { - using type = std::conditional_t::kHasWriteIn, - typename GateTrait::WriteInInType, - typename GetTIn::type>; - }; - - template - struct GetTOut { - using type = void; - }; - template - struct GetTOut { - using type = std::conditional_t< - // there may be WriteOut with void type - !std::is_void_v::WriteOutOutType>, - typename GateTrait::WriteOutOutType, - // else use out_date_event type - std::conditional_t::kHasOutDataEvent, - typename GateTrait::OutDataEventType, - typename GetTOut::type>>; - }; - - // this should be checked in reverse order - template - struct GetTInOut { - using type = void; - }; - template - struct GetTInOut { - using type = std::conditional_t::kHasWriteIn, - typename GateTrait::WriteInOutType, - typename GetTInOut::type>; - }; - template - using RGetTInOut = typename TypeListToTemplate< - GetTInOut, typename ReversTypeList>::type>::type; - - template - struct GetTOutIn { - using type = void; - }; - template - struct GetTOutIn { - using type = std::conditional_t::kHasWriteOut, - typename GateTrait::WriteOutInType, - typename GetTOutIn::type>; - }; - template - using RGetTOutIn = typename TypeListToTemplate< - GetTOutIn, typename ReversTypeList>::type>::type; - - using TInType = typename GetTIn::type; - using TOutType = typename GetTOut::type; - using TInOutType = typename RGetTInOut::type; - using TOutInType = typename RGetTOutIn::type; -}; -} // namespace _tied_stream_internal - -template -class GatesStream final - : public Stream...>::TInType, - typename _tied_stream_internal::GateListTrait< - std::decay_t...>::TOutType, - typename _tied_stream_internal::GateListTrait< - std::decay_t...>::TInOutType, - typename _tied_stream_internal::GateListTrait< - std::decay_t...>::TOutInType> { - struct HandleEvent { - template - void operator()(U&& data) const { - stream_->out_data_event_.Emit(std::forward(data)); - } - - GatesStream* stream_; - }; - - template - static constexpr auto MakeOutDataEventSubscriptions( - THandler&& handler, std::tuple& gates) { - return std::apply( - [&](auto&&... gates) { - return TiedEventOutData(std::forward(handler), - std::forward(gates)...); - }, - gates); - } - - public: - using Gates = std::tuple; - using Traits = _tied_stream_internal::GateListTrait...>; - using Base = Stream; - using OutDataEvent = typename Base::OutDataEvent; - using StreamUpdateEvent = typename Base::StreamUpdateEvent; - using OutStream = typename Base::OutStream; - - using SubscriptionsList = decltype(MakeOutDataEventSubscriptions( - std::declval(), std::declval())); - - explicit GatesStream(TGates... gates) - : gates_{std::forward(gates)...}, - out_data_event_subscription_{SubscribeToOutDataEvent()} {} - - WriteAction& Write(typename Base::TypeIn&& in_data) override { - assert(Base::out_); - return Base::out_->Write(std::apply( - [&](auto&... gates) -> typename OutStream::TypeIn { - return TiedWriteIn(std::move(in_data), gates...); - }, - gates_)); - } - - // Write out data to that stream directly - template - void WriteOut(U&& out_data) { - if constexpr (!std::is_void_v) { - static_assert( - std::is_same_v, typename OutStream::TypeOut>, - "Data must be of type OutStream::TypeOut"); - OutData(std::move(out_data)); - } else { - static_assert( - std::is_void_v, - "There is no implelementation for OutStream::TypeOut == void"); - } - } - - StreamInfo stream_info() const override { - if (Base::out_ == nullptr) { - return StreamInfo{}; - } - auto info = Base::out_->stream_info(); - auto overhead = std::apply( - [&](auto const&... gates) { return TiedOverhead(gates...); }, gates_); - if (info.rec_element_size < overhead) { - info.rec_element_size = 0; - } else { - info.rec_element_size -= overhead; - } - if (info.max_element_size < overhead) { - info.max_element_size = 0; - } else { - info.max_element_size -= overhead; - } - return info; - } - - void LinkOut(OutStream& out) override { - Base::out_ = &out; - - Base::out_data_sub_ = Base::out_->out_data_event().Subscribe( - MethodPtr<&GatesStream::OutData>{this}); - Base::update_sub_ = - Base::out_->stream_update_event().Subscribe(Base::stream_update_event_); - - Base::stream_update_event_.Emit(); - } - - void Unlink() override { - Base::out_ = nullptr; - Base::update_sub_.Reset(); - Base::out_data_sub_.Reset(); - - Base::stream_update_event_.Emit(); - } - - private: - void OutData(typename OutStream::TypeOut const& data) { - using WriteOutResType = std::invoke_result_t< - decltype(&TiedWriteOut), - typename OutStream::TypeOut const&, TGates...>; - if constexpr (!std::is_void_v) { - Base::out_data_event_.Emit(std::apply( - [&](auto&... gates) -> - typename Base::TypeOut { return TiedWriteOut(data, gates...); }, - gates_)); - } else { - std::apply([&](auto&... gates) { TiedWriteOut(data, gates...); }, gates_); - } - } - - constexpr auto SubscribeToOutDataEvent() { - return MakeOutDataEventSubscriptions(HandleEvent{this}, gates_); - } - - Gates gates_; - SubscriptionsList out_data_event_subscription_; -}; - -template -GatesStream(U&&...) -> GatesStream; -} // namespace ae - -#endif // AETHER_STREAM_API_GATES_STREAM_H_ diff --git a/aether/stream_api/istream.h b/aether/stream_api/istream.h index 754af5fc7..014cf2dd9 100644 --- a/aether/stream_api/istream.h +++ b/aether/stream_api/istream.h @@ -17,8 +17,10 @@ #ifndef AETHER_STREAM_API_ISTREAM_H_ #define AETHER_STREAM_API_ISTREAM_H_ -#include #include +#include + +#include "aether-miscpp/types/method_ptr.h" #include "aether/common.h" @@ -87,7 +89,7 @@ class IStream { /** * \brief Stream update event. */ - virtual StreamUpdateEvent::Subscriber stream_update_event() = 0; + virtual StreamUpdateEvent const& stream_update_event() = 0; /** * \brief Stream info */ @@ -95,7 +97,7 @@ class IStream { /** * \brief New data received event. */ - virtual OutDataEvent::Subscriber out_data_event() = 0; + virtual OutDataEvent const& out_data_event() = 0; /** * \brief Reconfigure the stream in case on user request. @@ -114,7 +116,9 @@ class Stream : public IStream { using OutStream = IStream; - Stream() = default; + explicit Stream(EventContext auto const& context) + : stream_update_event_{context}, out_data_event_{context} {} + ~Stream() override = default; AE_CLASS_MOVE_ONLY(Stream) @@ -126,13 +130,11 @@ class Stream : public IStream { return out_->stream_info(); } - StreamUpdateEvent::Subscriber stream_update_event() override { - return EventSubscriber{stream_update_event_}; + StreamUpdateEvent const& stream_update_event() override { + return stream_update_event_; } - OutDataEvent::Subscriber out_data_event() override { - return EventSubscriber{out_data_event_}; - } + OutDataEvent const& out_data_event() override { return out_data_event_; } void Restream() override { if (out_ == nullptr) { @@ -167,7 +169,9 @@ class Stream : public IStream { using OutStream = IStream; - Stream() = default; + explicit Stream(EventContext auto const& context) + : stream_update_event_{context}, out_data_event_{context} {} + ~Stream() override = default; AE_CLASS_MOVE_ONLY(Stream) @@ -183,13 +187,11 @@ class Stream : public IStream { return out_->stream_info(); } - StreamUpdateEvent::Subscriber stream_update_event() override { - return EventSubscriber{stream_update_event_}; + StreamUpdateEvent const& stream_update_event() override { + return stream_update_event_; } - OutDataEvent::Subscriber out_data_event() override { - return EventSubscriber{out_data_event_}; - } + OutDataEvent const& out_data_event() override { return out_data_event_; } void Restream() override { if (out_ == nullptr) { @@ -203,8 +205,10 @@ class Stream : public IStream { */ virtual void LinkOut(OutStream& out) { out_ = &out; - update_sub_ = out_->stream_update_event().Subscribe(stream_update_event_); - out_data_sub_ = out_->out_data_event().Subscribe(out_data_event_); + update_sub_ = out_->stream_update_event().Subscribe( + MethodPtr<&StreamUpdateEvent::Emit>{&stream_update_event_}); + out_data_sub_ = out_->out_data_event().Subscribe( + MethodPtr<&OutDataEvent::Emit>{&out_data_event_}); stream_update_event_.Emit(); } /** diff --git a/aether/stream_api/sized_packet_gate.cpp b/aether/stream_api/sized_packet_gate.cpp index 132acb622..3d7d94750 100644 --- a/aether/stream_api/sized_packet_gate.cpp +++ b/aether/stream_api/sized_packet_gate.cpp @@ -47,8 +47,7 @@ void SizedPacketGate::WriteOut(DataBuffer const& buffer) { std::size_t SizedPacketGate::Overhead() const { return kSizedPacketOverhead; } -EventSubscriber -SizedPacketGate::out_data_event() { - return EventSubscriber{out_data_event_}; +Event const& SizedPacketGate::out_data_event() { + return out_data_event_; } } // namespace ae diff --git a/aether/stream_api/sized_packet_gate.h b/aether/stream_api/sized_packet_gate.h index 99c349a7d..0fcdf3513 100644 --- a/aether/stream_api/sized_packet_gate.h +++ b/aether/stream_api/sized_packet_gate.h @@ -23,10 +23,13 @@ namespace ae { class SizedPacketGate { public: + explicit SizedPacketGate(EventContext auto const& context) + : out_data_event_{context} {} + DataBuffer WriteIn(DataBuffer&& buffer); void WriteOut(DataBuffer const& buffer); std::size_t Overhead() const; - EventSubscriber out_data_event(); + Event const& out_data_event(); private: void DataReceived(DataBuffer const& buffer); diff --git a/aether/stream_api/stream_api.cpp b/aether/stream_api/stream_api.cpp deleted file mode 100644 index 5ff2a3706..000000000 --- a/aether/stream_api/stream_api.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "aether/stream_api/stream_api.h" - -#include - -#include "aether/types/packed_size.h" - -namespace ae { -StreamApiImpl::StreamApiImpl(ProtocolContext& protocol_context) - : ApiClass{protocol_context}, stream{protocol_context} {} - -void StreamApiImpl::Stream(StreamId stream_id, DataBuffer data) { - stream_event_.Emit(stream_id, data); -} - -StreamApiImpl::StreamEvent::Subscriber StreamApiImpl::stream_event() { - return EventSubscriber{stream_event_}; -} - -std::uint8_t StreamIdGenerator::GetNextClientStreamId() { - static StreamId stream_id = 1; - auto val = stream_id; - stream_id += 2; - return val; -} - -std::uint8_t StreamIdGenerator::GetNextServerStreamId() { - static StreamId stream_id = 2; - auto val = stream_id; - stream_id += 2; - return val; -} - -static constexpr std::size_t kStreamMessageOverhead = - 1 + 1 + - PackedSize::kMaxWireBytes; // message code + stream id + child data size - -StreamApiGate::StreamApiGate(StreamApiImpl& stream_api, StreamId stream_id) - : stream_id_{stream_id}, - stream_api_{&stream_api}, - read_subscription_{stream_api_->stream_event().Subscribe( - MethodPtr<&StreamApiGate::OnStream>{this})} {} - -DataBuffer StreamApiGate::WriteIn(DataBuffer&& buffer) { - auto api_call = ApiContext{*stream_api_}; - api_call->stream(stream_id_, std::move(buffer)); - return DataBuffer{std::move(api_call)}; -} - -void StreamApiGate::WriteOut(DataBuffer const& buffer) { - out_data_event_.Emit(buffer); -} - -EventSubscriber StreamApiGate::out_data_event() { - return EventSubscriber{out_data_event_}; -} - -std::size_t StreamApiGate::Overhead() const { return kStreamMessageOverhead; } - -void StreamApiGate::OnStream(StreamId stream_id, DataBuffer const& data) { - if (stream_id_ == stream_id) { - out_data_event_.Emit(data); - } -} - -} // namespace ae diff --git a/aether/stream_api/stream_api.h b/aether/stream_api/stream_api.h deleted file mode 100644 index 448ceec87..000000000 --- a/aether/stream_api/stream_api.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef AETHER_STREAM_API_STREAM_API_H_ -#define AETHER_STREAM_API_STREAM_API_H_ - -#include "aether/events/events.h" -#include "aether/types/data_buffer.h" -#include "aether/api_protocol/api_method.h" -#include "aether/events/event_subscription.h" -#include "aether/api_protocol/api_protocol.h" -#include "aether/api_protocol/api_class_impl.h" - -namespace ae { -using StreamId = std::uint8_t; - -class StreamApiImpl : public ApiClass { - public: - using StreamEvent = Event; - - explicit StreamApiImpl(ProtocolContext& protocol_context); - virtual ~StreamApiImpl() = default; - - void Stream(StreamId stream_id, DataBuffer data); - AE_METHODS(RegMethod<02, &StreamApiImpl::Stream>); - - Method<02, void(StreamId stream_id, DataBuffer data)> stream; - - StreamEvent::Subscriber stream_event(); - - private: - StreamEvent stream_event_; -}; - -class StreamIdGenerator { - public: - static StreamId GetNextClientStreamId(); - static StreamId GetNextServerStreamId(); -}; - -class StreamApiGate { - public: - StreamApiGate(StreamApiImpl& stream_api, StreamId stream_id); - - DataBuffer WriteIn(DataBuffer&& buffer); - void WriteOut(DataBuffer const& buffer); - std::size_t Overhead() const; - - EventSubscriber out_data_event(); - - private: - void OnStream(StreamId stream_id, DataBuffer const& data); - - StreamId stream_id_; - StreamApiImpl* stream_api_; - Subscription read_subscription_; - Event out_data_event_; -}; - -} // namespace ae - -#endif // AETHER_STREAM_API_STREAM_API_H_ diff --git a/aether/stream_api/tied_gates.h b/aether/stream_api/tied_gates.h index 5661086b0..92af21260 100644 --- a/aether/stream_api/tied_gates.h +++ b/aether/stream_api/tied_gates.h @@ -17,10 +17,10 @@ #ifndef AETHER_STREAM_API_TIED_GATES_H_ #define AETHER_STREAM_API_TIED_GATES_H_ -#include "aether/events/event_subscription.h" +#include "aether/events/events.h" -#include "aether/type_traits.h" #include "aether/stream_api/gate_trait.h" +#include "aether/type_traits.h" namespace ae { namespace gates_internal { diff --git a/aether/tasks/manual_task_scheduler.h b/aether/tasks/manual_task_scheduler.h index eeb5d5cf3..34900e22e 100644 --- a/aether/tasks/manual_task_scheduler.h +++ b/aether/tasks/manual_task_scheduler.h @@ -16,11 +16,19 @@ #ifndef AETHER_TASKS_MANUAL_TASK_SCHEDULER_H_ #define AETHER_TASKS_MANUAL_TASK_SCHEDULER_H_ + +#include "aether/config.h" + // IWYU pragma: begin_exports +#include "aether/tasks/details/manual_task_scheduler.h" #include "aether/tasks/details/task.h" #include "aether/tasks/details/task_manager.h" #include "aether/tasks/details/task_subsctiption.h" -#include "aether/tasks/details/manual_task_scheduler.h" // IWYU pragma: end_exports +namespace ae { +using TaskScheduler = ManualTaskScheduler< + TaskManagerConf>; +} + #endif // AETHER_TASKS_MANUAL_TASK_SCHEDULER_H_ diff --git a/aether/tele.h b/aether/tele.h index ac8a59113..5a05ccd37 100644 --- a/aether/tele.h +++ b/aether/tele.h @@ -27,5 +27,4 @@ #include "aether-tele/tele.h" // IWYU pragma: end_keeps - #endif // AETHER_TELE_H_ diff --git a/aether/tele_compile_options.h b/aether/tele_compile_options.h index 4534daefa..25f722993 100644 --- a/aether/tele_compile_options.h +++ b/aether/tele_compile_options.h @@ -37,11 +37,11 @@ constexpr inline auto _compile_options_list = std::array{ _OPTION(AE_TASK_MAX_COUNT), _OPTION(AE_TASK_MAX_SIZE), _OPTION(AE_TASK_ALIGN), + _OPTION(AE_EVENTS_MAX_COUNT), + _OPTION(AE_EVENT_HANDLER_MAX_SIZE), + _OPTION(AE_EVENT_HANDLER_ALIGN), _OPTION(AE_API_PROTOCOL_MAX_PENDING_RESPONSES), - _OPTION(AE_API_PROTOCOL_MAX_PACKET_STACK_DEPTH), - _OPTION(AE_API_PROTOCOL_MAX_PARSER_PACKER_DEPTH), - _OPTION(AE_API_PROTOCOL_PENDING_RESPONSE_MAX_SIZE), - _OPTION(AE_API_PROTOCOL_PENDING_RESPONSE_ALIGN), + _OPTION(AE_API_PROTOCOL_MAX_API_CALL_PER_CONTEXT), _OPTION(AE_SUPPORT_IPV4), _OPTION(AE_SUPPORT_IPV6), _OPTION(AE_SUPPORT_UDP), diff --git a/aether/tele_statistics.cpp b/aether/tele_statistics.cpp index d91e2f994..d684ad45a 100644 --- a/aether/tele_statistics.cpp +++ b/aether/tele_statistics.cpp @@ -22,6 +22,8 @@ TeleStatistics::TeleStatistics(ObjProp prop) : Obj{prop} {} #endif #if AE_TELE_ENABLED && AE_TELE_LOG_TO_STATISTICS -auto TeleStatistics::trap() -> std::shared_ptr const& { return trap_; } +auto TeleStatistics::trap() -> std::shared_ptr const& { + return trap_; +} #endif } // namespace ae diff --git a/aether/tele_statistics.h b/aether/tele_statistics.h index e5e5a06cd..64541ad68 100644 --- a/aether/tele_statistics.h +++ b/aether/tele_statistics.h @@ -20,9 +20,9 @@ #include #include "aether-miscpp/serialization/binary_archive.h" -#include "aether-tele/traps/statistics_trap.h" #include "aether-objects/obj/obj.h" +#include "aether/aether_tele.h" #include "aether/config.h" #include "aether/types/packed_size.h" @@ -51,12 +51,11 @@ class TeleStatistics : public Obj { #endif #if AE_TELE_ENABLED && AE_TELE_LOG_TO_STATISTICS - using Trap = tele::StatisticsTrap; - - std::shared_ptr const& trap(); + std::shared_ptr const& trap(); private: - std::shared_ptr trap_ = std::make_shared(); + std::shared_ptr trap_ = + std::make_shared(); #endif }; diff --git a/aether/transport/gateway/gateway_device.h b/aether/transport/gateway/gateway_device.h index 021bfbc3d..a4c5f1137 100644 --- a/aether/transport/gateway/gateway_device.h +++ b/aether/transport/gateway/gateway_device.h @@ -21,12 +21,12 @@ #if AE_SUPPORT_GATEWAY -# include "aether/types/server_id.h" -# include "aether/types/client_id.h" -# include "aether/types/data_buffer.h" # include "aether/actions/action_ptr.h" # include "aether/gateway_api/gateway_api.h" # include "aether/gateway_api/server_endpoints.h" +# include "aether/types/client_id.h" +# include "aether/types/data_buffer.h" +# include "aether/types/server_id.h" # include "aether/write_action/write_action.h" namespace ae { @@ -56,7 +56,7 @@ class IGatewayDevice { /** * \brief Get data from server by endpoints. */ - virtual FromServerEvent::Subscriber from_server_event() = 0; + virtual FromServerEvent const& from_server_event() = 0; }; } // namespace ae diff --git a/aether/transport/gateway/gateway_transport.cpp b/aether/transport/gateway/gateway_transport.cpp index d468f5857..1ff3feaf9 100644 --- a/aether/transport/gateway/gateway_transport.cpp +++ b/aether/transport/gateway/gateway_transport.cpp @@ -51,15 +51,15 @@ ActionPtr GatewayTransport::Write(DataBuffer&& in_data) { server_kind_); } -GatewayTransport::StreamUpdateEvent::Subscriber +GatewayTransport::StreamUpdateEvent const& GatewayTransport::stream_update_event() { - return EventSubscriber{stream_update_event_}; + return stream_update_event_; } StreamInfo GatewayTransport::stream_info() const { return stream_info_; } -GatewayTransport::OutDataEvent::Subscriber GatewayTransport::out_data_event() { - return EventSubscriber{out_data_event_}; +GatewayTransport::OutDataEvent const& GatewayTransport::out_data_event() { + return out_data_event_; } void GatewayTransport::Restream() { diff --git a/aether/transport/gateway/gateway_transport.h b/aether/transport/gateway/gateway_transport.h index e0d51111b..2c9026d4b 100644 --- a/aether/transport/gateway/gateway_transport.h +++ b/aether/transport/gateway/gateway_transport.h @@ -23,10 +23,10 @@ #if AE_SUPPORT_GATEWAY -# include "aether/types/server_id.h" -# include "aether/stream_api/istream.h" # include "aether/gateway_api/server_endpoints.h" +# include "aether/stream_api/istream.h" # include "aether/transport/gateway/gateway_device.h" +# include "aether/types/server_id.h" namespace ae { class GatewayTransport : public ByteIStream { @@ -38,9 +38,9 @@ class GatewayTransport : public ByteIStream { AE_CLASS_NO_COPY_MOVE(GatewayTransport) ActionPtr Write(DataBuffer&& in_data) override; - StreamUpdateEvent::Subscriber stream_update_event() override; + StreamUpdateEvent const& stream_update_event() override; StreamInfo stream_info() const override; - OutDataEvent::Subscriber out_data_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; private: diff --git a/aether/transport/modems/modem_transport.cpp b/aether/transport/modems/modem_transport.cpp index e32224a5d..287d6a685 100644 --- a/aether/transport/modems/modem_transport.cpp +++ b/aether/transport/modems/modem_transport.cpp @@ -153,14 +153,13 @@ ModemTransport::ModemTransport(AeContext const& ae_context, ModemTransport::~ModemTransport() { Disconnect(); } -ModemTransport::StreamUpdateEvent::Subscriber -ModemTransport::stream_update_event() { +ModemTransport::StreamUpdateEvent const& ModemTransport::stream_update_event() { return stream_update_event_; } StreamInfo ModemTransport::stream_info() const { return stream_info_; } -ModemTransport::OutDataEvent::Subscriber ModemTransport::out_data_event() { +ModemTransport::OutDataEvent const& ModemTransport::out_data_event() { return out_data_event_; } diff --git a/aether/transport/modems/modem_transport.h b/aether/transport/modems/modem_transport.h index b162a8621..860bc0486 100644 --- a/aether/transport/modems/modem_transport.h +++ b/aether/transport/modems/modem_transport.h @@ -96,9 +96,9 @@ class ModemTransport final : public ByteIStream { ~ModemTransport() override; WriteAction& Write(DataBuffer&& in_data) override; - StreamUpdateEvent::Subscriber stream_update_event() override; + StreamUpdateEvent const& stream_update_event() override; StreamInfo stream_info() const override; - OutDataEvent::Subscriber out_data_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; private: diff --git a/aether/transport/packet_send_action.h b/aether/transport/packet_send_action.h index e1bf91eee..8afb54c67 100644 --- a/aether/transport/packet_send_action.h +++ b/aether/transport/packet_send_action.h @@ -22,7 +22,8 @@ namespace ae { class PacketSendAction : public WriteAction { public: - PacketSendAction() = default; + explicit PacketSendAction(EventContext auto const& context) + : WriteAction(context) {} AE_CLASS_MOVE_ONLY(PacketSendAction) // Trigger event to send data virtual void Send() = 0; diff --git a/aether/transport/system_sockets/sockets/get_sock_addr.cpp b/aether/transport/system_sockets/sockets/get_sock_addr.cpp index 0c80d4c5a..12446f472 100644 --- a/aether/transport/system_sockets/sockets/get_sock_addr.cpp +++ b/aether/transport/system_sockets/sockets/get_sock_addr.cpp @@ -22,7 +22,7 @@ # include "aether-miscpp/misc/override.h" -# include "aether/env.h" +# include "aether/common.h" namespace ae { diff --git a/aether/transport/system_sockets/sockets/win_socket.h b/aether/transport/system_sockets/sockets/win_socket.h index 141bdcb91..6e5309615 100644 --- a/aether/transport/system_sockets/sockets/win_socket.h +++ b/aether/transport/system_sockets/sockets/win_socket.h @@ -20,14 +20,13 @@ #if defined _WIN32 # define WIN_SOCKET_ENABLED 1 -# include # include # include +# include # include "aether/poller/win_poller.h" -# include "aether/types/data_buffer.h" # include "aether/socket_initializer.h" -# include "aether/events/event_subscription.h" +# include "aether/types/data_buffer.h" # include "aether/transport/system_sockets/sockets/isocket.h" diff --git a/aether/transport/system_sockets/tcp/tcp.cpp b/aether/transport/system_sockets/tcp/tcp.cpp index 0a1803d18..e73049cda 100644 --- a/aether/transport/system_sockets/tcp/tcp.cpp +++ b/aether/transport/system_sockets/tcp/tcp.cpp @@ -26,19 +26,22 @@ namespace ae { namespace tcp_internal { TcpBase::TcpBase(AeContext const& ae_context, AddressPort endpoint) noexcept - : ae_context_{ae_context}, endpoint_{std::move(endpoint)} { + : ae_context_{ae_context}, + endpoint_{std::move(endpoint)}, + out_data_event_{ae_context_}, + stream_update_event_{ae_context_} { stream_info_.link_state = LinkState::kUnlinked; stream_info_.is_reliable = true; // TODO: find a better value for max element size stream_info_.max_element_size = std::numeric_limits::max(); } -TcpBase::StreamUpdateEvent::Subscriber TcpBase::stream_update_event() { - return EventSubscriber{stream_update_event_}; +TcpBase::StreamUpdateEvent const& TcpBase::stream_update_event() { + return stream_update_event_; } StreamInfo TcpBase::stream_info() const { return stream_info_; } -TcpBase::OutDataEvent::Subscriber TcpBase::out_data_event() { - return EventSubscriber{out_data_event_}; +TcpBase::OutDataEvent const& TcpBase::out_data_event() { + return out_data_event_; } void TcpBase::Restream() { diff --git a/aether/transport/system_sockets/tcp/tcp.h b/aether/transport/system_sockets/tcp/tcp.h index 7f871c22f..546847e97 100644 --- a/aether/transport/system_sockets/tcp/tcp.h +++ b/aether/transport/system_sockets/tcp/tcp.h @@ -30,7 +30,7 @@ # include "aether/ae_context.h" # include "aether/common.h" -# include "aether/events/multi_subscription.h" +# include "aether/events/events.h" # include "aether/poller/poller.h" # include "aether/vector_buffer.h" @@ -50,7 +50,8 @@ class SendAction final : public PacketSendAction { public: SendAction(AeContext const& ae_context, Sock& socket, DataBuffer&& data_buffer) - : ae_context_{ae_context}, + : PacketSendAction{ae_context}, + ae_context_{ae_context}, socket_{&socket}, data_{std::move(data_buffer)} {} @@ -110,9 +111,9 @@ class TcpBase : public ByteIStream { public: TcpBase(AeContext const& ae_context, AddressPort endpoint) noexcept; - StreamUpdateEvent::Subscriber stream_update_event() override; + StreamUpdateEvent const& stream_update_event() override; StreamInfo stream_info() const override; - OutDataEvent::Subscriber out_data_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; protected: diff --git a/aether/transport/system_sockets/udp/udp.cpp b/aether/transport/system_sockets/udp/udp.cpp index ca01f48d0..133e0ba79 100644 --- a/aether/transport/system_sockets/udp/udp.cpp +++ b/aether/transport/system_sockets/udp/udp.cpp @@ -21,19 +21,23 @@ namespace ae::upd_internal { UdpBase::UdpBase(AeContext const& ae_context, AddressPort endpoint) - : ae_context_{ae_context}, endpoint_{std::move(endpoint)}, stream_info_{} { + : ae_context_{ae_context}, + endpoint_{std::move(endpoint)}, + stream_info_{}, + out_data_event_{ae_context_}, + stream_update_event_{ae_context_} { AE_TELE_INFO(kUdpTransport); stream_info_.link_state = LinkState::kUnlinked; stream_info_.is_reliable = false; stream_info_.max_element_size = std::numeric_limits::max(); } -UdpBase::StreamUpdateEvent::Subscriber UdpBase::stream_update_event() { - return EventSubscriber{stream_update_event_}; +UdpBase::StreamUpdateEvent const& UdpBase::stream_update_event() { + return stream_update_event_; } StreamInfo UdpBase::stream_info() const { return stream_info_; } -UdpBase::OutDataEvent::Subscriber UdpBase::out_data_event() { - return EventSubscriber{out_data_event_}; +UdpBase::OutDataEvent const& UdpBase::out_data_event() { + return out_data_event_; } void UdpBase::Restream() { AE_TELED_DEBUG("UDP restream"); diff --git a/aether/transport/system_sockets/udp/udp.h b/aether/transport/system_sockets/udp/udp.h index f176720c8..a9de95509 100644 --- a/aether/transport/system_sockets/udp/udp.h +++ b/aether/transport/system_sockets/udp/udp.h @@ -29,7 +29,7 @@ # include # include "aether/ae_context.h" -# include "aether/events/multi_subscription.h" +# include "aether/events/events.h" # include "aether/poller/poller.h" # include "aether/stream_api/istream.h" @@ -46,7 +46,8 @@ class SendAction final : public PacketSendAction { public: SendAction(AeContext const& ae_context, Socket& socket, DataBuffer&& data_buffer) - : ae_context_{ae_context}, + : PacketSendAction{ae_context}, + ae_context_{ae_context}, socket_{&socket}, data_{std::move(data_buffer)} {} @@ -103,9 +104,9 @@ class UdpBase : public ByteIStream { public: UdpBase(AeContext const& ae_context, AddressPort endpoint); - StreamUpdateEvent::Subscriber stream_update_event() override; + StreamUpdateEvent const& stream_update_event() override; StreamInfo stream_info() const override; - OutDataEvent::Subscriber out_data_event() override; + OutDataEvent const& out_data_event() override; void Restream() override; protected: diff --git a/aether/types/state_machine.h b/aether/types/state_machine.h index d7684bf3f..39134c220 100644 --- a/aether/types/state_machine.h +++ b/aether/types/state_machine.h @@ -74,9 +74,7 @@ class StateMachine { /** * \brief Event that is called when state is changed */ - EventSubscriber changed_event() const { - return EventSubscriber{changed_event_}; - } + Event const& changed_event() const { return changed_event_; } private: bool changed_; diff --git a/aether/wifi/esp_wifi_driver.cpp b/aether/wifi/esp_wifi_driver.cpp index db5c14bec..34dfeec9a 100644 --- a/aether/wifi/esp_wifi_driver.cpp +++ b/aether/wifi/esp_wifi_driver.cpp @@ -329,7 +329,7 @@ esp_err_t StartWifiConnection( } // namespace esp_wifi_driver_internal EspWifiDriver::EspWifiDriver(AeContext const& ae_context) - : ae_context_{ae_context} { + : ae_context_{ae_context}, connect_res_event_{ae_context_} { esp_log_level_set(esp_wifi_driver_internal::kTag, ESP_LOG_DEBUG); Init(); } @@ -357,12 +357,13 @@ void EspWifiDriver::Connect( // the connection result will be handled in ConnectingEventHandler if (err != ESP_OK) { // Emitting the error, 2 for example. - connect_res_event_.Emit(Error(2)); + auto result = Result{Error(2)}; + connect_res_event_.Emit(result); } } -EspWifiDriver::ConnectResEvent::Subscriber EspWifiDriver::connect_res_event() { - return EventSubscriber{connect_res_event_}; +EspWifiDriver::ConnectResEvent const& EspWifiDriver::connect_res_event() { + return connect_res_event_; } std::optional EspWifiDriver::connected_to() const { @@ -464,7 +465,8 @@ void EspWifiDriver::ConnectingEventHandler(esp_event_base_t event_base, event_task_sub_ = ae_context_.scheduler().Task([&]() { Disconnect(); // connection failed - connect_res_event_.Emit(Error(1)); + auto result = Result{Error(1)}; + connect_res_event_.Emit(result); }); } break; @@ -496,8 +498,10 @@ void EspWifiDriver::ConnectingEventHandler(esp_event_base_t event_base, static_cast(base_station.target_channel)); connection_state_.state = State::kConnected; - event_task_sub_ = ae_context_.scheduler().Task( - [&]() { connect_res_event_.Emit(Ok{base_station}); }); + event_task_sub_ = ae_context_.scheduler().Task([&, base_station]() { + auto result = Result{Ok{base_station}}; + connect_res_event_.Emit(result); + }); break; } default: diff --git a/aether/wifi/esp_wifi_driver.h b/aether/wifi/esp_wifi_driver.h index 3431249fb..0759eab29 100644 --- a/aether/wifi/esp_wifi_driver.h +++ b/aether/wifi/esp_wifi_driver.h @@ -25,8 +25,8 @@ # include "freertos/FreeRTOS.h" -# include "freertos/task.h" # include "freertos/event_groups.h" +# include "freertos/task.h" # include "esp_err.h" # include "esp_netif_types.h" @@ -66,7 +66,7 @@ class EspWifiDriver final : public WifiDriver { std::optional const& psp, std::optional const& base_station_) override; - ConnectResEvent::Subscriber connect_res_event() override; + ConnectResEvent const& connect_res_event() override; std::optional connected_to() const override; diff --git a/aether/wifi/wifi_driver.h b/aether/wifi/wifi_driver.h index 411f15c97..d33b3b082 100644 --- a/aether/wifi/wifi_driver.h +++ b/aether/wifi/wifi_driver.h @@ -33,8 +33,11 @@ class WifiDriver { /** * \brief Wifi connection result. * \param res - AP parameters or error code + * + * The result is transferred to the handler. This event must have exactly one + * subscriber. This is a usage contract and is not enforced at runtime. */ - using ConnectResEvent = Event&& res)>; + using ConnectResEvent = Event& res)>; virtual ~WifiDriver() = default; @@ -45,7 +48,7 @@ class WifiDriver { std::optional const& psp, std::optional const& base_station) = 0; - virtual ConnectResEvent::Subscriber connect_res_event() = 0; + virtual ConnectResEvent const& connect_res_event() = 0; /** * \brief Get the AP ssid if connected diff --git a/aether/work_cloud_api/client_api/client_api_safe.cpp b/aether/work_cloud_api/client_api/client_api_safe.cpp index 2b937bd3b..6240974ca 100644 --- a/aether/work_cloud_api/client_api/client_api_safe.cpp +++ b/aether/work_cloud_api/client_api/client_api_safe.cpp @@ -24,8 +24,12 @@ namespace ae { -ClientApiSafe::ClientApiSafe(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, return_result{protocol_context} {} +ClientApiSafe::ClientApiSafe(EventSystem& event_system) + : send_message_event_{event_system}, + send_server_descriptor_event_{event_system}, + send_cloud_event_{event_system}, + request_telemetry_event_{event_system}, + send_cloud_configs_{event_system} {} void ClientApiSafe::ChangeParent([[maybe_unused]] Uid const& uid) { AE_TELED_DEBUG("ChangeParent"); diff --git a/aether/work_cloud_api/client_api/client_api_safe.h b/aether/work_cloud_api/client_api/client_api_safe.h index e1f08b012..388286157 100644 --- a/aether/work_cloud_api/client_api/client_api_safe.h +++ b/aether/work_cloud_api/client_api/client_api_safe.h @@ -19,8 +19,8 @@ #include "aether/types/uid.h" -#include "aether/events/events.h" #include "aether/api_protocol/api_protocol.h" +#include "aether/events/events.h" #include "aether/work_cloud_api/ae_message.h" #include "aether/work_cloud_api/cloud_configs.h" @@ -41,9 +41,9 @@ struct AccessCheckResult { bool has_access; }; -class ClientApiSafe : public ApiClassImpl { +class ClientApiSafe : public DeclareApi { public: - explicit ClientApiSafe(ProtocolContext& protocol_context); + explicit ClientApiSafe(EventSystem& event_system); void ChangeParent(Uid const& uid); void ChangeAlias(Uid const& uid); @@ -77,36 +77,27 @@ class ClientApiSafe : public ApiClassImpl { ReturnResultApi return_result; - AE_METHODS(RegMethod<3, &ClientApiSafe::ChangeParent>, - RegMethod<4, &ClientApiSafe::ChangeAlias>, - RegMethod<5, &ClientApiSafe::NewChildren>, - RegMethod<6, &ClientApiSafe::SendMessages>, - RegMethod<7, &ClientApiSafe::SendServerDescriptor>, - RegMethod<8, &ClientApiSafe::SendServerDescriptors>, - RegMethod<9, &ClientApiSafe::SendCloud>, - RegMethod<10, &ClientApiSafe::SendClouds>, - RegMethod<11, &ClientApiSafe::RequestTelemetry>, - RegMethod<12, &ClientApiSafe::SendAccessGroups>, - RegMethod<13, &ClientApiSafe::SendAccessGroupForClient>, - RegMethod<14, &ClientApiSafe::AddItemsToAccessGroup>, - RegMethod<15, &ClientApiSafe::RemoveItemsFromAccessGroup>, - RegMethod<16, &ClientApiSafe::AddAccessGroupsToClient>, - RegMethod<17, &ClientApiSafe::RemoveAccessGroupsFromClient>, - RegMethod<18, &ClientApiSafe::SendAllAccessedClients>, - RegMethod<19, &ClientApiSafe::SendAccessCheckResults>, - RegMethod<20, &ClientApiSafe::SendMessage>, - RegMethod<21, &ClientApiSafe::SendCLoudConfig>, - ExtApi<&ClientApiSafe::return_result>); - - auto send_message_event() { return EventSubscriber{send_message_event_}; } - auto send_cloud_event() { return EventSubscriber{send_cloud_event_}; } - auto send_server_descriptor_event() { - return EventSubscriber{send_server_descriptor_event_}; - } - auto request_telemetry_event() { - return EventSubscriber{request_telemetry_event_}; + API_LIST(METHOD(3, ChangeParent), METHOD(4, ChangeAlias), + METHOD(5, NewChildren), METHOD(6, SendMessages), + METHOD(7, SendServerDescriptor), METHOD(8, SendServerDescriptors), + METHOD(9, SendCloud), METHOD(10, SendClouds), + METHOD(11, RequestTelemetry), METHOD(12, SendAccessGroups), + METHOD(13, SendAccessGroupForClient), + METHOD(14, AddItemsToAccessGroup), + METHOD(15, RemoveItemsFromAccessGroup), + METHOD(16, AddAccessGroupsToClient), + METHOD(17, RemoveAccessGroupsFromClient), + METHOD(18, SendAllAccessedClients), + METHOD(19, SendAccessCheckResults), METHOD(20, SendMessage), + METHOD(21, SendCLoudConfig)); + + auto const& send_message_event() { return send_message_event_; } + auto const& send_cloud_event() { return send_cloud_event_; } + auto const& send_server_descriptor_event() { + return send_server_descriptor_event_; } - auto send_cloud_configs() { return EventSubscriber{send_cloud_configs_}; } + auto const& request_telemetry_event() { return request_telemetry_event_; } + auto const& send_cloud_configs() { return send_cloud_configs_; } private: Event send_message_event_; diff --git a/aether/work_cloud_api/client_api/client_api_unsafe.cpp b/aether/work_cloud_api/client_api/client_api_unsafe.cpp index 7bb23793a..984510a22 100644 --- a/aether/work_cloud_api/client_api/client_api_unsafe.cpp +++ b/aether/work_cloud_api/client_api/client_api_unsafe.cpp @@ -19,27 +19,18 @@ #include "aether/tele.h" namespace ae { -ClientApiUnsafe::ClientApiUnsafe(ProtocolContext& protocol_context, +ClientApiUnsafe::ClientApiUnsafe(EventSystem& event_system, IDecryptProvider& decrypt_provider) - : ApiClassImpl{protocol_context}, - return_result{protocol_context}, - decrypt_provider_{&decrypt_provider}, - client_safe_api_{protocol_context} {} + : decrypt_provider_{&decrypt_provider}, client_safe_api_{event_system} {} -void ClientApiUnsafe::SendSafeApiData(SubApiImpl sub_api) { - sub_api.Parse(client_safe_api_, [this](auto const& data) { - auto decrypted = Decrypt(data); - if (decrypted.empty()) { - AE_TELED_WARNING("Dropped packet: client safe api decrypt failed"); - return decrypted; - } - AE_TELED_DEBUG("Client api unsafe data {}", decrypted); - return decrypted; - }); -} +void ClientApiUnsafe::SendSafeApiData(SubApi sub_api) { + auto unsafe_data = decrypt_provider_->Decrypt(std::move(sub_api).buffer()); + AE_TELED_DEBUG("Client api unsafe data {}", unsafe_data); -DataBuffer ClientApiUnsafe::Decrypt(DataBuffer const& data) { - return decrypt_provider_->Decrypt(data); + auto parser = ApiParser{server_context().protocol_context(), unsafe_data}; + // not fully parsed + if (!parser.Parse(client_safe_api_)) { + server_context().reader().Cancel(); + } } - } // namespace ae diff --git a/aether/work_cloud_api/client_api/client_api_unsafe.h b/aether/work_cloud_api/client_api/client_api_unsafe.h index bf1bcad4b..8848e8a88 100644 --- a/aether/work_cloud_api/client_api/client_api_unsafe.h +++ b/aether/work_cloud_api/client_api/client_api_unsafe.h @@ -17,30 +17,26 @@ #ifndef AETHER_WORK_CLOUD_API_CLIENT_API_CLIENT_API_UNSAFE_H_ #define AETHER_WORK_CLOUD_API_CLIENT_API_CLIENT_API_UNSAFE_H_ -#include "aether/types/data_buffer.h" -#include "aether/crypto/icrypto_provider.h" #include "aether/api_protocol/api_protocol.h" +#include "aether/crypto/icrypto_provider.h" #include "aether/work_cloud_api/client_api/client_api_safe.h" namespace ae { -class ClientApiUnsafe : public ApiClassImpl { +class ClientApiUnsafe : public DeclareApi { public: - explicit ClientApiUnsafe(ProtocolContext& protocol_context, + explicit ClientApiUnsafe(EventSystem& event_system, IDecryptProvider& decrypt_provider); - void SendSafeApiData(SubApiImpl sub_api); + void SendSafeApiData(SubApi sub_api); ReturnResultApi return_result; - AE_METHODS(RegMethod<4, &ClientApiUnsafe::SendSafeApiData>, - ExtApi<&ClientApiUnsafe::return_result>); + API_LIST(METHOD(4, SendSafeApiData)) ClientApiSafe& client_api_safe() { return client_safe_api_; } private: - DataBuffer Decrypt(DataBuffer const& data); - IDecryptProvider* decrypt_provider_; ClientApiSafe client_safe_api_; }; diff --git a/aether/work_cloud_api/work_server_api/authorized_api.cpp b/aether/work_cloud_api/work_server_api/authorized_api.cpp index 22542c945..056ffa7c2 100644 --- a/aether/work_cloud_api/work_server_api/authorized_api.cpp +++ b/aether/work_cloud_api/work_server_api/authorized_api.cpp @@ -17,14 +17,33 @@ #include "aether/work_cloud_api/work_server_api/authorized_api.h" namespace ae { -AuthorizedApi::AuthorizedApi(ProtocolContext& protocol_context) - : ApiClass{protocol_context}, - ping{protocol_context}, - send_message{protocol_context}, - send_messages{protocol_context}, - check_access_for_send_message{protocol_context}, - resolver_servers{protocol_context}, - resolver_clouds{protocol_context}, - send_telemetry{protocol_context}, - report_applied_config{protocol_context} {} +ApiPromise AuthorizedApi::Ping(std::uint64_t next_connect_ms_duration, + std::uint64_t rx_window_ms) { + return ClientMethod<&AuthorizedApi::Ping>(next_connect_ms_duration, + rx_window_ms); +} +void AuthorizedApi::SendMessage(AeMessage const& message) { + ClientMethod<&AuthorizedApi::SendMessage>(message); +} +void AuthorizedApi::SendMessages(std::vector const& messages) { + ClientMethod<&AuthorizedApi::SendMessages>(messages); +} +ApiPromise AuthorizedApi::CheckAccessForSendMessage(Uid const& uid) { + return ClientMethod<&AuthorizedApi::CheckAccessForSendMessage>(uid); +} +void AuthorizedApi::ResolveServer(std::vector const& sids) { + ClientMethod<&AuthorizedApi::ResolveServer>(sids); +} +void AuthorizedApi::ResolveClouds(std::vector const& uids) { + ClientMethod<&AuthorizedApi::ResolveClouds>(uids); +} + +void AuthorizedApi::SendTelemetry(Telemetric const& telemetric) { + ClientMethod<&AuthorizedApi::SendTelemetry>(telemetric); +} + +void AuthorizedApi::ReportAppliedConfigs( + std::vector const& configs) { + ClientMethod<&AuthorizedApi::ReportAppliedConfigs>(configs); +} } // namespace ae diff --git a/aether/work_cloud_api/work_server_api/authorized_api.h b/aether/work_cloud_api/work_server_api/authorized_api.h index d72c4d668..204797a03 100644 --- a/aether/work_cloud_api/work_server_api/authorized_api.h +++ b/aether/work_cloud_api/work_server_api/authorized_api.h @@ -19,32 +19,36 @@ #include -#include "aether/types/uid.h" -#include "aether/types/server_id.h" #include "aether/api_protocol/api_protocol.h" +#include "aether/types/server_id.h" +#include "aether/types/uid.h" #include "aether/work_cloud_api/ae_message.h" -#include "aether/work_cloud_api/telemetric.h" #include "aether/work_cloud_api/cloud_configs.h" +#include "aether/work_cloud_api/telemetric.h" namespace ae { -class AuthorizedApi : public ApiClass { +class AuthorizedApi : public DeclareApi { public: - explicit AuthorizedApi(ProtocolContext& protocol_context); + AuthorizedApi() = default; + + ApiPromise Ping(std::uint64_t next_connect_ms_duration, + std::uint64_t rx_window_ms); + void SendMessage(AeMessage const& message); + void SendMessages(std::vector const& messages); + ApiPromise CheckAccessForSendMessage(Uid const& uid); + void ResolveServer(std::vector const& sids); + void ResolveClouds(std::vector const& uids); - Method<4, ApiPromise(std::uint64_t next_connect_ms_duration, - std::uint64_t rx_window_ms)> - ping; - Method<6, void(AeMessage message)> send_message; - Method<7, void(std::vector messages)> send_messages; - Method<11, ApiPromise(Uid uid)> check_access_for_send_message; - Method<12, void(std::vector sids)> resolver_servers; - Method<13, void(std::vector uids)> resolver_clouds; + void SendTelemetry(Telemetric const& telemetric); - Method<18, void(Telemetric telemetric)> send_telemetry; + void ReportAppliedConfigs(std::vector const& configs); - Method<38, void(std::vector configs)> report_applied_config; + API_LIST(METHOD(4, Ping), METHOD(6, SendMessage), METHOD(7, SendMessages), + METHOD(11, CheckAccessForSendMessage), METHOD(12, ResolveServer), + METHOD(13, ResolveClouds), METHOD(18, SendTelemetry), + METHOD(38, ReportAppliedConfigs)) }; } // namespace ae diff --git a/aether/work_cloud_api/work_server_api/login_api.cpp b/aether/work_cloud_api/work_server_api/login_api.cpp index 07e45bc1a..9b6bc04dc 100644 --- a/aether/work_cloud_api/work_server_api/login_api.cpp +++ b/aether/work_cloud_api/work_server_api/login_api.cpp @@ -19,20 +19,24 @@ #include "aether/tele.h" namespace ae { -LoginApi::LoginApi(ProtocolContext& protocol_context, - IEncryptProvider& encrypt_provider) - : ApiClass{protocol_context}, - login_by_uid{protocol_context, LoginProc{*this}}, - login_by_alias{protocol_context, LoginProc{*this}}, - get_my_ip{protocol_context}, - encrypt_provider_{&encrypt_provider}, - auth_api_{protocol_context} {} +LoginApi::LoginApi(IEncryptProvider& encrypt_provider) + : encrypt_provider_{&encrypt_provider}, auth_api_{} {} -DataBuffer LoginApi::Encrypt(DataBuffer const& data) { - AE_TELED_DEBUG("Login api data {}", data); - auto enc_data = encrypt_provider_->Encrypt(data); - AE_TELED_DEBUG("Login api encrypted size {}, {}", enc_data.size(), enc_data); - return enc_data; +void LoginApi::LoginByUid(Uid const& uid, SubApi sub_api) { + auto auth_data = std::move(sub_api)(auth_api_, client_context()); + AE_TELED_DEBUG("Login by uid {} data [{}]", uid, auth_data); + auto enc_data = encrypt_provider_->Encrypt(auth_data); + ClientMethod<&LoginApi::LoginByAlias>(uid, std::move(enc_data)); } +void LoginApi::LoginByAlias(Uid const& alias, SubApi sub_api) { + auto auth_data = std::move(sub_api)(auth_api_, client_context()); + AE_TELED_DEBUG("Login by alias {} data [{}]", alias, auth_data); + auto enc_data = encrypt_provider_->Encrypt(auth_data); + ClientMethod<&LoginApi::LoginByAlias>(alias, std::move(enc_data)); +} + +ApiPromise LoginApi::GetMyIp() { + return ClientMethod<&LoginApi::GetMyIp>(); +} } // namespace ae diff --git a/aether/work_cloud_api/work_server_api/login_api.h b/aether/work_cloud_api/work_server_api/login_api.h index a41bf1026..64cb950c8 100644 --- a/aether/work_cloud_api/work_server_api/login_api.h +++ b/aether/work_cloud_api/work_server_api/login_api.h @@ -17,45 +17,27 @@ #ifndef AETHER_WORK_CLOUD_API_WORK_SERVER_API_LOGIN_API_H_ #define AETHER_WORK_CLOUD_API_WORK_SERVER_API_LOGIN_API_H_ -#include "aether/types/uid.h" -#include "aether/types/data_buffer.h" -#include "aether/crypto/icrypto_provider.h" #include "aether/api_protocol/api_protocol.h" +#include "aether/crypto/icrypto_provider.h" +#include "aether/types/uid.h" #include "aether/work_cloud_api/info_ip.h" #include "aether/work_cloud_api/work_server_api/authorized_api.h" namespace ae { -class LoginApi : public ApiClass { - class LoginProc { - public: - explicit LoginProc(LoginApi& login_api) : login_api_{&login_api} {} - - auto operator()(Uid uid, SubApi sub_api) { - auto def_proc = DefaultArgProc{}; - return def_proc(uid, login_api_->Encrypt(sub_api(login_api_->auth_api_))); - } - - private: - LoginApi* login_api_; - }; - +class LoginApi : public DeclareApi { public: - explicit LoginApi(ProtocolContext& protocol_context, - IEncryptProvider& encrypt_provider); + explicit LoginApi(IEncryptProvider& encrypt_provider); - Method<4, void(Uid uid, SubApi sub_api), LoginProc> - login_by_uid; - Method<5, void(Uid alias, SubApi sub_api), LoginProc> - login_by_alias; + void LoginByUid(Uid const& uid, SubApi sub_api); + void LoginByAlias(Uid const& alias, SubApi sub_api); + ApiPromise GetMyIp(); - Method<6, ApiPromise()> get_my_ip; + API_LIST(METHOD(4, LoginByUid), METHOD(5, LoginByAlias), METHOD(6, GetMyIp)) AuthorizedApi& authorized_api() { return auth_api_; } private: - DataBuffer Encrypt(DataBuffer const& data); - IEncryptProvider* encrypt_provider_; AuthorizedApi auth_api_; }; diff --git a/aether/write_action/buffer_write.cpp b/aether/write_action/buffer_write.cpp index 640668be4..705026b6e 100644 --- a/aether/write_action/buffer_write.cpp +++ b/aether/write_action/buffer_write.cpp @@ -19,8 +19,6 @@ #include namespace ae { -BufferedWriteAction::BufferedWriteAction() noexcept = default; - void BufferedWriteAction::Stop() noexcept { if (wa_sub_) { // wa_sub_ is used as alive indicator assert(wa_ != nullptr && "Child write action is null"); diff --git a/aether/write_action/buffer_write.h b/aether/write_action/buffer_write.h index 9d90fe789..45f366ab4 100644 --- a/aether/write_action/buffer_write.h +++ b/aether/write_action/buffer_write.h @@ -27,7 +27,7 @@ DISABLE_WARNING_POP() #include "aether-miscpp/types/small_function.h" #include "aether/ae_context.h" -#include "aether/events/multi_subscription.h" +#include "aether/events/events.h" #include "aether/write_action/failed_write_action.h" #include "aether/write_action/write_action.h" @@ -53,7 +53,8 @@ namespace ae { class BufferedWriteAction final : public WriteAction { public: - BufferedWriteAction() noexcept; + explicit BufferedWriteAction(EventContext auto const& context) + : WriteAction{context} {} AE_CLASS_MOVE_ONLY(BufferedWriteAction); @@ -128,7 +129,7 @@ class BufferWrite { return FailedWrite(); } buffer_.push(BufferEntry{ - BufferedWriteAction{}, + BufferedWriteAction{ae_context_}, std::move(data), }); auto& buff_entry = buffer_.back(); diff --git a/aether/write_action/done_write_action.h b/aether/write_action/done_write_action.h index 6cd0d32b7..43771899b 100644 --- a/aether/write_action/done_write_action.h +++ b/aether/write_action/done_write_action.h @@ -17,7 +17,7 @@ #ifndef AETHER_WRITE_ACTION_DONE_WRITE_ACTION_H_ #define AETHER_WRITE_ACTION_DONE_WRITE_ACTION_H_ -#include "aether/ae_context.h" +#include "aether/actions/action_context.h" #include "aether/write_action/write_action.h" namespace ae { @@ -26,7 +26,8 @@ namespace ae { */ class DoneWriteAction final : public WriteAction { public: - explicit DoneWriteAction(AeContext const& context) { + explicit DoneWriteAction(ActionContext auto const& context) + : WriteAction{context} { context.scheduler().Task( [&]() { SetStatus(WriteAction::Status::kSuccess); }); } diff --git a/aether/write_action/failed_write_action.h b/aether/write_action/failed_write_action.h index fc11ad7d9..ddba5476a 100644 --- a/aether/write_action/failed_write_action.h +++ b/aether/write_action/failed_write_action.h @@ -17,7 +17,7 @@ #ifndef AETHER_WRITE_ACTION_FAILED_WRITE_ACTION_H_ #define AETHER_WRITE_ACTION_FAILED_WRITE_ACTION_H_ -#include "aether/ae_context.h" +#include "aether/actions/action_context.h" #include "aether/write_action/write_action.h" namespace ae { @@ -26,7 +26,8 @@ namespace ae { */ class FailedWriteAction final : public WriteAction { public: - explicit FailedWriteAction(AeContext const& context) { + explicit FailedWriteAction(ActionContext auto const& context) + : WriteAction{context} { context.scheduler().Task([&]() { SetStatus(WriteAction::Status::kFail); }); } }; diff --git a/aether/write_action/write_action.h b/aether/write_action/write_action.h index 74ce6f0b7..6ebae53cf 100644 --- a/aether/write_action/write_action.h +++ b/aether/write_action/write_action.h @@ -17,8 +17,8 @@ #ifndef AETHER_WRITE_ACTION_WRITE_ACTION_H_ #define AETHER_WRITE_ACTION_WRITE_ACTION_H_ -#include "aether/events/events.h" #include "aether/actions/action.h" +#include "aether/events/events.h" namespace ae { /** @@ -33,7 +33,8 @@ class WriteAction : public Action { }; using StatusEvent = Event; - WriteAction() noexcept = default; + explicit WriteAction(EventContext auto const& context) noexcept + : Action{context}, status_event_{context} {} ~WriteAction() noexcept override = default; AE_CLASS_MOVE_ONLY(WriteAction) @@ -46,9 +47,7 @@ class WriteAction : public Action { /** * \brief Subscribe to status event of this action. */ - virtual StatusEvent::Subscriber status_event() noexcept { - return EventSubscriber{status_event_}; - } + virtual StatusEvent const& status_event() noexcept { return status_event_; } protected: virtual void SetStatus(Status status) noexcept { diff --git a/examples/a_b_message_exchange/a_b_message_exchange.cpp b/examples/a_b_message_exchange/a_b_message_exchange.cpp index 3f6c6a996..d85a2c06b 100644 --- a/examples/a_b_message_exchange/a_b_message_exchange.cpp +++ b/examples/a_b_message_exchange/a_b_message_exchange.cpp @@ -50,7 +50,10 @@ static std::string_view ToString(ae::DataBuffer const& data) { } struct State { - explicit State(ae::AetherApp& app) : aether{app.aether()} {} + explicit State(ae::AetherApp& app) + : aether{app.aether()}, + a_received_event{AeContext{app}}, + b_received_event{AeContext{app}} {} ae::Aether::ptr aether; ae::Client::ptr client_a; @@ -92,7 +95,7 @@ static void OpenAStream(State* state) { // subscribe to open new message stream state->client_b->message_stream_manager().new_port_event().Subscribe( - [state](ae::P2pPortHandle handle) { + [state](ae::P2pPortHandle& handle) { state->b_stream = std::make_shared( *state->aether, state->client_b.Load(), handle.destination(), std::move(handle)); @@ -115,13 +118,12 @@ static auto SendMessageAtoB(State* state, int message_num) { text); // Expect B receive the message - test_sub_ = - ae::EventSubscriber{state->b_received_event}.Subscribe( - [&](std::string_view message) noexcept { - Log("message.A_to_B.receive.done num={} text=[{}]", - message_num, message); - return ae::ex::set_value(std::move(ctx.receiver)); - }); + test_sub_ = state->b_received_event.Subscribe( + [&](std::string_view message) noexcept { + Log("message.A_to_B.receive.done num={} text=[{}]", + message_num, message); + return ae::ex::set_value(std::move(ctx.receiver)); + }); state->a_stream->Write(ToDataBuffer(text)) .status_event() @@ -144,13 +146,12 @@ static auto SendMessageBtoA(State* state, int message_num) { text); // Expect A receive the message - test_sub_ = - ae::EventSubscriber{state->a_received_event}.Subscribe( - [&](std::string_view message) noexcept { - Log("message.B_to_A.receive.done num={} text=[{}]", - message_num, message); - return ae::ex::set_value(std::move(ctx.receiver)); - }); + test_sub_ = state->a_received_event.Subscribe( + [&](std::string_view message) noexcept { + Log("message.B_to_A.receive.done num={} text=[{}]", + message_num, message); + return ae::ex::set_value(std::move(ctx.receiver)); + }); if (!state->b_stream) { Log("message.B_to_A.send.failed B has no stream to A"); diff --git a/examples/benches/send_message_delays/api/bench_delays_api.cpp b/examples/benches/send_message_delays/api/bench_delays_api.cpp index ca77fcedd..0769e502f 100644 --- a/examples/benches/send_message_delays/api/bench_delays_api.cpp +++ b/examples/benches/send_message_delays/api/bench_delays_api.cpp @@ -17,48 +17,50 @@ #include "send_message_delays/api/bench_delays_api.h" namespace ae::bench { -BenchDelaysApi::BenchDelaysApi(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, - warm_up{protocol_context}, - two_bytes{protocol_context}, - ten_bytes{protocol_context}, - hundred_bytes{protocol_context}, - thousand_bytes{protocol_context} {} -void BenchDelaysApi::WarmUpImpl(std::uint16_t id, Payload<98> payload) { - warm_up_event_.Emit(id, payload); +void BenchDelaysApi::WarmUp(std::uint16_t id, Payload<98> const& payload) { + ClientMethod<&BenchDelaysApi::WarmUp>(id, payload); } -void BenchDelaysApi::TwoBytesImpl(std::uint16_t id) { - two_bytes_event_.Emit(id); +void BenchDelaysApi::TwoBytes(std::uint16_t id) { + ClientMethod<&BenchDelaysApi::TwoBytes>(id); } -void BenchDelaysApi::TenBytesImpl(std::uint16_t id, Payload<8> payload) { - ten_bytes_event_.Emit(id, payload); +void BenchDelaysApi::TenBytes(std::uint16_t id, Payload<8> const& payload) { + ClientMethod<&BenchDelaysApi::TenBytes>(id, payload); } -void BenchDelaysApi::HundredBytesImpl(std::uint16_t id, Payload<98> payload) { - hundred_bytes_event_.Emit(id, payload); +void BenchDelaysApi::HundredBytes(std::uint16_t id, + Payload<98> const& payload) { + ClientMethod<&BenchDelaysApi::HundredBytes>(id, payload); } -void BenchDelaysApi::ThousandBytesImpl(std::uint16_t id, Payload<998> payload) { - thousand_bytes_event_.Emit(id, payload); +void BenchDelaysApi::ThousandBytes(std::uint16_t id, + Payload<998> const& payload) { + ClientMethod<&BenchDelaysApi::ThousandBytes>(id, payload); } -EventSubscriber)> -BenchDelaysApi::warm_up_event() { - return EventSubscriber{warm_up_event_}; +BenchDelaysApiServer::BenchDelaysApiServer(EventSystem& es) + : warm_up_event{es}, + two_bytes_event{es}, + ten_bytes_event{es}, + hundred_bytes_event{es}, + thousand_bytes_event{es} {} + +void BenchDelaysApiServer::WarmUp(std::uint16_t id, + Payload<98> const& payload) { + warm_up_event.Emit(id, payload); } -EventSubscriber BenchDelaysApi::two_bytes_event() { - return EventSubscriber{two_bytes_event_}; +void BenchDelaysApiServer::TwoBytes(std::uint16_t id) { + two_bytes_event.Emit(id); } -EventSubscriber)> -BenchDelaysApi::ten_bytes_event() { - return EventSubscriber{ten_bytes_event_}; +void BenchDelaysApiServer::TenBytes(std::uint16_t id, + Payload<8> const& payload) { + ten_bytes_event.Emit(id, payload); } -EventSubscriber)> -BenchDelaysApi::hundred_bytes_event() { - return EventSubscriber{hundred_bytes_event_}; +void BenchDelaysApiServer::HundredBytes(std::uint16_t id, + Payload<98> const& payload) { + hundred_bytes_event.Emit(id, payload); } -EventSubscriber)> -BenchDelaysApi::thousand_bytes_event() { - return EventSubscriber{thousand_bytes_event_}; +void BenchDelaysApiServer::ThousandBytes(std::uint16_t id, + Payload<998> const& payload) { + thousand_bytes_event.Emit(id, payload); } } // namespace ae::bench diff --git a/examples/benches/send_message_delays/api/bench_delays_api.h b/examples/benches/send_message_delays/api/bench_delays_api.h index 76bcdda31..f30d6d425 100644 --- a/examples/benches/send_message_delays/api/bench_delays_api.h +++ b/examples/benches/send_message_delays/api/bench_delays_api.h @@ -19,48 +19,42 @@ #include +#include "aether/api_protocol/api_protocol.h" #include "aether/events/events.h" -#include "aether/api_protocol/api_method.h" -#include "aether/api_protocol/api_class_impl.h" namespace ae::bench { -class BenchDelaysApi : public ApiClassImpl { +class BenchDelaysApi : public DeclareApi { public: + virtual ~BenchDelaysApi() = default; + template using Payload = std::array; - explicit BenchDelaysApi(ProtocolContext& protocol_context); - - Method<0x03, void(std::uint16_t id, Payload<98> payload)> warm_up; - Method<0x04, void(std::uint16_t id)> two_bytes; - Method<0x05, void(std::uint16_t id, Payload<8>)> ten_bytes; - Method<0x06, void(std::uint16_t id, Payload<98>)> hundred_bytes; - Method<0x08, void(std::uint16_t id, Payload<998>)> thousand_bytes; - - void WarmUpImpl(std::uint16_t id, Payload<98> payload); - void TwoBytesImpl(std::uint16_t id); - void TenBytesImpl(std::uint16_t id, Payload<8> payload); - void HundredBytesImpl(std::uint16_t id, Payload<98> payload); - void ThousandBytesImpl(std::uint16_t id, Payload<998> payload); + virtual void WarmUp(std::uint16_t id, Payload<98> const& payload); + virtual void TwoBytes(std::uint16_t id); + virtual void TenBytes(std::uint16_t id, Payload<8> const& payload); + virtual void HundredBytes(std::uint16_t id, Payload<98> const& payload); + virtual void ThousandBytes(std::uint16_t id, Payload<998> const& payload); - EventSubscriber)> warm_up_event(); - EventSubscriber two_bytes_event(); - EventSubscriber)> ten_bytes_event(); - EventSubscriber)> hundred_bytes_event(); - EventSubscriber)> thousand_bytes_event(); - - AE_METHODS(RegMethod<0x03, &BenchDelaysApi::WarmUpImpl>, - RegMethod<0x04, &BenchDelaysApi::TwoBytesImpl>, - RegMethod<0x05, &BenchDelaysApi::TenBytesImpl>, - RegMethod<0x06, &BenchDelaysApi::HundredBytesImpl>, - RegMethod<0x08, &BenchDelaysApi::ThousandBytesImpl>); + API_LIST(METHOD(0x03, WarmUp), METHOD(0x04, TwoBytes), METHOD(0x05, TenBytes), + METHOD(0x06, HundredBytes), METHOD(0x08, ThousandBytes)) +}; - private: - Event)> warm_up_event_; - Event two_bytes_event_; - Event)> ten_bytes_event_; - Event)> hundred_bytes_event_; - Event)> thousand_bytes_event_; +class BenchDelaysApiServer : public BenchDelaysApi { + public: + explicit BenchDelaysApiServer(EventSystem& es); + + void WarmUp(std::uint16_t id, Payload<98> const& payload) override; + void TwoBytes(std::uint16_t id) override; + void TenBytes(std::uint16_t id, Payload<8> const& payload) override; + void HundredBytes(std::uint16_t id, Payload<98> const& payload) override; + void ThousandBytes(std::uint16_t id, Payload<998> const& payload) override; + + Event const&)> warm_up_event; + Event two_bytes_event; + Event const&)> ten_bytes_event; + Event const&)> hundred_bytes_event; + Event const&)> thousand_bytes_event; }; } // namespace ae::bench diff --git a/examples/benches/send_message_delays/main.cpp b/examples/benches/send_message_delays/main.cpp index c38fb479d..8fe476c40 100644 --- a/examples/benches/send_message_delays/main.cpp +++ b/examples/benches/send_message_delays/main.cpp @@ -37,13 +37,12 @@ class TestSendMessageDelays { std::ostream& write_results_stream) : ae_context_{aether_app}, aether_{aether_app.aether()}, - write_results_stream_{write_results_stream} { + write_results_stream_{write_results_stream}, + test_finished_event_{ae_context_} { TestPipeline(); } - TestFinishedEvent::Subscriber test_finished() { - return EventSubscriber{test_finished_event_}; - } + TestFinishedEvent const& test_finished() { return test_finished_event_; } private: auto GetClients() { diff --git a/examples/benches/send_message_delays/receiver.cpp b/examples/benches/send_message_delays/receiver.cpp index f9bb1eee6..833abb20c 100644 --- a/examples/benches/send_message_delays/receiver.cpp +++ b/examples/benches/send_message_delays/receiver.cpp @@ -33,14 +33,15 @@ Receiver::Receiver(AeContext const& ae_context, Client::ptr client, : ae_context_{ae_context}, client_{std::move(client)}, safe_stream_config_{safe_stream_config}, - bench_delays_api_{protocol_context_} {} + protocol_context_{ae_context_}, + bench_delays_api_{ae_context_.event_system()} {} void Receiver::ConnectP2pStream() { AE_TELED_DEBUG("Receiver::ConnectP2pStream()"); message_stream_subscription_ = client_->message_stream_manager().new_port_event().Subscribe( - [this](ae::P2pPortHandle handle) { + [this](ae::P2pPortHandle& handle) { AE_TELED_DEBUG("Receive new connection"); auto dest = handle.destination(); receive_message_stream_ = std::make_shared( @@ -64,7 +65,7 @@ void Receiver::ConnectP2pSafeStream() { } else { message_stream_subscription_ = client_->message_stream_manager().new_port_event().Subscribe( - [this](ae::P2pPortHandle handle) { + [this](ae::P2pPortHandle& handle) { AE_TELED_DEBUG("Receive new safe stream connection"); auto dest = handle.destination(); auto p2p_stream = std::make_shared( @@ -81,31 +82,32 @@ void Receiver::ConnectP2pSafeStream() { void Receiver::Disconnect() { AE_TELED_DEBUG("Receiver::Disconnect()"); } TimedReceiver& Receiver::WarmUp(std::size_t message_count) { - return CreateBenchAction(bench_delays_api_.warm_up_event(), message_count); + return CreateBenchAction(bench_delays_api_.warm_up_event, message_count); } TimedReceiver& Receiver::Receive2Bytes(std::size_t message_count) { - return CreateBenchAction(bench_delays_api_.two_bytes_event(), message_count); + return CreateBenchAction(bench_delays_api_.two_bytes_event, message_count); } TimedReceiver& Receiver::Receive10Bytes(std::size_t message_count) { - return CreateBenchAction(bench_delays_api_.ten_bytes_event(), message_count); + return CreateBenchAction(bench_delays_api_.ten_bytes_event, message_count); } TimedReceiver& Receiver::Receive100Bytes(std::size_t message_count) { - return CreateBenchAction(bench_delays_api_.hundred_bytes_event(), + return CreateBenchAction(bench_delays_api_.hundred_bytes_event, message_count); } TimedReceiver& Receiver::Receive1000Bytes(std::size_t message_count) { - return CreateBenchAction(bench_delays_api_.thousand_bytes_event(), + return CreateBenchAction(bench_delays_api_.thousand_bytes_event, message_count); } template -TimedReceiver& Receiver::CreateBenchAction(TEvent event, std::size_t count) { +TimedReceiver& Receiver::CreateBenchAction(TEvent const& event, + std::size_t count) { receiver_action_ = std::make_unique(ae_context_, count); - api_recv_sub_ = event.Subscribe([this](auto&&... args) mutable { + api_recv_sub_ = event.Subscribe([this](auto&&... args) { if (receiver_action_) { receiver_action_->Receive( VarAt<0>(std::forward(args)...)); diff --git a/examples/benches/send_message_delays/receiver.h b/examples/benches/send_message_delays/receiver.h index 0b8aa49e3..bb4fc0cde 100644 --- a/examples/benches/send_message_delays/receiver.h +++ b/examples/benches/send_message_delays/receiver.h @@ -19,15 +19,15 @@ #include -#include "aether/memory.h" -#include "aether/client.h" #include "aether/ae_context.h" -#include "aether/events/event_subscription.h" +#include "aether/client.h" #include "aether/client_messages/p2p_message_stream.h" #include "aether/client_messages/p2p_safe_message_stream.h" +#include "aether/events/events.h" +#include "aether/memory.h" -#include "send_message_delays/timed_receiver.h" #include "send_message_delays/api/bench_delays_api.h" +#include "send_message_delays/timed_receiver.h" namespace ae::bench { class Receiver { @@ -47,7 +47,7 @@ class Receiver { private: template - TimedReceiver& CreateBenchAction(TEvent event, std::size_t count); + TimedReceiver& CreateBenchAction(TEvent const& event, std::size_t count); void OnRecvData(DataBuffer const& data); @@ -56,7 +56,7 @@ class Receiver { SafeStreamConfig safe_stream_config_; ProtocolContext protocol_context_; - BenchDelaysApi bench_delays_api_; + BenchDelaysApiServer bench_delays_api_; std::shared_ptr receive_message_stream_; std::unique_ptr receive_message_safe_stream_; diff --git a/examples/benches/send_message_delays/send_message_delays_manager.cpp b/examples/benches/send_message_delays/send_message_delays_manager.cpp index 7b605c9e5..6958b726a 100644 --- a/examples/benches/send_message_delays/send_message_delays_manager.cpp +++ b/examples/benches/send_message_delays/send_message_delays_manager.cpp @@ -27,13 +27,14 @@ SendMessageDelaysManager::TestAction::TestAction( : ae_context_{ae_context}, sender_{&sender}, receiver_{&receiver}, - config_{config} { + config_{config}, + result_event_{ae_context_} { TestPipeline(); } -SendMessageDelaysManager::TestAction::ResultEvent::Subscriber +SendMessageDelaysManager::TestAction::ResultEvent const& SendMessageDelaysManager::TestAction::result_event() { - return EventSubscriber{result_event_}; + return result_event_; } auto SendMessageDelaysManager::TestAction::ConnectPeers() { @@ -56,72 +57,56 @@ auto SendMessageDelaysManager::TestAction::SafeConnectPeers() { auto SendMessageDelaysManager::TestAction::WarmUp() { return ex::let_value([&]() noexcept { - return ex::create( - [&](auto& ctx) noexcept { - AE_TELED_INFO("WarmUp"); - res_event_ = make_unique>(); - - auto& sender_warm_up = sender_->WarmUp(); - auto& receiver_warm_up = - receiver_->WarmUp(config_.warm_up_message_count); - - res_event_->Connect([&](auto, auto const&) {}, - sender_warm_up.message_times_event(), - receiver_warm_up.message_times_event()); - - test_subscriptions_.Push( // - receiver_warm_up.on_timeout().Subscribe([&]() { - AE_TELED_ERROR("Warm up error"); - ex::set_error(std::move(ctx.receiver), 1); - }), - receiver_warm_up.on_received().Subscribe( - [&sender_warm_up](bool last) mutable { - if (last) { - sender_warm_up.Stop(); - } else { - sender_warm_up.Sync(); - } - }), - // on success go to tests - res_event_->Subscribe([&](auto const&) { - AE_TELED_INFO("WarmUp p2p stream finished"); - ex::set_value(std::move(ctx.receiver)); - })); + AE_TELED_INFO("WarmUp"); + + auto& sender_warm_up = sender_->WarmUp(); + auto& receiver_warm_up = receiver_->WarmUp(config_.warm_up_message_count); + + test_sync_sub_ = receiver_warm_up.on_received().Subscribe( + [&sender_warm_up](bool last) mutable { + if (last) { + sender_warm_up.Stop(); + } else { + sender_warm_up.Sync(); + } }); + + return ex::when_all( + ex::event_wait(sender_warm_up.result_event()), + ex::event_wait(receiver_warm_up.message_times_event())) | + ex::then([](auto const&...) noexcept { + AE_TELED_INFO("WarmUp p2p stream finished"); + return; + }) | + ex::let_error([](auto&&) noexcept { + AE_TELED_ERROR("Warm up error"); + return ex::just_error(1); + }); }); } auto SendMessageDelaysManager::TestAction::SubscribeToTest( TimedSender& sender_action, TimedReceiver& receiver_action) { - return ex::create( - [&](auto& ctx) noexcept { - test_subscriptions_.Reset(); - res_event_ = make_unique>(); - res_event_->Connect( - [&](auto times_setter, auto const& message_times) { - times_setter = message_times; - }, - sender_action.message_times_event(), - receiver_action.message_times_event()); - - test_subscriptions_.Push( - receiver_action.on_received().Subscribe([&](bool last) mutable { - if (!last) { - sender_action.Sync(); - } else { - sender_action.Stop(); - } - }), - receiver_action.on_timeout().Subscribe([&]() { - AE_TELED_ERROR("Test error"); - ex::set_error(std::move(ctx.receiver), 2); - }), - res_event_->Subscribe([&](auto const& res_event) { - AE_TELED_INFO("Test finished"); - TestResult(res_event[0], res_event[1]); - ex::set_value(std::move(ctx.receiver)); - })); + test_sync_sub_ = + receiver_action.on_received().Subscribe([&](bool last) mutable { + if (!last) { + sender_action.Sync(); + } else { + sender_action.Stop(); + } }); + + return ex::when_all(ex::event_wait(sender_action.result_event()), + ex::event_wait(receiver_action.message_times_event())) | + ex::then( + [&](auto const& res_sender, auto const& res_receiver) noexcept { + AE_TELED_INFO("Test finished"); + TestResult(res_sender, res_receiver); + }) | + ex::let_error([&](auto&&) noexcept { + AE_TELED_ERROR("Test error"); + return ex::just_error(2); + }); } auto SendMessageDelaysManager::TestAction::Test2Bytes() { diff --git a/examples/benches/send_message_delays/send_message_delays_manager.h b/examples/benches/send_message_delays/send_message_delays_manager.h index 8b971b6a5..1d3b7cb5f 100644 --- a/examples/benches/send_message_delays/send_message_delays_manager.h +++ b/examples/benches/send_message_delays/send_message_delays_manager.h @@ -18,15 +18,14 @@ #include -#include "aether/clock.h" #include "aether-miscpp/types/result.h" +#include "aether/clock.h" +#include "aether/events/events.h" #include "aether/executors/executors.h" -#include "aether/events/cumulative_event.h" -#include "aether/events/multi_subscription.h" -#include "send_message_delays/sender.h" -#include "send_message_delays/receiver.h" #include "send_message_delays/delay_statistics.h" +#include "send_message_delays/receiver.h" +#include "send_message_delays/sender.h" namespace ae::bench { @@ -44,7 +43,7 @@ class SendMessageDelaysManager { TestAction(AeContext const& ae_context, Sender& sender, Receiver& receiver, SendMessageDelaysManagerConfig config); - ResultEvent::Subscriber result_event(); + ResultEvent const& result_event(); private: auto ConnectPeers(); @@ -70,8 +69,7 @@ class SendMessageDelaysManager { Receiver* receiver_; SendMessageDelaysManagerConfig config_; - std::unique_ptr> res_event_; - MultiSubscription test_subscriptions_; + Subscription test_sync_sub_; std::unique_ptr> test_pipeline_; diff --git a/examples/benches/send_message_delays/sender.cpp b/examples/benches/send_message_delays/sender.cpp index a950beb43..f7492452a 100644 --- a/examples/benches/send_message_delays/sender.cpp +++ b/examples/benches/send_message_delays/sender.cpp @@ -18,7 +18,7 @@ #include -#include "aether/api_protocol/api_context.h" +#include "aether/api_protocol/api_protocol.h" #include "aether/client_messages/p2p_message_stream.h" #include "aether/client_messages/p2p_safe_message_stream.h" #include "aether/stream_api/api_call_adapter.h" @@ -35,7 +35,8 @@ Sender::Sender(AeContext const& ae_context, Client::ptr client, destination_uid_{destination_uid}, safe_stream_config_{safe_stream_config}, connected_stream_{}, - bench_delays_api_{protocol_context_} {} + protocol_context_{ae_context_}, + bench_delays_api_{} {} void Sender::ConnectP2pStream() { AE_TELED_DEBUG("Sender::ConnectP2pStream()"); @@ -63,25 +64,25 @@ void Sender::Disconnect() { } TimedSender& Sender::WarmUp() { - return CreateBenchAction([](auto& api, auto id) { api->warm_up(id, {}); }); + return CreateBenchAction([](auto& api, auto id) { api->WarmUp(id, {}); }); } TimedSender& Sender::Send2Bytes() { - return CreateBenchAction([](auto& api, auto id) { api->two_bytes(id); }); + return CreateBenchAction([](auto& api, auto id) { api->TwoBytes(id); }); } TimedSender& Sender::Send10Bytes() { - return CreateBenchAction([](auto& api, auto id) { api->ten_bytes(id, {}); }); + return CreateBenchAction([](auto& api, auto id) { api->TenBytes(id, {}); }); } TimedSender& Sender::Send100Bytes() { return CreateBenchAction( - [](auto& api, auto id) { api->hundred_bytes(id, {}); }); + [](auto& api, auto id) { api->HundredBytes(id, {}); }); } TimedSender& Sender::Send1000Bytes() { return CreateBenchAction( - [](auto& api, auto id) { api->thousand_bytes(id, {}); }); + [](auto& api, auto id) { api->ThousandBytes(id, {}); }); } template @@ -89,7 +90,7 @@ TimedSender& Sender::CreateBenchAction(Func&& func) { sender_action_ = std::make_unique( ae_context_, [&, f{std::forward(func)}](std::uint16_t id) { auto api_context = - ApiCallAdapter{ApiContext{bench_delays_api_}, *connected_stream_}; + ApiCallAdapter{bench_delays_api_, *connected_stream_}; f(api_context, id); api_context.Flush(); }); diff --git a/examples/benches/send_message_delays/sender.h b/examples/benches/send_message_delays/sender.h index c5e5b528a..abe78dd10 100644 --- a/examples/benches/send_message_delays/sender.h +++ b/examples/benches/send_message_delays/sender.h @@ -19,15 +19,15 @@ #include -#include "aether/memory.h" -#include "aether/client.h" -#include "aether/types/uid.h" #include "aether/ae_context.h" -#include "aether/events/multi_subscription.h" +#include "aether/client.h" #include "aether/client_messages/p2p_safe_message_stream.h" +#include "aether/events/events.h" +#include "aether/memory.h" +#include "aether/types/uid.h" -#include "send_message_delays/timed_sender.h" #include "send_message_delays/api/bench_delays_api.h" +#include "send_message_delays/timed_sender.h" namespace ae::bench { class Sender { diff --git a/examples/benches/send_message_delays/timed_receiver.cpp b/examples/benches/send_message_delays/timed_receiver.cpp index a7ca34c92..6a7684585 100644 --- a/examples/benches/send_message_delays/timed_receiver.cpp +++ b/examples/benches/send_message_delays/timed_receiver.cpp @@ -21,21 +21,20 @@ namespace ae::bench { TimedReceiver::TimedReceiver(AeContext const& ae_context, std::size_t wait_count) - : ae_context_{ae_context}, wait_count_{wait_count} { + : ae_context_{ae_context}, + wait_count_{wait_count}, + result_event_{ae_context_}, + received_event_{ae_context_} { AE_TELED_INFO("TimedReceiver waiting {} messages", wait_count); last_message_time_ = Now(); ScheduleTimeout(); } -TimedReceiver::ResultTimesEvent::Subscriber -TimedReceiver::message_times_event() { - return EventSubscriber{result_event_}; +TimedReceiver::ResultTimesEvent const& TimedReceiver::message_times_event() { + return result_event_; } -TimedReceiver::ReceivedEvent::Subscriber TimedReceiver::on_received() { - return EventSubscriber{received_event_}; -} -TimedReceiver::TimeoutEvent::Subscriber TimedReceiver::on_timeout() { - return EventSubscriber{timeout_event_}; +TimedReceiver::ReceivedEvent const& TimedReceiver::on_received() { + return received_event_; } void TimedReceiver::Receive(std::uint16_t id) { @@ -55,7 +54,7 @@ void TimedReceiver::Receive(std::uint16_t id) { ScheduleTimeout(); } else { scheduler_sub_.Reset(); - result_event_.Emit(message_times_); + result_event_.Emit(Ok{std::move(message_times_)}); } } @@ -68,7 +67,7 @@ void TimedReceiver::ScheduleTimeout() { auto diff = last_message_time_ - current_time; if (diff > kWaitTimeout) { AE_TELED_ERROR("Receive message timeout"); - timeout_event_.Emit(); + result_event_.Emit(Error{1}); } scheduler_sub_.Reset(); }, diff --git a/examples/benches/send_message_delays/timed_receiver.h b/examples/benches/send_message_delays/timed_receiver.h index 0c678c31e..08eafa432 100644 --- a/examples/benches/send_message_delays/timed_receiver.h +++ b/examples/benches/send_message_delays/timed_receiver.h @@ -19,6 +19,8 @@ #include +#include "aether-miscpp/types/result.h" + #include "aether/ae_context.h" #include "aether/events/events.h" @@ -34,15 +36,13 @@ class TimedReceiver { static constexpr auto kWaitTimeout = std::chrono::seconds{30}; public: - using ResultTimesEvent = Event; + using ResultTimesEvent = Event)>; using ReceivedEvent = Event; - using TimeoutEvent = Event; TimedReceiver(AeContext const& ae_context, std::size_t wait_count); - ResultTimesEvent::Subscriber message_times_event(); - ReceivedEvent::Subscriber on_received(); - TimeoutEvent::Subscriber on_timeout(); + ResultTimesEvent const& message_times_event(); + ReceivedEvent const& on_received(); void Receive(std::uint16_t id); @@ -57,7 +57,6 @@ class TimedReceiver { ResultTimesEvent result_event_; ReceivedEvent received_event_; - TimeoutEvent timeout_event_; TaskSubscription scheduler_sub_; }; } // namespace ae::bench diff --git a/examples/benches/send_message_delays/timed_sender.cpp b/examples/benches/send_message_delays/timed_sender.cpp index 39b290687..8dafd64ff 100644 --- a/examples/benches/send_message_delays/timed_sender.cpp +++ b/examples/benches/send_message_delays/timed_sender.cpp @@ -21,19 +21,21 @@ namespace ae::bench { TimedSender::TimedSender(AeContext const& ae_context, std::function send_proc) - : ae_context_{ae_context}, send_proc_{std::move(send_proc)} { + : ae_context_{ae_context}, + send_proc_{std::move(send_proc)}, + result_time_event_{ae_context_} { AE_TELED_DEBUG("TimedSender"); // send on next scheduler update scheduler_sub_ = ae_context_.scheduler().Task([this]() { Send(); }); } -TimedSender::ResultTimesEvent::Subscriber TimedSender::message_times_event() { - return EventSubscriber{result_time_event_}; +TimedSender::ResultTimesEvent const& TimedSender::result_event() { + return result_time_event_; } void TimedSender::Stop() { AE_TELED_DEBUG("Stop sending"); - result_time_event_.Emit(message_times_); + result_time_event_.Emit(Ok{std::move(message_times_)}); } void TimedSender::Sync() { diff --git a/examples/benches/send_message_delays/timed_sender.h b/examples/benches/send_message_delays/timed_sender.h index 1dc5666a9..6b543fb23 100644 --- a/examples/benches/send_message_delays/timed_sender.h +++ b/examples/benches/send_message_delays/timed_sender.h @@ -20,6 +20,8 @@ #include #include +#include "aether-miscpp/types/result.h" + #include "aether/ae_context.h" #include "aether/events/events.h" @@ -32,12 +34,12 @@ namespace ae::bench { */ class TimedSender { public: - using ResultTimesEvent = Event; + using ResultTimesEvent = Event)>; TimedSender(AeContext const& ae_context, std::function send_proc); - ResultTimesEvent::Subscriber message_times_event(); + ResultTimesEvent const& result_event(); void Stop(); void Sync(); diff --git a/examples/benches/send_messages_bandwidth/common/bandwidth_api.cpp b/examples/benches/send_messages_bandwidth/common/bandwidth_api.cpp index 67abc3206..7fd2b7e94 100644 --- a/examples/benches/send_messages_bandwidth/common/bandwidth_api.cpp +++ b/examples/benches/send_messages_bandwidth/common/bandwidth_api.cpp @@ -17,44 +17,39 @@ #include "send_messages_bandwidth/common/bandwidth_api.h" namespace ae::bench { -BandwidthApi::BandwidthApi(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, - handshake{protocol_context}, - start_test{protocol_context}, - stop_test{protocol_context}, - message{protocol_context}, - return_result{protocol_context} {} - -void BandwidthApi::HandshakeImpl(PromiseResult res) { - handshake_event_.Emit(res.request_id); +ApiPromise BandwidthApi::Handshake() { + return ClientMethod<&BandwidthApi::Handshake>(); } - -void BandwidthApi::StartTestImpl(PromiseResult res) { - start_test_event_.Emit(res.request_id); +ApiPromise BandwidthApi::StartTest() { + return ClientMethod<&BandwidthApi::StartTest>(); } - -void BandwidthApi::StopTestImpl(PromiseResult res) { - stop_test_event_.Emit(res.request_id); +ApiPromise BandwidthApi::StopTest() { + return ClientMethod<&BandwidthApi::StopTest>(); } - -void BandwidthApi::MessageImpl(std::uint16_t id, PayloadData data) { - message_event_.Emit(id, std::move(data)); +void BandwidthApi::Message(std::uint16_t id, PayloadData const& data) { + ClientMethod<&BandwidthApi::Message>(id, data); } -EventSubscriber BandwidthApi::handshake_event() { - return EventSubscriber{handshake_event_}; -} +BandwidthApiServer::BandwidthApiServer(EventSystem& es) + : handshake_event{es}, + start_test_event{es}, + stop_test_event{es}, + message_event{es} {} -EventSubscriber BandwidthApi::start_test_event() { - return EventSubscriber{start_test_event_}; +ApiPromise BandwidthApiServer::Handshake() { + handshake_event.Emit(server_context().request_id()); + return {}; } - -EventSubscriber BandwidthApi::stop_test_event() { - return EventSubscriber{stop_test_event_}; +ApiPromise BandwidthApiServer::StartTest() { + start_test_event.Emit(server_context().request_id()); + return {}; } - -EventSubscriber -BandwidthApi::message_event() { - return EventSubscriber{message_event_}; +ApiPromise BandwidthApiServer::StopTest() { + stop_test_event.Emit(server_context().request_id()); + return {}; } +void BandwidthApiServer::Message(std::uint16_t id, PayloadData const& data) { + message_event.Emit(id, data); +} + } // namespace ae::bench diff --git a/examples/benches/send_messages_bandwidth/common/bandwidth_api.h b/examples/benches/send_messages_bandwidth/common/bandwidth_api.h index 3487285fe..006ea0c52 100644 --- a/examples/benches/send_messages_bandwidth/common/bandwidth_api.h +++ b/examples/benches/send_messages_bandwidth/common/bandwidth_api.h @@ -17,52 +17,45 @@ #ifndef EXAMPLES_BENCHES_SEND_MESSAGES_BANDWIDTH_COMMON_BANDWIDTH_API_H_ #define EXAMPLES_BENCHES_SEND_MESSAGES_BANDWIDTH_COMMON_BANDWIDTH_API_H_ +#include "aether/api_protocol/api_protocol.h" #include "aether/events/events.h" #include "aether/types/data_buffer.h" -#include "aether/api_protocol/api_method.h" -#include "aether/api_protocol/api_class_impl.h" -#include "aether/api_protocol/return_result_api.h" namespace ae::bench { -class BandwidthApi : public ApiClassImpl { +class BandwidthApi : public DeclareApi { public: using PayloadData = DataBuffer; - explicit BandwidthApi(ProtocolContext& protocol_context); + virtual ~BandwidthApi() = default; // sender sends handshake until receiver doesn't answers true - Method<0x03, ApiPromise()> handshake; + virtual ApiPromise Handshake(); // sender sends start or stop test and wait for receiver's response true to // continue/stop tests - Method<0x04, ApiPromise()> start_test; - Method<0x05, ApiPromise()> stop_test; + virtual ApiPromise StartTest(); + virtual ApiPromise StopTest(); - Method<0x06, void(std::uint16_t id, PayloadData data)> message; + virtual void Message(std::uint16_t id, PayloadData const& data); - void HandshakeImpl(PromiseResult res); - void StartTestImpl(PromiseResult res); - void StopTestImpl(PromiseResult res); - void MessageImpl(std::uint16_t id, PayloadData data); - - ReturnResultApi return_result; + API_LIST(METHOD(0x03, Handshake), METHOD(0x04, StartTest), + METHOD(0x05, StopTest), METHOD(0x06, Message)) +}; - AE_METHODS(RegMethod<0x03, &BandwidthApi::HandshakeImpl>, - RegMethod<0x04, &BandwidthApi::StartTestImpl>, - RegMethod<0x05, &BandwidthApi::StopTestImpl>, - RegMethod<0x06, &BandwidthApi::MessageImpl>, - ExtApi<&BandwidthApi::return_result>); +class BandwidthApiServer : public BandwidthApi { + public: + explicit BandwidthApiServer(EventSystem& es); - EventSubscriber handshake_event(); - EventSubscriber start_test_event(); - EventSubscriber stop_test_event(); - EventSubscriber message_event(); + ApiPromise Handshake() override; + ApiPromise StartTest() override; + ApiPromise StopTest() override; + void Message(std::uint16_t id, PayloadData const& data) override; - private: - Event handshake_event_; - Event start_test_event_; - Event stop_test_event_; - Event message_event_; + Event handshake_event; + Event start_test_event; + Event stop_test_event; + Event message_event; }; + } // namespace ae::bench #endif // EXAMPLES_BENCHES_SEND_MESSAGES_BANDWIDTH_COMMON_BANDWIDTH_API_H_ diff --git a/examples/benches/send_messages_bandwidth/common/test_action.h b/examples/benches/send_messages_bandwidth/common/test_action.h index 36b89f4a4..09b20fa9d 100644 --- a/examples/benches/send_messages_bandwidth/common/test_action.h +++ b/examples/benches/send_messages_bandwidth/common/test_action.h @@ -22,7 +22,6 @@ #include "aether-miscpp/types/result.h" #include "aether/ae_context.h" -#include "aether/events/event_subscription.h" #include "aether/events/events.h" #include "aether/executors/executors.h" @@ -40,13 +39,12 @@ class TestAction { std::size_t test_message_count) : ae_context_{ae_context}, agent_{&agent}, - test_message_count_{test_message_count} { + test_message_count_{test_message_count}, + result_event_{ae_context_} { TestPipeline(); } - ResultEvent::Subscriber result_event() { - return EventSubscriber{result_event_}; - } + ResultEvent const& result_event() { return result_event_; } private: auto Connect() { diff --git a/examples/benches/send_messages_bandwidth/receiver/message_receiver.cpp b/examples/benches/send_messages_bandwidth/receiver/message_receiver.cpp index e942db162..db7765a1c 100644 --- a/examples/benches/send_messages_bandwidth/receiver/message_receiver.cpp +++ b/examples/benches/send_messages_bandwidth/receiver/message_receiver.cpp @@ -22,12 +22,13 @@ namespace ae::bench { MessageReceiver::MessageReceiver(AeContext const& ae_context) : ae_context_{ae_context}, first_message_received_time_{HighResTimePoint::clock::now()}, - received_message_time_{Now()} { + received_message_time_{Now()}, + result_event_{ae_context_} { SchedulerReceiveTimeout(); } -MessageReceiver::ResultEvent::Subscriber MessageReceiver::result_event() { - return EventSubscriber{result_event_}; +MessageReceiver::ResultEvent const& MessageReceiver::result_event() { + return result_event_; } void MessageReceiver::MessageReceived(std::uint16_t id) { diff --git a/examples/benches/send_messages_bandwidth/receiver/message_receiver.h b/examples/benches/send_messages_bandwidth/receiver/message_receiver.h index 4117691db..6d7f3e517 100644 --- a/examples/benches/send_messages_bandwidth/receiver/message_receiver.h +++ b/examples/benches/send_messages_bandwidth/receiver/message_receiver.h @@ -17,9 +17,9 @@ #ifndef EXAMPLES_BENCHES_SEND_MESSAGES_BANDWIDTH_RECEIVER_MESSAGE_RECEIVER_H_ #define EXAMPLES_BENCHES_SEND_MESSAGES_BANDWIDTH_RECEIVER_MESSAGE_RECEIVER_H_ -#include "aether/clock.h" -#include "aether/ae_context.h" #include "aether-miscpp/types/result.h" +#include "aether/ae_context.h" +#include "aether/clock.h" #include "aether/events/events.h" #include "send_messages_bandwidth/common/bandwidth.h" @@ -38,7 +38,7 @@ class MessageReceiver { explicit MessageReceiver(AeContext const& ae_context); - ResultEvent::Subscriber result_event(); + ResultEvent const& result_event(); void MessageReceived(std::uint16_t id); void StopTest(); diff --git a/examples/benches/send_messages_bandwidth/receiver/receiver.cpp b/examples/benches/send_messages_bandwidth/receiver/receiver.cpp index 3f112e338..5a4342692 100644 --- a/examples/benches/send_messages_bandwidth/receiver/receiver.cpp +++ b/examples/benches/send_messages_bandwidth/receiver/receiver.cpp @@ -25,14 +25,19 @@ namespace ae::bench { Receiver::Receiver(AeContext const& ae_context, Client::ptr client) : ae_context_{ae_context}, client_{std::move(client)}, - bandwidth_api_{protocol_context_} {} + protocol_context_{ae_context_}, + bandwidth_api_{ae_context_.event_system()}, + test_finished_event_{ae_context_}, + handshake_made_event_{ae_context_}, + sync_made_event_{ae_context_}, + error_event_{ae_context_} {} -EventSubscriber Receiver::error_event() { return error_event_; } +Event const& Receiver::error_event() { return error_event_; } void Receiver::Connect() { message_stream_subscription_ = client_->message_stream_manager().new_port_event().Subscribe( - [this](ae::P2pPortHandle handle) { + [this](ae::P2pPortHandle& handle) { auto dest = handle.destination(); AE_TELED_DEBUG("Received message stream from {}", dest); message_stream_ = std::make_shared( @@ -44,11 +49,11 @@ void Receiver::Connect() { void Receiver::Disconnect() { message_stream_.reset(); } -EventSubscriber Receiver::Handshake() { - bandwidth_api_.handshake_event().Subscribe([this](RequestId req_id) { +Event const& Receiver::Handshake() { + bandwidth_api_.handshake_event.Subscribe([this](RequestId req_id) { AE_TELED_DEBUG("Received handshake request {}", req_id); - auto api = ApiCallAdapter{ApiContext{bandwidth_api_}, *message_stream_}; - api->return_result.SendResult(req_id, true); + auto api = ApiCallAdapter{bandwidth_api_, *message_stream_}; + api->SendResult(req_id, true); api.Flush(); handshake_made_event_.Emit(); }); @@ -56,26 +61,26 @@ EventSubscriber Receiver::Handshake() { return handshake_made_event_; } -EventSubscriber Receiver::TestMessages( +Event const& Receiver::TestMessages( std::size_t message_count, std::size_t message_size) { test_start_sub_ = - bandwidth_api_.start_test_event().Subscribe([this](RequestId req_id) { + bandwidth_api_.start_test_event.Subscribe([this](RequestId req_id) { AE_TELED_DEBUG("Received start test request {}", req_id); - auto api = ApiCallAdapter{ApiContext{bandwidth_api_}, *message_stream_}; - api->return_result.SendResult(req_id, true); + auto api = ApiCallAdapter{bandwidth_api_, *message_stream_}; + api->SendResult(req_id, true); api.Flush(); }); test_stopped_ = false; test_stop_sub_ = - bandwidth_api_.stop_test_event().Subscribe([this](RequestId req_id) { + bandwidth_api_.stop_test_event.Subscribe([this](RequestId req_id) { AE_TELED_DEBUG("Received stop test request {}", req_id); - auto api = ApiCallAdapter{ApiContext{bandwidth_api_}, *message_stream_}; + auto api = ApiCallAdapter{bandwidth_api_, *message_stream_}; if (message_receiver_ && !test_stopped_) { message_receiver_->StopTest(); test_stopped_ = true; } - api->return_result.SendResult(req_id, true); + api->SendResult(req_id, true); api.Flush(); }); @@ -94,7 +99,7 @@ EventSubscriber Receiver::TestMessages( }); message_recv_sub_ = - bandwidth_api_.message_event().Subscribe([this](auto id, auto&&) { + bandwidth_api_.message_event.Subscribe([this](auto id, auto&&) { test_start_sub_.Reset(); message_receiver_->MessageReceived(id); }); diff --git a/examples/benches/send_messages_bandwidth/receiver/receiver.h b/examples/benches/send_messages_bandwidth/receiver/receiver.h index b83f2158c..3b2fe6173 100644 --- a/examples/benches/send_messages_bandwidth/receiver/receiver.h +++ b/examples/benches/send_messages_bandwidth/receiver/receiver.h @@ -19,11 +19,10 @@ #include -#include "aether/client.h" -#include "aether/memory.h" #include "aether/ae_context.h" +#include "aether/client.h" #include "aether/events/events.h" -#include "aether/events/event_subscription.h" +#include "aether/memory.h" #include "send_messages_bandwidth/common/bandwidth.h" #include "send_messages_bandwidth/common/bandwidth_api.h" @@ -34,14 +33,14 @@ class Receiver { public: Receiver(AeContext const& ae_context, Client::ptr client); - EventSubscriber error_event(); + Event const& error_event(); void Connect(); void Disconnect(); - EventSubscriber Handshake(); + Event const& Handshake(); - EventSubscriber TestMessages( - std::size_t message_count, std::size_t message_size); + Event const& TestMessages(std::size_t message_count, + std::size_t message_size); private: MessageReceiver CreateTestAction(std::size_t message_count); @@ -51,7 +50,7 @@ class Receiver { AeContext ae_context_; Client::ptr client_; ProtocolContext protocol_context_; - BandwidthApi bandwidth_api_; + BandwidthApiServer bandwidth_api_; std::shared_ptr message_stream_; diff --git a/examples/benches/send_messages_bandwidth/sender/message_sender.cpp b/examples/benches/send_messages_bandwidth/sender/message_sender.cpp index 3e1e0eac8..1f7b9619f 100644 --- a/examples/benches/send_messages_bandwidth/sender/message_sender.cpp +++ b/examples/benches/send_messages_bandwidth/sender/message_sender.cpp @@ -24,7 +24,8 @@ MessageSender::MessageSender(AeContext const& ae_context, SendProc send_proc, std::size_t send_count) : ae_context_{ae_context}, send_proc_{std::move(send_proc)}, - send_count_{send_count} { + send_count_{send_count}, + result_event_{ae_context_} { AE_TELED_INFO("MessageSender created"); send_sub_ = ae_context_.scheduler().Task([this]() { first_send_time_ = HighResTimePoint::clock::now(); @@ -32,8 +33,8 @@ MessageSender::MessageSender(AeContext const& ae_context, SendProc send_proc, }); } -MessageSender::ResultEvent::Subscriber MessageSender::result_event() { - return EventSubscriber{result_event_}; +MessageSender::ResultEvent const& MessageSender::result_event() { + return result_event_; } void MessageSender::Stop() { diff --git a/examples/benches/send_messages_bandwidth/sender/message_sender.h b/examples/benches/send_messages_bandwidth/sender/message_sender.h index 81728074b..54451b02a 100644 --- a/examples/benches/send_messages_bandwidth/sender/message_sender.h +++ b/examples/benches/send_messages_bandwidth/sender/message_sender.h @@ -19,11 +19,10 @@ #include -#include "aether/clock.h" -#include "aether/ae_context.h" #include "aether-miscpp/types/result.h" +#include "aether/ae_context.h" +#include "aether/clock.h" #include "aether/events/events.h" -#include "aether/events/multi_subscription.h" #include "aether/write_action/write_action.h" #include "send_messages_bandwidth/common/bandwidth.h" @@ -41,7 +40,7 @@ class MessageSender { MessageSender(AeContext const& ae_context, SendProc send_proc, std::size_t send_count); - ResultEvent::Subscriber result_event(); + ResultEvent const& result_event(); void Stop(); private: diff --git a/examples/benches/send_messages_bandwidth/sender/sender.cpp b/examples/benches/send_messages_bandwidth/sender/sender.cpp index 583979221..85eea5b0e 100644 --- a/examples/benches/send_messages_bandwidth/sender/sender.cpp +++ b/examples/benches/send_messages_bandwidth/sender/sender.cpp @@ -16,7 +16,7 @@ #include "send_messages_bandwidth/sender/sender.h" -#include "aether/api_protocol/api_context.h" +#include "aether/api_protocol/api_protocol.h" #include "aether/client_messages/p2p_message_stream.h" #include "aether/stream_api/api_call_adapter.h" @@ -27,9 +27,15 @@ Sender::Sender(AeContext const& ae_context, Client::ptr client, Uid destination) : ae_context_{ae_context}, client_{std::move(client)}, destination_{destination}, - bandwidth_api_{protocol_context_} {} + protocol_context_(ae_context_), + bandwidth_api_{}, + test_finished_event_{ae_context_}, + handshake_made_{ae_context_}, + test_started_event_{ae_context_}, + test_stopped_event_{ae_context_}, + error_event_{ae_context_} {} -EventSubscriber Sender::error_event() { return error_event_; } +Event const& Sender::error_event() { return error_event_; } void Sender::Connect() { auto handle = client_->message_stream_manager().CreatePort(destination_); @@ -42,10 +48,10 @@ void Sender::Connect() { void Sender::Disconnect() { message_stream_.reset(); } -EventSubscriber Sender::Handshake() { - auto api = ApiCallAdapter{ApiContext{bandwidth_api_}, *message_stream_}; +Event const& Sender::Handshake() { + auto api = ApiCallAdapter{bandwidth_api_, *message_stream_}; - auto res = api->handshake(); + auto res = api->Handshake(); handshake_sub_ = res.Subscribe([this](auto const& res) { AE_TELED_DEBUG("Handshake received"); if (res && res.value()) { @@ -61,7 +67,7 @@ EventSubscriber Sender::Handshake() { return handshake_made_; } -EventSubscriber Sender::TestMessages( +Event const& Sender::TestMessages( std::size_t message_count, std::size_t message_size) { // Start test // Make test @@ -72,9 +78,8 @@ EventSubscriber Sender::TestMessages( message_sender_ = std::make_unique( ae_context_, [this, payload_size](std::uint16_t id) -> decltype(auto) { - auto api = - ApiCallAdapter{ApiContext{bandwidth_api_}, *message_stream_}; - api->message(id, DataBuffer(payload_size)); + auto api = ApiCallAdapter{bandwidth_api_, *message_stream_}; + api->Message(id, DataBuffer(payload_size)); return api.Flush(); }, message_count); @@ -97,14 +102,14 @@ EventSubscriber Sender::TestMessages( return test_finished_event_; } -EventSubscriber Sender::StartTest() { +Event const& Sender::StartTest() { start_test_action_.emplace( ae_context_, [this]() { AE_TELED_DEBUG("Sending start test request"); - auto api = ApiCallAdapter{ApiContext{bandwidth_api_}, *message_stream_}; + auto api = ApiCallAdapter{bandwidth_api_, *message_stream_}; - sync_subs_ += api->start_test().Subscribe([this](auto const& res) { + sync_subs_ += api->StartTest().Subscribe([this](auto const& res) { if (res.IsOk()) { start_test_action_->Stop(); test_started_event_.Emit(); @@ -121,14 +126,14 @@ EventSubscriber Sender::StartTest() { return test_started_event_; } -EventSubscriber Sender::StopTest() { +Event const& Sender::StopTest() { stop_test_action_.emplace( ae_context_, [this]() { AE_TELED_DEBUG("Sending stop test request"); - auto api = ApiCallAdapter{ApiContext{bandwidth_api_}, *message_stream_}; + auto api = ApiCallAdapter{bandwidth_api_, *message_stream_}; - sync_subs_ += api->stop_test().Subscribe([this](auto const& res) { + sync_subs_ += api->StopTest().Subscribe([this](auto const& res) { if (res.IsOk()) { stop_test_action_->Stop(); test_stopped_event_.Emit(); diff --git a/examples/benches/send_messages_bandwidth/sender/sender.h b/examples/benches/send_messages_bandwidth/sender/sender.h index 7ac853a94..a7d0dc3c3 100644 --- a/examples/benches/send_messages_bandwidth/sender/sender.h +++ b/examples/benches/send_messages_bandwidth/sender/sender.h @@ -20,12 +20,10 @@ #include #include -#include "aether/client.h" +#include "aether/actions/repeatable_task.h" #include "aether/ae_context.h" +#include "aether/client.h" #include "aether/events/events.h" -#include "aether/actions/repeatable_task.h" -#include "aether/events/event_subscription.h" -#include "aether/events/multi_subscription.h" #include "send_messages_bandwidth/common/bandwidth.h" #include "send_messages_bandwidth/common/bandwidth_api.h" @@ -37,19 +35,19 @@ class Sender { public: Sender(AeContext const& ae_context, Client::ptr client, Uid destination); - EventSubscriber error_event(); + Event const& error_event(); void Connect(); void Disconnect(); - EventSubscriber Handshake(); + Event const& Handshake(); - EventSubscriber TestMessages( - std::size_t message_count, std::size_t message_size); + Event const& TestMessages(std::size_t message_count, + std::size_t message_size); private: - EventSubscriber StartTest(); - EventSubscriber StopTest(); + Event const& StartTest(); + Event const& StopTest(); void OnRecvData(DataBuffer const& data); diff --git a/examples/cloud/cloud_test.cpp b/examples/cloud/cloud_test.cpp index c12589991..0bdf0cee7 100644 --- a/examples/cloud/cloud_test.cpp +++ b/examples/cloud/cloud_test.cpp @@ -132,7 +132,7 @@ int AetherCloudExample() { * Send confirmation to received message. */ client_a->message_stream_manager().new_port_event().Subscribe( - [&](ae::P2pPortHandle handle) { + [&](ae::P2pPortHandle& handle) { auto dest = handle.destination(); receiver_stream = ae::make_unique( *aether_app, ae::examples::kSafeStreamConfig, diff --git a/examples/message_server/message_server.cpp b/examples/message_server/message_server.cpp index be1501236..24420de71 100644 --- a/examples/message_server/message_server.cpp +++ b/examples/message_server/message_server.cpp @@ -74,7 +74,7 @@ void SubscribeToMessages(ae::Client::ptr const& client, // listen for new port open events from the other clients client_ptr->message_stream_manager().new_port_event().Subscribe( [&, c_ = ae::Ptr(client_ptr), - ctx_ = context](ae::P2pPortHandle&& p2p_handle) { + ctx_ = context](ae::P2pPortHandle& p2p_handle) { auto dest = p2p_handle.destination(); // insert new stream into client streams map auto [stream, _] = client_streams.insert_or_assign( diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7c0ec4eb7..6d8b32d94 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,15 +30,15 @@ include(inline_tests/inline_tests.cmake) add_subdirectory(inline_tests) generate_inline_tests(${CMAKE_CURRENT_LIST_DIR}/../aether aether) -#tests +# tests add_subdirectory(test-types) add_subdirectory(test-api-protocol) add_subdirectory(test-prepared-packet) add_subdirectory(test-actions) add_subdirectory(test-events) add_subdirectory(test-transport) -add_subdirectory(test-stream) add_subdirectory(test-safe-stream) +add_subdirectory(test-stream) add_subdirectory(test-domain-storage) add_subdirectory(test-serial-port) add_subdirectory(test-tasks) diff --git a/tests/test-actions/CMakeLists.txt b/tests/test-actions/CMakeLists.txt index 92b145ec6..c7140c679 100644 --- a/tests/test-actions/CMakeLists.txt +++ b/tests/test-actions/CMakeLists.txt @@ -14,13 +14,6 @@ cmake_minimum_required( VERSION 3.18 ) -list(APPEND action_srcs - ${ROOT_DIR}/aether/events/event_list.cpp - ${ROOT_DIR}/aether/events/event_deleter.cpp - ${ROOT_DIR}/aether/events/event_subscription.cpp - ${ROOT_DIR}/aether/events/multi_subscription.cpp -) - list(APPEND test_srcs main.cpp test-actions-queue.cpp @@ -31,7 +24,7 @@ if(NOT CM_PLATFORM) project(test-actions LANGUAGES CXX) add_executable(${PROJECT_NAME}) - target_sources(${PROJECT_NAME} PRIVATE ${action_srcs} ${test_srcs}) + target_sources(${PROJECT_NAME} PRIVATE ${test_srcs}) # for aether target_include_directories(${PROJECT_NAME} PRIVATE ${ROOT_DIR}) target_link_libraries(${PROJECT_NAME} PRIVATE unity etl aether::miscpp) diff --git a/tests/test-actions/test-actions-queue.cpp b/tests/test-actions/test-actions-queue.cpp index c62c93b58..f8b1bc2c3 100644 --- a/tests/test-actions/test-actions-queue.cpp +++ b/tests/test-actions/test-actions-queue.cpp @@ -20,8 +20,9 @@ #include #include "aether/actions/action.h" -#include "aether/actions/actions_queue.h" #include "aether/actions/action_context.h" +#include "aether/actions/actions_queue.h" +#include "aether/events/events.h" #include "aether/tasks/manual_task_scheduler.h" namespace ae::test_actions_queue { @@ -29,13 +30,16 @@ using Scheduler = ManualTaskScheduler>; struct TestContext { Scheduler& scheduler() const { return *sched; } + EventSystem& event_system() const { return *es; } Scheduler* sched; + EventSystem* es; }; template class TestSyncGenAction final : public Action { public: - TestSyncGenAction(ActionContext auto const&, TBody&& body) { + TestSyncGenAction(ActionContext auto const& context, TBody&& body) + : Action{context} { std::invoke(std::move(body)); Finish(); } @@ -45,7 +49,7 @@ template class TestAsyncGenAction final : public Action { public: TestAsyncGenAction(ActionContext auto const& context, TBody&& body) - : body_{std::move(body)} { + : Action{context}, body_{std::move(body)} { context.scheduler().Task([&]() { if (!stopped_) { std::invoke(body_); @@ -92,11 +96,12 @@ auto Stage(ActionContext auto const& context, Func&& body) { void test_SingleStageExecution() { Scheduler sched; + EventSystem es; auto queue = ActionsQueue{}; int execution_counter = 0; // Push a single successful stage - queue.Push(Stage(TestContext{&sched}, + queue.Push(Stage(TestContext{&sched, &es}, [&]() { execution_counter++; })); // stage already executed TEST_ASSERT_EQUAL(1, execution_counter); @@ -104,7 +109,8 @@ void test_SingleStageExecution() { void test_MultipleStageSequentialExecution() { Scheduler sched; - auto context = TestContext{&sched}; + EventSystem es; + auto context = TestContext{&sched, &es}; auto queue = ActionsQueue{}; @@ -137,7 +143,8 @@ void test_MultipleStageSequentialExecution() { void test_NullStageHandling() { Scheduler sched; - auto context = TestContext{&sched}; + EventSystem es; + auto context = TestContext{&sched, &es}; auto queue = ActionsQueue{}; @@ -168,7 +175,8 @@ void test_StopEmptyQueue() { void test_StopDuringExecution() { Scheduler sched; - auto context = TestContext{&sched}; + EventSystem es; + auto context = TestContext{&sched, &es}; auto queue = ActionsQueue{}; @@ -199,7 +207,8 @@ void test_StopDuringExecution() { void test_StopAfterStageCompletion() { Scheduler sched; - auto context = TestContext{&sched}; + EventSystem es; + auto context = TestContext{&sched, &es}; auto queue = ActionsQueue{}; @@ -227,7 +236,8 @@ void test_StopAfterStageCompletion() { void test_DynamicStageAddition() { Scheduler sched; - auto context = TestContext{&sched}; + EventSystem es; + auto context = TestContext{&sched, &es}; auto queue = ActionsQueue{}; @@ -256,7 +266,8 @@ void test_DynamicStageAddition() { void test_SinglePushStopCycle() { Scheduler sched; - auto context = TestContext{&sched}; + EventSystem es; + auto context = TestContext{&sched, &es}; auto queue = ActionsQueue{}; diff --git a/tests/test-api-protocol/CMakeLists.txt b/tests/test-api-protocol/CMakeLists.txt index d45027820..9c0f3d0fd 100644 --- a/tests/test-api-protocol/CMakeLists.txt +++ b/tests/test-api-protocol/CMakeLists.txt @@ -14,9 +14,23 @@ cmake_minimum_required( VERSION 3.16 ) -list(APPEND test_srcs +set(test_api_protocol_suites + packet + method-call + request-response + pending-responses + sender-lifecycle + registration +) + +set(test_srcs main.cpp + test-packet.cpp test-method-call.cpp + test-request-response.cpp + test-pending-responses.cpp + test-sender-lifecycle.cpp + test-registration.cpp ) if(NOT CM_PLATFORM) @@ -26,10 +40,23 @@ if(NOT CM_PLATFORM) add_executable(${PROJECT_NAME}) target_sources(${PROJECT_NAME} PRIVATE ${test_srcs}) # for aether - target_include_directories(${PROJECT_NAME} PRIVATE ${ROOT_DIR}) - target_link_libraries(${PROJECT_NAME} PRIVATE aether unity gcem) + target_link_libraries(${PROJECT_NAME} PRIVATE aether unity gcem etl) add_test(NAME ${PROJECT_NAME} COMMAND $) + + foreach(suite IN LISTS test_api_protocol_suites) + string(REPLACE "-" "_" suite_entry ${suite}) + set(suite_target "${PROJECT_NAME}-${suite}") + + add_executable(${suite_target} suite-main.cpp "test-${suite}.cpp") + target_compile_definitions( + ${suite_target} + PRIVATE API_PROTOCOL_SUITE=test_api_protocol_${suite_entry} + ) + target_link_libraries(${suite_target} PRIVATE aether unity gcem etl) + + add_test(NAME ${suite_target} COMMAND $) + endforeach() else() message(WARNING "Not implemented for ${CM_PLATFORM}") endif() diff --git a/tests/test-api-protocol/assert_packet.h b/tests/test-api-protocol/assert_packet.h index 3a68c47ce..983766ece 100644 --- a/tests/test-api-protocol/assert_packet.h +++ b/tests/test-api-protocol/assert_packet.h @@ -23,9 +23,10 @@ #include #include "aether-miscpp/serialization/binary_archive.h" +#include "aether-miscpp/serialization/serialization.h" -#include "aether/api_protocol/api_message.h" #include "aether/types/packed_size.h" // IWYU pragma: export +#include "aether/vector_buffer.h" #if defined(__clang__) || defined(__GNUC__) # define FUNCTION_NAME __PRETTY_FUNCTION__ diff --git a/tests/test-api-protocol/main.cpp b/tests/test-api-protocol/main.cpp index fd6c15917..53d9fb06d 100644 --- a/tests/test-api-protocol/main.cpp +++ b/tests/test-api-protocol/main.cpp @@ -19,11 +19,21 @@ void setUp() {} void tearDown() {} -extern int test_method_call(); +extern int test_api_protocol_method_call(); +extern int test_api_protocol_packet(); +extern int test_api_protocol_pending_responses(); +extern int test_api_protocol_registration(); +extern int test_api_protocol_request_response(); +extern int test_api_protocol_sender_lifecycle(); int main() { int res = 0; - res += test_method_call(); + res += test_api_protocol_packet(); + res += test_api_protocol_method_call(); + res += test_api_protocol_request_response(); + res += test_api_protocol_pending_responses(); + res += test_api_protocol_sender_lifecycle(); + res += test_api_protocol_registration(); return res; } diff --git a/aether/registration/api/client_reg_api_safe.cpp b/tests/test-api-protocol/suite-main.cpp similarity index 62% rename from aether/registration/api/client_reg_api_safe.cpp rename to tests/test-api-protocol/suite-main.cpp index 536a0f020..b7cd3ea1c 100644 --- a/aether/registration/api/client_reg_api_safe.cpp +++ b/tests/test-api-protocol/suite-main.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2024 Aethernet Inc. + * Copyright 2026 Aethernet Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License. */ -#include "aether/registration/api/client_reg_api_safe.h" +#include -#if AE_SUPPORT_REGISTRATION +#ifndef API_PROTOCOL_SUITE +# error "API_PROTOCOL_SUITE must name the suite entry point" +#endif -# include "aether/api_protocol/api_protocol.h" +void setUp() {} +void tearDown() {} -namespace ae { -ClientRegApiSafe::ClientRegApiSafe(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, return_result{protocol_context} {} +extern int API_PROTOCOL_SUITE(); -} // namespace ae -#endif +int main() { return API_PROTOCOL_SUITE(); } diff --git a/tests/test-api-protocol/test-method-call.cpp b/tests/test-api-protocol/test-method-call.cpp index 03053f4e2..d8a24c0ff 100644 --- a/tests/test-api-protocol/test-method-call.cpp +++ b/tests/test-api-protocol/test-method-call.cpp @@ -18,95 +18,121 @@ #include #include -#include #include "aether/api_protocol/api_protocol.h" #include "aether/events/events.h" +#include "aether/tasks/manual_task_scheduler.h" #include "aether/types/data_buffer.h" #include "assert_packet.h" -namespace ae::test_method_call { +namespace ae::test_api_protocol_method_call { struct Number { AE_REFLECT_MEMBERS(value) int value; }; -class ApiLevel1 : public ApiClassImpl { +class ApiLevel1 : public ae::DeclareApi { public: - explicit ApiLevel1(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, method_3{protocol_context} {} + virtual ~ApiLevel1() = default; - void Method3Impl(float a) { method_3_event.Emit(a); } + virtual void Method3(float a) = 0; - AE_METHODS(RegMethod<03, &ApiLevel1::Method3Impl>); - - Method<03, void(float a)> method_3; - - Event method_3_event; + API_LIST(METHOD(3, Method3)) }; -class ApiLevel0 : public ApiClassImpl { - class Method6Proc { - public: - explicit Method6Proc(ApiLevel1& api_level1) : api_{&api_level1} {} +class ApiLevel0 : public ae::DeclareApi { + public: + virtual ~ApiLevel0() = default; - auto operator()(int a, SubApi const& sub_api) { - return DefaultArgProc{}(a, sub_api(*api_)); - } + virtual void Method3(int a, std::string const& b) = 0; + virtual ae::ApiPromise Method4(int a) = 0; + virtual void Method6(int a, ae::SubApi sub_api) = 0; - private: - ApiLevel1* api_; - }; + API_LIST(METHOD(3, Method3), METHOD(4, Method4), METHOD(6, Method6)) +}; + +namespace client { +class ApiLevel1 : public test_api_protocol_method_call::ApiLevel1 { + public: + void Method3(float a) override { + ClientMethod<&test_api_protocol_method_call::ApiLevel1::Method3>(a); + } +}; +class ApiLevel0 : public test_api_protocol_method_call::ApiLevel0 { public: - explicit ApiLevel0(ProtocolContext& protocol_context) - : ApiClassImpl{protocol_context}, - api_level1{protocol_context}, - method_3{protocol_context}, - method_4{protocol_context}, - method_6{protocol_context, Method6Proc{api_level1}}, - return_result_api{protocol_context} {} - - // methods invoked then packet received - void Method3Impl(int a, std::string b) { method_3_event.Emit(a, b); } - void Method4Impl(PromiseResult promise, int a) { - method_4_event.Emit(promise.request_id, a); + void Method3(int a, std::string const& b) override { + ClientMethod<&test_api_protocol_method_call::ApiLevel0::Method3>(a, b); } - void Method6Impl(int a, SubApiImpl sub_api) { - sub_api.Parse(api_level1); + ae::ApiPromise Method4(int a) override { + return ClientMethod<&test_api_protocol_method_call::ApiLevel0::Method4>(a); } + void Method6( + int a, + ae::SubApi sub_api) override { + ClientMethod<&test_api_protocol_method_call::ApiLevel0::Method6>( + a, std::move(sub_api)(level1, client_context())); + } + + ApiLevel1 level1; +}; +} // namespace client + +namespace server { +class ApiLevel1 : public test_api_protocol_method_call::ApiLevel1 { + public: + explicit ApiLevel1(ae::EventSystem& es) : on_method3{es} {} - ReturnResultApi return_result_api; + void Method3(float a) override { on_method3.Emit(a); } - AE_METHODS(RegMethod<03, &ApiLevel0::Method3Impl>, - RegMethod<04, &ApiLevel0::Method4Impl>, - RegMethod<06, &ApiLevel0::Method6Impl>, - ExtApi<&ApiLevel0::return_result_api>); + ae::Event on_method3; +}; + +class ApiLevel0 : public test_api_protocol_method_call::ApiLevel0 { + public: + explicit ApiLevel0(ae::EventSystem& es) + : on_method3{es}, on_method4{es}, on_method6{es}, level1{es} {} - // call methods to make packet - Method<03, void(int a, std::string b)> method_3; - Method<04, ApiPromise(int a)> method_4; - Method<06, void(int a, SubApi sub), Method6Proc> method_6; + void Method3(int a, std::string const& b) override { on_method3.Emit(a, b); } + ae::ApiPromise Method4(int a) override { + on_method4.Emit(a, server_context().request_id()); + return {}; + } + void Method6( + int a, + ae::SubApi sub_api) override { + on_method6.Emit(a); + auto buff = std::move(sub_api).buffer(); + auto parser = ae::ApiParser{server_context().protocol_context(), buff}; + parser.Parse(level1); + } + + ae::Event on_method3; + ae::Event on_method4; + ae::Event on_method6; + ApiLevel1 level1; +}; - // to signal method impl is called - Event method_3_event; - Event method_4_event; +} // namespace server - ApiLevel1 api_level1; +struct MethodCallFixture { + ae::EventSystem event_system; + ae::TaskScheduler scheduler; + ae::ProtocolContext protocol_context{scheduler, event_system}; }; void test_ReturnResult() { - ProtocolContext pc; + MethodCallFixture fxtr; bool promise_get_value = false; - auto api_level0 = ApiLevel0{pc}; - auto call_context = ApiContext{api_level0}; + auto client_level0 = client::ApiLevel0{}; + auto call_context = ae::ApiContext{client_level0, fxtr.protocol_context}; - auto promise = call_context->method_4(42); + auto promise = call_context->Method4(42); promise.Subscribe([&](auto const& res) { if (res.IsOk()) { auto value = res.value().value; @@ -117,275 +143,105 @@ void test_ReturnResult() { } }); - DataBuffer packet = std::move(call_context); + ae::DataBuffer packet = std::move(call_context); - AssertPacket(packet, MessageId{4}, Skip{}, int{42}); + AssertPacket(packet, ae::MessageId{4}, Skip{}, int{42}); bool level0_method4_called = false; - EventSubscriber{api_level0.method_4_event}.Subscribe( - [&](RequestId req_id, int) { - level0_method4_called = true; - auto response_context = ApiContext{api_level0}; - response_context->return_result_api.SendResult(req_id, Number{78}); - auto data = std::move(response_context).Pack(); - auto parser = ApiParser{pc, data}; - // send/receive - parser.Parse(api_level0); - }); - - // send/receive - auto parser = ApiParser{pc, packet}; - parser.Parse(api_level0); + auto server_level0 = server::ApiLevel0{fxtr.event_system}; + + server_level0.on_method4.Subscribe([&](int, ae::RequestId req_id) { + level0_method4_called = true; + // send response to req_id + auto response_context = ae::ApiContext{server_level0}; + response_context->SendResult(req_id, Number{78}); + auto data = std::move(response_context).Pack(); + // simulate transport and parse response here + // response should be parsed by server implementation on sender side, but in + // our case more matter to use same protocol_context + auto parser = ae::ApiParser{fxtr.protocol_context, data}; + parser.Parse(server_level0); + }); + + // simulate transport and parse sent packet + auto parser = ae::ApiParser{fxtr.protocol_context, packet}; + parser.Parse(server_level0); TEST_ASSERT(level0_method4_called); TEST_ASSERT(promise_get_value); } void test_MethodWithSubApi() { - ProtocolContext pc; + MethodCallFixture fxtr; - auto api_level0 = ApiLevel0{pc}; - auto call_context = ApiContext{api_level0}; + auto client_level0 = client::ApiLevel0{}; + auto call_context = ae::ApiContext{client_level0, fxtr.protocol_context}; - call_context->method_6( - 12, SubApi{[&](ApiContext& api) { api->method_3(42.12F); }}); + call_context->Method6( + 12, [&](ae::ApiContext& api) { api->Method3(42.12F); }); - DataBuffer packet = std::move(call_context); - AssertPacket(packet, MessageId{6}, int{12}, Skip{}, MessageId{3}, - float{42.12F}); + ae::DataBuffer packet = std::move(call_context); + AssertPacket(packet, ae::MessageId{6}, int{12}, Skip{}, + ae::MessageId{3}, float{42.12F}); + bool level0_method6_called = false; bool level1_method3_called = false; - EventSubscriber{api_level0.api_level1.method_3_event}.Subscribe( - [&](float a) { level1_method3_called = true; }); - - auto parser = ApiParser{pc, packet}; - parser.Parse(api_level0); - - TEST_ASSERT_TRUE(level1_method3_called); -} - -void test_ProtocolContextStackAccess() { - ProtocolContext pc; - - TEST_ASSERT_NULL(pc.packet_stack()); - TEST_ASSERT_NULL(pc.parser()); - TEST_ASSERT_NULL(pc.packer()); - - auto packet_stack0 = PacketStack{}; - auto packet_stack1 = PacketStack{}; - pc.PushPacketStack(packet_stack0); - TEST_ASSERT_EQUAL_PTR(&packet_stack0, pc.packet_stack()); - pc.PushPacketStack(packet_stack1); - TEST_ASSERT_EQUAL_PTR(&packet_stack1, pc.packet_stack()); - pc.PopPacketStack(); - TEST_ASSERT_EQUAL_PTR(&packet_stack0, pc.packet_stack()); - pc.PopPacketStack(); - TEST_ASSERT_NULL(pc.packet_stack()); - - auto data0 = std::vector{}; - auto data1 = std::vector{}; - { - auto parser0 = ApiParser{pc, data0}; - TEST_ASSERT_EQUAL_PTR(&parser0, pc.parser()); - { - auto parser1 = ApiParser{pc, data1}; - TEST_ASSERT_EQUAL_PTR(&parser1, pc.parser()); - } - TEST_ASSERT_EQUAL_PTR(&parser0, pc.parser()); - } - TEST_ASSERT_NULL(pc.parser()); - - { - auto packer0 = ApiPacker{pc, data0}; - TEST_ASSERT_EQUAL_PTR(&packer0, pc.packer()); - { - auto packer1 = ApiPacker{pc, data1}; - TEST_ASSERT_EQUAL_PTR(&packer1, pc.packer()); - } - TEST_ASSERT_EQUAL_PTR(&packer0, pc.packer()); - } - TEST_ASSERT_NULL(pc.packer()); -} - -void test_PendingResponseCapacityEvictsOldest() { - ProtocolContext pc; - - bool first_promise_evicted = false; - - auto api_level0 = ApiLevel0{pc}; - auto call_context = ApiContext{api_level0}; - auto subscriptions = std::vector{}; - subscriptions.reserve(AE_API_PROTOCOL_MAX_PENDING_RESPONSES + 1); - - for (auto i = 0U; i < AE_API_PROTOCOL_MAX_PENDING_RESPONSES + 1; ++i) { - auto promise = call_context->method_4(static_cast(i)); - auto subscription = promise.Subscribe([&, i](auto const& res) { - if (i == 0U) { - TEST_ASSERT_FALSE(res.IsOk()); - TEST_ASSERT_EQUAL(-1, res.error()); - first_promise_evicted = true; - } - }); - subscriptions.emplace_back(std::move(subscription)); - } - - TEST_ASSERT_TRUE(first_promise_evicted); -} - -void test_PendingResponseDuplicateRequestIdReplacesOld() { - ProtocolContext pc; - - auto request_id = RequestId{42}; - auto first_promise = ApiPromise{pc, request_id}; - auto first_evicted = false; - auto first_subscription = first_promise.Subscribe([&](auto const& res) { - TEST_ASSERT_FALSE(res.IsOk()); - TEST_ASSERT_EQUAL(-1, res.error()); - first_evicted = true; - }); - - auto second_promise = ApiPromise{pc, request_id}; - auto second_subscription = second_promise.Subscribe([](auto const&) {}); - - static_cast(first_subscription); - static_cast(second_subscription); - TEST_ASSERT_TRUE(first_evicted); -} - -void test_PendingResponseFreshSameTypeReplacesOld() { - ProtocolContext pc; - - auto request_id = RequestId{42}; - auto first_promise = ApiPromise{pc, request_id}; - auto first_evicted = false; - auto first_subscription = first_promise.Subscribe([&](auto const& res) { - TEST_ASSERT_FALSE(res.IsOk()); - TEST_ASSERT_EQUAL(-1, res.error()); - first_evicted = true; - }); - - auto second_promise = ApiPromise{pc, request_id}; - auto second_subscription = second_promise.Subscribe([](auto const&) {}); - - static_cast(first_subscription); - static_cast(second_subscription); - TEST_ASSERT_TRUE(first_evicted); -} + auto server_level0 = server::ApiLevel0{fxtr.event_system}; -void test_PendingResponseCompletionFreesPoolSlot() { - ProtocolContext pc; - - auto second_promise_evicted = false; - auto subscriptions = std::vector{}; - subscriptions.reserve(AE_API_PROTOCOL_MAX_PENDING_RESPONSES + 1); - - for (auto i = 0U; i < AE_API_PROTOCOL_MAX_PENDING_RESPONSES; ++i) { - auto promise = ApiPromise{pc, RequestId{i + 1}}; - auto subscription = promise.Subscribe([&, i](auto const& res) { - if (i == 1U) { - TEST_ASSERT_FALSE(res.IsOk()); - TEST_ASSERT_EQUAL(static_cast(-1), res.error()); - second_promise_evicted = true; - } - }); - subscriptions.emplace_back(std::move(subscription)); - } + server_level0.on_method6.Subscribe( + [&](int) { level0_method6_called = true; }); - pc.SetSendErrorResponse(RequestId{1}, 0, 7); + server_level0.level1.on_method3.Subscribe( + [&](float) { level1_method3_called = true; }); - auto extra_promise = ApiPromise{pc, RequestId{1000}}; - subscriptions.emplace_back(extra_promise.Subscribe([](auto const&) {})); + auto parser = ae::ApiParser{fxtr.protocol_context, packet}; + parser.Parse(server_level0); - TEST_ASSERT_FALSE(second_promise_evicted); + TEST_ASSERT_TRUE(level0_method6_called); + TEST_ASSERT_TRUE(level1_method3_called); } -void test_PendingResponseFifoAfterMiddleRemoval() { - ProtocolContext pc; - - auto first_evicted = false; - auto second_evicted = false; - auto subscriptions = std::vector{}; - subscriptions.reserve(AE_API_PROTOCOL_MAX_PENDING_RESPONSES + 2); - - for (auto i = 0U; i < AE_API_PROTOCOL_MAX_PENDING_RESPONSES; ++i) { - auto id = i + 1U; - auto promise = ApiPromise{pc, RequestId{id}}; - auto subscription = promise.Subscribe([&, id](auto const& res) { - if (!res.IsOk() && (res.error() == static_cast(-1))) { - if (id == 1U) { - first_evicted = true; - } else if (id == 2U) { - second_evicted = true; - } - } - }); - subscriptions.emplace_back(std::move(subscription)); - } - - pc.SetSendErrorResponse(RequestId{3}, 0, 7); +void test_TruncatedArgumentsDoNotInvokeMethod() { + MethodCallFixture fxtr; + auto server_level0 = server::ApiLevel0{fxtr.event_system}; + auto method_called = false; + Subscription subscription = server_level0.on_method3.Subscribe( + [&](int, std::string const&) { method_called = true; }); + (void)subscription; - auto fill_promise = ApiPromise{pc, RequestId{1000}}; - subscriptions.emplace_back(fill_promise.Subscribe([](auto const&) {})); - auto overflow_promise = ApiPromise{pc, RequestId{1001}}; - subscriptions.emplace_back(overflow_promise.Subscribe([](auto const&) {})); + auto malformed_packet = DataBuffer{3}; + auto parser = ApiParser{fxtr.protocol_context, malformed_packet}; - TEST_ASSERT_TRUE(first_evicted); - TEST_ASSERT_FALSE(second_evicted); + TEST_ASSERT_FALSE(parser.Parse(server_level0)); + TEST_ASSERT_FALSE(method_called); } -void test_PendingResponseDuplicateReplacementPreservesFifo() { - ProtocolContext pc; - - auto first_evicted = false; - auto second_evicted = false; - auto third_evicted = false; - auto subscriptions = std::vector{}; - subscriptions.reserve(AE_API_PROTOCOL_MAX_PENDING_RESPONSES + 2); - - for (auto i = 0U; i < AE_API_PROTOCOL_MAX_PENDING_RESPONSES; ++i) { - auto id = i + 1U; - auto promise = ApiPromise{pc, RequestId{id}}; - auto subscription = promise.Subscribe([&, id](auto const& res) { - if (!res.IsOk() && (res.error() == static_cast(-1))) { - if (id == 1U) { - first_evicted = true; - } else if (id == 2U) { - second_evicted = true; - } else if (id == 3U) { - third_evicted = true; - } - } - }); - subscriptions.emplace_back(std::move(subscription)); - } - - auto replacement_promise = ApiPromise{pc, RequestId{3}}; - subscriptions.emplace_back(replacement_promise.Subscribe([](auto const&) {})); - TEST_ASSERT_TRUE(third_evicted); +void test_TruncatedRequestIdDoesNotInvokeMethod() { + MethodCallFixture fxtr; + auto server_level0 = server::ApiLevel0{fxtr.event_system}; + auto method_called = false; + Subscription subscription = server_level0.on_method4.Subscribe( + [&](int, RequestId) { method_called = true; }); + (void)subscription; - auto overflow_promise = ApiPromise{pc, RequestId{1000}}; - subscriptions.emplace_back(overflow_promise.Subscribe([](auto const&) {})); + auto malformed_packet = DataBuffer{4}; + auto parser = ApiParser{fxtr.protocol_context, malformed_packet}; - TEST_ASSERT_TRUE(first_evicted); - TEST_ASSERT_FALSE(second_evicted); + TEST_ASSERT_FALSE(parser.Parse(server_level0)); + TEST_ASSERT_FALSE(method_called); } -} // namespace ae::test_method_call +} // namespace ae::test_api_protocol_method_call -int test_method_call() { +int test_api_protocol_method_call() { UNITY_BEGIN(); - RUN_TEST(ae::test_method_call::test_ReturnResult); - RUN_TEST(ae::test_method_call::test_MethodWithSubApi); - RUN_TEST(ae::test_method_call::test_ProtocolContextStackAccess); - RUN_TEST(ae::test_method_call::test_PendingResponseCapacityEvictsOldest); - RUN_TEST( - ae::test_method_call::test_PendingResponseDuplicateRequestIdReplacesOld); - RUN_TEST(ae::test_method_call::test_PendingResponseFreshSameTypeReplacesOld); - RUN_TEST(ae::test_method_call::test_PendingResponseCompletionFreesPoolSlot); - RUN_TEST(ae::test_method_call::test_PendingResponseFifoAfterMiddleRemoval); - RUN_TEST(ae::test_method_call:: - test_PendingResponseDuplicateReplacementPreservesFifo); + using namespace ae::test_api_protocol_method_call; // NOLINT + + RUN_TEST(test_ReturnResult); + RUN_TEST(test_MethodWithSubApi); + RUN_TEST(test_TruncatedArgumentsDoNotInvokeMethod); + RUN_TEST(test_TruncatedRequestIdDoesNotInvokeMethod); return UNITY_END(); } diff --git a/tests/test-api-protocol/test-packet.cpp b/tests/test-api-protocol/test-packet.cpp new file mode 100644 index 000000000..f79069e61 --- /dev/null +++ b/tests/test-api-protocol/test-packet.cpp @@ -0,0 +1,137 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include + +#include "aether/api_protocol/api_protocol.h" +#include "aether/api_protocol/details/packet_reader.h" +#include "aether/stream_api/api_call_adapter.h" + +#include "assert_packet.h" + +namespace ae::test_api_protocol_packet { + +class PacketApi : public DeclareApi { + public: + virtual ~PacketApi() = default; + + virtual void Call(int value) = 0; + + API_LIST(METHOD(3, Call)) +}; + +class ClientPacketApi final : public PacketApi { + public: + void Call(int value) override { ClientMethod<&PacketApi::Call>(value); } +}; + +using PacketList = + decltype(PacketApi::template MakePacketList()); + +template +concept CanPackLvalue = requires(T& value) { value.Pack(); }; + +template +concept CanPackConstRvalue = + requires(T const&& value) { std::move(value).Pack(); }; + +static_assert(!std::is_copy_constructible_v); +static_assert(!std::is_copy_assignable_v); +static_assert(!std::is_move_constructible_v); +static_assert(!std::is_move_assignable_v); +static_assert(!CanPackLvalue); +static_assert(!CanPackConstRvalue); + +static_assert(!std::is_copy_constructible_v>); +static_assert(!std::is_copy_assignable_v>); +static_assert(!std::is_move_constructible_v>); +static_assert(!std::is_move_assignable_v>); +static_assert(!CanPackLvalue>); +static_assert(!CanPackConstRvalue>); + +static_assert(!std::is_move_constructible_v>); +static_assert(!std::is_move_assignable_v>); + +void test_DirectContextPacks() { + auto api = ClientPacketApi{}; + auto context = ApiContext{api}; + + context->Call(42); + auto packet = std::move(context).Pack(); + + AssertPacket(packet, MessageId{3}, int{42}); +} + +void test_TryExtract() { + auto buffer = DataBuffer{42}; + auto reader = PacketReader{buffer}; + + auto message_id = reader.TryExtract(); + + TEST_ASSERT_TRUE(message_id.has_value()); + TEST_ASSERT_EQUAL(42, *message_id); + TEST_ASSERT_FALSE(reader.canceled()); +} + +void test_FailedTryExtractDoesNotCancel() { + auto buffer = DataBuffer{}; + auto reader = PacketReader{buffer}; + + auto message_id = reader.TryExtract(); + + TEST_ASSERT_FALSE(message_id.has_value()); + TEST_ASSERT_FALSE(reader.canceled()); +} + +void test_Extract() { + auto buffer = DataBuffer{42}; + auto reader = PacketReader{buffer}; + + auto message_id = reader.Extract(); + + TEST_ASSERT_EQUAL(42, message_id); + TEST_ASSERT_FALSE(reader.canceled()); +} + +#if defined(NDEBUG) +void test_FailedExtractCancelsReader() { + auto buffer = DataBuffer{}; + auto reader = PacketReader{buffer}; + + (void)reader.Extract(); + + TEST_ASSERT_TRUE(reader.canceled()); +} +#endif +} // namespace ae::test_api_protocol_packet + +int test_api_protocol_packet() { + UNITY_BEGIN(); + using namespace ae::test_api_protocol_packet; // NOLINT + + RUN_TEST(test_DirectContextPacks); + RUN_TEST(test_TryExtract); + RUN_TEST(test_FailedTryExtractDoesNotCancel); + RUN_TEST(test_Extract); +#if defined(NDEBUG) + RUN_TEST(test_FailedExtractCancelsReader); +#endif + return UNITY_END(); +} diff --git a/tests/test-api-protocol/test-pending-responses.cpp b/tests/test-api-protocol/test-pending-responses.cpp new file mode 100644 index 000000000..bdfb98c33 --- /dev/null +++ b/tests/test-api-protocol/test-pending-responses.cpp @@ -0,0 +1,177 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "aether/events/events.h" +#include "aether/tasks/manual_task_scheduler.h" + +#include "aether/api_protocol/api_protocol.h" + +namespace ae::test_api_protocol_pending_responses { + +class PendingResponsesApi : public DeclareApi { + public: + virtual ~PendingResponsesApi() = default; + + virtual ApiPromise Method3(int value) = 0; + + API_LIST(METHOD(3, Method3)) +}; + +class PendingResponsesClientApi : public PendingResponsesApi { + public: + ApiPromise Method3(int value) override { + return ClientMethod<&PendingResponsesApi::Method3>(value); + } +}; + +struct PendingResponsesFixture { + EventSystem event_system; + TaskScheduler scheduler; + ProtocolContext protocol_context{scheduler, event_system}; +}; + +void test_RequestIdsStartIndependentlyAndAreSequential() { + PendingResponsesFixture first; + PendingResponsesFixture second; + + TEST_ASSERT_EQUAL_UINT32(0, first.protocol_context.NextRequestId().id); + TEST_ASSERT_EQUAL_UINT32(1, first.protocol_context.NextRequestId().id); + TEST_ASSERT_EQUAL_UINT32(0, second.protocol_context.NextRequestId().id); + TEST_ASSERT_EQUAL_UINT32(1, second.protocol_context.NextRequestId().id); +} + +void test_ApiPromiseBoolReflectsWhetherFutureIsPresent() { + PendingResponsesFixture fxtr; + + auto empty_value = ApiPromise{}; + auto filled_value = ApiPromise{ + fxtr.protocol_context.CreateFuture(RequestId{1})}; + auto empty_void = ApiPromise{}; + auto filled_void = + ApiPromise{fxtr.protocol_context.CreateFuture(RequestId{2})}; + + TEST_ASSERT_FALSE(static_cast(empty_value)); + TEST_ASSERT_TRUE(static_cast(filled_value)); + TEST_ASSERT_FALSE(static_cast(empty_void)); + TEST_ASSERT_TRUE(static_cast(filled_void)); +} + +void test_CapacityEvictsOldest() { + PendingResponsesFixture fxtr; + auto& pc = fxtr.protocol_context; + + bool first_promise_evicted = false; + auto client_api = PendingResponsesClientApi{}; + auto call_context = ApiContext{client_api, pc}; + auto subscriptions = std::vector{}; + + for (auto i = 0U; i < ProtocolContext::kMaxPendingResponses + 1; ++i) { + auto promise = call_context->Method3(static_cast(i)); + auto subscription = promise.Subscribe([&, i](auto const& res) { + if (i == 0U) { + TEST_ASSERT_FALSE(res.IsOk()); + TEST_ASSERT_EQUAL(-1, res.error()); + first_promise_evicted = true; + } + }); + subscriptions.emplace_back(std::move(subscription)); + } + + TEST_ASSERT_TRUE(first_promise_evicted); +} + +void test_CompletionFreesPoolSlot() { + PendingResponsesFixture fxtr; + auto& pc = fxtr.protocol_context; + + auto second_promise_evicted = false; + auto subscriptions = std::vector{}; + + for (auto i = 0U; i < ProtocolContext::kMaxPendingResponses; ++i) { + auto promise = ApiPromise{pc.CreateFuture(RequestId{i + 1})}; + + auto subscription = promise.Subscribe([&, i](auto const& res) { + if (i == 1U) { + TEST_ASSERT_FALSE(res.IsOk()); + TEST_ASSERT_EQUAL(static_cast(-1), res.error()); + second_promise_evicted = true; + } + }); + subscriptions.emplace_back(std::move(subscription)); + } + + pc.SetSendErrorResponse(RequestId{1}, 0, 7); + + auto extra_promise = ApiPromise{ + fxtr.protocol_context.CreateFuture(RequestId{1000})}; + subscriptions.emplace_back(extra_promise.Subscribe([](auto const&) {})); + + TEST_ASSERT_FALSE(second_promise_evicted); +} + +void test_FifoAfterMiddleRemoval() { + PendingResponsesFixture fxtr; + auto& pc = fxtr.protocol_context; + + auto first_evicted = false; + auto second_evicted = false; + auto subscriptions = std::vector{}; + + for (auto i = 0U; i < ProtocolContext::kMaxPendingResponses; ++i) { + auto id = i + 1U; + auto promise = ApiPromise{pc.CreateFuture(RequestId{id})}; + auto subscription = promise.Subscribe([&, id](auto const& res) { + if (!res.IsOk() && (res.error() == static_cast(-1))) { + if (id == 1U) { + first_evicted = true; + } else if (id == 2U) { + second_evicted = true; + } + } + }); + subscriptions.emplace_back(std::move(subscription)); + } + + pc.SetSendErrorResponse(RequestId{3}, 0, 7); + + auto fill_promise = + ApiPromise{pc.CreateFuture(RequestId{1000})}; + subscriptions.emplace_back(fill_promise.Subscribe([](auto const&) {})); + auto overflow_promise = + ApiPromise{pc.CreateFuture(RequestId{1001})}; + subscriptions.emplace_back(overflow_promise.Subscribe([](auto const&) {})); + + TEST_ASSERT_TRUE(first_evicted); + TEST_ASSERT_FALSE(second_evicted); +} + +} // namespace ae::test_api_protocol_pending_responses + +int test_api_protocol_pending_responses() { + UNITY_BEGIN(); + using namespace ae::test_api_protocol_pending_responses; // NOLINT + + RUN_TEST(test_CapacityEvictsOldest); + RUN_TEST(test_RequestIdsStartIndependentlyAndAreSequential); + RUN_TEST(test_ApiPromiseBoolReflectsWhetherFutureIsPresent); + RUN_TEST(test_CompletionFreesPoolSlot); + RUN_TEST(test_FifoAfterMiddleRemoval); + return UNITY_END(); +} diff --git a/tests/test-api-protocol/test-registration.cpp b/tests/test-api-protocol/test-registration.cpp new file mode 100644 index 000000000..c02ea686d --- /dev/null +++ b/tests/test-api-protocol/test-registration.cpp @@ -0,0 +1,53 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "aether/api_protocol/api_protocol.h" + +namespace ae::test_api_protocol_registration { + +class RegistrationApi : public DeclareApi { + public: + void Method2() {} + void Method3() {} + + API_LIST(METHOD(2, Method2), METHOD(3, Method3)) +}; + +constexpr auto kRegistrationMethods = + RegistrationApi::ApiList(); +using RegistrationMethods = decltype(kRegistrationMethods); +using DuplicateIds = TypeList, + RegMethod<2, &RegistrationApi::Method3>>; +using ReservedResultId = TypeList>; +using ReservedErrorId = TypeList>; + +static_assert( + api_class_declate_internal::AreApiMessageIdsValid(RegistrationMethods{})); +static_assert( + !api_class_declate_internal::AreApiMessageIdsValid(DuplicateIds{})); +static_assert( + !api_class_declate_internal::AreApiMessageIdsValid(ReservedResultId{})); +static_assert( + !api_class_declate_internal::AreApiMessageIdsValid(ReservedErrorId{})); + +} // namespace ae::test_api_protocol_registration + +int test_api_protocol_registration() { + UNITY_BEGIN(); + return UNITY_END(); +} diff --git a/tests/test-api-protocol/test-request-response.cpp b/tests/test-api-protocol/test-request-response.cpp new file mode 100644 index 000000000..fe2e921b7 --- /dev/null +++ b/tests/test-api-protocol/test-request-response.cpp @@ -0,0 +1,191 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "aether/api_protocol/api_protocol.h" +#include "aether/events/events.h" +#include "aether/tasks/manual_task_scheduler.h" + +#include "assert_packet.h" + +namespace ae::test_api_protocol_request_response { + +class ResponseApi : public DeclareApi { + public: + virtual ~ResponseApi() = default; + + virtual ApiPromise Request() = 0; + + API_LIST(METHOD(2, Request)) +}; + +class ClientResponseApi final : public ResponseApi { + public: + ApiPromise Request() override { + return ClientMethod<&ResponseApi::Request>(); + } +}; + +class ServerResponseApi final : public ResponseApi { + public: + explicit ServerResponseApi(EventSystem& event_system) + : request{event_system} {} + + ApiPromise Request() override { + request.Emit(server_context().request_id()); + return {}; + } + + Event request; +}; + +struct RequestResponseFixture { + EventSystem event_system; + TaskScheduler scheduler; + ProtocolContext protocol_context{scheduler, event_system}; +}; + +void test_SendErrorWireFormat() { + EventSystem event_system; + auto api = ServerResponseApi{event_system}; + auto context = ApiContext{api}; + context->SendError(RequestId{42}, 7, -9); + + DataBuffer packet = std::move(context); + AssertPacket(packet, MessageId{1}, RequestId{42}, std::uint8_t{7}, + std::int32_t{-9}); +} + +void test_ErrorResponseCompletesPromiseOnce() { + RequestResponseFixture fxtr; + auto client_api = ClientResponseApi{}; + auto client_context = ApiContext{client_api, fxtr.protocol_context}; + + auto promise = client_context->Request(); + auto completion_count = 0; + auto error_code = std::int32_t{}; + Subscription subscription = promise.Subscribe([&](auto const& result) { + TEST_ASSERT_FALSE(result.IsOk()); + error_code = static_cast(result.error()); + ++completion_count; + }); + (void)subscription; + + DataBuffer request = std::move(client_context); + auto server_api = ServerResponseApi{fxtr.event_system}; + DataBuffer response; + Subscription response_subscription = + server_api.request.Subscribe([&](RequestId request_id) { + auto response_context = ApiContext{server_api}; + response_context->SendError(request_id, 3, -42); + response = std::move(response_context); + + auto response_parser = ApiParser{fxtr.protocol_context, response}; + TEST_ASSERT_TRUE(response_parser.Parse(server_api)); + }); + (void)response_subscription; + + auto request_parser = ApiParser{fxtr.protocol_context, request}; + TEST_ASSERT_TRUE(request_parser.Parse(server_api)); + TEST_ASSERT_EQUAL(1, completion_count); + TEST_ASSERT_EQUAL(-42, error_code); + + auto duplicate_response_parser = ApiParser{fxtr.protocol_context, response}; + TEST_ASSERT_FALSE(duplicate_response_parser.Parse(server_api)); + TEST_ASSERT_EQUAL(1, completion_count); +} + +void test_TruncatedResultCompletesPromiseWithDecodeError() { + RequestResponseFixture fxtr; + auto client_api = ClientResponseApi{}; + auto client_context = ApiContext{client_api, fxtr.protocol_context}; + auto promise = client_context->Request(); + auto completion_count = 0; + auto error_code = std::int32_t{}; + Subscription subscription = promise.Subscribe([&](auto const& result) { + TEST_ASSERT_FALSE(result.IsOk()); + error_code = static_cast(result.error()); + ++completion_count; + }); + (void)subscription; + + auto server_api = ServerResponseApi{fxtr.event_system}; + auto response_context = ApiContext{server_api}; + response_context->SendResult(promise.request_id(), std::int32_t{42}); + DataBuffer response = std::move(response_context); + response.pop_back(); + + auto parser = ApiParser{fxtr.protocol_context, response}; + TEST_ASSERT_FALSE(parser.Parse(server_api)); + TEST_ASSERT_EQUAL(1, completion_count); + TEST_ASSERT_EQUAL(-2, error_code); +} + +void test_TruncatedResponseRequestIdDoesNotCompletePromise() { + RequestResponseFixture fxtr; + auto client_api = ClientResponseApi{}; + auto client_context = ApiContext{client_api, fxtr.protocol_context}; + auto promise = client_context->Request(); + auto completion_count = 0; + Subscription subscription = + promise.Subscribe([&](auto const&) { ++completion_count; }); + (void)subscription; + + auto server_api = ServerResponseApi{fxtr.event_system}; + auto malformed_packet = DataBuffer{0}; + auto parser = ApiParser{fxtr.protocol_context, malformed_packet}; + + TEST_ASSERT_FALSE(parser.Parse(server_api)); + TEST_ASSERT_EQUAL(0, completion_count); +} + +void test_TruncatedErrorDoesNotCompletePromise() { + RequestResponseFixture fxtr; + auto client_api = ClientResponseApi{}; + auto client_context = ApiContext{client_api, fxtr.protocol_context}; + auto promise = client_context->Request(); + auto completion_count = 0; + Subscription subscription = + promise.Subscribe([&](auto const&) { ++completion_count; }); + (void)subscription; + + auto server_api = ServerResponseApi{fxtr.event_system}; + auto response_context = ApiContext{server_api}; + response_context->SendError(promise.request_id(), 3, -42); + DataBuffer response = std::move(response_context); + response.pop_back(); + + auto parser = ApiParser{fxtr.protocol_context, response}; + TEST_ASSERT_FALSE(parser.Parse(server_api)); + TEST_ASSERT_EQUAL(0, completion_count); +} + +} // namespace ae::test_api_protocol_request_response + +int test_api_protocol_request_response() { + UNITY_BEGIN(); + using namespace ae::test_api_protocol_request_response; // NOLINT + + RUN_TEST(test_SendErrorWireFormat); + RUN_TEST(test_ErrorResponseCompletesPromiseOnce); + RUN_TEST(test_TruncatedResultCompletesPromiseWithDecodeError); + RUN_TEST(test_TruncatedResponseRequestIdDoesNotCompletePromise); + RUN_TEST(test_TruncatedErrorDoesNotCompletePromise); + return UNITY_END(); +} diff --git a/tests/test-api-protocol/test-sender-lifecycle.cpp b/tests/test-api-protocol/test-sender-lifecycle.cpp new file mode 100644 index 000000000..394fe87fc --- /dev/null +++ b/tests/test-api-protocol/test-sender-lifecycle.cpp @@ -0,0 +1,392 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include +#include +#include +#include + +#include "aether/api_protocol/api_protocol.h" +#include "aether/api_protocol/make_api_call_sender.h" +#include "aether/events/events.h" +#include "aether/executors/executors.h" +#include "aether/tasks/manual_task_scheduler.h" + +namespace ae::test_api_protocol_sender_lifecycle { + +constexpr std::int32_t kResultValue{42}; +constexpr std::int32_t kApiErrorValue{-42}; +constexpr std::int32_t kIgnoredApiError{-1}; +constexpr std::int32_t kFailureResultValue{7}; +constexpr std::int32_t kStoppedResultValue{8}; +constexpr std::int32_t kNoRequestError{0}; +constexpr std::uint32_t kNotifyMethodId{2}; +constexpr std::uint32_t kFirstRequestId{1}; +constexpr std::uint32_t kSecondRequestId{2}; +constexpr int kNoWrites{0}; +constexpr int kOneWrite{1}; +constexpr int kTwoWrites{2}; +constexpr int kOneCompletion{1}; + +class SenderApi : public DeclareApi { + public: + virtual ~SenderApi() = default; + + virtual void Notify() = 0; + + API_LIST(METHOD(kNotifyMethodId, Notify)) +}; + +class ClientSenderApi final : public SenderApi { + public: + void Notify() override { ClientMethod<&SenderApi::Notify>(); } +}; + +struct SenderFixture { + mutable TaskScheduler scheduler_; + mutable EventSystem event_system_; + ProtocolContext protocol_context{scheduler_, event_system_}; + + TaskScheduler& scheduler() const { return scheduler_; } + EventSystem& event_system() const { return event_system_; } +}; + +class TestWriteAction final : public WriteAction { + public: + explicit TestWriteAction(SenderFixture const& fixture) + : WriteAction{fixture_context(fixture)} {} + + void Complete(Status status) { SetStatus(status); } + + private: + struct Context { + explicit Context(SenderFixture const& fixture) : fixture_{&fixture} {} + + TaskScheduler& scheduler() const { return fixture_->scheduler(); } + EventSystem& event_system() const { return fixture_->event_system(); } + + private: + SenderFixture const* fixture_; + }; + + static Context fixture_context(SenderFixture const& fixture) { + return Context{fixture}; + } +}; + +class TestStream { + public: + explicit TestStream(SenderFixture const& fixture) : fixture_{&fixture} {} + + WriteAction& Write(DataBuffer&& data) { + written_data.emplace(std::move(data)); + ++write_count; + auto& action = write_action.emplace(*fixture_); + return action; + } + + void CompleteWrite(WriteAction::Status status) { + assert(write_action.has_value() && "Write action must be available"); + write_action->Complete(status); + } + + int write_count{}; + std::optional write_action; + std::optional written_data; + + private: + SenderFixture const* fixture_; +}; + +enum class Completion : std::uint8_t { + kNone, + kValue, + kApiError, + kWriteError, + kStopped +}; + +struct ReceiverState { + Completion completion{Completion::kNone}; + int completion_count{}; + std::int32_t value{}; + std::int32_t api_error{}; +}; + +struct Receiver { + using receiver_concept = ex::receiver_t; + + void set_value() const&& noexcept { + state->completion = Completion::kValue; + ++state->completion_count; + } + void set_value(std::int32_t value) const&& noexcept { + state->completion = Completion::kValue; + state->value = value; + ++state->completion_count; + } + void set_error(std::int32_t error) const&& noexcept { + state->completion = Completion::kApiError; + state->api_error = error; + ++state->completion_count; + } + void set_error(WriteFailed) const&& noexcept { + state->completion = Completion::kWriteError; + ++state->completion_count; + } + void set_stopped() const&& noexcept { + state->completion = Completion::kStopped; + ++state->completion_count; + } + + ReceiverState* state; +}; + +void test_EmptyPromiseDoesNotWriteAndStops() { + SenderFixture fxtr; + ClientSenderApi api; + TestStream stream{fxtr}; + ReceiverState state; + + auto sender = make_api_call( + api, fxtr.protocol_context, stream, + [](ApiContext&) { return ApiPromise{}; }); + auto operation = ex::connect(std::move(sender), Receiver{&state}); + ex::start(operation); + + TEST_ASSERT_EQUAL(kNoWrites, stream.write_count); + TEST_ASSERT_EQUAL(kOneCompletion, state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kStopped), + static_cast(state.completion)); +} + +void test_FilledPromiseCompletesWithValueAndError() { + SenderFixture fxtr; + ClientSenderApi api; + TestStream stream{fxtr}; + ReceiverState success_state; + ApiFuture* success_future = nullptr; + + auto success_sender = make_api_call( + api, fxtr.protocol_context, stream, [&](ApiContext&) { + success_future = + &fxtr.protocol_context.CreateFuture(kFirstRequestId); + return ApiPromise{*success_future}; + }); + auto success_operation = + ex::connect(std::move(success_sender), Receiver{&success_state}); + ex::start(success_operation); + success_future->OnResult(std::int32_t{kResultValue}); + + TEST_ASSERT_EQUAL(kOneWrite, stream.write_count); + TEST_ASSERT_EQUAL(kOneCompletion, success_state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kValue), + static_cast(success_state.completion)); + TEST_ASSERT_EQUAL(kResultValue, success_state.value); + + ReceiverState error_state; + ApiFuture* error_future = nullptr; + auto error_sender = make_api_call( + api, fxtr.protocol_context, stream, [&](ApiContext&) { + error_future = + &fxtr.protocol_context.CreateFuture(kSecondRequestId); + return ApiPromise{*error_future}; + }); + auto error_operation = + ex::connect(std::move(error_sender), Receiver{&error_state}); + ex::start(error_operation); + error_future->OnError(kNoRequestError, kApiErrorValue); + + TEST_ASSERT_EQUAL(kTwoWrites, stream.write_count); + TEST_ASSERT_EQUAL(kOneCompletion, error_state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kApiError), + static_cast(error_state.completion)); + TEST_ASSERT_EQUAL(kApiErrorValue, error_state.api_error); +} + +void test_VoidPromiseCompletesWithValue() { + SenderFixture fxtr; + ClientSenderApi api; + TestStream stream{fxtr}; + ReceiverState state; + ApiFuture* future = nullptr; + + auto sender = make_api_call( + api, fxtr.protocol_context, stream, [&](ApiContext&) { + future = &fxtr.protocol_context.CreateFuture(kFirstRequestId); + return ApiPromise{*future}; + }); + auto operation = ex::connect(std::move(sender), Receiver{&state}); + ex::start(operation); + future->OnResult(); + + TEST_ASSERT_EQUAL(kOneWrite, stream.write_count); + TEST_ASSERT_EQUAL(kOneCompletion, state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kValue), + static_cast(state.completion)); +} + +void test_EmptyVoidPromiseDoesNotWriteAndStops() { + SenderFixture fxtr; + ClientSenderApi api; + TestStream stream{fxtr}; + ReceiverState state; + + auto sender = make_api_call( + api, fxtr.protocol_context, stream, + [](ApiContext&) { return ApiPromise{}; }); + auto operation = ex::connect(std::move(sender), Receiver{&state}); + ex::start(operation); + + TEST_ASSERT_EQUAL(kNoWrites, stream.write_count); + TEST_ASSERT_EQUAL(kOneCompletion, state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kStopped), + static_cast(state.completion)); +} + +void test_ResponseAfterStartCompletesAndReleasesSubscriptions() { + SenderFixture fxtr; + ClientSenderApi api; + TestStream stream{fxtr}; + ReceiverState state; + ApiFuture* future = nullptr; + + auto sender = make_api_call( + api, fxtr.protocol_context, stream, [&](ApiContext&) { + future = + &fxtr.protocol_context.CreateFuture(kFirstRequestId); + return ApiPromise{*future}; + }); + auto operation = ex::connect(std::move(sender), Receiver{&state}); + ex::start(operation); + + TEST_ASSERT_EQUAL(kOneWrite, stream.write_count); + TEST_ASSERT_EQUAL(0, state.completion_count); + + future->OnResult(std::int32_t{kResultValue}); + + Event const probe_event{fxtr}; + MultiSubscription probe_subscriptions; + for (std::size_t i = 0; i < kEventsHandlerCapacity; ++i) { + probe_subscriptions += probe_event.Subscribe([]() noexcept {}); + } + TEST_ASSERT_EQUAL(kOneWrite, stream.write_count); + TEST_ASSERT_EQUAL(kOneCompletion, state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kValue), + static_cast(state.completion)); + TEST_ASSERT_EQUAL(kResultValue, state.value); +} + +void test_VoidPromiseCallbackIsSupported() { + using VoidPromiseCallback = + decltype([](ApiContext& context) -> ApiPromise { + context->Notify(); + return {}; + }); + static_assert(make_api_call_sender_internal::ApiCall); + static_assert(requires(ClientSenderApi& api, + ProtocolContext& protocol_context, + TestStream& stream) { + make_api_call(api, protocol_context, stream, VoidPromiseCallback{}); + }); +} + +void test_WriteFailureAndStopCompleteAndReleaseSubscriptions() { + SenderFixture fxtr; + ClientSenderApi api; + TestStream stream{fxtr}; + ReceiverState failure_state; + ApiFuture* failure_future = nullptr; + + auto failure_sender = make_api_call( + api, fxtr.protocol_context, stream, [&](ApiContext&) { + failure_future = + &fxtr.protocol_context.CreateFuture(kFirstRequestId); + return ApiPromise{*failure_future}; + }); + auto failure_operation = + ex::connect(std::move(failure_sender), Receiver{&failure_state}); + ex::start(failure_operation); + stream.CompleteWrite(WriteAction::Status::kFail); + failure_future->OnResult(std::int32_t{kFailureResultValue}); + + TEST_ASSERT_EQUAL(kOneCompletion, failure_state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kWriteError), + static_cast(failure_state.completion)); + + ReceiverState stopped_state; + ApiFuture* stopped_future = nullptr; + auto stopped_sender = make_api_call( + api, fxtr.protocol_context, stream, [&](ApiContext&) { + stopped_future = + &fxtr.protocol_context.CreateFuture(kSecondRequestId); + return ApiPromise{*stopped_future}; + }); + auto stopped_operation = + ex::connect(std::move(stopped_sender), Receiver{&stopped_state}); + ex::start(stopped_operation); + stream.CompleteWrite(WriteAction::Status::kStop); + stopped_future->OnResult(std::int32_t{kStoppedResultValue}); + + TEST_ASSERT_EQUAL(kOneCompletion, stopped_state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kStopped), + static_cast(stopped_state.completion)); +} + +void test_TerminalCompletionIsExactlyOnce() { + SenderFixture fxtr; + ClientSenderApi api; + TestStream stream{fxtr}; + ReceiverState state; + ApiFuture* future = nullptr; + + auto sender = make_api_call( + api, fxtr.protocol_context, stream, [&](ApiContext&) { + future = + &fxtr.protocol_context.CreateFuture(kFirstRequestId); + return ApiPromise{*future}; + }); + auto operation = ex::connect(std::move(sender), Receiver{&state}); + ex::start(operation); + future->OnResult(std::int32_t{kResultValue}); + stream.CompleteWrite(WriteAction::Status::kFail); + future->OnError(kNoRequestError, kIgnoredApiError); + + TEST_ASSERT_EQUAL(kOneCompletion, state.completion_count); + TEST_ASSERT_EQUAL_INT(static_cast(Completion::kValue), + static_cast(state.completion)); + TEST_ASSERT_EQUAL(kResultValue, state.value); +} +} // namespace ae::test_api_protocol_sender_lifecycle + +int test_api_protocol_sender_lifecycle() { + UNITY_BEGIN(); + using namespace ae::test_api_protocol_sender_lifecycle; // NOLINT + + RUN_TEST(test_EmptyPromiseDoesNotWriteAndStops); + RUN_TEST(test_FilledPromiseCompletesWithValueAndError); + RUN_TEST(test_VoidPromiseCompletesWithValue); + RUN_TEST(test_EmptyVoidPromiseDoesNotWriteAndStops); + RUN_TEST(test_ResponseAfterStartCompletesAndReleasesSubscriptions); + RUN_TEST(test_VoidPromiseCallbackIsSupported); + RUN_TEST(test_WriteFailureAndStopCompleteAndReleaseSubscriptions); + RUN_TEST(test_TerminalCompletionIsExactlyOnce); + return UNITY_END(); +} diff --git a/tests/test-events/CMakeLists.txt b/tests/test-events/CMakeLists.txt index 34466e3a5..a876ecee8 100644 --- a/tests/test-events/CMakeLists.txt +++ b/tests/test-events/CMakeLists.txt @@ -15,14 +15,11 @@ cmake_minimum_required( VERSION 3.16 ) list(APPEND test_events_srcs - ${ROOT_DIR}/aether/events/event_list.cpp - ${ROOT_DIR}/aether/events/event_deleter.cpp - ${ROOT_DIR}/aether/events/event_subscription.cpp - ${ROOT_DIR}/aether/events/multi_subscription.cpp - main.cpp test-events.cpp - test-events-mt.cpp + test-events-key-registry.cpp + test-event-handler-map.cpp + test-event-system.cpp ) if(NOT CM_PLATFORM) diff --git a/tests/test-events/main.cpp b/tests/test-events/main.cpp index afb21ad46..09180621a 100644 --- a/tests/test-events/main.cpp +++ b/tests/test-events/main.cpp @@ -20,11 +20,16 @@ void setUp() {} void tearDown() {} extern int test_events(); -extern int test_events_mt(); + +extern int test_event_key_registry(); +extern int test_event_handler_map(); +extern int test_event_system(); int main() { auto res = 0; res += test_events(); - res += test_events_mt(); + res += test_event_key_registry(); + res += test_event_handler_map(); + res += test_event_system(); return res; } diff --git a/tests/test-events/test-event-handler-map.cpp b/tests/test-events/test-event-handler-map.cpp new file mode 100644 index 000000000..fe4bc0a4e --- /dev/null +++ b/tests/test-events/test-event-handler-map.cpp @@ -0,0 +1,154 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include "aether/events/details/event_handler.h" +#include "aether/events/details/event_handler_map.h" + +namespace ae::test_event_handler_map { +struct TestHandler : public events::IHandler { + explicit TestHandler(int& destructions) : destructions_{&destructions} {} + TestHandler(TestHandler&& other) noexcept + : TestHandler{other.destructions_, other} {} + ~TestHandler() override { ++*destructions_; } + + private: + TestHandler(int* destructions, TestHandler& other) noexcept + : IHandler{std::move(other)}, destructions_{destructions} {} + + int* destructions_; +}; + +using Map = + events::EventHandlerMap<3, sizeof(TestHandler), alignof(TestHandler)>; + +void test_DeactivateImmediatelyDestroysHandler() { + int destructions = 0; + auto map = Map{}; + auto* handler = map.Add(1, TestHandler{destructions}); + TEST_ASSERT_TRUE(handler != nullptr); + auto const baseline = destructions; + + map.Deactivate(1, handler); + + TEST_ASSERT_EQUAL(baseline + 1, destructions); + auto const [begin, end] = map.Get(1); + TEST_ASSERT_TRUE(begin == end); +} + +void test_DeactivateDefersReferencedHandler() { + int destructions = 0; + auto map = Map{}; + auto* handler = map.Add(1, TestHandler{destructions}); + TEST_ASSERT_TRUE(handler != nullptr); + auto const baseline = destructions; + ++handler->ref_cntr; + + map.Deactivate(1, handler); + + TEST_ASSERT_FALSE(handler->active); + TEST_ASSERT_EQUAL(baseline, destructions); + --handler->ref_cntr; + map.Deactivate(1, handler); + TEST_ASSERT_EQUAL(baseline + 1, destructions); +} + +void test_PruneRetainsReferencedHandlersWithoutDeactivating() { + int first_destructions = 0; + int second_destructions = 0; + auto map = Map{}; + auto* first = map.Add(1, TestHandler{first_destructions}); + auto* second = map.Add(1, TestHandler{second_destructions}); + TEST_ASSERT_TRUE(first != nullptr); + TEST_ASSERT_TRUE(second != nullptr); + auto const first_baseline = first_destructions; + auto const second_baseline = second_destructions; + ++first->ref_cntr; + + map.Prune(1); + + TEST_ASSERT_TRUE(first->active); + TEST_ASSERT_EQUAL(first_baseline, first_destructions); + TEST_ASSERT_EQUAL(second_baseline + 1, second_destructions); + auto const [begin, end] = map.Get(1); + TEST_ASSERT_TRUE(begin != end); + TEST_ASSERT_EQUAL(first, begin->second); + --first->ref_cntr; + map.Prune(1); + TEST_ASSERT_EQUAL(first_baseline + 1, first_destructions); +} + +void test_PruneDeactivatedRetainsActiveHandlers() { + int active_destructions = 0; + int inactive_destructions = 0; + auto map = Map{}; + auto* active = map.Add(1, TestHandler{active_destructions}); + auto* inactive = map.Add(1, TestHandler{inactive_destructions}); + TEST_ASSERT_TRUE(active != nullptr); + TEST_ASSERT_TRUE(inactive != nullptr); + auto const active_baseline = active_destructions; + auto const inactive_baseline = inactive_destructions; + + inactive->active = false; + map.PruneDeactivated(1); + + TEST_ASSERT_EQUAL(active_baseline, active_destructions); + TEST_ASSERT_EQUAL(inactive_baseline + 1, inactive_destructions); + auto const [begin, end] = map.Get(1); + TEST_ASSERT_TRUE(begin != end); + TEST_ASSERT_EQUAL(active, begin->second); +} + +void test_MapTeardownDestroysHandlers() { + int destructions = 0; + { + auto map = Map{}; + TEST_ASSERT_TRUE(map.Add(1, TestHandler{destructions}) != nullptr); + TEST_ASSERT_TRUE(map.Add(2, TestHandler{destructions}) != nullptr); + } + // Each temporary and each stored concrete handler is destroyed exactly once. + TEST_ASSERT_EQUAL(4, destructions); +} + +void test_CapacityOverflowAndReuseAfterPrune() { + int destructions = 0; + auto map = Map{}; + auto* first = map.Add(1, TestHandler{destructions}); + auto* second = map.Add(2, TestHandler{destructions}); + auto* third = map.Add(3, TestHandler{destructions}); + TEST_ASSERT_TRUE(first != nullptr); + TEST_ASSERT_TRUE(second != nullptr); + TEST_ASSERT_TRUE(third != nullptr); + TEST_ASSERT_TRUE(map.Add(4, TestHandler{destructions}) == nullptr); + + map.Prune(1); + TEST_ASSERT_TRUE(map.Add(4, TestHandler{destructions}) != nullptr); +} + +} // namespace ae::test_event_handler_map + +int test_event_handler_map() { + using namespace ae::test_event_handler_map; // NOLINT + UNITY_BEGIN(); + RUN_TEST(test_DeactivateImmediatelyDestroysHandler); + RUN_TEST(test_DeactivateDefersReferencedHandler); + RUN_TEST(test_PruneRetainsReferencedHandlersWithoutDeactivating); + RUN_TEST(test_PruneDeactivatedRetainsActiveHandlers); + RUN_TEST(test_MapTeardownDestroysHandlers); + RUN_TEST(test_CapacityOverflowAndReuseAfterPrune); + return UNITY_END(); +} diff --git a/tests/test-events/test-event-system.cpp b/tests/test-events/test-event-system.cpp new file mode 100644 index 000000000..d2eca194e --- /dev/null +++ b/tests/test-events/test-event-system.cpp @@ -0,0 +1,533 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include +#include + +#include "aether/events/details/event_handler.h" +#include "aether/events/details/event_system.h" +#include "aether/events/details/generic_event_handler.h" + +namespace ae::test_event_system { +using events::EventSystem; + +struct TestHandler : public events::Handler { + explicit TestHandler(int& i) : res{&i} {} + + void Invoke(int i) noexcept override { *res = i; } + + int* res{}; +}; + +struct CountedHandler : public events::Handler { + explicit CountedHandler(int& destructions) : destructions{&destructions} {} + CountedHandler(CountedHandler&&) noexcept = default; + ~CountedHandler() override { ++*destructions; } + + void Invoke() noexcept override {} + + int* destructions{}; +}; + +static constexpr auto kS = sizeof(TestHandler); +static constexpr auto kA = alignof(TestHandler); +static constexpr auto kSmallHandlerStorageSize = 32; +static constexpr auto kLargeHandlerStorageSize = 64; +template +using TestEventSystem = EventSystem; + +void test_AddRemoveEvents() { + auto es = TestEventSystem<5>{}; + + // Reg some events + auto k0 = es.RegEvent(); + TEST_ASSERT_TRUE(k0.has_value()); + auto k1 = es.RegEvent(); + TEST_ASSERT_TRUE(k1.has_value()); + + // check if they registered + TEST_ASSERT_TRUE(es.IsRegistered(*k0)); + TEST_ASSERT_TRUE(es.IsRegistered(*k1)); + + // unregister and check if it removed + es.UnregEvent(*k0); + TEST_ASSERT_FALSE(es.IsRegistered(*k0)); + es.UnregEvent(*k1); + TEST_ASSERT_FALSE(es.IsRegistered(*k1)); +} + +void test_AddRemoveOverflow() { + auto es = TestEventSystem<5>{}; + // reg till overflow + std::array::Key, 5> keys{}; + for (auto i = 0; i < 5; ++i) { + auto k = es.RegEvent(); + TEST_ASSERT_TRUE(k.has_value()); + keys[i] = *k; + } + // extra event could not be registered + auto extra = es.RegEvent(); + TEST_ASSERT_FALSE(extra.has_value()); + + // remove all and try again + for (auto const& k : keys) { + es.UnregEvent(k); + } + auto k6 = es.RegEvent(); + TEST_ASSERT_TRUE(k6.has_value()); +} + +void test_AddHandlers() { + auto es = TestEventSystem<5>{}; + // reg one event + auto k = es.RegEvent(); + TEST_ASSERT_TRUE(k.has_value()); + + // add a handler + int res{}; + auto* h = es.AddHandler(*k, TestHandler{res}); + TEST_ASSERT_TRUE(h != nullptr); + + // invoke handlers for event + es.Invoke(*k, 12); + TEST_ASSERT_EQUAL(12, res); + + // remove the handler and try invoke again + es.RemoveHandler(*k, h); + es.Invoke(*k, 13); + // res should be unchanged + TEST_ASSERT_EQUAL(12, res); + + // add handler again + h = es.AddHandler(*k, TestHandler{res}); + TEST_ASSERT_TRUE(h != nullptr); + // unreg event + es.UnregEvent(*k); + es.Invoke(*k, 14); + // res should be unchanged + TEST_ASSERT_EQUAL(12, res); +} + +void test_HandlerCapacityFailureIsSafe() { + constexpr int kInvocationValue = 42; + auto es = TestEventSystem<1>{}; + auto key = es.RegEvent(); + TEST_ASSERT_TRUE(key.has_value()); + + int result = 0; + auto* first = es.AddHandler(*key, TestHandler{result}); + TEST_ASSERT_TRUE(first != nullptr); + auto* second = es.AddHandler(*key, TestHandler{result}); + TEST_ASSERT_TRUE(second == nullptr); + + es.Invoke(*key, kInvocationValue); + TEST_ASSERT_EQUAL(kInvocationValue, result); +} + +void test_AddHandlersMultiThread() { + auto es = TestEventSystem<100>{}; + + auto k = es.RegEvent(); + + std::atomic invoke_counter = {}; + + // the logic is + // Add and remove handlers in cycle from different threads + // Invoke the event from invoker thread + auto subscribers = std::array{}; + for (std::size_t i = 0; i < 5; ++i) { + subscribers[i] = std::jthread{[&](std::stop_token const& st) { + while (!st.stop_requested()) { + int res{}; + auto* h = es.AddHandler(*k, TestHandler{res}); + TEST_ASSERT_TRUE(h != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + es.RemoveHandler(*k, h); + // We are not sure if handler was invoked during sleep, so we can't test + // res value, but check if any invoked + if (res != int{}) { + invoke_counter++; + } + } + }}; + } + + auto invoker = std::thread([&]() { + for (auto i = 0; i < 1000; i++) { + es.Invoke(*k, i); + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } + }); + + invoker.join(); + for (auto& s : subscribers) { + s.request_stop(); + if (s.joinable()) { + s.join(); + } + } + + TEST_ASSERT_GREATER_THAN(5, invoke_counter.load()); +} + +void test_AddRegEventsMultiThread() { + auto es = TestEventSystem<100>{}; + + std::atomic invoke_counter = {}; + std::array::Key, 5> keys{}; + + // the logic is + // Add event, and add a handler, then remove the event + // Invoke all events from invoker thread + auto subscribers = std::array{}; + for (std::size_t i = 0; i < 5; ++i) { + subscribers[i] = std::jthread{[&, i](std::stop_token const& st) { + while (!st.stop_requested()) { + int res{}; + keys[i] = es.RegEvent().value_or(0); + auto* h = es.AddHandler(keys[i], TestHandler{res}); + TEST_ASSERT_TRUE(h != nullptr); + std::this_thread::sleep_for(std::chrono::milliseconds{50}); + es.UnregEvent(keys[i]); + // We are not sure if handler was invoked during sleep, so we can't test + // res value, but check if any invoked + if (res != int{}) { + invoke_counter++; + } + } + }}; + } + + auto invoker = std::thread([&]() { + for (auto i = 0; i < 1000; i++) { + for (auto k : keys) { + es.Invoke(k, i); + } + std::this_thread::sleep_for(std::chrono::milliseconds{1}); + } + }); + + invoker.join(); + for (auto& s : subscribers) { + s.request_stop(); + if (s.joinable()) { + s.join(); + } + } + + TEST_ASSERT_GREATER_THAN(5, invoke_counter.load()); +} + +void test_RecursiveInvoke() { + using Es = EventSystem<5, 5, 32 + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + + auto k = es.RegEvent(); + TEST_ASSERT_TRUE(k.has_value()); + + // make handler what invokes event k recursively + int invoke_counter = 0; + auto f = [&]() { + if (++invoke_counter < 16) { + es.Invoke(*k); + } + }; + + auto* h = es.AddHandler( + *k, events::GenericHandler{std::move(f)}); + TEST_ASSERT_TRUE(h != nullptr); + + es.Invoke(*k); + + TEST_ASSERT_EQUAL(16, invoke_counter); +} + +void test_InHandlerRemoveHandler() { + using Es = EventSystem<5, 5, 32 + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + + auto k = es.RegEvent(); + TEST_ASSERT_TRUE(k.has_value()); + + int invoke_counter = 0; + + events::IHandler* next_handler{}; + + // this removes one handler from the other handler + auto f0 = [&]() { + TEST_ASSERT_TRUE(next_handler != nullptr); + es.RemoveHandler(*k, next_handler); + }; + + auto* h0 = es.AddHandler( + *k, events::GenericHandler{std::move(f0)}); + TEST_ASSERT_TRUE(h0 != nullptr); + + // we expected this to not been invoked + auto f1 = [&]() { invoke_counter++; }; + auto* h1 = es.AddHandler( + *k, events::GenericHandler{std::move(f1)}); + + TEST_ASSERT_TRUE(h1 != nullptr); + next_handler = h1; + + es.Invoke(*k); + + // h1 handler should not be invoked + TEST_ASSERT_EQUAL(0, invoke_counter); +} + +void test_HandlerAddedDuringEmissionWaitsForNextSnapshot() { + using Es = + EventSystem<1, 3, kLargeHandlerStorageSize + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + auto key = es.RegEvent(); + TEST_ASSERT_TRUE(key.has_value()); + + int first_calls = 0; + int added_calls = 0; + auto add_handler = [&]() { + ++first_calls; + auto added = [&]() { ++added_calls; }; + TEST_ASSERT_TRUE( + es.AddHandler(*key, events::GenericHandler{ + added}) != nullptr); + }; + TEST_ASSERT_TRUE( + es.AddHandler(*key, events::GenericHandler{ + add_handler}) != nullptr); + + es.Invoke(*key); + TEST_ASSERT_EQUAL(1, first_calls); + TEST_ASSERT_EQUAL(0, added_calls); + es.Invoke(*key); + TEST_ASSERT_EQUAL(2, first_calls); + TEST_ASSERT_EQUAL(1, added_calls); +} + +void test_InHandlerUnregister() { + using Es = EventSystem<5, 5, 32 + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + + auto k = es.RegEvent(); + TEST_ASSERT_TRUE(k.has_value()); + + int invoke_counter = 0; + + // this unregister the event from handler + auto f0 = [&]() { + es.UnregEvent(*k); + es.Invoke(*k); + }; + auto* h0 = es.AddHandler( + *k, events::GenericHandler{std::move(f0)}); + TEST_ASSERT_TRUE(h0 != nullptr); + + // The active snapshot still invokes this handler after event retirement. + auto f1 = [&]() { invoke_counter++; }; + auto* h1 = es.AddHandler( + *k, events::GenericHandler{std::move(f1)}); + + TEST_ASSERT_TRUE(h1 != nullptr); + + es.Invoke(*k); + + // h1 runs from the active snapshot; the recursive invoke is a no-op. + TEST_ASSERT_EQUAL(1, invoke_counter); + // and k event should be unregistered + TEST_ASSERT_FALSE(es.IsRegistered(*k)); +} + +void test_UnregisterReclaimsHandlersAndReusesCapacity() { + using Es = + EventSystem<1, 1, kSmallHandlerStorageSize + sizeof(events::IHandler), + alignof(std::max_align_t)>; + int destructions = 0; + auto es = Es{}; + auto key = es.RegEvent(); + TEST_ASSERT_TRUE(key.has_value()); + + auto* handler = es.AddHandler(*key, CountedHandler{destructions}); + TEST_ASSERT_TRUE(handler != nullptr); + auto const destructions_after_temporary = destructions; + + es.UnregEvent(*key); + TEST_ASSERT_EQUAL(destructions_after_temporary + 1, destructions); + + auto next_key = es.RegEvent(); + TEST_ASSERT_TRUE(next_key.has_value()); + auto* next_handler = es.AddHandler(*next_key, CountedHandler{destructions}); + TEST_ASSERT_TRUE(next_handler != nullptr); +} + +void test_RemoveHandlerReclaimsMapRecord() { + using Es = + EventSystem<1, 2, kLargeHandlerStorageSize + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + auto key = es.RegEvent(); + TEST_ASSERT_TRUE(key.has_value()); + + int replacement_result = 0; + bool replacement_added = false; + events::IHandler* self = nullptr; + auto remove_and_replace = [&]() { + es.RemoveHandler(*key, self); + auto temporary_callback = [] {}; + auto* temporary = es.AddHandler( + *key, events::GenericHandler{ + temporary_callback}); + TEST_ASSERT_TRUE(temporary != nullptr); + es.RemoveHandler(*key, temporary); + auto replacement = [&]() { ++replacement_result; }; + replacement_added = + es.AddHandler(*key, + events::GenericHandler{ + replacement}) != nullptr; + }; + self = es.AddHandler( + *key, events::GenericHandler{ + remove_and_replace}); + TEST_ASSERT_TRUE(self != nullptr); + + es.Invoke(*key); + TEST_ASSERT_TRUE(replacement_added); + es.Invoke(*key); + TEST_ASSERT_EQUAL(1, replacement_result); +} + +void test_UnregisterRetainsKeyUntilSnapshotReturns() { + using Es = + EventSystem<1, 2, kLargeHandlerStorageSize + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + auto key = es.RegEvent(); + TEST_ASSERT_TRUE(key.has_value()); + + bool key_was_reserved = false; + auto unregister = [&]() { + es.UnregEvent(*key); + key_was_reserved = !es.RegEvent().has_value(); + }; + auto* handler = es.AddHandler( + *key, events::GenericHandler{unregister}); + TEST_ASSERT_TRUE(handler != nullptr); + + es.Invoke(*key); + TEST_ASSERT_TRUE(key_was_reserved); + TEST_ASSERT_TRUE(es.RegEvent().has_value()); +} + +void test_NestedSnapshotRetirementReclaimsAllHandlers() { + using Es = + EventSystem<1, 2, kLargeHandlerStorageSize + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + auto key = es.RegEvent(); + TEST_ASSERT_TRUE(key.has_value()); + + bool nested = false; + bool key_was_reserved = false; + auto retire = [&]() { + if (!nested) { + nested = true; + es.Invoke(*key); + return; + } + es.UnregEvent(*key); + key_was_reserved = !es.RegEvent().has_value(); + }; + auto* handler = es.AddHandler( + *key, events::GenericHandler{retire}); + TEST_ASSERT_TRUE(handler != nullptr); + + es.Invoke(*key); + TEST_ASSERT_TRUE(key_was_reserved); + TEST_ASSERT_TRUE(es.RegEvent().has_value()); +} + +void test_InHandlerUnregisterRemoveHandler() { + // Unregister event from the handler and remove one of the handlers + using Es = EventSystem<5, 5, 32 + sizeof(events::IHandler), + alignof(std::max_align_t)>; + auto es = Es{}; + + auto k = es.RegEvent(); + TEST_ASSERT_TRUE(k.has_value()); + + int invoke_counter = 0; + events::IHandler* next_handler{}; + + // this unregisters event from handler + auto f0 = [&]() { es.UnregEvent(*k); }; + auto* h0 = es.AddHandler( + *k, events::GenericHandler{std::move(f0)}); + TEST_ASSERT_TRUE(h0 != nullptr); + + // This runs from the active snapshot and removes the next handler. + auto f1 = [&]() { + invoke_counter++; + TEST_ASSERT_TRUE(next_handler != nullptr); + es.RemoveHandler(*k, next_handler); + }; + auto* h1 = es.AddHandler( + *k, events::GenericHandler{std::move(f1)}); + TEST_ASSERT_TRUE(h1 != nullptr); + + // this handler should be removed and not been invoked + auto f2 = [&] { invoke_counter++; }; + auto* h2 = es.AddHandler( + *k, events::GenericHandler{std::move(f2)}); + TEST_ASSERT_TRUE(h2 != nullptr); + next_handler = h2; + + es.Invoke(*k); + + // Event retirement preserves the snapshot, but individual unsubscription + // still skips the not-yet-started handler. + TEST_ASSERT_EQUAL(1, invoke_counter); + // and k event should be unregistered + TEST_ASSERT_FALSE(es.IsRegistered(*k)); +} + +} // namespace ae::test_event_system + +int test_event_system() { + using namespace ae::test_event_system; // NOLINT + UNITY_BEGIN(); + RUN_TEST(test_AddRemoveEvents); + RUN_TEST(test_AddRemoveOverflow); + RUN_TEST(test_AddHandlers); + RUN_TEST(test_HandlerCapacityFailureIsSafe); + RUN_TEST(test_AddHandlersMultiThread); + RUN_TEST(test_AddRegEventsMultiThread); + RUN_TEST(test_RecursiveInvoke); + RUN_TEST(test_InHandlerRemoveHandler); + RUN_TEST(test_HandlerAddedDuringEmissionWaitsForNextSnapshot); + RUN_TEST(test_InHandlerUnregister); + RUN_TEST(test_InHandlerUnregisterRemoveHandler); + RUN_TEST(test_UnregisterReclaimsHandlersAndReusesCapacity); + RUN_TEST(test_RemoveHandlerReclaimsMapRecord); + RUN_TEST(test_UnregisterRetainsKeyUntilSnapshotReturns); + RUN_TEST(test_NestedSnapshotRetirementReclaimsAllHandlers); + return UNITY_END(); +} diff --git a/tests/test-events/test-events-key-registry.cpp b/tests/test-events/test-events-key-registry.cpp new file mode 100644 index 000000000..ea83a0d79 --- /dev/null +++ b/tests/test-events/test-events-key-registry.cpp @@ -0,0 +1,111 @@ +/* + * Copyright 2026 Aethernet Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include "aether/events/details/event_key_registry.h" + +namespace ae::test_event_key_registry { +using events::Key; +using events::KeyRegistry; + +void test_RegisterEvents() { + auto reg = KeyRegistry<5>{}; + // check if registry register a new key + auto k0 = reg.Register(); + TEST_ASSERT_TRUE(k0.has_value()); + TEST_ASSERT_EQUAL(0, k0.value()); + + // check if registry register another key and they are contiguous + auto k1 = reg.Register(); + TEST_ASSERT_TRUE(k1.has_value()); + TEST_ASSERT_EQUAL(1, k1.value()); + auto k2 = reg.Register(); + TEST_ASSERT_TRUE(k2.has_value()); + TEST_ASSERT_EQUAL(2, k2.value()); + + // check if key registered + TEST_ASSERT_TRUE(reg.IsRegistered(k0.value())); + TEST_ASSERT_TRUE(reg.IsRegistered(k1.value())); + TEST_ASSERT_TRUE(reg.IsRegistered(k2.value())); + + // unregister key + reg.Retire(k1.value()); + + // check if all except unregistered keys still registered + TEST_ASSERT_TRUE(reg.IsRegistered(k0.value())); + TEST_ASSERT_FALSE(reg.IsRegistered(k1.value())); + TEST_ASSERT_TRUE(reg.IsRegistered(k2.value())); + + // unregister another one and repeat + reg.Retire(k0.value()); + reg.Release(k0.value()); + + TEST_ASSERT_FALSE(reg.IsRegistered(k0.value())); + TEST_ASSERT_FALSE(reg.IsRegistered(k1.value())); + TEST_ASSERT_TRUE(reg.IsRetiring(k1.value())); + TEST_ASSERT_TRUE(reg.IsRegistered(k2.value())); +} + +void test_RegisterOverflow() { + auto reg = KeyRegistry<5>{}; + // register all 5 + std::vector keys; + for (auto i = 0; i < 5; i++) { + auto k = reg.Register(); + TEST_ASSERT_TRUE(k.has_value()); + keys.emplace_back(k.value()); + } + TEST_ASSERT_EQUAL(5, keys.size()); + + // new register should fail + auto k_extra = reg.Register(); + TEST_ASSERT_FALSE(k_extra.has_value()); + + // after unregister one + // new registration should be success + reg.Retire(keys.at(2)); + reg.Release(keys.at(2)); + auto k_new = reg.Register(); + TEST_ASSERT_TRUE(k_new.has_value()); + // and it's still contiguous + TEST_ASSERT_EQUAL(keys[4] + 1, k_new.value()); +} + +void test_IsRegistered() { + auto reg = KeyRegistry<5>{}; + + auto k = reg.Register(); + TEST_ASSERT_TRUE(reg.IsRegistered(k.value())); + + // check if any random values are not registered + TEST_ASSERT_FALSE(reg.IsRegistered(1)); + TEST_ASSERT_FALSE(reg.IsRegistered(6)); + TEST_ASSERT_FALSE(reg.IsRegistered(4)); +} + +} // namespace ae::test_event_key_registry + +int test_event_key_registry() { + using namespace ae::test_event_key_registry; // NOLINT + UNITY_BEGIN(); + RUN_TEST(test_RegisterEvents); + RUN_TEST(test_RegisterOverflow); + RUN_TEST(test_IsRegistered); + return UNITY_END(); +} diff --git a/tests/test-events/test-events-mt.cpp b/tests/test-events/test-events-mt.cpp deleted file mode 100644 index c7836376d..000000000 --- a/tests/test-events/test-events-mt.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2025 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include -#include -#include -#include -#include - -#include "aether/events/events.h" - -namespace ae::test_events_mt { -using TestEvent = Event; -using TestSubscriber = EventSubscriber; - -// Test one emitter thread with multiple subscriber threads -void test_OneEmitterMultipleSubscribers() { - TestEvent event; - std::atomic_bool running{true}; - std::atomic_int event_count{0}; - std::atomic_int active_subscribers{0}; - - // Emitter thread - emits events periodically - auto emitter_thread = std::thread([&] { - while (running) { - event.Emit(1); - event_count++; - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - }); - - // Subscriber worker function - auto subscriber_func = [&] { - TestSubscriber subscriber(event); - while (running) { - // Subscribe - active_subscribers++; - Subscription sub = subscriber.Subscribe([&](int) { - // No-op handler, we just care about the notification - }); - - // Wait until an event is emitted - int initial_count = event_count.load(); - while (event_count.load() == initial_count && running) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - - // Unsubscribe - sub.Reset(); - active_subscribers--; - - // Short delay before next cycle - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - }; - - // Create subscriber threads - const int num_subscribers = 4; - std::vector subscribers; - subscribers.reserve(num_subscribers); - for (int i = 0; i < num_subscribers; i++) { - subscribers.emplace_back(subscriber_func); - } - - // Run test for 2 seconds - std::this_thread::sleep_for(std::chrono::seconds(1)); - running = false; - - // Join all threads - emitter_thread.join(); - for (auto& t : subscribers) { - t.join(); - } - - // Verify we processed some events - TEST_ASSERT_GREATER_THAN(0, event_count.load()); - TEST_ASSERT_EQUAL(0, active_subscribers.load()); -} - -// Test thread safety of EventSystem with MutexSyncPolicy -void test_ThreadSafety() { - TestEvent event; - std::atomic_int active_handlers{0}; // Tracks active subscriptions - std::atomic_int total_events{0}; // Tracks total events processed - std::atomic_bool running{true}; // Controls test duration - - // Shared vector for subscriptions (protected by mutex) - std::mutex subscriptions_mutex; - std::vector subscriptions; - - // Worker function for subscription/unsubscription - auto worker_func = [&] { - TestSubscriber subscriber(event); - std::random_device rd; - std::mt19937 gen(rd()); - - while (running) { - // Subscribe a new handler - if (active_handlers < 1024) { - Subscription sub = subscriber.Subscribe([&](int) { total_events++; }); - active_handlers++; - - { - std::lock_guard lock(subscriptions_mutex); - subscriptions.push_back(std::move(sub)); - } - } - - // Randomly unsubscribe handlers 50% of the time - if (std::uniform_int_distribution<>(0, 1)(gen) == 1) { - Subscription to_delete; - { - std::lock_guard lock(subscriptions_mutex); - if (subscriptions.empty()) { - continue; - } - - // Select random handler to unsubscribe - std::size_t index = std::uniform_int_distribution( - 0, subscriptions.size() - 1)(gen); - - to_delete = std::move(subscriptions[index]); - subscriptions.erase(subscriptions.begin() + index); - } - - to_delete.Reset(); - active_handlers--; - } - - // Emit the event - event.Emit(1); - - // Short delay to allow other threads to run - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - }; - - // Create worker threads - const int num_threads = 4; - std::vector workers; - workers.reserve(num_threads); - for (int i = 0; i < num_threads; i++) { - workers.emplace_back(worker_func); - } - - // Run test for 2 seconds - std::this_thread::sleep_for(std::chrono::seconds(1)); - running = false; - - // Join all threads - for (auto& t : workers) { - if (t.joinable()) { - t.join(); - } - } - - // Verify no crashes occurred and we processed some events - TEST_ASSERT_GREATER_THAN(0, total_events.load()); - TEST_ASSERT_GREATER_OR_EQUAL(0, active_handlers.load()); -} -} // namespace ae::test_events_mt - -int test_events_mt() { - UNITY_BEGIN(); - RUN_TEST(ae::test_events_mt::test_OneEmitterMultipleSubscribers); - RUN_TEST(ae::test_events_mt::test_ThreadSafety); - return UNITY_END(); -} diff --git a/tests/test-events/test-events.cpp b/tests/test-events/test-events.cpp index 86899f6cb..86b20cfbf 100644 --- a/tests/test-events/test-events.cpp +++ b/tests/test-events/test-events.cpp @@ -16,210 +16,535 @@ #include -#include "aether/events/events.h" -#include "aether/events/multi_subscription.h" +#include +#include +#include +#include + +#include "aether-miscpp/types/method_ptr.h" +#include "aether-miscpp/types/small_function.h" + +#include "aether/events/details/event_multi_subscription.h" +#include "aether/events/details/event_object.h" +#include "aether/events/details/event_subscription.h" namespace ae::test_events { -void test_EventCreate() { - Event event; - bool cb_called = false; - auto lambda = [&cb_called](int i) { - cb_called = true; - TEST_ASSERT_EQUAL(i, 1); - }; +using events::EventObject; +using events::EventSystem; +using events::MultiSubscriptionObjectDyn; +using events::MultiSubscriptionObjectFix; +using events::SubscriptionObject; + +using Es = EventSystem<5, 5, 32, alignof(std::max_align_t)>; + +void test_EventObject() { + auto es = Es{}; + + // Create event object and test if it invokes subscription handler via Emit + // multiple times + int invoke_counter = 0; + auto ev = EventObject{es}; + ev.Subscribe([&]() { invoke_counter++; }); + TEST_ASSERT_EQUAL(0, invoke_counter); + ev.Emit(); + TEST_ASSERT_EQUAL(1, invoke_counter); + ev.Emit(); + TEST_ASSERT_EQUAL(2, invoke_counter); + ev.Emit(); + TEST_ASSERT_EQUAL(3, invoke_counter); +} + +void test_EventObjectSubscription() { + using Subscription = SubscriptionObject; + auto es = Es{}; + + // Create event object and test if it invokes subscription handler via Emit + // And resetting subscription remove handler + int invoke_counter = 0; + auto ev = EventObject{es}; + Subscription s = ev.Subscribe([&]() { invoke_counter++; }); + TEST_ASSERT_EQUAL(0, invoke_counter); + ev.Emit(); + TEST_ASSERT_EQUAL(1, invoke_counter); + ev.Emit(); + TEST_ASSERT_EQUAL(2, invoke_counter); + + s.Reset(); + // after reset subscription were removed + ev.Emit(); + TEST_ASSERT_EQUAL(2, invoke_counter); +} + +void test_EventObjectMoveAssignment() { + auto es = Es{}; + int source_calls = 0; + int destination_calls = 0; + auto source = EventObject{es}; + auto destination = EventObject{es}; + source.Subscribe([&]() { ++source_calls; }); + destination.Subscribe([&]() { ++destination_calls; }); + + destination = std::move(source); + // Verify the move assignment contract leaves the source empty. + // NOLINTNEXTLINE(bugprone-use-after-move) + TEST_ASSERT_FALSE(static_cast(source)); + destination.Emit(); + TEST_ASSERT_EQUAL(1, source_calls); + TEST_ASSERT_EQUAL(0, destination_calls); + + destination = std::move(destination); + destination.Emit(); + TEST_ASSERT_EQUAL(2, source_calls); +} + +void test_SubscriptionRaii() { + using Subscription = SubscriptionObject; + auto es = Es{}; + + // Create event object and test if it invokes subscription handler via Emit + // And resetting subscription remove handler + int invoke_counter = 0; + auto ev = EventObject{es}; + { - auto deleter = EventSubscriber{event}.Subscribe( - MethodPtr<&decltype(lambda)::operator()>{&lambda}); - // callback must be called until s is alive - event.Emit(1); - TEST_ASSERT(cb_called); - cb_called = false; - deleter.Delete(); + Subscription s = ev.Subscribe([&]() { invoke_counter++; }); + TEST_ASSERT_EQUAL(0, invoke_counter); + ev.Emit(); + TEST_ASSERT_EQUAL(1, invoke_counter); } - // callback must not be called because s is destroyed - event.Emit(1); - TEST_ASSERT(!cb_called); -} - -void test_EventDelete() { - Event event; - std::array cb_called{}; - auto sub1 = EventSubscriber{event}.Subscribe( - [&cb_called](int i) { cb_called[0] = true; }); - auto sub2 = EventSubscriber{event}.Subscribe( - [&cb_called](int i) { cb_called[1] = true; }); - auto sub3 = EventSubscriber{event}.Subscribe( - [&cb_called](int i) { cb_called[2] = true; }); - sub1.Delete(); - sub2.Delete(); - auto sub4 = EventSubscriber{event}.Subscribe( - [&cb_called](int i) { cb_called[3] = true; }); - auto sub5 = EventSubscriber{event}.Subscribe( - [&cb_called](int i) { cb_called[4] = true; }); - - event.Emit(12); - TEST_ASSERT_FALSE(cb_called[0]); - TEST_ASSERT_FALSE(cb_called[1]); - TEST_ASSERT_TRUE(cb_called[2]); - TEST_ASSERT_TRUE(cb_called[3]); - TEST_ASSERT_TRUE(cb_called[4]); -} - -void test_Subscriptions() { - Event event; - bool cb_called = false; - auto lambda = [&cb_called](int i) { - cb_called = true; - TEST_ASSERT_EQUAL(i, 1); - }; - Subscription sub; - TEST_ASSERT(!sub); - sub = EventSubscriber{event}.Subscribe(lambda); - TEST_ASSERT(!!sub); - event.Emit(1); - TEST_ASSERT(cb_called); - cb_called = false; - sub.Reset(); - event.Emit(1); - TEST_ASSERT(!cb_called); + + // after reset subscription were removed + ev.Emit(); + TEST_ASSERT_EQUAL(1, invoke_counter); +} + +void test_SubscriptionMove() { + using Subscription = SubscriptionObject; + auto es = Es{}; + + // Test subscription move constructible and move assignable and handler stays + // after all operations + int invoke_counter = 0; + auto ev = EventObject{es}; + + Subscription s = ev.Subscribe([&]() { invoke_counter++; }); + Subscription s1 = std::move(s); + ev.Emit(); + TEST_ASSERT_EQUAL(1, invoke_counter); + + s = std::move(s1); + ev.Emit(); + TEST_ASSERT_EQUAL(2, invoke_counter); + { - Subscription scoped_sub = EventSubscriber{event}.Subscribe(lambda); - event.Emit(1); - TEST_ASSERT(cb_called); - cb_called = false; - } - event.Emit(1); - TEST_ASSERT(!cb_called); - - sub = EventSubscriber{event}.Subscribe(lambda); - Subscription sub1 = std::move(sub); - event.Emit(1); - TEST_ASSERT(cb_called); - cb_called = false; - sub1 = EventSubscriber{event}.Subscribe(lambda); - event.Emit(1); - TEST_ASSERT(cb_called); -} - -void test_FewSubscriptions() { - Event event; - std::vector cb_called(3, false); - for (std::size_t i = 0; i < 3; i++) { - Subscription s = EventSubscriber{event}.Subscribe([&cb_called, i](int x) { - cb_called[i] = true; - TEST_ASSERT_EQUAL(x, 1); - }); - } - // callback must not be called because subscription is ignored - event.Emit(1); - for (auto b : cb_called) { - TEST_ASSERT(!b); + auto s2 = std::move(s); + ev.Emit(); + TEST_ASSERT_EQUAL(3, invoke_counter); } + // s2 automatically reset + ev.Emit(); + TEST_ASSERT_EQUAL(3, invoke_counter); +} - auto ms = MultiSubscription{}; +void test_RemoveSubscriptionFromHandler() { + using Subscription = SubscriptionObject; + auto es = Es{}; - for (std::size_t i = 0; i < 3; i++) { - ms.Push(EventSubscriber{event}.Subscribe([&cb_called, i](int x) { - cb_called[i] = true; - TEST_ASSERT_EQUAL(x, 1); - })); - } - // callback must be called because subscription is leaked and not managed - // externally anymore - event.Emit(1); - for (auto b : cb_called) { - TEST_ASSERT(b); - } + // Remove one subscription from another handler and test it's not invoked + Subscription s1; + Subscription s2; + int s1_invoke = 0; + int s2_invoke = 0; + + auto ev = EventObject{es}; + s1 = ev.Subscribe([&]() { + s1_invoke++; + s2.Reset(); + }); + + s2 = ev.Subscribe([&]() { s2_invoke++; }); + + ev.Emit(); + + TEST_ASSERT_EQUAL(1, s1_invoke); + TEST_ASSERT_EQUAL(0, s2_invoke); } -void test_MultiSubscription() { - Event event; - EventSubscriber sub{event}; - std::vector cb_called(3, false); - auto subscriptions_ = MultiSubscription{}; +void test_RemoveEventInHandler() { + using Subscription = SubscriptionObject; + auto es = Es{}; - auto lambda1 = [&](int x) { - cb_called[0] = true; - TEST_ASSERT_EQUAL(1, x); - }; - auto lambda2 = [&](int x) { - cb_called[1] = true; - TEST_ASSERT_EQUAL(1, x); - }; - auto lambda3 = [&](int x) { - cb_called[2] = true; - TEST_ASSERT_EQUAL(1, x); - }; + // Remove event from event handler + // All subscribe handlers should be invoked - subscriptions_.Push(sub.Subscribe(lambda1), sub.Subscribe(lambda2), - sub.Subscribe(lambda3)); - event.Emit(1); - for (auto b : cb_called) { - TEST_ASSERT(b); - } + auto opt_ev = std::optional>{es}; + + int s1_invoke = 0; + int s2_invoke = 0; + Subscription s1 = opt_ev->Subscribe([&]() { + s1_invoke++; + opt_ev.reset(); + }); + Subscription s2 = opt_ev->Subscribe([&]() { s2_invoke++; }); + + opt_ev->Emit(); + + TEST_ASSERT_EQUAL(1, s1_invoke); + TEST_ASSERT_EQUAL(1, s2_invoke); + TEST_ASSERT_FALSE(opt_ev); } -void test_EventRecursionCall() { - Event event; - EventSubscriber sub{event}; +void test_DifferentSignatures() { + using Subscription = SubscriptionObject; + auto es = Es{}; - bool cb_called_first = false; - bool cb_called_second = false; + // create event objects with different signatures and try to emit each + int ir1 = 0; + int ir2 = 0; + bool br2 = false; + int oir3 = 0; - auto lambda = [&](int x) { - if (x == 1) { - cb_called_first = true; - event.Emit(2); - return; + auto ev1 = EventObject{es}; + ev1.Subscribe([&](int v) { ir1 = v; }); + auto ev2 = EventObject{es}; + ev2.Subscribe([&](int v, bool f) { + ir2 = v; + br2 = f; + }); + auto ev3 = EventObject)>{es}; + ev3.Subscribe([&](std::optional v) { + if (v) { + oir3 = *v; + } else { + oir3 = -1; } - cb_called_second = true; - TEST_ASSERT_EQUAL(2, x); - }; + }); + + ev1.Emit(12); + TEST_ASSERT_EQUAL(12, ir1); + ev2.Emit(42, true); + TEST_ASSERT_EQUAL(42, ir2); + TEST_ASSERT_EQUAL(br2, true); + ev3.Emit(11); + TEST_ASSERT_EQUAL(11, oir3); + ev3.Emit(std::nullopt); + TEST_ASSERT_EQUAL(-1, oir3); +} - auto s = sub.Subscribe(lambda); +void test_CopyablePayloadDeliveredToEveryHandler() { + auto es = Es{}; + auto event = EventObject{es}; + std::string first; + std::string second; + event.Subscribe([&](std::string value) { first = std::move(value); }); + event.Subscribe([&](std::string value) { second = std::move(value); }); - event.Emit(1); - TEST_ASSERT(cb_called_first); - TEST_ASSERT(cb_called_second); + event.Emit("payload"); + TEST_ASSERT_EQUAL_STRING("payload", first.c_str()); + TEST_ASSERT_EQUAL_STRING("payload", second.c_str()); } -void test_EventReSubscribeOnHandler() { - Event event; - EventSubscriber sub{event}; +void test_FlexibleSignatures() { + using Subscription = SubscriptionObject; + auto es = Es{}; + + // create event object and try to subscribe with handlers with different but + // compatible signatures + + int ir1 = 0; + auto ev1 = EventObject{es}; + ev1.Subscribe([&](int v) { ir1 = v; }); + ev1.Emit(12); + TEST_ASSERT_EQUAL(12, ir1); + + int res2 = 0; + auto ev2 = EventObject)>{es}; + ev2.Subscribe([&](std::vector&& v) { + res2 = static_cast(std::move(v).size()); + }); + ev2.Emit(std::vector{1, 2, 3, 4}); + TEST_ASSERT_EQUAL(4, res2); + + struct Base {}; + struct Derived : public Base {}; + Base const* b{}; + auto ev3 = EventObject{es}; + ev3.Subscribe([&](Base const& v) { b = &v; }); + auto d = Derived{}; + ev3.Emit(d); + TEST_ASSERT_EQUAL(&d, b); +} - bool cb_called_first = false; - bool cb_called_second = false; +void test_SpecialHandlers() { + using Subscription = SubscriptionObject; + auto es = Es{}; - Subscription s; + // create event objects and try to use not generic but special handlers + auto ev1 = EventObject{es}; - auto inner_lambda = [&](int x) { - TEST_ASSERT_EQUAL(2, x); - cb_called_second = true; + // define special handler for this signature + struct H1 : public events::Handler { + explicit H1(int& r) : r_{&r} {} + void Invoke(int v) noexcept override { *r_ = v; } + int* r_{}; }; - auto outer_lambda = [&](int x) { - TEST_ASSERT_EQUAL(1, x); - cb_called_first = true; - s = sub.Subscribe(inner_lambda); + int res = 0; + ev1.Subscribe(H1{res}); + + ev1.Emit(654); + TEST_ASSERT_EQUAL(654, res); +} + +void test_MultiSubscriptions() { + using Es = EventSystem<10, 10, 32, alignof(std::max_align_t)>; + using Subscription = SubscriptionObject; + auto es = Es{}; + + // Create different event objects and subscribe to then using one + // multisubscription + + auto msub = MultiSubscriptionObjectDyn{}; + + int res1 = 0; + auto ev1 = EventObject{es}; + // common way to add one subscription + msub += ev1.Subscribe([&](int v) { res1 = v; }); + + bool res2 = false; + auto ev2 = EventObject{es}; + // another way to add subscription + msub.Push(ev2.Subscribe([&](bool v) { res2 = v; })); + + int res3 = 0; + auto ev3 = EventObject{es}; + int res4 = 0; + auto ev4 = EventObject const&)>{es}; + int ires5 = 0; + bool bres5 = false; + auto ev5 = EventObject{es}; + + // add multiple subscription at once + msub.Push(ev3.Subscribe([&]() { res3 = 1; }), + ev4.Subscribe([&](std::vector const& v) { + res4 = static_cast(v.size()); + }), + ev5.Subscribe([&](int v, bool f) { + ires5 = v; + bres5 = f; + })); + + ev1.Emit(12); + ev2.Emit(true); + ev3.Emit(); + ev4.Emit(std::vector{1, 23, 3}); + ev5.Emit(55, true); + + TEST_ASSERT_EQUAL(12, res1); + TEST_ASSERT_EQUAL(true, res2); + TEST_ASSERT_EQUAL(1, res3); + TEST_ASSERT_EQUAL(3, res4); + TEST_ASSERT_EQUAL(55, ires5); + TEST_ASSERT_EQUAL(true, bres5); + + // unsubscribe from all at once + msub.Reset(); + res1 = 0; + ev1.Emit(12); + // did not invoked + TEST_ASSERT_EQUAL(0, res1); +} + +void test_MultiSubscriptionMoveAssignment() { + using Multi = MultiSubscriptionObjectDyn; + auto first_system = Es{}; + auto second_system = Es{}; + auto first_event = EventObject{first_system}; + auto second_event = EventObject{second_system}; + int first_calls = 0; + int second_calls = 0; + Multi destination; + destination += first_event.Subscribe([&]() { ++first_calls; }); + Multi source; + source += second_event.Subscribe([&]() { ++second_calls; }); + + destination = std::move(source); + // Verify the move assignment contract leaves the source empty. + // NOLINTNEXTLINE(bugprone-use-after-move) + TEST_ASSERT_FALSE(static_cast(source)); + first_event.Emit(); + second_event.Emit(); + TEST_ASSERT_EQUAL(0, first_calls); + TEST_ASSERT_EQUAL(1, second_calls); + + source += first_event.Subscribe([&]() { ++first_calls; }); + source.Reset(); + first_event.Emit(); + TEST_ASSERT_EQUAL(0, first_calls); +} + +void test_FixedMultiSubscription() { + using Multi = MultiSubscriptionObjectFix; + auto es = Es{}; + auto first_event = EventObject{es}; + auto second_event = EventObject{es}; + int calls = 0; + Multi subscriptions; + subscriptions.Push(first_event.Subscribe([&]() { ++calls; }), + second_event.Subscribe([&]() { ++calls; })); + + first_event.Emit(); + second_event.Emit(); + TEST_ASSERT_EQUAL(2, calls); + subscriptions.Reset(); + first_event.Emit(); + TEST_ASSERT_EQUAL(2, calls); +} + +void test_EventSubscribeToCallable() { + using Es = EventSystem<5, 5, 64, alignof(std::max_align_t)>; + using Subscription = SubscriptionObject; + auto es = Es{}; + + // subscribe to event with different callables + auto ev = EventObject{es}; + + // Small function + int res1 = 0; + ev.Subscribe(SmallFunction{[&](int v) { res1 = v; }}); + + // MethodPtr + struct Test { + void Invoke(int v) { res = v; } + + int res{}; }; + Test test; + ev.Subscribe(MethodPtr<&Test::Invoke>{&test}); - s = sub.Subscribe(outer_lambda); + // std::function + int res2 = 0; + ev.Subscribe(std::function{[&](int v) { res2 = v; }}); - event.Emit(1); - TEST_ASSERT(cb_called_first); - event.Emit(2); - TEST_ASSERT(cb_called_second); + // ref to std::function + int res3 = 0; + auto f = std::function{[&](int v) { res3 = v; }}; + ev.Subscribe(f); + // replace value on ref + int res4 = 0; + f = std::function{[&](int v) { res4 = v; }}; + + // all handlers get the same value + ev.Emit(341); + + TEST_ASSERT_EQUAL(341, res1); + TEST_ASSERT_EQUAL(341, test.res); + TEST_ASSERT_EQUAL(341, res2); + TEST_ASSERT_EQUAL(0, res3); + TEST_ASSERT_EQUAL(341, res4); +} + +template +using GH = events::GenericHandler; + +struct Callable { + static inline int copy_count = 0; + static inline int move_count = 0; + + Callable() = default; + Callable(Callable const&) { copy_count++; } + Callable(Callable&&) noexcept { move_count++; } + + void operator()(int) const {} +}; + +void test_GenericHandler() { + { + // Test no copy made + Callable::copy_count = 0; + Callable::move_count = 0; + + auto gh = GH{Callable{}}; + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(1, Callable::move_count); + auto gh1 = std::move(gh); + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(2, Callable::move_count); + } + { + // Test take reference, no copy, no move + Callable::copy_count = 0; + Callable::move_count = 0; + + Callable clbl; + auto gh = GH{clbl}; + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(0, Callable::move_count); + auto gh1 = std::move(gh); + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(0, Callable::move_count); + } + { + // Test take const reference, no copy, no move + Callable::copy_count = 0; + Callable::move_count = 0; + + Callable clbl; + auto gh = GH{clbl}; + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(0, Callable::move_count); + auto gh1 = std::move(gh); + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(0, Callable::move_count); + } + { + // Test take &&ref, no copy, no move + Callable::copy_count = 0; + Callable::move_count = 0; + + Callable clbl; + auto gh = GH{std::move(clbl)}; + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(0, Callable::move_count); + auto gh1 = std::move(gh); + TEST_ASSERT_EQUAL(0, Callable::copy_count); + TEST_ASSERT_EQUAL(0, Callable::move_count); + } + { + // Test make one intentional copy + Callable::copy_count = 0; + Callable::move_count = 0; + + Callable clbl; + auto gh = GH{clbl}; + TEST_ASSERT_EQUAL(1, Callable::copy_count); + TEST_ASSERT_EQUAL(0, Callable::move_count); + auto gh1 = std::move(gh); + TEST_ASSERT_EQUAL(1, Callable::copy_count); + TEST_ASSERT_EQUAL(1, Callable::move_count); + } } + } // namespace ae::test_events int test_events() { + using namespace ae::test_events; // NOLINT UNITY_BEGIN(); - RUN_TEST(ae::test_events::test_EventCreate); - RUN_TEST(ae::test_events::test_EventDelete); - RUN_TEST(ae::test_events::test_Subscriptions); - RUN_TEST(ae::test_events::test_FewSubscriptions); - RUN_TEST(ae::test_events::test_MultiSubscription); - RUN_TEST(ae::test_events::test_EventRecursionCall); - RUN_TEST(ae::test_events::test_EventReSubscribeOnHandler); + RUN_TEST(test_EventObject); + RUN_TEST(test_EventObjectSubscription); + RUN_TEST(test_EventObjectMoveAssignment); + RUN_TEST(test_SubscriptionRaii); + RUN_TEST(test_SubscriptionMove); + RUN_TEST(test_RemoveSubscriptionFromHandler); + RUN_TEST(test_RemoveEventInHandler); + RUN_TEST(test_DifferentSignatures); + RUN_TEST(test_CopyablePayloadDeliveredToEveryHandler); + RUN_TEST(test_FlexibleSignatures); + RUN_TEST(test_SpecialHandlers); + RUN_TEST(test_MultiSubscriptions); + RUN_TEST(test_MultiSubscriptionMoveAssignment); + RUN_TEST(test_FixedMultiSubscription); + RUN_TEST(test_EventSubscribeToCallable); + RUN_TEST(test_GenericHandler); return UNITY_END(); } diff --git a/tests/test-prepared-packet/test-prepared-packet.cpp b/tests/test-prepared-packet/test-prepared-packet.cpp index 5a2985d2b..b7199f236 100644 --- a/tests/test-prepared-packet/test-prepared-packet.cpp +++ b/tests/test-prepared-packet/test-prepared-packet.cpp @@ -36,6 +36,7 @@ #include "aether/crypto/ikey_provider.h" #include "aether/crypto/key_gen.h" #include "aether/crypto/sync_crypto_provider.h" +#include "aether/env.h" #include "aether/prepared_packet/packet_encoder.h" #include "aether/prepared_packet/prepared_send_message.h" #include "aether/server.h" @@ -93,10 +94,11 @@ struct PreparedPacketFixture { Uid{std::array{4}}; PreparedPacketFixture() - : domain{storage}, + : domain{storage, &context}, aether{Aether::ptr::Create(CreateWith{domain})}, registry{AdapterRegistry::ptr::Create(CreateWith{domain})} { aether->adapter_registry = registry; + context.SetAether(aether); aether->client_prefab = Client::ptr::Create(CreateWith{domain}, aether); aether->client_prefab.Save(); @@ -142,6 +144,7 @@ struct PreparedPacketFixture { client->cloud().Load()->SetServers(servers); } + AeEnv context; RamDomainStorage storage; Domain domain; Aether::ptr aether; diff --git a/tests/test-safe-stream/mock_bad_streams.cpp b/tests/test-safe-stream/mock_bad_streams.cpp index bac387377..6fa565e0d 100644 --- a/tests/test-safe-stream/mock_bad_streams.cpp +++ b/tests/test-safe-stream/mock_bad_streams.cpp @@ -32,7 +32,8 @@ float RandomPercent() { bool IsHitTheRate(float hit_rate) { return RandomPercent() < hit_rate; } -DoneStreamWriteAction::DoneStreamWriteAction(AeContext const& ae_context) { +DoneStreamWriteAction::DoneStreamWriteAction(AeContext const& ae_context) + : WriteAction{ae_context} { ae_context.scheduler().Task([&]() { SetStatus(Status::kSuccess); }); } @@ -40,7 +41,7 @@ DoneStreamWriteAction::DoneStreamWriteAction(AeContext const& ae_context) { LostPacketsStream::LostPacketsStream(AeContext const& ae_context, float loss_rate) - : ae_context_{ae_context}, loss_rate_{loss_rate} {} + : ByteStream{ae_context}, ae_context_{ae_context}, loss_rate_{loss_rate} {} WriteAction& LostPacketsStream::Write(DataBuffer&& data_buffer) { if (bad_streams_internal::IsHitTheRate(loss_rate_)) { @@ -58,13 +59,17 @@ WriteAction& LostPacketsStream::Write(DataBuffer&& data_buffer) { void LostPacketsStream::LinkOut(ByteIStream& out) { out_ = &out; - out_data_sub_ = out_->out_data_event().Subscribe(out_data_event_); + out_data_sub_ = out_->out_data_event().Subscribe( + MethodPtr<&OutDataEvent::Emit>{&out_data_event_}); stream_update_event_.Emit(); } PacketDelayStream::PacketDelayStream(AeContext const& ae_context, float delay_rate, Duration max_delay) - : ae_context_{ae_context}, delay_rate_{delay_rate}, max_delay_{max_delay} {} + : ByteStream{ae_context}, + ae_context_{ae_context}, + delay_rate_{delay_rate}, + max_delay_{max_delay} {} WriteAction& PacketDelayStream::Write(DataBuffer&& data_buffer) { assert(out_); @@ -95,7 +100,8 @@ WriteAction& PacketDelayStream::Write(DataBuffer&& data_buffer) { void PacketDelayStream::LinkOut(ByteIStream& out) { out_ = &out; - out_data_sub_ = out_->out_data_event().Subscribe(out_data_event_); + out_data_sub_ = out_->out_data_event().Subscribe( + MethodPtr<&OutDataEvent::Emit>{&out_data_event_}); stream_update_event_.Emit(); } diff --git a/tests/test-safe-stream/stream-test-ctx.h b/tests/test-safe-stream/stream-test-ctx.h index 0e0b883cd..4945942a7 100644 --- a/tests/test-safe-stream/stream-test-ctx.h +++ b/tests/test-safe-stream/stream-test-ctx.h @@ -20,24 +20,27 @@ #include "aether/ae_context.h" namespace ae { -struct TestContext { - AeCtx ToAeContext() const { - static constexpr auto table = - AeCtxTable{nullptr, [](void* obj) -> TaskScheduler& { - return static_cast(obj)->sched; - }}; - return AeCtx{ - const_cast(this), // NOLINT - &table, - }; - } - +struct TestContext : public Env { template decltype(auto) Update(A&&... a) { return sched.Update(std::forward(a)...); } - TaskScheduler sched; + void* find_component(EnvId id) noexcept override { + if (id == EnvTypeId::value) { + return &sched; + } + if (id == EnvTypeId::value) { + return &event_system_; + } + return nullptr; + } + + TaskScheduler& scheduler() const { return sched; } + EventSystem& event_system() const { return event_system_; } + + mutable TaskScheduler sched; + mutable EventSystem event_system_; }; } // namespace ae diff --git a/tests/test-safe-stream/test_safe_stream.cpp b/tests/test-safe-stream/test_safe_stream.cpp index e6134f459..73a1bc7de 100644 --- a/tests/test-safe-stream/test_safe_stream.cpp +++ b/tests/test-safe-stream/test_safe_stream.cpp @@ -16,17 +16,17 @@ #include -#include "aether/api_protocol/api_message.h" +#include #include "aether/ae_context.h" -#include "aether/api_protocol/protocol_context.h" +#include "aether/api_protocol/details/packet_reader.h" #include "aether/safe_stream/safe_stream.h" -#include "tests/test-stream/to_data_buffer.h" #include "tests/test-safe-stream/stream-test-ctx.h" #include "tests/test-stream/mock_read_stream.h" #include "tests/test-stream/mock_write_stream.h" +#include "tests/test-stream/to_data_buffer.h" namespace ae::test_safe_stream { constexpr auto config = SafeStreamConfig{ @@ -56,17 +56,18 @@ void test_SafeStreamWriteFewData() { auto received_packet = DataBuffer{}; - auto read_stream = MockReadStream{}; + auto read_stream = MockReadStream{ctx}; auto write_stream = MockWriteStream{ctx, std::size_t{120}}; auto safe_stream = SafeStream{ctx, config}; + MultiSubscription subscriptions; Tie(read_stream, safe_stream, write_stream); - write_stream.on_write_event().Subscribe( - [&](auto data) { write_stream.WriteOut(std::move(data)); }); + subscriptions += write_stream.on_write_event().Subscribe( + [&](DataBuffer& data) { write_stream.WriteOut(data); }); - read_stream.out_data_event().Subscribe([&](auto data) { + subscriptions += read_stream.out_data_event().Subscribe([&](auto data) { received_packet.insert(std::end(received_packet), std::begin(data), std::end(data)); }); @@ -95,28 +96,30 @@ void test_SafeStreamPacketLoss() { TestContext ctx; - auto pc = ProtocolContext{}; - auto received_packet = DataBuffer{}; + auto send_packet_dropped = false; - auto read_stream = MockReadStream{}; + auto read_stream = MockReadStream{ctx}; auto write_stream = MockWriteStream{ctx, std::size_t{120}}; auto safe_stream = SafeStream{ctx, config}; + MultiSubscription subscriptions; Tie(read_stream, safe_stream, write_stream); // loop data to itself - write_stream.on_write_event().Subscribe([&](auto data) { - auto api_parser = ApiParser{pc, data}; - auto mid = api_parser.Extract(); - if (mid == 7) { - // packet "send" is lost - return; - } - write_stream.WriteOut(std::move(data)); - }); - - read_stream.out_data_event().Subscribe([&](auto data) { + subscriptions += + write_stream.on_write_event().Subscribe([&](DataBuffer& data) { + auto packet_reader = PacketReader{data}; + auto mid = packet_reader.Extract(); + if (((mid == 5) || (mid == 6)) && !send_packet_dropped) { + // The initial reset/send packet is lost. + send_packet_dropped = true; + return; + } + write_stream.WriteOut(data); + }); + + subscriptions += read_stream.out_data_event().Subscribe([&](auto data) { received_packet.insert(std::end(received_packet), std::begin(data), std::end(data)); }); @@ -140,11 +143,34 @@ void test_SafeStreamPacketLoss() { TEST_ASSERT_EQUAL(sizeof(_200_bytes_data), received_packet.size()); } +void test_SafeStreamStopBeforeTransmission() { + TestContext ctx; + auto write_stream = MockWriteStream{ctx, std::size_t{120}}; + auto safe_stream = SafeStream{ctx, config}; + bool stopped = false; + bool transmitted = false; + + safe_stream.LinkOut(write_stream); + Subscription write_subscription = write_stream.on_write_event().Subscribe( + [&](auto const&) { transmitted = true; }); + + auto& action = safe_stream.Write(ToDataBuffer(_100_bytes_data)); + Subscription status_subscription = action.status_event().Subscribe( + [&](auto status) { stopped = status == WriteAction::Status::kStop; }); + action.Stop(); + + ctx.Update(Now()); + + TEST_ASSERT_TRUE(stopped); + TEST_ASSERT_FALSE(transmitted); +} + } // namespace ae::test_safe_stream int test_safe_stream() { UNITY_BEGIN(); RUN_TEST(ae::test_safe_stream::test_SafeStreamWriteFewData); RUN_TEST(ae::test_safe_stream::test_SafeStreamPacketLoss); + RUN_TEST(ae::test_safe_stream::test_SafeStreamStopBeforeTransmission); return UNITY_END(); } diff --git a/tests/test-safe-stream/test_safe_stream_recv.cpp b/tests/test-safe-stream/test_safe_stream_recv.cpp index 96ac505cc..b1a13a7a3 100644 --- a/tests/test-safe-stream/test_safe_stream_recv.cpp +++ b/tests/test-safe-stream/test_safe_stream_recv.cpp @@ -16,15 +16,16 @@ #include -#include +#include #include +#include #include "aether/config.h" -#include "aether/safe_stream/safe_stream_config.h" #include "aether/safe_stream/details/safe_stream_recv_action.h" +#include "aether/safe_stream/safe_stream_config.h" -#include "tests/test-stream/to_data_buffer.h" #include "tests/test-safe-stream/stream-test-ctx.h" +#include "tests/test-stream/to_data_buffer.h" namespace ae::test_safe_stream_recv { constexpr auto kTick = std::chrono::milliseconds{5}; @@ -54,7 +55,7 @@ using Receiver = SafeStreamRecvAction; using IndexType = RingIndex; using IndexRangeType = RingIndexRange; -class MockSendConfirmRepeat final : public ISendAckRepeat { +class MockSendConfirmRepeat final : public ISafeStreamRecvActionDelegate { public: struct AckData { std::uint16_t index; @@ -70,6 +71,12 @@ class MockSendConfirmRepeat final : public ISendAckRepeat { repeat_request_data = RepeatRequestData{index}; } + void ReceiveData(DataBuffer& data) override { + if (on_receive) { + on_receive(data); + } + } + void Reset() { ack_data.reset(); repeat_request_data.reset(); @@ -77,6 +84,7 @@ class MockSendConfirmRepeat final : public ISendAckRepeat { std::optional ack_data; std::optional repeat_request_data; + std::function on_receive; }; // Creative test data strings @@ -124,8 +132,9 @@ void test_RecvActionCreateAndReceive() { // Track received data std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; auto message = CreateDataMessage(quantum_data, 0); receiver.PushData(begin_offset, message); @@ -160,8 +169,9 @@ void test_RecvActionInOrderDataChain() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; // Create data chunks using DataChunk structure auto message1 = CreateDataMessage(network_poetry, 0); @@ -200,8 +210,9 @@ void test_RecvActionOutOfOrderDataChain() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; // Create data chunks using DataChunk structure auto message1 = CreateDataMessage(protocol_haiku, 0); @@ -251,8 +262,9 @@ void test_RecvActionSendConfirmOnTimeout() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; // Send some data using DataChunk auto message = CreateDataMessage(network_poetry, 0); @@ -286,8 +298,9 @@ void test_RecvActionRequestRepeatOnMissing() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; // Create chunks with a gap auto message1 = CreateDataMessage(dev_wisdom, 0); @@ -328,8 +341,9 @@ void test_RecvActionRepeatDataHandling() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; auto message = CreateDataMessage(protocol_haiku, 0); @@ -368,8 +382,9 @@ void test_RecvActionDuplicateDataHandling() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; auto message = CreateDataMessage(protocol_haiku, 0); @@ -399,8 +414,9 @@ void test_RecvActionRepeatOverlappingDataHandling() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; // two messages: part of bug_report and full bug_report auto message1 = DataMessage{ @@ -440,8 +456,9 @@ void test_RecvActionInOrderCombinedDataChain() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; // Create multiple small contiguous chunks that should be combined auto message1 = CreateDataMessage("Part1:", 0); @@ -480,8 +497,9 @@ void test_RecvActionWindowSizeLimit() { auto receiver = Receiver{ctx, mock_sender, config}; std::vector received_data; - auto recv_sub = receiver.receive_event().Subscribe( - [&](DataBuffer&& data) { received_data.push_back(std::move(data)); }); + mock_sender.on_receive = [&](DataBuffer& data) { + received_data.push_back(std::move(data)); + }; // Send first chunk auto message1 = CreateDataMessage("First chunk: ", 0); diff --git a/tests/test-safe-stream/test_safe_stream_reliability.cpp b/tests/test-safe-stream/test_safe_stream_reliability.cpp index 103a07382..5b9853ef8 100644 --- a/tests/test-safe-stream/test_safe_stream_reliability.cpp +++ b/tests/test-safe-stream/test_safe_stream_reliability.cpp @@ -56,13 +56,14 @@ void TestSendPackets(TestContext& ctx, SafeStream& sender, int sent_messages = 0; int failed_messages = 0; std::size_t received_size = 0; + MultiSubscription subscriptions; // send messages periodically auto task = RepeatableTask{ AeContext{ctx}, [&]() { auto& sent_action = sender.Write(ToDataBuffer(test_data)); - sent_action.status_event().Subscribe([&](auto status) { + subscriptions += sent_action.status_event().Subscribe([&](auto status) { if (status != WriteAction::Status::kSuccess) { failed_messages++; } @@ -73,7 +74,7 @@ void TestSendPackets(TestContext& ctx, SafeStream& sender, std::chrono::milliseconds{50}, wait_messages}; // count received messages - receiver.out_data_event().Subscribe([&](auto const& d) { + subscriptions += receiver.out_data_event().Subscribe([&](auto const& d) { AE_TELED_DEBUG("Received message size {}", d.size()); received_size += d.size(); }); @@ -100,10 +101,11 @@ void test_SafeStreamLostPackets() { auto r_packet_loss = LostPacketsStream{ctx, 0.2}; auto s_mock_stream = MockWriteStream{ctx, 1024}; auto r_mock_stream = MockWriteStream{ctx, 1024}; + MultiSubscription subscriptions; - s_mock_stream.on_write_event().Subscribe( + subscriptions += s_mock_stream.on_write_event().Subscribe( [&](auto&& data) { r_mock_stream.WriteOut(data); }); - r_mock_stream.on_write_event().Subscribe( + subscriptions += r_mock_stream.on_write_event().Subscribe( [&](auto&& data) { s_mock_stream.WriteOut(data); }); auto sender = SafeStream{ctx, config}; @@ -126,10 +128,11 @@ void test_SafeStreamPacketsReordered() { PacketDelayStream{ctx, 0.2, std::chrono::milliseconds{50}}; auto s_mock_stream = MockWriteStream{ctx, 1024}; auto r_mock_stream = MockWriteStream{ctx, 1024}; + MultiSubscription subscriptions; - s_mock_stream.on_write_event().Subscribe( + subscriptions += s_mock_stream.on_write_event().Subscribe( [&](auto&& data) { r_mock_stream.WriteOut(data); }); - r_mock_stream.on_write_event().Subscribe( + subscriptions += r_mock_stream.on_write_event().Subscribe( [&](auto&& data) { s_mock_stream.WriteOut(data); }); auto sender = SafeStream{ctx, config}; @@ -156,10 +159,11 @@ void test_SafeStreamPacketsLostAndReordered() { auto s_mock_stream = MockWriteStream{ctx, 1024}; auto r_mock_stream = MockWriteStream{ctx, 1024}; + MultiSubscription subscriptions; - s_mock_stream.on_write_event().Subscribe( + subscriptions += s_mock_stream.on_write_event().Subscribe( [&](auto&& data) { r_mock_stream.WriteOut(data); }); - r_mock_stream.on_write_event().Subscribe( + subscriptions += r_mock_stream.on_write_event().Subscribe( [&](auto&& data) { s_mock_stream.WriteOut(data); }); auto sender = SafeStream{ctx, config}; diff --git a/tests/test-safe-stream/test_safe_stream_send.cpp b/tests/test-safe-stream/test_safe_stream_send.cpp index 909321f79..99d815d1a 100644 --- a/tests/test-safe-stream/test_safe_stream_send.cpp +++ b/tests/test-safe-stream/test_safe_stream_send.cpp @@ -16,15 +16,16 @@ #include +#include #include -#include "aether/config.h" #include "aether/ae_context.h" -#include "aether/safe_stream/safe_stream_config.h" +#include "aether/config.h" #include "aether/safe_stream/details/safe_stream_send_action.h" +#include "aether/safe_stream/safe_stream_config.h" -#include "tests/test-stream/to_data_buffer.h" #include "tests/test-safe-stream/stream-test-ctx.h" +#include "tests/test-stream/to_data_buffer.h" namespace ae::test_safe_stream_send { constexpr auto kTick = std::chrono::milliseconds{5}; @@ -43,7 +44,7 @@ using Sender = SafeStreamSendAction; using IndexType = RingIndex; using IndexRangeType = RingIndexRange; -class MockSendDataPush final : public ISendDataPush { +class MockSendDataPush final : public ISafeStreamSendActionDelegate { public: struct SendData { std::uint16_t index; @@ -52,7 +53,8 @@ class MockSendDataPush final : public ISendDataPush { class MockStreamWriteAction final : public WriteAction { public: - explicit MockStreamWriteAction(AeContext const& context) { + explicit MockStreamWriteAction(AeContext const& context) + : WriteAction{context} { context.scheduler().Task( [&]() { WriteAction::SetStatus(Status::kSuccess); }); } @@ -70,9 +72,30 @@ class MockSendDataPush final : public ISendDataPush { return *wa_; } + void WriteAcknowledged(IndexType buffer_begin, IndexType ack_index) override { + if (on_acknowledged) { + on_acknowledged(buffer_begin, ack_index); + } + } + + void WriteStopped(IndexType buffer_begin, IndexType stop_index) override { + if (on_stopped) { + on_stopped(buffer_begin, stop_index); + } + } + + void WriteFailed(IndexType buffer_begin, IndexType failed_index) override { + if (on_failed) { + on_failed(buffer_begin, failed_index); + } + } + AeContext context_; std::optional send_data; std::optional wa_; + std::function on_acknowledged; + std::function on_stopped; + std::function on_failed; }; // Creative test data strings for improved tests (under 200 bytes) @@ -125,16 +148,16 @@ void test_SendActionCreateAndSend() { IndexRangeType expected_range{}; auto sender = Sender{ctx, send_data_push, config}; - sender.acknowledged_event().Subscribe([&](auto, auto index) { + send_data_push.on_acknowledged = [&](auto, auto index) { if (expected_range.right == index) { is_sent = true; } - }); - sender.send_failed_event().Subscribe([&](auto, auto index) { + }; + send_data_push.on_failed = [&](auto, auto index) { if (expected_range.right == index) { is_error = true; } - }); + }; sender.SetMaxPayload(config.max_packet_size); auto send_data = sender.SendData(ToSpan(packet_poetry)); @@ -182,16 +205,16 @@ void test_SendActionRepeatOnTimeout() { IndexRangeType expected_range{}; auto sender = Sender{ctx, send_data_push, config}; - sender.acknowledged_event().Subscribe([&](auto, auto index) { + send_data_push.on_acknowledged = [&](auto, auto index) { if (expected_range.right == index) { is_sent = true; } - }); - sender.send_failed_event().Subscribe([&](auto, auto index) { + }; + send_data_push.on_failed = [&](auto, auto index) { if (expected_range.right == index) { is_error = true; } - }); + }; sender.SetMaxPayload(config.max_packet_size); auto send_data = sender.SendData(ToSpan(retry_humor)); @@ -238,16 +261,16 @@ void test_SendActionErrorOnMaxRepeatExceeded() { IndexRangeType expected_range{}; auto sender = Sender{ctx, send_data_push, config}; - sender.acknowledged_event().Subscribe([&](auto, auto index) { + send_data_push.on_acknowledged = [&](auto, auto index) { if (expected_range.right == index) { is_sent = true; } - }); - sender.send_failed_event().Subscribe([&](auto, auto index) { + }; + send_data_push.on_failed = [&](auto, auto index) { if (expected_range.right == index) { is_error = true; } - }); + }; sender.SetMaxPayload(config.max_packet_size); auto send_data = sender.SendData(ToSpan(confirmation_comedy)); @@ -470,22 +493,22 @@ void test_SendActionMultipleDataQueueing() { IndexRangeType expected_range2{}; auto sender = Sender{ctx, send_data_push, config}; - sender.acknowledged_event().Subscribe([&](auto buffer_begin, auto index) { + send_data_push.on_acknowledged = [&](auto buffer_begin, auto index) { if (IndexComparable{expected_range1.right, buffer_begin} <= index) { sent1 = true; } if (IndexComparable{expected_range2.right, buffer_begin} <= index) { sent2 = true; } - }); - sender.send_failed_event().Subscribe([&](auto buffer_begin, auto index) { + }; + send_data_push.on_failed = [&](auto buffer_begin, auto index) { if (IndexComparable{expected_range1.right, buffer_begin} <= index) { error1 = true; } if (IndexComparable{expected_range2.right, buffer_begin} <= index) { error2 = true; } - }); + }; sender.SetMaxPayload(config.max_packet_size); // Send multiple data chunks rapidly @@ -539,16 +562,16 @@ void test_SendDataBiggerThanMaxPacketSize() { IndexRangeType expected_range{}; auto sender = Sender{ctx, send_data_push, config}; - sender.acknowledged_event().Subscribe([&](auto, auto index) { + send_data_push.on_acknowledged = [&](auto, auto index) { if (expected_range.right == index) { sent = true; } - }); - sender.send_failed_event().Subscribe([&](auto, auto index) { + }; + send_data_push.on_failed = [&](auto, auto index) { if (expected_range.right == index) { error = true; } - }); + }; sender.SetMaxPayload(max_packet_size); // Send data bigger than max packet size @@ -622,21 +645,21 @@ void test_SendDataAndStop() { IndexRangeType expected_range{}; auto sender = Sender{ctx, send_data_push, config}; - sender.acknowledged_event().Subscribe([&](auto, auto index) { + send_data_push.on_acknowledged = [&](auto, auto index) { if (expected_range.right == index) { sent = true; } - }); - sender.send_failed_event().Subscribe([&](auto, auto index) { + }; + send_data_push.on_failed = [&](auto, auto index) { if (expected_range.right == index) { error = true; } - }); - sender.stopped_event().Subscribe([&](auto, auto index) { + }; + send_data_push.on_stopped = [&](auto, auto index) { if (expected_range.right == index) { stopped = true; } - }); + }; sender.SetMaxPayload(max_packet_size); auto send_range0 = sender.SendData(ToSpan(packet_poetry)); diff --git a/tests/test-safe-stream/test_safe_stream_send_recv.cpp b/tests/test-safe-stream/test_safe_stream_send_recv.cpp index dab002c36..17d027afd 100644 --- a/tests/test-safe-stream/test_safe_stream_send_recv.cpp +++ b/tests/test-safe-stream/test_safe_stream_send_recv.cpp @@ -16,14 +16,15 @@ #include +#include #include -#include "aether/safe_stream/safe_stream_config.h" #include "aether/safe_stream/details/safe_stream_recv_action.h" #include "aether/safe_stream/details/safe_stream_send_action.h" +#include "aether/safe_stream/safe_stream_config.h" -#include "tests/test-stream/to_data_buffer.h" #include "tests/test-safe-stream/stream-test-ctx.h" +#include "tests/test-stream/to_data_buffer.h" namespace ae::test_safe_stream_send_recv { constexpr auto config = SafeStreamConfig{ @@ -97,10 +98,11 @@ class DelayPushDataImpl : public TestSafeStreamActionsTransport { std::optional push_message; }; -class MockSendDataPush : public ISendDataPush { +class MockSendDataPush : public ISafeStreamSendActionDelegate { class DoneStreamWriteAction : public WriteAction { public: - explicit DoneStreamWriteAction(AeContext const& context) { + explicit DoneStreamWriteAction(AeContext const& context) + : WriteAction{context} { context.scheduler().Task([&]() { SetStatus(Status::kSuccess); }); } }; @@ -121,12 +123,22 @@ class MockSendDataPush : public ISendDataPush { return *dswa_; } + void WriteAcknowledged(IndexType buffer_begin, IndexType end) override { + if (on_acknowledged) { + on_acknowledged(buffer_begin, end); + } + } + + void WriteStopped(IndexType, IndexType) override {} + void WriteFailed(IndexType, IndexType) override {} + AeContext context_; TestSafeStreamActionsTransport* transport_{}; std::optional dswa_; + std::function on_acknowledged; }; -class MockSendAckRepeat : public ISendAckRepeat { +class MockSendAckRepeat : public ISafeStreamRecvActionDelegate { public: MockSendAckRepeat() = default; @@ -139,7 +151,14 @@ class MockSendAckRepeat : public ISendAckRepeat { transport_->SendRepeatRequest(offset); } + void ReceiveData(DataBuffer& data) override { + if (on_receive) { + on_receive(data); + } + } + TestSafeStreamActionsTransport* transport_{}; + std::function on_receive; }; static constexpr std::string_view test_data = @@ -162,11 +181,11 @@ void test_SafeStreamInitHandshake() { auto recv_transport = MockSendAckRepeat{}; auto sender = Sender{ctx, send_transport, config}; - sender.acknowledged_event().Subscribe([&](auto buffer_begin, auto end) { + send_transport.on_acknowledged = [&](auto buffer_begin, auto end) { if (IndexComparable{expected_range.right, buffer_begin} <= end) { acked = true; } - }); + }; sender.SetMaxPayload(config.max_packet_size); auto receiver = Receiver{ctx, recv_transport, config}; @@ -175,8 +194,7 @@ void test_SafeStreamInitHandshake() { send_transport.Link(sender_to_receiver); recv_transport.Link(sender_to_receiver); - receiver.receive_event().Subscribe( - [&](auto const& data) { received = data; }); + recv_transport.on_receive = [&](auto const& data) { received = data; }; auto send_data = sender.SendData(ToSpan(test_data)); TEST_ASSERT_TRUE(send_data.IsOk()); @@ -209,11 +227,11 @@ void test_SafeStreamReInitSender() { // sender is optional and will be replaced auto sender = std::optional{std::in_place, ctx, send_transport, config}; - sender->acknowledged_event().Subscribe([&](auto buffer_begin, auto end) { + send_transport.on_acknowledged = [&](auto buffer_begin, auto end) { if (IndexComparable{expected_range.right, buffer_begin} <= end) { acked = true; } - }); + }; sender->SetMaxPayload(config.max_packet_size); auto receiver = Receiver{ctx, recv_transport, config}; @@ -221,8 +239,7 @@ void test_SafeStreamReInitSender() { send_transport.Link(sender_to_receiver); recv_transport.Link(sender_to_receiver); - receiver.receive_event().Subscribe( - [&](auto const& data) { received = data; }); + recv_transport.on_receive = [&](auto const& data) { received = data; }; auto send_data1 = sender->SendData(ToSpan(test_data)); TEST_ASSERT_TRUE(send_data1.IsOk()); @@ -244,11 +261,11 @@ void test_SafeStreamReInitSender() { // create new sender sender.emplace(ctx, send_transport, config); - sender->acknowledged_event().Subscribe([&](auto buffer_begin, auto end) { + send_transport.on_acknowledged = [&](auto buffer_begin, auto end) { if (IndexComparable{expected_range.right, buffer_begin} <= end) { acked = true; } - }); + }; sender->SetMaxPayload(config.max_packet_size); sender_to_receiver = TestSafeStreamActionsTransport{*sender, receiver}; @@ -285,11 +302,11 @@ void test_SafeStreamReInitReceiver() { // sender is optional and will be replaced auto sender = Sender{ctx, send_transport, config}; - sender.acknowledged_event().Subscribe([&](auto buffer_begin, auto end) { + send_transport.on_acknowledged = [&](auto buffer_begin, auto end) { if (IndexComparable{expected_range.right, buffer_begin} <= end) { acked = true; } - }); + }; sender.SetMaxPayload(config.max_packet_size); auto receiver = std::optional{std::in_place, ctx, recv_transport, config}; @@ -298,8 +315,7 @@ void test_SafeStreamReInitReceiver() { send_transport.Link(sender_to_receiver); recv_transport.Link(sender_to_receiver); - receiver->receive_event().Subscribe( - [&](auto const& data) { received = data; }); + recv_transport.on_receive = [&](auto const& data) { received = data; }; auto send_data1 = sender.SendData(ToSpan(test_data)); TEST_ASSERT_TRUE(send_data1.IsOk()); @@ -324,8 +340,7 @@ void test_SafeStreamReInitReceiver() { sender_to_receiver = TestSafeStreamActionsTransport{sender, *receiver}; send_transport.Link(sender_to_receiver); recv_transport.Link(sender_to_receiver); - receiver->receive_event().Subscribe( - [&](auto const& data) { received = data; }); + recv_transport.on_receive = [&](auto const& data) { received = data; }; auto send_data2 = sender.SendData(ToSpan(test_data)); TEST_ASSERT_TRUE(send_data2.IsOk()); diff --git a/tests/test-serial-port/mock-at-observer.h b/tests/test-serial-port/mock-at-observer.h index ff89b8e4d..5fe4eb480 100644 --- a/tests/test-serial-port/mock-at-observer.h +++ b/tests/test-serial-port/mock-at-observer.h @@ -17,11 +17,11 @@ #ifndef TESTS_TEST_SERIAL_PORT_MOCK_AT_OBSERVER_H_ #define TESTS_TEST_SERIAL_PORT_MOCK_AT_OBSERVER_H_ -#include #include +#include -#include "aether/serial_ports/at_support/at_dispatcher.h" #include "aether/serial_ports/at_support/at_buffer.h" +#include "aether/serial_ports/at_support/at_dispatcher.h" namespace ae::tests { class MockAtObserver final : public IAtObserver { diff --git a/tests/test-serial-port/mock-serial-port.h b/tests/test-serial-port/mock-serial-port.h index 765eb40ad..cf3ce5bb6 100644 --- a/tests/test-serial-port/mock-serial-port.h +++ b/tests/test-serial-port/mock-serial-port.h @@ -26,7 +26,8 @@ namespace ae::tests { class MockSerialPort final : public ISerialPort { public: - MockSerialPort() = default; + explicit MockSerialPort(EventContext auto const& context) + : read_event_{context}, write_event_{context} {} bool IsOpen() override { return is_open_; } @@ -34,15 +35,13 @@ class MockSerialPort final : public ISerialPort { write_event_.Emit(data); } - DataReadEvent::Subscriber read_event() override { - return EventSubscriber{read_event_}; - } + DataReadEvent const& read_event() override { return read_event_; } // calls read_event void WriteOut(std::span data) { read_event_.Emit(data); } - Event data)>::Subscriber write_event() { - return EventSubscriber{write_event_}; + Event data)> const& write_event() { + return write_event_; } void close() { is_open_ = false; } diff --git a/aether/api_protocol/api_class.h b/tests/test-serial-port/mock-test-context.h similarity index 53% rename from aether/api_protocol/api_class.h rename to tests/test-serial-port/mock-test-context.h index ab68a25c0..b4c06fd2c 100644 --- a/aether/api_protocol/api_class.h +++ b/tests/test-serial-port/mock-test-context.h @@ -1,5 +1,5 @@ /* - * Copyright 2025 Aethernet Inc. + * Copyright 2026 Aethernet Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,22 +14,20 @@ * limitations under the License. */ -#ifndef AETHER_API_PROTOCOL_API_CLASS_H_ -#define AETHER_API_PROTOCOL_API_CLASS_H_ +#ifndef TEST_TEST_SERIAL_PORT_MOCK_TEST_CONTEXT_H_ +#define TEST_TEST_SERIAL_PORT_MOCK_TEST_CONTEXT_H_ -#include "aether/api_protocol/protocol_context.h" +#include -namespace ae { -class ApiClass { - public: - explicit ApiClass(ProtocolContext& protocol_context) - : protocol_context_{&protocol_context} {} +#include "aether/env.h" - ProtocolContext& protocol_context() const { return *protocol_context_; } - - private: - ProtocolContext* protocol_context_; +namespace ae::tests { +struct PrintLifetime { + PrintLifetime() { printf("TestContext construction\n"); } + virtual ~PrintLifetime() { printf("TestContext destruction\n"); } }; -} // namespace ae -#endif // AETHER_API_PROTOCOL_API_CLASS_H_ +struct TestContext : PrintLifetime, public AeEnv {}; +} // namespace ae::tests + +#endif // TEST_TEST_SERIAL_PORT_MOCK_TEST_CONTEXT_H_ diff --git a/tests/test-serial-port/test-at-buffer.cpp b/tests/test-serial-port/test-at-buffer.cpp index 2fbd17c79..0d8dddde1 100644 --- a/tests/test-serial-port/test-at-buffer.cpp +++ b/tests/test-serial-port/test-at-buffer.cpp @@ -18,12 +18,14 @@ #include "aether/serial_ports/at_support/at_buffer.h" #include "tests/test-serial-port/mock-serial-port.h" +#include "tests/test-serial-port/mock-test-context.h" namespace ae::test_at_buffer { void test_AtBufferSingleCompleteLine() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Send a single complete line with \r\n DataBuffer data; @@ -41,8 +43,9 @@ void test_AtBufferSingleCompleteLine() { } void test_AtBufferMultipleCompleteLines() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Send multiple complete lines with \r\n DataBuffer data; @@ -70,8 +73,9 @@ void test_AtBufferMultipleCompleteLines() { } void test_AtBufferIncompleteLine() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Send incomplete line without \r\n DataBuffer data; @@ -90,8 +94,9 @@ void test_AtBufferIncompleteLine() { } void test_AtBufferFindPatternBasic() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Add some data to the buffer DataBuffer data; @@ -114,8 +119,9 @@ void test_AtBufferFindPatternBasic() { } void test_AtBufferFindPatternWithStartIterator() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Add some data to the buffer DataBuffer data; @@ -142,8 +148,9 @@ void test_AtBufferFindPatternWithStartIterator() { } void test_AtBufferGetCrateBasic() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Add some data to the buffer DataBuffer data; @@ -161,8 +168,9 @@ void test_AtBufferGetCrateBasic() { } void test_AtBufferGetCrateWithOffset() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Add some data to the buffer DataBuffer data; @@ -181,8 +189,9 @@ void test_AtBufferGetCrateWithOffset() { } void test_AtBufferGetCrateWithStartIterator() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Add some data to the buffer DataBuffer data; @@ -204,8 +213,9 @@ void test_AtBufferGetCrateWithStartIterator() { } void test_AtBufferGetCrateWithStartIteratorAndOffset() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Add some data to the buffer DataBuffer data; @@ -226,8 +236,9 @@ void test_AtBufferGetCrateWithStartIteratorAndOffset() { } void test_AtBufferGetCrateWithStartIteratorAndOffsetBiggerThanLine() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; // Add some data to the buffer DataBuffer data; diff --git a/tests/test-serial-port/test-at-dispatcher.cpp b/tests/test-serial-port/test-at-dispatcher.cpp index d7a72e4d6..72629872d 100644 --- a/tests/test-serial-port/test-at-dispatcher.cpp +++ b/tests/test-serial-port/test-at-dispatcher.cpp @@ -19,13 +19,15 @@ #include "aether/serial_ports/at_support/at_buffer.h" #include "aether/serial_ports/at_support/at_dispatcher.h" -#include "tests/test-serial-port/mock-serial-port.h" #include "tests/test-serial-port/mock-at-observer.h" +#include "tests/test-serial-port/mock-serial-port.h" +#include "tests/test-serial-port/mock-test-context.h" namespace ae::test_at_dispatcher { void test_AtDispatcherRegisterSingleObserver() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; tests::MockAtObserver observer{}; int observation_count = 0; @@ -63,8 +65,9 @@ void test_AtDispatcherRegisterSingleObserver() { } void test_AtDispatcherRegisterMultipleObservers() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; tests::MockAtObserver observer1{}; tests::MockAtObserver observer2{}; @@ -144,8 +147,9 @@ void test_AtDispatcherRegisterMultipleObservers() { } void test_AtDispatcherRegisterSameCommandMultipleTimes() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; tests::MockAtObserver observer1{}; tests::MockAtObserver observer2{}; @@ -189,8 +193,9 @@ void test_AtDispatcherRegisterSameCommandMultipleTimes() { } void test_AtDispatcherRemoveObserver() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; tests::MockAtObserver observer{}; int observation_count = 0; @@ -215,8 +220,9 @@ void test_AtDispatcherRemoveObserver() { } void test_AtDispatcherRemoveObserverMultipleCommands() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; tests::MockAtObserver observer{}; int observation_count = 0; @@ -242,8 +248,9 @@ void test_AtDispatcherRemoveObserverMultipleCommands() { } void test_AtDispatcherComplexPatternScenarios() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; // Test overlapping and complex pattern scenarios diff --git a/tests/test-serial-port/test-at-listener.cpp b/tests/test-serial-port/test-at-listener.cpp index 30b3167c6..650775bb3 100644 --- a/tests/test-serial-port/test-at-listener.cpp +++ b/tests/test-serial-port/test-at-listener.cpp @@ -16,17 +16,19 @@ #include -#include "aether/serial_ports/at_support/at_listener.h" -#include "aether/serial_ports/at_support/at_dispatcher.h" #include "aether/serial_ports/at_support/at_buffer.h" -#include "tests/test-serial-port/mock-serial-port.h" +#include "aether/serial_ports/at_support/at_dispatcher.h" +#include "aether/serial_ports/at_support/at_listener.h" #include "tests/test-serial-port/mock-at-observer.h" +#include "tests/test-serial-port/mock-serial-port.h" +#include "tests/test-serial-port/mock-test-context.h" namespace ae::test_at_listener { void test_AtListenerConstructorRegistersWithDispatcher() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; int handler_call_count = 0; AtBuffer::iterator observed_pos{}; @@ -57,8 +59,9 @@ void test_AtListenerConstructorRegistersWithDispatcher() { } void test_AtListenerStoresHandlerCorrectly() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; int handler_call_count = 0; AtBuffer::iterator observed_pos{}; @@ -89,8 +92,9 @@ void test_AtListenerStoresHandlerCorrectly() { } void test_AtListenerMultipleListenersDifferentPatterns() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; int handler1_call_count = 0; int handler2_call_count = 0; @@ -150,8 +154,9 @@ void test_AtListenerMultipleListenersDifferentPatterns() { } void test_AtListenerSingleListenerMultipleMatch() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; int handler_call_count = 0; @@ -171,8 +176,9 @@ void test_AtListenerSingleListenerMultipleMatch() { } void test_AtListenerDestructorUnregistersFromDispatcher() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; int handler_call_count = 0; @@ -205,8 +211,9 @@ void test_AtListenerDestructorUnregistersFromDispatcher() { } void test_AtListenerNoHandlerCallsAfterDestruction() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; int handler_call_count = 0; @@ -228,8 +235,9 @@ void test_AtListenerNoHandlerCallsAfterDestruction() { } void test_AtListenerRAIIBehavior() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; int outer_handler_count = 0; int inner_handler_count = 0; @@ -314,8 +322,9 @@ void test_AtListenerRAIIBehavior() { } void test_AtListenerIntegrationWithObserver() { - tests::MockSerialPort mock_serial{}; - AtBuffer buffer{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + AtBuffer buffer{context, mock_serial}; AtDispatcher dispatcher{buffer}; // Test mixed usage of observers and listeners diff --git a/tests/test-serial-port/test-at-request.cpp b/tests/test-serial-port/test-at-request.cpp index 44a609d29..adf154564 100644 --- a/tests/test-serial-port/test-at-request.cpp +++ b/tests/test-serial-port/test-at-request.cpp @@ -28,6 +28,7 @@ #include "aether/serial_ports/at_support/at_support.h" #include "tests/test-serial-port/mock-serial-port.h" +#include "tests/test-serial-port/mock-test-context.h" namespace ae::test_at_request { @@ -38,8 +39,9 @@ struct TestContext { }; void test_AtRequestStringCommand0WaitsSuccess() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; DataBuffer received; mock_serial.write_event().Subscribe([&](auto data) { @@ -55,8 +57,9 @@ void test_AtRequestStringCommand0WaitsSuccess() { } void test_AtRequestStringCommand0WaitsError() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; DataBuffer received; mock_serial.write_event().Subscribe([&](auto data) { @@ -77,8 +80,9 @@ void test_AtRequestStringCommand0WaitsError() { } void test_AtRequestStringCommand1WaitSuccess() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with string command and 1 wait for "OK" auto at_request = at::MakeRequest(ex::just(), at_support, std::string{"AT"}, @@ -118,8 +122,9 @@ struct AtReceiver { void test_AtRequestStringCommand1WaitErrorTimeout() { TaskScheduler scheduler; - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; DataBuffer received; mock_serial.write_event().Subscribe([&](auto data) { @@ -152,8 +157,9 @@ void test_AtRequestStringCommand1WaitErrorTimeout() { } void test_AtRequestStringCommand1WaitErrorResponse() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with string command and 1 wait auto at_request = @@ -176,8 +182,9 @@ void test_AtRequestStringCommand1WaitErrorResponse() { } void test_AtRequestStringCommand2WaitsSuccess() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with string command and 2 waits auto at_request = @@ -210,8 +217,9 @@ void test_AtRequestStringCommand2WaitsSuccess() { void test_AtRequestStringCommand2WaitsErrorPartial() { TaskScheduler scheduler; - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with string command and 2 waits with short timeout auto at_request = at::MakeRequest(ex::just(), at_support, "AT", @@ -241,8 +249,9 @@ void test_AtRequestStringCommand2WaitsErrorPartial() { } void test_AtRequestStringCommand5WaitsSuccess() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with string command and 5 waits auto at_request = @@ -271,8 +280,9 @@ void test_AtRequestStringCommand5WaitsSuccess() { void test_AtRequestStringCommand5WaitsErrorTimeout() { TaskScheduler scheduler; - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with string command and 5 waits with short timeout auto at_request = @@ -308,8 +318,9 @@ void test_AtRequestStringCommand5WaitsErrorTimeout() { } void test_AtRequestCommandMaker0WaitsSuccess() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with command maker function and 0 waits auto at_request = at::MakeRequest( @@ -322,8 +333,9 @@ void test_AtRequestCommandMaker0WaitsSuccess() { } void test_AtRequestCommandMaker0WaitsError() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with command maker function and 0 waits auto at_request = at::MakeRequest( @@ -339,8 +351,9 @@ void test_AtRequestCommandMaker0WaitsError() { } void test_AtRequestCommandMaker2WaitsSuccess() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with command maker and 2 waits auto at_request = at::MakeRequest( @@ -372,8 +385,9 @@ void test_AtRequestCommandMaker2WaitsSuccess() { } void test_AtRequestWithWaitHandler() { - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext context{}; + tests::MockSerialPort mock_serial{context}; + auto at_support = AtSupport{context, mock_serial}; // Create AtRequest with command maker and 2 waits auto at_request = at::MakeRequest( diff --git a/tests/test-serial-port/test-at-stages.cpp b/tests/test-serial-port/test-at-stages.cpp index 069226d43..90b4c7fe6 100644 --- a/tests/test-serial-port/test-at-stages.cpp +++ b/tests/test-serial-port/test-at-stages.cpp @@ -18,37 +18,19 @@ #include "aether/actions/actions_queue.h" #include "aether/ae_context.h" -#include "aether/clock.h" #include "aether/serial_ports/at_support/at_request.h" #include "aether/serial_ports/at_support/at_stage.h" #include "aether/serial_ports/at_support/at_support.h" #include "tests/test-serial-port/mock-serial-port.h" +#include "tests/test-serial-port/mock-test-context.h" namespace ae::test_at_stages { -struct TestContext { - auto ToAeContext() const { - static constexpr AeCtxTable vtable = { - .aether_getter = nullptr, - .scheduler_getter = [](void* obj) -> TaskScheduler& { - return static_cast(obj)->scheduler_; - }, - }; - return AeCtx{ - .obj = const_cast(this), // NOLINT - .vtable = &vtable, - }; - } - - auto Update(TimePoint tp = Now()) { return scheduler_.Update(tp); } - - TaskScheduler scheduler_; -}; - void test_RunAtStage() { - TestContext ctx; - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext ctx; + AeContext ae_context{ctx}; + tests::MockSerialPort mock_serial{ae_context}; + auto at_support = AtSupport{ae_context, mock_serial}; ActionsQueue queue; @@ -57,18 +39,20 @@ void test_RunAtStage() { std::copy(std::begin(data), std::end(data), std::back_inserter(received)); }); - queue.Push(at::Stage( - ctx, [&]() { return at::MakeRequest(ex::just(), at_support, "AT"); })); + queue.Push(at::Stage(ae_context, [&]() { + return at::MakeRequest(ex::just(), at_support, "AT"); + })); - ctx.Update(); + ctx.scheduler().Update(); TEST_ASSERT_EQUAL_STRING_LEN("AT\r\n", received.data(), 5); } void test_RunMultipleStages() { - TestContext ctx; - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext ctx; + AeContext ae_context{ctx}; + tests::MockSerialPort mock_serial{ae_context}; + auto at_support = AtSupport{ae_context, mock_serial}; ActionsQueue queue; @@ -78,14 +62,17 @@ void test_RunMultipleStages() { std::copy(std::begin(data), std::end(data), std::back_inserter(r)); }); - queue.Push(at::Stage( - ctx, [&]() { return at::MakeRequest(ex::just(), at_support, "AT"); })); - queue.Push(at::Stage( - ctx, [&]() { return at::MakeRequest(ex::just(), at_support, "MT"); })); - queue.Push(at::Stage( - ctx, [&]() { return at::MakeRequest(ex::just(), at_support, "UT"); })); + queue.Push(at::Stage(ae_context, [&]() { + return at::MakeRequest(ex::just(), at_support, "AT"); + })); + queue.Push(at::Stage(ae_context, [&]() { + return at::MakeRequest(ex::just(), at_support, "MT"); + })); + queue.Push(at::Stage(ae_context, [&]() { + return at::MakeRequest(ex::just(), at_support, "UT"); + })); - ctx.Update(); + ctx.scheduler().Update(); TEST_ASSERT_EQUAL_STRING_LEN("AT\r\n", received[0].data(), 5); TEST_ASSERT_EQUAL_STRING_LEN("MT\r\n", received[1].data(), 5); @@ -93,9 +80,10 @@ void test_RunMultipleStages() { } void test_RunAtWithWaitStage() { - TestContext ctx; - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext ctx; + AeContext ae_context{ctx}; + tests::MockSerialPort mock_serial{ae_context}; + auto at_support = AtSupport{ae_context, mock_serial}; ActionsQueue queue; @@ -104,12 +92,12 @@ void test_RunAtWithWaitStage() { }); bool executed = false; - queue.Push(at::Stage(ctx, [&]() { + queue.Push(at::Stage(ae_context, [&]() { return ex::just() | at::MakeRequest(at_support, "AT", at::Wait{"OK"}) | ex::then([&]() noexcept { executed = true; }); })); - ctx.Update(); + ctx.scheduler().Update(); // waiting for OK TEST_ASSERT_FALSE(executed); @@ -122,9 +110,10 @@ void test_RunAtWithWaitStage() { } void test_RunMultipleStagesWithWait() { - TestContext ctx; - tests::MockSerialPort mock_serial{}; - auto at_support = AtSupport{mock_serial}; + tests::TestContext ctx; + AeContext ae_context{ctx}; + tests::MockSerialPort mock_serial{ae_context}; + auto at_support = AtSupport{ae_context, mock_serial}; ActionsQueue queue; @@ -135,17 +124,17 @@ void test_RunMultipleStagesWithWait() { bool executed1 = false; bool pre_executed2 = false; bool post_executed2 = false; - queue.Push(at::Stage(ctx, [&]() { + queue.Push(at::Stage(ae_context, [&]() { return ex::just() | at::MakeRequest(at_support, "AT", at::Wait{"OK"}) | ex::then([&]() noexcept { executed1 = true; }); })); - queue.Push(at::Stage(ctx, [&]() { + queue.Push(at::Stage(ae_context, [&]() { return ex::just() | ex::then([&]() noexcept { pre_executed2 = true; }) | at::MakeRequest(at_support, "AT", at::Wait{"OK"}) | ex::then([&]() noexcept { post_executed2 = true; }); })); - ctx.Update(); + ctx.scheduler().Update(); // waiting for OK TEST_ASSERT_FALSE(executed1); TEST_ASSERT_FALSE(pre_executed2); diff --git a/tests/test-server-connection/test_cloud_persistence.cpp b/tests/test-server-connection/test_cloud_persistence.cpp index f1b33f8d7..c573741a6 100644 --- a/tests/test-server-connection/test_cloud_persistence.cpp +++ b/tests/test-server-connection/test_cloud_persistence.cpp @@ -36,16 +36,24 @@ namespace ae { namespace test_cloud_persistence { -struct TestContext { - AeCtx ToAeContext() const { - static constexpr auto table = - AeCtxTable{nullptr, [](void* obj) -> TaskScheduler& { - return static_cast(obj)->sched; - }}; - return AeCtx{const_cast(this), &table}; // NOLINT +struct TestContext : public Env { + template + decltype(auto) Update(A&&... a) { + return sched.Update(std::forward(a)...); + } + + void* find_component(EnvId id) noexcept override { + if (id == EnvTypeId::value) { + return &sched; + } + if (id == EnvTypeId::value) { + return &event_system; + } + return nullptr; } TaskScheduler sched; + EventSystem event_system; }; class CountingNullFactory final : public IServerConnectionFactory { @@ -62,7 +70,7 @@ struct CloudFixture { CloudFixture(std::unique_ptr factory, IServerConnectionFactory* raw) : ae_ctx{ctx}, - domain{storage}, + domain{storage, &ctx}, registry{AdapterRegistry::ptr::Create(CreateWith{domain})}, server{Server::ptr::Create(CreateWith{domain}, ServerId{7}, std::vector{}, registry)}, @@ -85,8 +93,9 @@ struct CloudFixture { }; void test_CloudSetServersReplacesEntries() { + TestContext ctx; RamDomainStorage storage; - Domain domain{storage}; + Domain domain{storage, &ctx}; auto registry = AdapterRegistry::ptr::Create(CreateWith{domain}); auto first = Server::ptr::Create(CreateWith{domain}, ServerId{7}, std::vector{}, registry); @@ -181,8 +190,9 @@ void test_CloudServerConnectionServerReferencesCloudMapEntry() { } void test_CloudServerConnectionPriorityRoundTripsAndRestoresSelectionOrder() { + TestContext ctx; RamDomainStorage storage; - Domain domain{storage}; + Domain domain{storage, &ctx}; auto registry = AdapterRegistry::ptr::Create(CreateWith{domain}); auto first = Server::ptr::Create(CreateWith{domain}, ServerId{10}, std::vector{}, registry); diff --git a/tests/test-server-connection/test_cloud_quarantine_loop.cpp b/tests/test-server-connection/test_cloud_quarantine_loop.cpp index 315fab312..fcb9ce173 100644 --- a/tests/test-server-connection/test_cloud_quarantine_loop.cpp +++ b/tests/test-server-connection/test_cloud_quarantine_loop.cpp @@ -31,6 +31,7 @@ #include "aether/cloud.h" #include "aether/cloud_connections/cloud_server_connections.h" #include "aether/config.h" +#include "aether/env.h" #include "aether/server.h" #include "aether/server_connections/client_server_connection.h" #include "aether/server_connections/iserver_connection_factory.h" @@ -67,22 +68,12 @@ struct CloudServerConnectionsTestAccess { }; namespace test_cloud_quarantine_loop { -struct TestContext { - AeCtx ToAeContext() const { - static constexpr auto table = - AeCtxTable{nullptr, [](void* obj) -> TaskScheduler& { - return static_cast(obj)->sched; - }}; - return AeCtx{const_cast(this), &table}; // NOLINT - } - +struct TestContext : public AeEnv { void PumpAt(std::chrono::system_clock::time_point now, int rounds = 8) { for (int i = 0; i < rounds; ++i) { - (void)sched.Update(now); + scheduler().Update(now); } } - - TaskScheduler sched; }; class CountingNullFactory final : public IServerConnectionFactory { @@ -111,8 +102,8 @@ struct CloudFixture { IServerConnectionFactory* raw, std::vector additional_server_ids = {}, std::size_t max_connections = 1) - : ae_ctx{ctx}, - domain{storage}, + : ae_ctx{ctx}, + domain{storage, &ctx}, registry{AdapterRegistry::ptr::Create(CreateWith{domain})}, server{Server::ptr::Create(CreateWith{domain}, ServerId{7}, std::vector{}, registry)}, diff --git a/tests/test-server-connection/test_registration_root_server_select.cpp b/tests/test-server-connection/test_registration_root_server_select.cpp index d30aee28b..482d18a3f 100644 --- a/tests/test-server-connection/test_registration_root_server_select.cpp +++ b/tests/test-server-connection/test_registration_root_server_select.cpp @@ -49,22 +49,30 @@ struct RootServerSelectStreamTestAccess { namespace test_registration_root_server_select { -struct TestContext { - AeCtx ToAeContext() const { - static constexpr auto table = - AeCtxTable{nullptr, [](void* obj) -> TaskScheduler& { - return static_cast(obj)->scheduler; - }}; - return AeCtx{const_cast(this), &table}; // NOLINT +struct TestContext : public Env { + template + decltype(auto) Update(A&&... a) { + return sched.Update(std::forward(a)...); } - TaskScheduler scheduler; + void* find_component(EnvId id) noexcept override { + if (id == EnvTypeId::value) { + return &sched; + } + if (id == EnvTypeId::value) { + return &event_system; + } + return nullptr; + } + + TaskScheduler sched; + EventSystem event_system; }; struct Fixture { Fixture() : ae_context{context}, - domain{storage}, + domain{storage, &context}, aether{Aether::ptr::Create(CreateWith{domain})}, registry{AdapterRegistry::ptr::Create(CreateWith{domain})}, cloud{RegistrationCloud::ptr::Create(CreateWith{domain}, aether)} {} diff --git a/tests/test-server-connection/test_server_connection_recovery.cpp b/tests/test-server-connection/test_server_connection_recovery.cpp index 57d92e775..d4e9c44d5 100644 --- a/tests/test-server-connection/test_server_connection_recovery.cpp +++ b/tests/test-server-connection/test_server_connection_recovery.cpp @@ -68,31 +68,22 @@ struct ServerConnectionTestAccess { namespace test_server_connection_recovery { -struct TestContext { - AeCtx ToAeContext() const { - static constexpr auto table = - AeCtxTable{nullptr, [](void* obj) -> TaskScheduler& { - return static_cast(obj)->sched; - }}; - return AeCtx{const_cast(this), &table}; // NOLINT - } - +struct TestContext : public AeEnv { void Pump(int rounds = 64) { auto now = std::chrono::system_clock::now(); for (int i = 0; i < rounds; ++i) { - now = sched.Update(now); + now = scheduler().Update(now); if (now == std::chrono::system_clock::time_point::max()) { now = std::chrono::system_clock::now(); } } } - - TaskScheduler sched; }; class ImmediateWriteAction : public WriteAction { public: - explicit ImmediateWriteAction(AeContext const& context) { + explicit ImmediateWriteAction(AeContext const& context) + : WriteAction{context} { context.scheduler().Task( [&]() { WriteAction::SetStatus(Status::kSuccess); }); } @@ -102,18 +93,18 @@ class LinkedMockStream final : public ByteIStream { public: explicit LinkedMockStream(AeContext const& context) : context_{context}, - stream_info_{512, 1024, true, LinkState::kLinked, true} {} + stream_info_{512, 1024, true, LinkState::kLinked, true}, + out_data_event_{context_}, + stream_update_event_{context_} {} WriteAction& Write(DataBuffer&& /*data*/) override { return last_action_.emplace(context_); } - StreamUpdateEvent::Subscriber stream_update_event() override { - return EventSubscriber{stream_update_event_}; + StreamUpdateEvent const& stream_update_event() override { + return stream_update_event_; } StreamInfo stream_info() const override { return stream_info_; } - OutDataEvent::Subscriber out_data_event() override { - return EventSubscriber{out_data_event_}; - } + OutDataEvent const& out_data_event() override { return out_data_event_; } void Restream() override {} private: @@ -206,7 +197,7 @@ void test_SingleChannelBuildFailureReachesLinkError() { TestContext ctx; AeContext ae_ctx{ctx}; RamDomainStorage storage; - Domain domain{storage}; + Domain domain{storage, &ctx}; int builds = 0; FakeBuildPolicy policy{FakeBuildPolicy::Mode::kAlwaysFail, &builds, &ae_ctx}; @@ -252,7 +243,7 @@ void test_ResultCallbackSeesFinishedAction() { TestContext ctx; AeContext ae_ctx{ctx}; RamDomainStorage storage; - Domain domain{storage}; + Domain domain{storage, &ctx}; int builds = 0; FakeBuildPolicy policy{FakeBuildPolicy::Mode::kAlwaysFail, &builds, &ae_ctx}; @@ -272,7 +263,7 @@ void test_SecondChannelSucceedsAfterFirstFailure() { TestContext ctx; AeContext ae_ctx{ctx}; RamDomainStorage storage; - Domain domain{storage}; + Domain domain{storage, &ctx}; int builds = 0; FakeBuildPolicy policy{FakeBuildPolicy::Mode::kFailThenSucceed, &builds, @@ -298,7 +289,7 @@ void test_FullPoolStillReachesLinkError() { TestContext ctx; AeContext ae_ctx{ctx}; RamDomainStorage storage; - Domain domain{storage}; + Domain domain{storage, &ctx}; // Leave a few slots for transport timeout plumbing; keep the rest occupied // with active delayed tasks so deferred reselect cannot allocate. @@ -306,7 +297,7 @@ void test_FullPoolStillReachesLinkError() { AE_TASK_MAX_COUNT > 8 ? AE_TASK_MAX_COUNT - 8 : AE_TASK_MAX_COUNT / 2; std::array blockers{}; for (auto& sub : blockers) { - sub = ctx.sched.DelayedTask([]() {}, std::chrono::seconds{60}); + sub = ctx.scheduler().DelayedTask([]() {}, std::chrono::seconds{60}); TEST_ASSERT_TRUE_MESSAGE(static_cast(sub), "expected to fill scheduler pool"); } @@ -324,7 +315,7 @@ void test_FullPoolStillReachesLinkError() { // Exhaust remaining slots so DeferSelectChannel / DeferServerError fail. std::vector extra; for (;;) { - auto sub = ctx.sched.DelayedTask([]() {}, std::chrono::seconds{60}); + auto sub = ctx.scheduler().DelayedTask([]() {}, std::chrono::seconds{60}); if (!sub) { break; } @@ -347,7 +338,7 @@ void test_NoBusyLoopOnPermanentFailure() { TestContext ctx; AeContext ae_ctx{ctx}; RamDomainStorage storage; - Domain domain{storage}; + Domain domain{storage, &ctx}; int builds = 0; FakeBuildPolicy policy{FakeBuildPolicy::Mode::kAlwaysFail, &builds, &ae_ctx}; diff --git a/tests/test-stream/CMakeLists.txt b/tests/test-stream/CMakeLists.txt index 82498b377..489d50afc 100644 --- a/tests/test-stream/CMakeLists.txt +++ b/tests/test-stream/CMakeLists.txt @@ -16,7 +16,6 @@ cmake_minimum_required( VERSION 3.18 ) list(APPEND test_srcs main.cpp - templated-streams/test_templated_streams.cpp test-tied-gates.cpp ) diff --git a/tests/test-stream/main.cpp b/tests/test-stream/main.cpp index 0d4a41510..538b18650 100644 --- a/tests/test-stream/main.cpp +++ b/tests/test-stream/main.cpp @@ -19,12 +19,10 @@ void setUp() {} void tearDown() {} -extern int test_templated_streams(); extern int test_tied_gates(); int main() { int res = 0; - res += test_templated_streams(); res += test_tied_gates(); return res; } diff --git a/tests/test-stream/mock_read_stream.h b/tests/test-stream/mock_read_stream.h index 320e02fe6..9bb5d9412 100644 --- a/tests/test-stream/mock_read_stream.h +++ b/tests/test-stream/mock_read_stream.h @@ -22,6 +22,8 @@ namespace ae { class MockReadStream final : public ByteStream { public: + explicit MockReadStream(EventContext auto const& context) + : ByteStream{context} {} }; } // namespace ae diff --git a/tests/test-stream/mock_write_stream.h b/tests/test-stream/mock_write_stream.h index 8fed6d54a..4fedf9e2b 100644 --- a/tests/test-stream/mock_write_stream.h +++ b/tests/test-stream/mock_write_stream.h @@ -18,8 +18,6 @@ #define TESTS_TEST_STREAM_MOCK_WRITE_STREAM_H_ #include -#include - #include "aether/ae_context.h" #include "aether/events/events.h" #include "aether/stream_api/istream.h" @@ -27,7 +25,8 @@ namespace ae { class MockStreamWriteAction : public WriteAction { public: - explicit MockStreamWriteAction(AeContext const& context) { + explicit MockStreamWriteAction(AeContext const& context) + : WriteAction{context} { context.scheduler().Task( [&]() { WriteAction::SetStatus(Status::kSuccess); }); } @@ -38,30 +37,38 @@ class MockStreamWriteAction : public WriteAction { class MockWriteStream : public ByteStream { public: explicit MockWriteStream(AeContext const& context, std::size_t max_data_size) - : context_{context}, - stream_info_{max_data_size, max_data_size, {}, {}, {}} {} + : ByteStream{context}, + context_{context}, + stream_info_{max_data_size, max_data_size, {}, {}, {}}, + on_write_{context_}, + out_data_event_{context_}, + stream_update_event_{context_} {} WriteAction& Write(DataBuffer&& buffer) override { - on_write_.Emit(std::move(buffer)); + on_write_.Emit(buffer); return last_action_.emplace(context_); } StreamInfo stream_info() const override { return stream_info_; } - StreamUpdateEvent::Subscriber stream_update_event() override { - return EventSubscriber{stream_update_event_}; - } - OutDataEvent::Subscriber out_data_event() override { - return EventSubscriber{out_data_event_}; + StreamUpdateEvent const& stream_update_event() override { + return stream_update_event_; } + OutDataEvent const& out_data_event() override { return out_data_event_; } - EventSubscriber on_write_event() { return on_write_; } + /** + * \brief Written data transferred to a handler. + * + * This event must have exactly one subscriber. This is a usage contract and + * is not enforced at runtime. + */ + Event const& on_write_event() { return on_write_; } void WriteOut(DataBuffer const& buffer) { out_data_event_.Emit(buffer); } private: AeContext context_; StreamInfo stream_info_; - Event on_write_; + Event on_write_; OutDataEvent out_data_event_; StreamUpdateEvent stream_update_event_; std::optional last_action_; diff --git a/tests/test-stream/templated-streams/test_templated_streams.cpp b/tests/test-stream/templated-streams/test_templated_streams.cpp deleted file mode 100644 index 6eb251aec..000000000 --- a/tests/test-stream/templated-streams/test_templated_streams.cpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2024 Aethernet Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#include "aether/stream_api/gates_stream.h" - -#include "tests/test-stream/to_data_buffer.h" -#include "tests/test-safe-stream/stream-test-ctx.h" -#include "tests/test-stream/mock_write_stream.h" - -namespace ae::tes_templated_streams { - -class IntToBytesGate { - public: - DataBuffer WriteIn(int in_data) { - return DataBuffer{ - reinterpret_cast(&in_data), - reinterpret_cast(&in_data) + sizeof(in_data)}; - } - - int WriteOut(DataBuffer const& data) { - int value = *reinterpret_cast(data.data()); - return value; - } -}; - -class StringIntGate { - public: - int WriteIn(std::string const& in_data) { return std::stoi(in_data); } -}; - -void test_IntToBytesGate() { - TestContext ctx; - - DataBuffer written_data; - int read_data; - - auto stream = GatesStream{IntToBytesGate{}}; - - auto write_gate = MockWriteStream{ctx, 1000}; - - Tie(stream, write_gate); - - write_gate.on_write_event().Subscribe( - [&](auto data) { written_data = std::move(data); }); - - stream.Write(10); - - stream.out_data_event().Subscribe( - [&](auto const& data) { read_data = data; }); - - TEST_ASSERT_EQUAL(sizeof(int), written_data.size()); - - write_gate.WriteOut(written_data); - - TEST_ASSERT_EQUAL(10, read_data); -} - -void test_StringIntGate() { - TestContext ctx; - - DataBuffer written_data; - int read_data; - - auto write_gate = MockWriteStream{ctx, static_cast(1000)}; - auto _0 = write_gate.on_write_event().Subscribe( - [&](auto data) { written_data = std::move(data); }); - - auto stream = GatesStream{StringIntGate{}, IntToBytesGate{}}; - auto r = TiedWriteOut(ToDataBuffer("const T (&arr)[size]"), StringIntGate{}, - IntToBytesGate{}); - // using gt = decltype(stream)::Base; - // using os = decltype(stream)::OutStream; - // using out = gt::TypeOut; - // using in = gt::TypeIn; - // using os_out = os::TypeOut; - // using os_in = os::TypeIn; - - Tie(stream, write_gate); - - stream.out_data_event().Subscribe( - [&](auto const& data) { read_data = data; }); - - stream.Write("10"); - - TEST_ASSERT_EQUAL(sizeof(int), written_data.size()); - - write_gate.WriteOut(written_data); - - TEST_ASSERT_EQUAL(10, read_data); -} - -} // namespace ae::tes_templated_streams -int test_templated_streams() { - UNITY_BEGIN(); - - RUN_TEST(ae::tes_templated_streams::test_IntToBytesGate); - RUN_TEST(ae::tes_templated_streams::test_StringIntGate); - - return UNITY_END(); -} diff --git a/tests/test-stream/test-tied-gates.cpp b/tests/test-stream/test-tied-gates.cpp index 2e009ec91..48875c155 100644 --- a/tests/test-stream/test-tied-gates.cpp +++ b/tests/test-stream/test-tied-gates.cpp @@ -20,10 +20,9 @@ #include #include "aether/events/events.h" -#include "aether/types/data_buffer.h" #include "aether/stream_api/gate_trait.h" #include "aether/stream_api/tied_gates.h" -#include "aether/stream_api/gates_stream.h" +#include "aether/types/data_buffer.h" #include "tests/test-stream/to_data_buffer.h" @@ -74,23 +73,29 @@ struct RemoveFoxGate { }; struct NotifyThenFoxJumpedGate { + explicit NotifyThenFoxJumpedGate(EventContext auto const& context) + : out_data_event_{context} {} + void WriteOut(DataBuffer data) { out_data_event_.Emit(std::move(data)); } - EventSubscriber out_data_event() { - return EventSubscriber{out_data_event_}; + Event const& out_data_event() { + return out_data_event_; } - Event out_data_event_{}; + Event out_data_event_; }; struct NotifyThenLazyDogWokeUpGate { + explicit NotifyThenLazyDogWokeUpGate(EventContext auto const& context) + : out_data_event_{context} {} + void WriteOut(DataBuffer data) { out_data_event_.Emit(std::move(data)); } - EventSubscriber out_data_event() { - return EventSubscriber{out_data_event_}; + Event const& out_data_event() { + return out_data_event_; } - Event out_data_event_{}; + Event out_data_event_; }; static constexpr char test_data[] = @@ -141,8 +146,16 @@ void test_Overhead() { } void test_SubscribeOutDataEvent() { - auto notify_then_fox_jumped_gate = NotifyThenFoxJumpedGate{}; - auto notify_then_lazy_dog_woke_up_gate = NotifyThenLazyDogWokeUpGate{}; + EventSystem event_system; + struct TiedGatesEventContext { + EventSystem& event_system() const { return event_system_; } + + EventSystem& event_system_; + }; + auto event_context = TiedGatesEventContext{event_system}; + auto notify_then_fox_jumped_gate = NotifyThenFoxJumpedGate{event_context}; + auto notify_then_lazy_dog_woke_up_gate = + NotifyThenLazyDogWokeUpGate{event_context}; auto remove_first_three_gate = RemoveFirstThreeGate{}; auto remove_fox_gate = RemoveFoxGate{}; @@ -162,15 +175,6 @@ void test_SubscribeOutDataEvent() { TEST_ASSERT_EQUAL(data.size() - 3 - 3, received.size()); } -void test_TiedStream() { - auto stream = GatesStream( - AddHelloGate{}, RemoveFoxGate{}, NotifyThenFoxJumpedGate{}, SortGate{}, - NotifyThenLazyDogWokeUpGate{}, RemoveFirstThreeGate{}, AddOneGate{}); - - DataBuffer data{ToDataBuffer(test_data)}; - stream.Write(std::move(data)); -} - } // namespace ae::test_tied_gates int test_tied_gates() { diff --git a/tools/registrator/register_wifi.cpp b/tools/registrator/register_wifi.cpp index e0d14c94b..d47224bac 100644 --- a/tools/registrator/register_wifi.cpp +++ b/tools/registrator/register_wifi.cpp @@ -33,7 +33,7 @@ std::vector RegisterWifiAdapter::access_points() { return ethernet_adapter_->access_points(); } -RegisterWifiAdapter::NewAccessPoint::Subscriber +RegisterWifiAdapter::NewAccessPoint const& RegisterWifiAdapter::new_access_point() { return ethernet_adapter_->new_access_point(); } diff --git a/tools/registrator/register_wifi.h b/tools/registrator/register_wifi.h index c9aedad3e..f2391d165 100644 --- a/tools/registrator/register_wifi.h +++ b/tools/registrator/register_wifi.h @@ -20,8 +20,8 @@ #include "aether/config.h" #if AE_SUPPORT_WIFIS -# include "aether/adapters/parent_wifi.h" # include "aether/adapters/ethernet.h" +# include "aether/adapters/parent_wifi.h" # include "aether/wifi/wifi_driver_types.h" namespace ae::reg { @@ -37,7 +37,7 @@ class RegisterWifiAdapter : public ParentWifiAdapter { AE_OBJECT_REFLECT(AE_MMBR(ethernet_adapter_)) std::vector access_points() override; - NewAccessPoint::Subscriber new_access_point() override; + NewAccessPoint const& new_access_point() override; private: // whose doing all job diff --git a/tools/registrator/registrator_action.cpp b/tools/registrator/registrator_action.cpp index 333e645fd..5bdd7bc41 100644 --- a/tools/registrator/registrator_action.cpp +++ b/tools/registrator/registrator_action.cpp @@ -17,22 +17,26 @@ #include "registrator/registrator_action.h" #include "aether/registration/registration.h" +#include "aether/tele.h" namespace ae::reg { RegistratorAction::RegistratorAction( ae::AeContext const& ae_context, ae::AetherApp& aether_app, std::vector const& client_configs) - : ae_context_{ae_context} { + : Action{ae_context}, + ae_context_{ae_context}, + registered_event_{ae_context_}, + failed_event_{ae_context_} { AE_TELED_INFO("RegistratorAction"); RegisterClients(aether_app.aether(), client_configs); } -RegistratorAction::RegisteredEvent::Subscriber +RegistratorAction::RegisteredEvent const& RegistratorAction::registered_event() { - return EventSubscriber{registered_event_}; + return registered_event_; } -RegistratorAction::FailedEvent::Subscriber RegistratorAction::failed_event() { - return EventSubscriber{failed_event_}; +RegistratorAction::FailedEvent const& RegistratorAction::failed_event() { + return failed_event_; } /** diff --git a/tools/registrator/registrator_action.h b/tools/registrator/registrator_action.h index be17fe8e5..1a5bc6f89 100644 --- a/tools/registrator/registrator_action.h +++ b/tools/registrator/registrator_action.h @@ -24,7 +24,6 @@ #include "aether/aether.h" #include "aether/aether_app.h" #include "aether/events/events.h" -#include "aether/events/multi_subscription.h" #include "registrator/registrator_config.h" @@ -43,8 +42,8 @@ class RegistratorAction : public Action { ae::AeContext const& ae_context, ae::AetherApp& aether_app, std::vector const& client_configs); - RegisteredEvent::Subscriber registered_event(); - FailedEvent::Subscriber failed_event(); + RegisteredEvent const& registered_event(); + FailedEvent const& failed_event(); private: void RegisterClients(ae::Aether::ptr const& aether,