// // AsyncUpdater.h // Clock Signal // // Created by Thomas Harte on 06/07/2022. // Copyright © 2022 Thomas Harte. All rights reserved. // #ifndef AsyncUpdater_hpp #define AsyncUpdater_hpp #include #include #include #include "../ClockReceiver/TimeTypes.hpp" namespace Concurrency { template class AsyncUpdater { public: template AsyncUpdater(Args&&... args) : performer(std::forward(args)...), performer_thread_{ [this] { Time::Nanos last_fired = Time::nanos_now(); ActionVector actions; while(!should_quit) { // Wait for new actions to be signalled, and grab them. std::unique_lock lock(condition_mutex_); while(actions_.empty()) { condition_.wait(lock); } std::swap(actions, actions_); lock.unlock(); // Update to now. auto time_now = Time::nanos_now(); performer.perform(time_now - last_fired); last_fired = time_now; // Perform the actions. for(const auto& action: actions) { action(); } actions.clear(); } } } {} /// Run the performer up to 'now' and then perform @c post_action. /// /// @c post_action will be performed asynchronously, on the same /// thread as the performer. /// /// Actions may be elided, void update(const std::function &post_action) { std::lock_guard guard(condition_mutex_); actions_.push_back(post_action); condition_.notify_all(); } void stop() { if(performer_thread_.joinable()) { should_quit = true; update([] {}); performer_thread_.join(); } } ~AsyncUpdater() { stop(); } // The object that will actually receive time advances. Performer performer; private: // The list of actions waiting be performed. These will be elided, // increasing their latency, if the emulation thread falls behind. using ActionVector = std::vector>; ActionVector actions_; // Necessary synchronisation parts. std::atomic should_quit = false; std::mutex condition_mutex_; std::condition_variable condition_; // Ensure the thread isn't constructed until after the mutex // and condition variable. std::thread performer_thread_; }; } #endif /* AsyncUpdater_hpp */