3#ifndef TIME_SHIELD_HEADER_TIMERS_TIMERSCHEDULER_HPP_INCLUDED
4#define TIME_SHIELD_HEADER_TIMERS_TIMERSCHEDULER_HPP_INCLUDED
21#include <condition_variable>
29#include <unordered_map>
58 static TIME_SHIELD_THREAD_LOCAL
TimerState* state =
nullptr;
80 ScheduledTimer(TimerClock::time_point fire_time, std::size_t timer_id, std::uint64_t generation)
100 std::uint64_t generation,
101 std::shared_ptr<TimerState> state)
171 std::unordered_map<std::size_t, std::weak_ptr<detail::TimerState>>
m_timers;
192 template<
class Rep,
class Period>
196 std::chrono::milliseconds
interval() const noexcept;
202 template<class Rep, class Period>
235 template<class Rep, class Period>
237 std::chrono::duration<Rep, Period>
interval,
253 std::lock_guard<std::mutex> lock(m_mutex);
254 for (
auto& entry : m_timers) {
255 if (
auto state = entry.second.lock()) {
256 std::lock_guard<std::mutex> callback_lock(state->m_callback_mutex);
257 state->m_callback = {};
261 while (!m_queue.empty()) {
267 std::lock_guard<std::mutex> lock(m_mutex);
268 if (m_is_worker_running) {
271 m_stop_requested =
false;
272 m_is_worker_running =
true;
277 std::vector<timer_state_ptr> orphan_states;
278 std::thread worker_to_join;
281 std::unique_lock<std::mutex> lock(m_mutex);
282 if (m_is_worker_running) {
283 m_stop_requested =
true;
285 worker_to_join = std::move(m_thread);
287 m_stop_requested =
false;
290 for (
auto it = m_timers.begin(); it != m_timers.end();) {
291 auto state = it->second.lock();
293 it = m_timers.erase(it);
297 if (!state->m_has_external_owner.load(std::memory_order_relaxed)) {
298 orphan_states.push_back(state);
299 it = m_timers.erase(it);
306 if (worker_to_join.joinable()) {
307 worker_to_join.join();
311 std::lock_guard<std::mutex> lock(m_mutex);
312 m_is_worker_running =
false;
313 m_stop_requested =
false;
316 for (
auto& state : orphan_states) {
320 std::lock_guard<std::mutex> callback_lock(state->m_callback_mutex);
321 state->m_callback = {};
322 state->m_is_active.store(
false, std::memory_order_relaxed);
327 std::vector<detail::DueTimer> due;
329 std::lock_guard<std::mutex> lock(m_mutex);
330 assert(!m_is_worker_running &&
"process() must not be called while the worker thread is active");
331 const auto now = clock::now();
332 collect_due_timers_locked(due,
now);
334 execute_due_timers(due);
342 std::lock_guard<std::mutex> lock(m_mutex);
343 std::size_t count = 0;
344 for (
const auto& entry : m_timers) {
345 if (!entry.second.expired()) {
353 auto state = std::make_shared<detail::TimerState>();
354 state->m_scheduler =
this;
355 std::lock_guard<std::mutex> lock(m_mutex);
356 state->m_id = m_next_id++;
357 m_timers[state->m_id] = state;
366 std::lock_guard<std::mutex> callback_lock(state->m_callback_mutex);
367 state->m_callback = {};
369 std::lock_guard<std::mutex> lock(m_mutex);
370 state->m_is_active.store(
false, std::memory_order_relaxed);
371 state->m_generation.fetch_add(1, std::memory_order_relaxed);
372 if (state->m_id != 0) {
373 m_timers.erase(state->m_id);
375 state->m_scheduler =
nullptr;
382 std::lock_guard<std::mutex> lock(m_mutex);
383 state->m_is_active.store(
true, std::memory_order_relaxed);
384 const auto generation = state->m_generation.fetch_add(1, std::memory_order_relaxed) + 1;
393 std::lock_guard<std::mutex> lock(m_mutex);
394 state->m_is_active.store(
false, std::memory_order_relaxed);
395 state->m_generation.fetch_add(1, std::memory_order_relaxed);
400 std::vector<detail::DueTimer> due;
401 std::unique_lock<std::mutex> lock(m_mutex);
402 while (!m_stop_requested) {
403 if (m_queue.empty()) {
404 m_cv.wait(lock, [
this] {
return m_stop_requested || !m_queue.empty(); });
408 const auto next_fire_time = m_queue.top().m_fire_time;
409 const bool woke_by_condition = m_cv.wait_until(
412 [
this, next_fire_time] {
413 return m_stop_requested || m_queue.empty() || m_queue.top().m_fire_time < next_fire_time;
417 if (m_stop_requested) {
421 if (woke_by_condition) {
425 const auto now = clock::now();
426 collect_due_timers_locked(due,
now);
429 execute_due_timers(due);
436 while (!m_queue.empty()) {
437 const auto& top = m_queue.top();
438 if (top.m_fire_time >
now) {
446 if (it == m_timers.end()) {
450 auto state = it->second.lock();
456 if (!state->m_is_active.load(std::memory_order_relaxed) ||
457 state->m_generation.load(std::memory_order_relaxed) != item.
m_generation) {
461 state->m_is_running.store(
true, std::memory_order_release);
467 for (
auto& timer : due) {
470 std::lock_guard<std::mutex> callback_lock(timer.m_state->m_callback_mutex);
471 callback = timer.m_state->m_callback;
481 finalize_timer(timer);
486 auto state = due_timer.
m_state;
491 std::unique_lock<std::mutex> lock(m_mutex);
492 state->m_is_running.store(
false, std::memory_order_release);
493 if (!state->m_is_active.load(std::memory_order_relaxed)) {
497 if (state->m_is_single_shot.load(std::memory_order_relaxed)) {
498 state->m_is_active.store(
false, std::memory_order_relaxed);
499 state->m_generation.fetch_add(1, std::memory_order_relaxed);
503 if (state->m_generation.load(std::memory_order_relaxed) != due_timer.
m_generation) {
507 const auto interval_ms = state->m_interval_ms.load(std::memory_order_relaxed);
508 const auto next_fire_time = due_timer.
m_fire_time + std::chrono::milliseconds(interval_ms);
509 const auto next_generation = state->m_generation.fetch_add(1, std::memory_order_relaxed) + 1;
521 m_state->m_has_external_owner.store(
true, std::memory_order_relaxed);
536 m_state->m_has_external_owner.store(
false, std::memory_order_relaxed);
540 template<
class Rep,
class Period>
542 auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(interval).count();
543 if (milliseconds < 0) {
546 m_state->m_interval_ms.store(milliseconds, std::memory_order_relaxed);
550 const auto milliseconds =
m_state->m_interval_ms.load(std::memory_order_relaxed);
551 return std::chrono::milliseconds(milliseconds);
555 const auto milliseconds =
m_state->m_interval_ms.load(std::memory_order_relaxed);
556 const auto delay = TimerScheduler::clock::now() + std::chrono::milliseconds(milliseconds);
560 template<
class Rep,
class Period>
572 &&
"stop_and_wait() must not be called from inside callback");
574 while (
m_state->m_is_running.load(std::memory_order_acquire)) {
575 std::this_thread::yield();
580 m_state->m_is_single_shot.store(is_single_shot, std::memory_order_relaxed);
584 return m_state->m_is_single_shot.load(std::memory_order_relaxed);
588 return m_state->m_is_active.load(std::memory_order_relaxed);
592 return m_state->m_is_running.load(std::memory_order_relaxed);
596 std::lock_guard<std::mutex> lock(
m_state->m_callback_mutex);
597 m_state->m_callback = std::move(callback);
600 template<
class Rep,
class Period>
602 std::chrono::duration<Rep, Period> interval,
609 auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(
interval).count();
610 if (milliseconds < 0) {
614 state->m_is_single_shot.store(
true, std::memory_order_relaxed);
615 state->m_interval_ms.store(milliseconds, std::memory_order_relaxed);
617 auto* scheduler_ptr = state->m_scheduler;
619 Callback user_callback_local = std::move(callback);
622 std::lock_guard<std::mutex> lock(state->m_callback_mutex);
623 state->m_callback = [state, scheduler_ptr, user_callback_local]()
mutable {
624 if (user_callback_local) {
625 user_callback_local();
628 auto state_ptr = state;
634 std::lock_guard<std::mutex> callback_lock(state_ptr->m_callback_mutex);
635 state_ptr->m_callback = {};
639 scheduler_ptr->destroy_timer_state(state_ptr);
644 const auto fire_time = TimerScheduler::clock::now() + std::chrono::milliseconds(milliseconds);
Scheduler that manages timer execution.
void stop()
Requests the worker thread to stop and waits for it to exit.
TimerScheduler(const TimerScheduler &)=delete
std::unordered_map< std::size_t, std::weak_ptr< detail::TimerState > > m_timers
void run()
Starts a dedicated worker thread that processes timers.
std::size_t active_timer_count_for_testing()
Returns number of timer states that remain alive.
std::priority_queue< detail::ScheduledTimer, std::vector< detail::ScheduledTimer >, detail::ScheduledComparator > m_queue
TimerScheduler & operator=(TimerScheduler &&)=delete
void stop_timer(const timer_state_ptr &state)
void process()
Processes all timers that are ready to fire at the moment of the call.
TimerScheduler & operator=(const TimerScheduler &)=delete
void update()
Alias for process() for compatibility with update-based loops.
void finalize_timer(const detail::DueTimer &due_timer)
std::condition_variable m_cv
void collect_due_timers_locked(std::vector< detail::DueTimer > &due, clock::time_point now)
TimerScheduler(TimerScheduler &&)=delete
void execute_due_timers(std::vector< detail::DueTimer > &due)
timer_state_ptr create_timer_state()
void start_timer(const timer_state_ptr &state, clock::time_point when)
void destroy_timer_state(const timer_state_ptr &state)
Timer that mimics the behavior of Qt timers.
void start()
Starts the timer using the configured interval.
void set_single_shot(bool is_single_shot) noexcept
Sets whether the timer should fire only once.
Timer(TimerScheduler &scheduler)
detail::TimerCallback Callback
Timer & operator=(Timer &&)=delete
void set_callback(Callback callback)
Sets the callback that should be invoked when the timer fires.
bool is_active() const noexcept
Returns true if the timer is active.
Timer(const Timer &)=delete
static void single_shot(TimerScheduler &scheduler, std::chrono::duration< Rep, Period > interval, Callback callback)
Creates a single-shot timer that invokes the callback once.
void stop()
Stops the timer.
Timer & operator=(const Timer &)=delete
std::chrono::milliseconds interval() const noexcept
Returns the configured interval.
TimerScheduler & m_scheduler
bool is_single_shot() const noexcept
Returns true if the timer fires only once.
void stop_and_wait()
Stops the timer and waits until an active callback finishes.
void set_interval(std::chrono::duration< Rep, Period > interval) noexcept
Sets the interval used by the timer.
bool is_running() const noexcept
Returns true if the timer callback is being executed.
ts_ms_t now() noexcept
Get the current UTC timestamp in milliseconds.
std::chrono::steady_clock TimerClock
TimerState *& current_timer_state()
std::function< void()> TimerCallback
Main namespace for the Time Shield library.
std::shared_ptr< detail::TimerState > timer_state_ptr
Helper structure that represents a timer ready to run.
std::shared_ptr< TimerState > m_state
TimerClock::time_point m_fire_time
DueTimer(TimerClock::time_point fire_time, std::uint64_t generation, std::shared_ptr< TimerState > state)
std::uint64_t m_generation
RunningTimerScope(TimerState *state)
Comparator that orders timers by earliest fire time.
bool operator()(const ScheduledTimer &lhs, const ScheduledTimer &rhs) const
Data stored in the priority queue of scheduled timers.
ScheduledTimer(TimerClock::time_point fire_time, std::size_t timer_id, std::uint64_t generation)
TimerClock::time_point m_fire_time
std::uint64_t m_generation
Internal state shared between Timer and TimerScheduler.
TimerScheduler * m_scheduler
std::atomic< bool > m_is_active
std::mutex m_callback_mutex
std::atomic< std::int64_t > m_interval_ms
std::atomic< std::uint64_t > m_generation
std::atomic< bool > m_is_running
std::atomic< bool > m_has_external_owner
std::atomic< bool > m_is_single_shot