cpp-libraries/include/crow/task_timer.h
2025-12-14 21:34:10 +01:00

171 lines
5.8 KiB
C++

#pragma once
#ifdef CROW_USE_BOOST
#include <boost/asio.hpp>
#include <boost/asio/basic_waitable_timer.hpp>
#else
#ifndef ASIO_STANDALONE
#define ASIO_STANDALONE
#endif
#include <asio.hpp>
#include <asio/basic_waitable_timer.hpp>
#endif
#include <chrono>
#include <functional>
#include <map>
#include <vector>
#include "crow/logging.h"
namespace crow
{
#ifdef CROW_USE_BOOST
namespace asio = boost::asio;
using error_code = boost::system::error_code;
#else
using error_code = asio::error_code;
#endif
namespace detail
{
/// A class for scheduling functions to be called after a specific
/// amount of ticks. Ther tick length can be handed over in constructor,
/// the default tick length is equal to 1 second.
class task_timer
{
public:
using task_type = std::function<void()>;
using identifier_type = size_t;
private:
using clock_type = std::chrono::steady_clock;
using time_type = clock_type::time_point;
public:
task_timer(asio::io_context& io_context,
const std::chrono::milliseconds tick_length =
std::chrono::seconds(1)) :
io_context_(io_context), timer_(io_context_),
tick_length_ms_(tick_length)
{
timer_.expires_after(tick_length_ms_);
timer_.async_wait(
std::bind(&task_timer::tick_handler, this,
std::placeholders::_1));
}
~task_timer() { timer_.cancel(); }
/// Cancel the scheduling of the given task
///
/// \param identifier_type task identifier of the task to cancel.
void cancel(identifier_type id)
{
tasks_.erase(id);
CROW_LOG_DEBUG << "task_timer task cancelled: " << this << ' ' << id;
}
/// Schedule the given task to be executed after the default amount
/// of ticks.
///
/// \return identifier_type Used to cancel the thread.
/// It is not bound to this task_timer instance and in some cases
/// could lead to undefined behavior if used with other task_timer
/// objects or after the task has been successfully executed.
identifier_type schedule(const task_type& task)
{
return schedule(task, get_default_timeout());
}
/// Schedule the given task to be executed after the given time.
///
/// \param timeout The amount of ticks to wait before execution.
///
/// \return identifier_type Used to cancel the thread.
/// It is not bound to this task_timer instance and in some cases
/// could lead to undefined behavior if used with other task_timer
/// objects or after the task has been successfully executed.
identifier_type schedule(const task_type& task, uint8_t timeout)
{
tasks_.insert({++highest_id_,
{clock_type::now() + (timeout * tick_length_ms_),
task}});
CROW_LOG_DEBUG << "task_timer scheduled: " << this << ' ' <<
highest_id_;
return highest_id_;
}
/// Set the default timeout for this task_timer instance.
/// (Default: 5)
///
/// \param timeout The amount of ticks to wait before
/// execution.
/// For tick length \see tick_length_ms_
void set_default_timeout(uint8_t timeout) {
default_timeout_ = timeout;
}
/// Get the default timeout. (Default: 5)
uint8_t get_default_timeout() const {
return default_timeout_;
}
/// returns the length of one tick.
std::chrono::milliseconds get_tick_length() const {
return tick_length_ms_;
}
private:
void process_tasks()
{
time_type current_time = clock_type::now();
std::vector<identifier_type> finished_tasks;
for (const auto& task : tasks_)
{
if (task.second.first < current_time)
{
(task.second.second)();
finished_tasks.push_back(task.first);
CROW_LOG_DEBUG << "task_timer called: " << this <<
' ' << task.first;
}
}
for (const auto& task : finished_tasks)
tasks_.erase(task);
// If no task is currently scheduled, reset the issued ids back
// to 0.
if (tasks_.empty()) highest_id_ = 0;
}
void tick_handler(const error_code& ec)
{
if (ec) return;
process_tasks();
timer_.expires_after(tick_length_ms_);
timer_.async_wait(
std::bind(&task_timer::tick_handler, this, std::placeholders::_1));
}
private:
asio::io_context& io_context_;
asio::basic_waitable_timer<clock_type> timer_;
std::map<identifier_type, std::pair<time_type, task_type>> tasks_;
// A continuously increasing number to be issued to threads to
// identify them. If no tasks are scheduled, it will be reset to 0.
identifier_type highest_id_{0};
std::chrono::milliseconds tick_length_ms_;
uint8_t default_timeout_{5};
};
} // namespace detail
} // namespace crow