diff --git a/aether/actions/action_ptr.h b/aether/actions/action_ptr.h index 864fbd87..7440f437 100644 --- a/aether/actions/action_ptr.h +++ b/aether/actions/action_ptr.h @@ -106,12 +106,13 @@ class OwnActionPtr : public ActionPtr { template friend class OwnActionPtr; - static_assert(ActionStoppable::value, "TAction must be stoppable"); OwnActionPtr() = default; template explicit OwnActionPtr(ActionContext action_context, TArgs&&... args) - : ActionPtr{action_context, std::forward(args)...} {} + : ActionPtr{action_context, std::forward(args)...} { + static_assert(ActionStoppable::value, "TAction must be stoppable"); + } OwnActionPtr(OwnActionPtr&& other) noexcept : ActionPtr{std::move(other)} {} @@ -145,8 +146,8 @@ class OwnActionPtr : public ActionPtr { void reset() { if (ActionPtr::operator bool()) { - if (!ActionPtr::operator->() -> IsFinished()) { - ActionPtr::operator->() -> Stop(); + if (!ActionPtr::operator->()->IsFinished()) { + ActionPtr::operator->()->Stop(); } } ActionPtr::reset(); diff --git a/aether/ae_actions/time_sync.cpp b/aether/ae_actions/time_sync.cpp index b0f757ba..3e3998bf 100644 --- a/aether/ae_actions/time_sync.cpp +++ b/aether/ae_actions/time_sync.cpp @@ -18,18 +18,207 @@ #if AE_TIME_SYNC_ENABLED +# include "aether/client.h" +# include "aether/aether.h" +# include "aether/uap/uap.h" # include "aether/cloud_connections/cloud_visit.h" # include "aether/cloud_connections/cloud_server_connection.h" # include "aether/tele/tele.h" namespace ae { +namespace time_sync_internal { +class TimeSyncRequest : public Action { + enum class State : char { + kEnsureConnected, + kMakeRequest, + kWaitResponse, + kRetry, + kResult, + kFailed + }; + + static constexpr auto kRequestTimeout = 10s; + static constexpr auto kMaxTries = 5; + + public: + TimeSyncRequest(ActionContext action_context, Ptr const& client) + : Action{action_context}, + client_{client}, + state_{State::kEnsureConnected} { + state_.changed_event().Subscribe([this](auto) { Action::Trigger(); }); + } + + UpdateStatus Update() { + if (state_.changed()) { + switch (state_.Acquire()) { + case State::kEnsureConnected: + EnsureConnected(); + break; + case State::kMakeRequest: + SyncRequest(); + break; + case State::kRetry: { + tries_++; + if (tries_ >= kMaxTries) { + state_ = State::kFailed; + } else { + state_ = State::kEnsureConnected; + } + break; + } + case State::kWaitResponse: + break; + case State::kResult: + return UpdateStatus::Result(); + case State::kFailed: + return UpdateStatus::Error(); + } + } + + if (state_ == State::kWaitResponse) { + return WaitResponse(); + } + + return {}; + } + + private: + void EnsureConnected() { + auto client_ptr = client_.Lock(); + if (!client_ptr) { + state_ = State::kFailed; + return; + } + + // check or subscribe for connection state to main server + CloudVisit::Visit( + [this](CloudServerConnection* sc) { + assert((sc != nullptr) && "Server connection is null!"); + + auto* cc = sc->client_connection(); + assert((cc != nullptr) && "Client connection is null!"); + + // if already connected + if (cc->stream_info().link_state == LinkState::kLinked) { + state_ = State::kMakeRequest; + return; + } + + // wait till connected + link_state_sub_ = cc->stream_update_event().Subscribe([this, cc]() { + switch (cc->stream_info().link_state) { + case LinkState::kLinked: + state_ = State::kMakeRequest; + break; + case LinkState::kLinkError: + state_ = State::kRetry; + break; + default: + break; + } + }); + }, + client_ptr->cloud_connection(), RequestPolicy::MainServer{}); + } + + void SyncRequest() { + auto client_ptr = client_.Lock(); + if (!client_ptr) { + state_ = State::kFailed; + return; + } + + // send get_time_utc request to main server + // get_time_utc return server utc time point in microseconds + CloudVisit::Visit( + [this](CloudServerConnection* sc) { + assert((sc != nullptr) && "Server connection is null!"); + + auto* cc = sc->client_connection(); + assert(((cc != nullptr) && + (cc->stream_info().link_state == LinkState::kLinked)) && + "Client connection is not linked!"); + + 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{Clock::now()}](auto& p) { + HandleResponse( + std::chrono::milliseconds{ + static_cast(p.value())}, + request_time, Clock::now()); + // time synced + state_ = State::kResult; + }}); + }}); + write_action_sub_ = + write_action->StatusEvent().Subscribe(OnError{[this]() { + AE_TELED_ERROR("Time sync write error, retry"); + state_ = State::kRetry; + }}); + }, + client_ptr->cloud_connection(), RequestPolicy::MainServer{}); + + // use raw time to avoid sync jumps + request_time_ = SystemTime(); + state_ = State::kWaitResponse; + } + + UpdateStatus WaitResponse() { + auto current_time = SystemTime(); + auto timeout = request_time_ + kRequestTimeout; + if (current_time > timeout) { + AE_TELED_ERROR("Time sync response timeout"); + state_ = State::kFailed; + return {}; + } + return UpdateStatus::Delay(ToSyncTime(timeout)); + } + + static void HandleResponse(std::chrono::milliseconds server_epoch, + TimePoint request_time, TimePoint response_time) { + auto server_time = TimePoint{server_epoch}; + auto round_trip = response_time - request_time; + AE_TELED_DEBUG( + "Time sync roundtrip {:%S} request_time {:%Y-%m-%d %H:%M:%S}, " + "response_time {:%Y-%m-%d %H:%M:%S} server_time {:%Y-%m-%d %H:%M:%S}", + std::chrono::duration_cast(round_trip), request_time, + response_time, server_time); + + auto diff_time = server_time - request_time - round_trip / 2; + AE_TELED_INFO( + "Time sync diff_time is {} ms", + std::chrono::duration_cast(diff_time) + .count()); + // update diff time + Clock::SyncTimeDiff += + std::chrono::duration_cast(diff_time); + AE_TELED_DEBUG("Current time {:%Y-%m-%d %H:%M:%S}", Now()); + } + + PtrView client_; + StateMachine state_; + SystemTimePoint request_time_; + int tries_{}; + Subscription link_state_sub_; + Subscription response_sub_; + Subscription write_action_sub_; +}; +} // namespace time_sync_internal + +// set end of time - this means last_sync_time is not set SystemTimePoint TimeSyncAction::last_sync_time = SystemTimePoint::max(); TimeSyncAction::TimeSyncAction(ActionContext action_context, + Ptr const& aether, Ptr const& client, Duration sync_interval) : Action{action_context}, + action_context_{action_context}, + aether_{aether}, client_{client}, sync_interval_{sync_interval}, state_{State::kWaitInterval} { @@ -40,145 +229,65 @@ TimeSyncAction::TimeSyncAction(ActionContext action_context, UpdateStatus TimeSyncAction::Update() { if (state_.changed()) { switch (state_.Acquire()) { - case State::kEnsureConnected: - EnsureConnected(); - break; case State::kMakeRequest: - SyncRequest(); + MakeRequest(); break; - case State::kWaitResponse: case State::kWaitInterval: break; case State::kFailed: return UpdateStatus::Error(); } } - if (state_ == State::kWaitResponse) { - return WaitResponse(); - } if (state_ == State::kWaitInterval) { return WaitInterval(); } return {}; } -void TimeSyncAction::EnsureConnected() { - auto client_ptr = client_.Lock(); - if (!client_ptr) { +void TimeSyncAction::MakeRequest() { + auto aether_ptr = aether_.Lock(); + assert(aether_ptr); + + auto uap = aether_ptr->uap.Load(); + if (!uap) { state_ = State::kFailed; return; } - CloudVisit::Visit( - [this](CloudServerConnection* sc) { - assert((sc != nullptr) && "Server connection is null!"); - auto is_linked = [sc]() { - return sc->client_connection()->stream_info().link_state == - LinkState::kLinked; - }; - - if (is_linked()) { - state_ = State::kMakeRequest; - return; - } - link_state_sub_ = - sc->client_connection()->stream_update_event().Subscribe( - [this, is_linked]() { - if (is_linked()) { - state_ = State::kMakeRequest; - } - }); - }, - client_ptr->cloud_connection(), RequestPolicy::MainServer{}); -} + // send request only if SendReceive Uap interval enabled + if (auto timer = uap->timer(); + timer.has_value() && + timer->interval().interval.type != IntervalType::kSendReceive) { + state_ = State::kWaitInterval; + return; + } -void TimeSyncAction::SyncRequest() { auto client_ptr = client_.Lock(); if (!client_ptr) { state_ = State::kFailed; return; } - // send get_time_utc request to main server - // get_time_utc return server utc time point in microseconds - CloudVisit::Visit( - [this](CloudServerConnection* sc) { - assert((sc != nullptr) && "Server connection is null!"); - - auto* cc = sc->client_connection(); - assert((cc->stream_info().link_state == LinkState::kLinked) && - "Client connection is not linked!"); - - write_action_sub_ = - 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{Clock::now()}](auto& p) { - HandleResponse( - std::chrono::milliseconds{ - static_cast(p.value())}, - request_time, Clock::now()); - // time synced, wait for next sync interval - state_ = State::kWaitInterval; - }}); - }}) - ->StatusEvent() - .Subscribe(OnError{[this]() { - AE_TELED_ERROR("Time sync write error, repeat"); - state_ = State::kEnsureConnected; - }}); - }, - client_ptr->cloud_connection(), RequestPolicy::MainServer{}); + time_sync_request_ = ActionPtr{ + action_context_, client_ptr}; + uap->RegisterAction(*time_sync_request_); // use raw time to avoid sync jumps last_sync_time = SystemTime(); - state_ = State::kWaitResponse; -} - -void TimeSyncAction::HandleResponse(std::chrono::milliseconds server_epoch, - TimePoint request_time, - TimePoint response_time) { - auto server_time = TimePoint{server_epoch}; - auto round_trip = response_time - request_time; - AE_TELED_DEBUG( - "Time sync roundtrip {:%S} request_time {:%Y-%m-%d %H:%M:%S}, " - "response_time {:%Y-%m-%d %H:%M:%S} server_time {:%Y-%m-%d %H:%M:%S}", - std::chrono::duration_cast(round_trip), request_time, - response_time, server_time); - - auto diff_time = server_time - request_time - round_trip / 2; - AE_TELED_INFO( - "Time sync diff_time is {} ms", - std::chrono::duration_cast(diff_time).count()); - // update diff time - Clock::SyncTimeDiff += - std::chrono::duration_cast(diff_time); - AE_TELED_DEBUG("Current time {:%Y-%m-%d %H:%M:%S}", Now()); -} - -UpdateStatus TimeSyncAction::WaitResponse() { - auto current_time = SystemTime(); - auto timeout = last_sync_time + kRequestTimeout; - if (current_time > timeout) { - AE_TELED_ERROR("Time sync response timeout, make new request"); - state_ = State::kEnsureConnected; - return {}; - } - return UpdateStatus::Delay(ToSyncTime(timeout)); + state_ = State::kWaitInterval; } UpdateStatus TimeSyncAction::WaitInterval() { // the end of time! if (last_sync_time == SystemTimePoint::max()) { - state_ = State::kEnsureConnected; + state_ = State::kMakeRequest; return {}; } auto current_time = SystemTime(); auto timeout = last_sync_time + sync_interval_; if (current_time > timeout) { AE_TELED_INFO("Time sync interval timeout, make new request"); - state_ = State::kEnsureConnected; + state_ = State::kMakeRequest; return {}; } return UpdateStatus::Delay(ToSyncTime(timeout)); diff --git a/aether/ae_actions/time_sync.h b/aether/ae_actions/time_sync.h index 1ef20949..f2c72184 100644 --- a/aether/ae_actions/time_sync.h +++ b/aether/ae_actions/time_sync.h @@ -23,44 +23,42 @@ # include "aether/env.h" # include "aether/clock.h" -# include "aether/client.h" # include "aether/ptr/ptr_view.h" # include "aether/actions/action.h" +# include "aether/actions/action_ptr.h" # include "aether/types/state_machine.h" # include "aether/events/event_subscription.h" namespace ae { +class Aether; +class Client; +namespace time_sync_internal { +class TimeSyncRequest; +} + class TimeSyncAction : public Action { enum class State : char { - kEnsureConnected, kMakeRequest, - kWaitResponse, kWaitInterval, kFailed, }; - static constexpr auto kRequestTimeout = 10s; - public: - TimeSyncAction(ActionContext action_context, Ptr const& client, - Duration sync_interval); + TimeSyncAction(ActionContext action_context, Ptr const& aether, + Ptr const& client, Duration sync_interval); UpdateStatus Update(); private: - void EnsureConnected(); - void SyncRequest(); - static void HandleResponse(std::chrono::milliseconds server_epoch, - TimePoint request_time, TimePoint response_time); - UpdateStatus WaitResponse(); + void MakeRequest(); UpdateStatus WaitInterval(); + ActionContext action_context_; + PtrView aether_; PtrView client_; Duration sync_interval_; StateMachine state_; - Subscription link_state_sub_; - Subscription write_action_sub_; - Subscription response_sub_; + ActionPtr time_sync_request_; static RTC_STORAGE_ATTR SystemTimePoint last_sync_time; }; diff --git a/aether/aether.cpp b/aether/aether.cpp index 9d499dd8..fa164781 100644 --- a/aether/aether.cpp +++ b/aether/aether.cpp @@ -192,8 +192,9 @@ void Aether::MakeTimeSyncAction([[maybe_unused]] Client::ptr const& client) { client.WithLoaded([this](auto const& c) { static constexpr auto kTimeSyncInterval = std::chrono::seconds{AE_TIME_SYNC_INTERVAL_S}; - time_sync_action_ = - ActionPtr{*action_processor, c, kTimeSyncInterval}; + time_sync_action_ = ActionPtr{ + *action_processor, Aether::ptr::MakeFromThis(this).Load(), c, + kTimeSyncInterval}; }); #endif } diff --git a/aether/meta/index_sequence.h b/aether/meta/index_sequence.h new file mode 100644 index 00000000..92f49102 --- /dev/null +++ b/aether/meta/index_sequence.h @@ -0,0 +1,83 @@ +/* + * 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_META_INDEX_SEQUENCE_H_ +#define AETHER_META_INDEX_SEQUENCE_H_ + +#include +#include +#include + +namespace ae { +namespace index_sequence_internal { +template +constexpr auto reverse_sequence_helper(std::integer_sequence, + std::index_sequence) { + constexpr auto array = std::array{N...}; + return std::integer_sequence(); +} + +template +constexpr auto make_range_sequence_helper(std::integer_sequence) { + return std::integer_sequence(); +} +} // namespace index_sequence_internal + +template +constexpr auto reverse_sequence(std::integer_sequence sequence) { + return index_sequence_internal::reverse_sequence_helper( + sequence, std::make_index_sequence()); +} + +template +constexpr auto make_range_sequence() { + if constexpr (from <= to) { + return index_sequence_internal::make_range_sequence_helper( + std::make_integer_sequence()); + } else { + // make reverse sequence from bigger to lesser + return reverse_sequence( + index_sequence_internal::make_range_sequence_helper( + std::make_integer_sequence())); + } +} + +} // namespace ae + +#if AE_TESTS +# include "tests/inline.h" + +namespace tests::index_sequence_h { +inline void test_ReverseSequence() { + constexpr auto rev_indices = + ae::reverse_sequence(std::make_index_sequence<10>()); + static_assert( + std::is_same_v, + std::decay_t>); + + constexpr auto types_rev_indices = + ae::reverse_sequence(std::index_sequence_for()); + static_assert(std::is_same_v, + std::decay_t>); + TEST_PASS(); +} +} // namespace tests::index_sequence_h + +AE_TEST_INLINE { RUN_TEST(tests::index_sequence_h::test_ReverseSequence); } + +#endif + +#endif // AETHER_META_INDEX_SEQUENCE_H_ diff --git a/aether/meta/type_list.h b/aether/meta/type_list.h index 75ec6ea1..82d4dd11 100644 --- a/aether/meta/type_list.h +++ b/aether/meta/type_list.h @@ -18,8 +18,11 @@ #define AETHER_META_TYPE_LIST_H_ #include +#include #include +#include "aether/meta/index_sequence.h" + namespace ae { template struct TypeList {}; @@ -35,55 +38,43 @@ struct TypeListMaker { template TypeListMaker(T&&...) -> TypeListMaker; -static inline constexpr std::size_t GetTypeAtChunkSize = 10; - -template -static constexpr auto GetTypeAtChunk() { - static_assert(I < GetTypeAtChunkSize); - if constexpr (I == 0) { - return std::type_identity{}; - } else if constexpr (I == 1) { - return std::type_identity{}; - } else if constexpr (I == 2) { - return std::type_identity{}; - } else if constexpr (I == 3) { - return std::type_identity{}; - } else if constexpr (I == 4) { - return std::type_identity{}; - } else if constexpr (I == 5) { - return std::type_identity{}; - } else if constexpr (I == 6) { - return std::type_identity{}; - } else if constexpr (I == 7) { - return std::type_identity{}; - } else if constexpr (I == 8) { - return std::type_identity{}; - } else if constexpr (I == 9) { - return std::type_identity{}; - } -} - -template -static constexpr auto GetTypeAt() { - if constexpr (I < GetTypeAtChunkSize) { - return GetTypeAtChunk(); - } else { - return GetTypeAt(); - } -} +namespace type_list_internal { +template +using index_constant = std::integral_constant; + +template +struct NType { + static auto get(index_constant) -> U; +}; + +template +struct NTypeSelector { + template + struct Selector; + + template + struct Selector> : NType... { + using NType::get...; + }; + + template + static auto get() + -> decltype(Selector())>::get( + index_constant{})); + + template + using type = decltype(get()); +}; + +} // namespace type_list_internal template struct TypeAt; template struct TypeAt> { - using type = typename decltype(GetTypeAt())::type; + using type = + typename type_list_internal::NTypeSelector::template type; }; template @@ -132,10 +123,16 @@ struct TypeListToTemplate> { template struct ReversTypeList; -template -struct ReversTypeList> { - using type = JoinedTypeList_t>::type, - TypeList>; +template +struct ReversTypeList> { + using n_type_selector = type_list_internal::NTypeSelector; + + template + static auto ReselectTypes(std::index_sequence) + -> TypeList...>; + + using type = decltype(ReselectTypes( + reverse_sequence(std::index_sequence_for{}))); }; template diff --git a/aether/type_traits.h b/aether/type_traits.h index fd43ef82..a2be58ed 100644 --- a/aether/type_traits.h +++ b/aether/type_traits.h @@ -25,22 +25,11 @@ #include "aether/meta/arg_at.h" #include "aether/meta/type_list.h" +#include "aether/meta/index_sequence.h" namespace ae { namespace _internal { -template -constexpr auto reverse_sequence_helper(std::integer_sequence, - std::index_sequence) { - constexpr auto array = std::array{N...}; - return std::integer_sequence(); -} - -template -constexpr auto make_range_sequence_helper(std::integer_sequence) { - return std::integer_sequence(); -} - template decltype(auto) ApplyByIndices(TFunc&& func, std::index_sequence, TArgs&&... args) { @@ -49,24 +38,6 @@ decltype(auto) ApplyByIndices(TFunc&& func, std::index_sequence, } } // namespace _internal -template -constexpr auto reverse_sequence(std::integer_sequence sequence) { - return _internal::reverse_sequence_helper( - sequence, std::make_index_sequence()); -} - -template -constexpr auto make_range_sequence() { - if constexpr (from <= to) { - return _internal::make_range_sequence_helper( - std::make_integer_sequence()); - } else { - // make reverse sequence from bigger to lesser - return reverse_sequence(_internal::make_range_sequence_helper( - std::make_integer_sequence())); - } -} - template decltype(auto) ApplyRerverse(TFunc&& func, T&&... args) { return _internal::ApplyByIndices( diff --git a/aether/uap/uap.cpp b/aether/uap/uap.cpp index 12982dd1..56dedf67 100644 --- a/aether/uap/uap.cpp +++ b/aether/uap/uap.cpp @@ -51,12 +51,13 @@ Uap::Uap(ObjProp prop, ObjPtr aether, start_time_ = Now(); } +Uap::~Uap() = default; + void Uap::SleepReady() { - // TODO: add checks if all buffers are empty - if (!timer_before_sleep_) { - timer_before_sleep_ = OwnActionPtr{*aether_.Load(), 5s}; - timer_before_sleep_->StatusEvent().Subscribe( - OnResult{[this]() { GoToSleep(); }}); + ready_to_sleep_ = true; + // if all registered actions is finished else wait /see RegisterAction + if (wait_actions_cnt_ == 0) { + AllActionsFinished(); } } @@ -79,6 +80,24 @@ std::optional Uap::timer() { return Timer{Uap::ptr::MakeFromThis(this)}; } +void Uap::RegisterAction(IAction& action) { + wait_actions_cnt_++; + wait_actions_subs_ += action.FinishedEvent().Subscribe([this]() { + assert(wait_actions_cnt_ > 0); + wait_actions_cnt_--; + if (wait_actions_cnt_ == 0) { + AllActionsFinished(); + } + }); +} + +void Uap::GoToSleep() { + if (intervals_.empty()) { + return; + } + sleep_event_.Emit(Timer{Uap::ptr::MakeFromThis(this)}); +} + Uap::IntervalState Uap::UpdateInterval(Duration time_offset) { assert(!intervals_.empty()); @@ -131,10 +150,11 @@ Uap::IntervalState Uap::UpdateInterval(Duration time_offset) { return IntervalState{intervals_[current_interval_index_], start_time_}; } -void Uap::GoToSleep() { - if (intervals_.empty()) { - return; +void Uap::AllActionsFinished() { + AE_TELED_DEBUG("All registered actions finished"); + if (ready_to_sleep_) { + GoToSleep(); } - sleep_event_.Emit(Timer{Uap::ptr::MakeFromThis(this)}); } + } // namespace ae diff --git a/aether/uap/uap.h b/aether/uap/uap.h index a3ff67ce..64f38ce9 100644 --- a/aether/uap/uap.h +++ b/aether/uap/uap.h @@ -23,8 +23,8 @@ #include "aether/clock.h" #include "aether/obj/obj.h" #include "aether/events/events.h" -#include "aether/actions/action_ptr.h" -#include "aether/actions/timer_action.h" +#include "aether/actions/action.h" +#include "aether/events/multi_subscription.h" namespace ae { class Aether; @@ -92,14 +92,15 @@ class Uap final : public Obj { Uap(ObjProp prop, ObjPtr aether, std::initializer_list const& interval_list); + ~Uap() override; AE_OBJECT_REFLECT(AE_MMBRS(aether_)) + // save next interval and load current interval on its place template void Load(CurrentVersion, Dnv& dnv) { dnv(base_, aether_, current_interval_index_, intervals_); } - template void Save(CurrentVersion, Dnv& dnv) const { dnv(base_, aether_, next_interval_index_, intervals_); @@ -127,6 +128,11 @@ class Uap final : public Obj { */ std::optional timer(); + /** + * \brief Register action which should be finished before sleep event. + */ + void RegisterAction(IAction& action); + private: void GoToSleep(); /** @@ -134,6 +140,9 @@ class Uap final : public Obj { */ IntervalState UpdateInterval(Duration time_offset); + // called when all registered actions is finished + void AllActionsFinished(); + SleepEvent sleep_event_; ObjPtr aether_; @@ -141,7 +150,9 @@ class Uap final : public Obj { std::size_t current_interval_index_{}; std::size_t next_interval_index_{}; TimePoint start_time_; - OwnActionPtr timer_before_sleep_; + MultiSubscription wait_actions_subs_; + std::size_t wait_actions_cnt_{}; + bool ready_to_sleep_{false}; }; } // namespace ae