3#ifndef _TIME_SHIELD_TIMER_SCHEDULER_HPP_INCLUDED
4#define _TIME_SHIELD_TIMER_SCHEDULER_HPP_INCLUDED
21#include <condition_variable>
29#include <unordered_map>
58 static 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);
255 if (
auto state = entry.second.lock()) {
256 std::lock_guard<std::mutex> callback_lock(state->m_callback_mutex);
257 state->m_callback = {};
267 std::lock_guard<std::mutex> lock(
m_mutex);
277 std::vector<timer_state_ptr> orphan_states;
278 std::thread worker_to_join;
281 std::unique_lock<std::mutex> lock(
m_mutex);
285 worker_to_join = std::move(
m_thread);
291 auto state = it->second.lock();
297 if (!state->m_has_external_owner.load(std::memory_order_relaxed)) {
298 orphan_states.push_back(state);
306 if (worker_to_join.joinable()) {
307 worker_to_join.join();
311 std::lock_guard<std::mutex> lock(
m_mutex);
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();
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);
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) {
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);
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] {
421 if (woke_by_condition) {
425 const auto now = clock::now();
437 const auto& top =
m_queue.top();
438 if (top.m_fire_time >
now) {
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;
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();
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 are still 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 previously 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 currently 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.
Configuration macros for the library.
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