diff --git a/aether/access_points/wifi_access_point.cpp b/aether/access_points/wifi_access_point.cpp index dc3f9cd9..af98e6aa 100644 --- a/aether/access_points/wifi_access_point.cpp +++ b/aether/access_points/wifi_access_point.cpp @@ -118,7 +118,7 @@ ActionPtr WifiAccessPoint::Connect() { assert(driver.has_value()); connect_action_ = ActionPtr{ - *aether_.Load().as(), **driver, wifi_ap_, psp_, base_station_}; + ActionContext{*aether_.Load().as()}, **driver, wifi_ap_, psp_, base_station_}; connect_sub_ = connect_action_->FinishedEvent().Subscribe( [this]() { connect_action_.reset(); }); } diff --git a/aether/actions/action_ptr.h b/aether/actions/action_ptr.h index 7440f437..5d37d1c9 100644 --- a/aether/actions/action_ptr.h +++ b/aether/actions/action_ptr.h @@ -22,6 +22,7 @@ #include #include "aether/common.h" +#include "aether/ae_context.h" #include "aether/actions/action_context.h" namespace ae { @@ -50,6 +51,12 @@ class ActionPtr { action_context.get_registry().PushBack(ptr_); } + template + explicit ActionPtr(AeContext ae_context, TArgs&&... args) + : ptr_{std::make_shared(ae_context, + std::forward(args)...)} { + } + template ))> ActionPtr(ActionPtr const& other) : ptr_(other.ptr_) {} diff --git a/aether/ae_actions/check_access_for_send_message.cpp b/aether/ae_actions/check_access_for_send_message.cpp index bfaf3174..ec81dfac 100644 --- a/aether/ae_actions/check_access_for_send_message.cpp +++ b/aether/ae_actions/check_access_for_send_message.cpp @@ -30,19 +30,17 @@ CheckAccessForSendMessage::CheckAccessForSendMessage( action_context, AuthApiRequest{[this](ApiContext& auth_api, auto*, auto* request) { - auto check_promise = - auth_api->check_access_for_send_message(destination_); wait_check_sub_ = - check_promise->StatusEvent().Subscribe(ActionHandler{ - OnResult{[&]() { - ResponseReceived(); - request->Succeeded(); - }}, - OnError{[&]() { - ErrorReceived(); - request->Failed(); - }}, - }); + auth_api->check_access_for_send_message(destination_) + .Subscribe([&](auto const& res) { + if (res) { + ResponseReceived(); + request->Succeeded(); + } else { + ErrorReceived(); + request->Failed(); + } + }); }}, cloud_connection, request_policy, diff --git a/aether/ae_actions/ping.cpp b/aether/ae_actions/ping.cpp index 7c4ef6d2..e8832c91 100644 --- a/aether/ae_actions/ping.cpp +++ b/aether/ae_actions/ping.cpp @@ -71,7 +71,7 @@ void Ping::SendPing() { auto write_action = client_server_connection_->AuthorizedApiCall( SubApi{[this](ApiContext& auth_api) { - auto pong_promise = auth_api->ping(static_cast( + auto& pong_promise = auth_api->ping(static_cast( std::chrono::duration_cast( ping_interval_) .count())); @@ -85,11 +85,12 @@ void Ping::SendPing() { ping_requests_.push(PingRequest{ current_time, current_time + expected_ping_time, - pong_promise->request_id(), + pong_promise.request_id(), }); // Wait for response - wait_responses_.Push(pong_promise->StatusEvent().Subscribe(OnResult{ - [&](auto const& promise) { PingResponse(promise.request_id()); }})); + wait_responses_.Push( + pong_promise.Subscribe([&, req_id{pong_promise.request_id()}]( + auto&&) { PingResponse(req_id); })); }}); write_subscription_ = write_action->StatusEvent().Subscribe(OnError{[this]() { diff --git a/aether/ae_actions/select_client.cpp b/aether/ae_actions/select_client.cpp index 591adcd3..3c6fae60 100644 --- a/aether/ae_actions/select_client.cpp +++ b/aether/ae_actions/select_client.cpp @@ -37,20 +37,21 @@ SelectClientAction::SelectClientAction(ActionContext action_context, #if AE_SUPPORT_REGISTRATION // only if registration is supported SelectClientAction::SelectClientAction(ActionContext action_context, Aether& aether, - ActionPtr registration, + Registration& registration, std::string client_id) : Action{action_context}, state_{State::kWaitRegistration}, - client_id_{std::move(client_id)}, - registration_{std::move(registration)} { + client_id_{std::move(client_id)} { AE_TELED_DEBUG("Waiting for client registration"); - registration_sub_ = registration_->StatusEvent().Subscribe(ActionHandler{ - OnResult{[this, aeth{&aether}](auto& action) { - client_ = aeth->CreateClient(action.client_config(), client_id_); - state_ = State::kClientRegistered; - }}, - OnError{[this]() { state_ = State::kClientRegistrationError; }}, - }); + registration_sub_ = registration.registration().Subscribe( + [this, aeth{&aether}](auto const& res) { + if (res) { + client_ = aeth->CreateClient(res.value(), client_id_); + state_ = State::kClientRegistered; + } else { + state_ = State::kClientRegistrationError; + } + }); state_.changed_event().Subscribe([this](auto) { Action::Trigger(); }); } diff --git a/aether/ae_actions/select_client.h b/aether/ae_actions/select_client.h index 07138637..fb656b7c 100644 --- a/aether/ae_actions/select_client.h +++ b/aether/ae_actions/select_client.h @@ -51,8 +51,7 @@ class SelectClientAction final : public Action { * \brief Wait for client registration or error. */ SelectClientAction(ActionContext action_context, Aether& aether, - ActionPtr registration, - std::string client_id); + Registration& registration, std::string client_id); #endif /** diff --git a/aether/ae_actions/time_sync.cpp b/aether/ae_actions/time_sync.cpp index 15fdb0cc..ffaa148e 100644 --- a/aether/ae_actions/time_sync.cpp +++ b/aether/ae_actions/time_sync.cpp @@ -143,16 +143,16 @@ class TimeSyncRequest : public Action { auto write_action = cc->LoginApiCall(SubApi{[this](auto& api) { AE_TELED_DEBUG("Make time sync request"); - ApiPromisePtr promise = api->get_time_utc(); - response_sub_ = promise->StatusEvent().Subscribe( - OnResult{[this, request_time{Now()}](auto& p) { + response_sub_ = api->get_time_utc().Subscribe( + [this, request_time{Now()}](auto const& p) { + assert(p.IsOk()); HandleResponse( std::chrono::milliseconds{ static_cast(p.value())}, request_time, Now()); // time synced state_ = State::kResult; - }}); + }); }}); write_action_sub_ = write_action->StatusEvent().Subscribe(OnError{[this]() { diff --git a/aether/aether.cpp b/aether/aether.cpp index dd9dba9e..b8d628c8 100644 --- a/aether/aether.cpp +++ b/aether/aether.cpp @@ -125,8 +125,7 @@ ActionPtr Aether::SelectClient( } // register new client #if AE_SUPPORT_REGISTRATION - auto registration = RegisterClient(parent_uid); - return MakeSelectClient(std::move(registration), client_id); + return MakeSelectClient(RegisterClient(client_id, parent_uid), client_id); #else return MakeSelectClient(); #endif @@ -180,22 +179,21 @@ ActionPtr Aether::MakeSelectClient( #if AE_SUPPORT_REGISTRATION ActionPtr Aether::MakeSelectClient( - ActionPtr registration, std::string const& client_id) { - auto select_action = ActionPtr{ - *action_processor, *this, std::move(registration), client_id}; + Registration& registration, std::string const& client_id) { + auto select_action = ActionPtr{*action_processor, *this, + registration, client_id}; select_client_actions_[client_id] = select_action; return select_action; } -ActionPtr Aether::RegisterClient(Uid parent_uid) { +Registration& Aether::RegisterClient(std::string const& client_id, + Uid parent_uid) { auto reg_cloud = registration_cloud.Load(); assert(reg_cloud && "Registration cloud not loaded"); - // registration new client is long termed process - // after registration done, add it to clients list - // user also can get new client after - return ActionPtr(*action_processor, *this, reg_cloud, - parent_uid); + auto& reg = registrations_[client_id] = + std::make_unique(AeContext{*this}, reg_cloud, parent_uid); + return *reg; } #endif diff --git a/aether/aether.h b/aether/aether.h index abe93f1e..68704d9f 100644 --- a/aether/aether.h +++ b/aether/aether.h @@ -120,13 +120,14 @@ class Aether : public Obj { ActionPtr MakeSelectClient( ObjPtr const& client) const; #if AE_SUPPORT_REGISTRATION - ActionPtr MakeSelectClient( - ActionPtr registration, std::string const& client_id); + ActionPtr MakeSelectClient(Registration& registration, + std::string const& client_id); public: - ActionPtr RegisterClient(Uid parent_uid); + Registration& RegisterClient(std::string const& client_id, Uid parent_uid); private: + std::map> registrations_; #endif void MakeTimeSyncAction(ObjPtr const& client); diff --git a/aether/api_protocol/api_method.h b/aether/api_protocol/api_method.h index e60c94bc..6cf5f194 100644 --- a/aether/api_protocol/api_method.h +++ b/aether/api_protocol/api_method.h @@ -17,12 +17,11 @@ #ifndef AETHER_API_PROTOCOL_API_METHOD_H_ #define AETHER_API_PROTOCOL_API_METHOD_H_ -#include "aether/actions/action_context.h" #include "aether/api_protocol/api_message.h" #include "aether/api_protocol/api_context.h" #include "aether/api_protocol/api_pack_parser.h" #include "aether/api_protocol/protocol_context.h" -#include "aether/api_protocol/api_promise_action.h" +#include "aether/api_protocol/api_promise.h" namespace ae { class DefaultArgProc { @@ -74,39 +73,40 @@ struct Method { * return PromiseView for waiting the result or error. */ template -struct Method(Args...), ArgProc> { - explicit Method(ProtocolContext& protocol_context, - ActionContext action_context, ArgProc arg_proc = {}) - : protocol_context_{&protocol_context}, - action_context_{std::move(action_context)}, - arg_proc_{std::move(arg_proc)} {} +struct Method(Args...), ArgProc> { + explicit Method(ProtocolContext& protocol_context, ArgProc arg_proc = {}) + : protocol_context_{&protocol_context}, arg_proc_{std::move(arg_proc)} {} - ApiPromisePtr operator()(Args... args) { + ApiPromise& operator()(Args... args) { auto request_id = RequestId::GenRequestId(); auto* packet_stack = protocol_context_->packet_stack(); assert(packet_stack); packet_stack->Push(*this, arg_proc_(request_id, std::forward(args)...)); - - auto promise_ptr = ApiPromisePtr{action_context_, request_id}; + api_promise_.emplace(request_id); if constexpr (!std::is_same_v) { - protocol_context_->AddSendResultCallback( - request_id, - [p_ptr{promise_ptr}, context{protocol_context_}]() mutable { - p_ptr->SetValue(context->parser()->template Extract()); - }); + protocol_context_->AddSendResultCallback(request_id, [this]() { + assert(api_promise_); + api_promise_->SetValue( + protocol_context_->parser()->template Extract()); + api_promise_.reset(); + }); } else { - protocol_context_->AddSendResultCallback( - request_id, [p_ptr{promise_ptr}]() mutable { p_ptr->SetValue(); }); + protocol_context_->AddSendResultCallback(request_id, [this]() { + assert(api_promise_); + api_promise_->SetValue(); + api_promise_.reset(); + }); } protocol_context_->AddSendErrorCallback( - request_id, [p_ptr{promise_ptr}](auto, auto) mutable { - assert(p_ptr); - p_ptr->Reject(); + request_id, [this](auto, std::uint32_t err) { + assert(api_promise_); + api_promise_->SetError(std::move(err)); + api_promise_.reset(); }); - return promise_ptr; + return *api_promise_; } template @@ -116,7 +116,7 @@ struct Method(Args...), ArgProc> { private: ProtocolContext* protocol_context_; - ActionContext action_context_; + std::optional> api_promise_; ArgProc arg_proc_; }; diff --git a/aether/api_protocol/api_promise.h b/aether/api_protocol/api_promise.h new file mode 100644 index 00000000..bf672302 --- /dev/null +++ b/aether/api_protocol/api_promise.h @@ -0,0 +1,92 @@ +/* + * 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 "aether/types/result.h" +#include "aether/events/events.h" +#include "aether/api_protocol/request_id.h" + +namespace ae { +namespace api_promise_action_internal { +struct Empty {}; +}; // namespace api_promise_action_internal + +template +class ApiPromise { + public: + using value_type = Value; + using error_type = Error; + using result_type = Result; + + constexpr explicit ApiPromise(RequestId req_id) noexcept + : request_id_{req_id} {} + + constexpr void SetValue(value_type&& val) noexcept { + event_.Emit(result_type{Ok{std::move(val)}}); + } + + constexpr void SetError(error_type&& err) noexcept { + event_.Emit(result_type{Error{std::move(err)}}); + } + + constexpr RequestId request_id() const { return request_id_; } + + template + constexpr decltype(auto) Subscribe(Fn&& fn) noexcept { + return EventSubscriber{event_}.Subscribe(std::forward(fn)); + } + + private: + RequestId request_id_; + Event event_; +}; + +template +class ApiPromise { + public: + using value_type = api_promise_action_internal::Empty; + using error_type = Error; + using result_type = Result; + + constexpr explicit ApiPromise(RequestId req_id) noexcept + : request_id_{req_id} {} + + constexpr void SetValue() noexcept { + event_.Emit(result_type{api_promise_action_internal::Empty{}}); + } + + constexpr void SetError(error_type&& err) noexcept { + event_.Emit(result_type{Error{std::move(err)}}); + } + + constexpr RequestId request_id() const { return request_id_; } + + template + [[nodiscard]] constexpr decltype(auto) Subscribe(Fn&& fn) noexcept { + return EventSubscriber{event_}.Subscribe(std::forward(fn)); + } + + private: + RequestId request_id_; + Event event_; +}; + +} // namespace ae +#endif // AETHER_API_PROTOCOL_API_PROMISE_H_ diff --git a/aether/api_protocol/api_promise_action.h b/aether/api_protocol/api_promise_action.h deleted file mode 100644 index fafcd0ef..00000000 --- a/aether/api_protocol/api_promise_action.h +++ /dev/null @@ -1,84 +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_ACTION_H_ -#define AETHER_API_PROTOCOL_API_PROMISE_ACTION_H_ - -#include "aether/actions/action_ptr.h" -#include "aether/actions/promise_action.h" -#include "aether/actions/action_context.h" - -#include "aether/api_protocol/request_id.h" - -namespace ae { -template -class ApiPromiseAction : public Action> { - public: - using BaseAction = Action>; - - ApiPromiseAction(ActionContext action_context, RequestId req_id) - : BaseAction{action_context}, - promise_{action_context}, - request_id_{req_id} {} - - AE_CLASS_MOVE_ONLY(ApiPromiseAction); - - UpdateStatus Update() { return promise_.Update(); } - - RequestId const& request_id() const { return request_id_; } - - void SetValue(TValue&& value) { promise_.SetValue(std::move(value)); } - - void Reject() { promise_.Reject(); } - - TValue& value() { return promise_.value(); } - - TValue const& value() const { return promise_.value(); } - - private: - PromiseAction promise_; - RequestId request_id_; -}; - -template <> -class ApiPromiseAction : public Action> { - public: - using BaseAction = Action>; - - ApiPromiseAction(ActionContext action_context, RequestId req_id) - : BaseAction{action_context}, - promise_{action_context}, - request_id_{req_id} {} - - AE_CLASS_MOVE_ONLY(ApiPromiseAction); - - UpdateStatus Update() { return promise_.Update(); } - - RequestId const& request_id() const { return request_id_; } - - void SetValue() { promise_.SetValue(); } - - void Reject() { promise_.Reject(); } - - private: - PromiseAction promise_; - RequestId request_id_; -}; - -template -using ApiPromisePtr = ActionPtr>; -} // namespace ae -#endif // AETHER_API_PROTOCOL_API_PROMISE_ACTION_H_ diff --git a/aether/api_protocol/api_protocol.h b/aether/api_protocol/api_protocol.h index 434b3fed..55a21e83 100644 --- a/aether/api_protocol/api_protocol.h +++ b/aether/api_protocol/api_protocol.h @@ -27,7 +27,7 @@ #include "aether/api_protocol/api_class_impl.h" #include "aether/api_protocol/api_pack_parser.h" #include "aether/api_protocol/protocol_context.h" -#include "aether/api_protocol/api_promise_action.h" +#include "aether/api_protocol/api_promise.h" #include "aether/api_protocol/return_result_api.h" // IWYU pragma: end_exports diff --git a/aether/api_protocol/make_api_call_sender.h b/aether/api_protocol/make_api_call_sender.h new file mode 100644 index 00000000..1d8b4e1d --- /dev/null +++ b/aether/api_protocol/make_api_call_sender.h @@ -0,0 +1,182 @@ +/* + * 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_MAKE_API_CALL_SENDER_H_ +#define AETHER_API_PROTOCOL_MAKE_API_CALL_SENDER_H_ + +#include +#include + +#include "aether/actions/action.h" +#include "aether/executors/executors.h" +#include "aether/api_protocol/api_context.h" +#include "aether/events/event_subscription.h" + +namespace ae { +struct WriteFailed {}; + +namespace make_api_call_internal { +template +struct OpBase { + constexpr explicit OpBase(R&& r) noexcept : recv{std::move(r)} {} + + virtual void Reset() noexcept {} + virtual bool is_reset() const noexcept { return false; } + + R recv; + + protected: + ~OpBase() = default; +}; + +template +struct Receiver { + using receiver_concept = ex::receiver_t; + + 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); + } + + OpBase* op; +}; + +template +class Operation final : public OpBase { + public: + using receiver = Receiver; + + constexpr Operation(R&& recv, Api* api, Stream* stream, Fn&& fn) noexcept + : OpBase{std::move(recv)}, + api_{api}, + 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()); + } + // 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->StatusEvent().Subscribe(ActionHandler{ + OnResult{[]() noexcept { + /* do nothing */ + }}, + OnError{[&]() noexcept { + Reset(); + ex::set_error(std::move(OpBase::recv), WriteFailed{}); + }}, + OnStop{[&]() noexcept { + Reset(); + ex::set_stopped(std::move(OpBase::recv)); + }}, + }); + } + + void Reset() noexcept override { sub_.Reset(); } + bool is_reset() const noexcept override { return !!sub_; } + + private: + Api* api_; + Stream* stream_; + Fn fn_; + Subscription sub_; + std::optional recv_; +}; + +template +class Sender { + public: + using sender_concept = ex::sender_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{}); + } + + constexpr Sender(Api& api, Stream& stream, Fn&& fn) noexcept + : api_{&api}, stream_{&stream}, fn_{std::move(fn)} {} + + constexpr auto connect(ex::receiver auto&& recv) noexcept { + return Operation>{ + std::forward(recv), + api_, + stream_, + std::move(fn_), + }; + } + + private: + Api* api_; + 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), + }; + } +}; + +} // namespace make_api_call_internal + +template +static constexpr inline auto make_api_call = + make_api_call_internal::MakeApiCall{}; + +} // namespace ae + +#endif // AETHER_API_PROTOCOL_MAKE_API_CALL_SENDER_H_ diff --git a/aether/client.cpp b/aether/client.cpp index 7327d436..7ca66229 100644 --- a/aether/client.cpp +++ b/aether/client.cpp @@ -76,7 +76,7 @@ CloudServerConnections& Client::cloud_connection() { #if AE_TELE_ENABLED // also create telemetry telemetry_ = - ActionPtr(*aether_.Load().as(), + ActionPtr(ActionContext{*aether_.Load().as()}, Aether::ptr{aether_}.Load(), *cloud_connection_); #endif } diff --git a/aether/connection_manager/client_cloud_manager.cpp b/aether/connection_manager/client_cloud_manager.cpp index e7d7b111..8f78faff 100644 --- a/aether/connection_manager/client_cloud_manager.cpp +++ b/aether/connection_manager/client_cloud_manager.cpp @@ -205,7 +205,7 @@ ActionPtr ClientCloudManager::GetCloud(Uid client_uid) { if (cached != cloud_cache_.end()) { assert(cached->second.is_valid()); return ActionPtr{ - *aether, cached->second}; + ActionContext{*aether}, cached->second}; } // get from aethernet @@ -213,7 +213,7 @@ ActionPtr ClientCloudManager::GetCloud(Uid client_uid) { assert(client); return ActionPtr{ - *aether, *aether, *this, client->cloud_connection(), client_uid}; + ActionContext{*aether}, *aether, *this, client->cloud_connection(), client_uid}; } Cloud::ptr ClientCloudManager::RegisterCloud(Uid uid, diff --git a/aether/executors/async_wait.h b/aether/executors/async_wait.h index 79237941..00d0e838 100644 --- a/aether/executors/async_wait.h +++ b/aether/executors/async_wait.h @@ -26,6 +26,7 @@ #include #include "aether/types/result.h" +#include "aether/meta/ignore_t.h" #include "aether/meta/type_list.h" #include "aether/executors/scheduler_on_tasks.h" @@ -34,6 +35,8 @@ namespace ae::ex { namespace async_wait_internal { +namespace { + template