aboutsummaryrefslogtreecommitdiff
path: root/include/cru/base
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-11-03 14:54:51 +0800
committerYuqian Yang <crupest@crupest.life>2025-11-03 15:29:07 +0800
commit9c897e8727d90345c2db7f36f52ab678778db936 (patch)
treee4eeb7454677a40603785cab77061aaaf6f7c66e /include/cru/base
parent95d061e4fca2f7903ac903a2426cb5ad30c737f7 (diff)
downloadcru-9c897e8727d90345c2db7f36f52ab678778db936.tar.gz
cru-9c897e8727d90345c2db7f36f52ab678778db936.tar.bz2
cru-9c897e8727d90345c2db7f36f52ab678778db936.zip
Add TimerRegistry.
Diffstat (limited to 'include/cru/base')
-rw-r--r--include/cru/base/Timer.h123
1 files changed, 123 insertions, 0 deletions
diff --git a/include/cru/base/Timer.h b/include/cru/base/Timer.h
new file mode 100644
index 00000000..80929f17
--- /dev/null
+++ b/include/cru/base/Timer.h
@@ -0,0 +1,123 @@
+#pragma once
+
+#include "Base.h"
+#include "Exception.h"
+
+#include <algorithm>
+#include <chrono>
+#include <list>
+#include <mutex>
+#include <optional>
+#include <ranges>
+
+namespace cru {
+template <typename D>
+class TimerRegistry : public Object2 {
+ private:
+ struct TimerData {
+ int id;
+ D data;
+ std::chrono::steady_clock::time_point created;
+ std::chrono::steady_clock::time_point last_check;
+ std::chrono::milliseconds interval;
+ bool repeat;
+
+ TimerData(int id, std::chrono::milliseconds interval, bool repeat,
+ std::chrono::steady_clock::time_point created, D data)
+ : id(id),
+ created(created),
+ last_check(created),
+ interval(interval),
+ repeat(repeat),
+ data(std::move(data)) {}
+
+ std::chrono::milliseconds NextTimeout(
+ std::chrono::steady_clock::time_point now) const {
+ return std::chrono::duration_cast<std::chrono::milliseconds>(
+ interval - (now - created) % interval);
+ }
+
+ bool Update(std::chrono::steady_clock::time_point now) {
+ auto next_trigger =
+ last_check - (last_check - created) % interval + interval;
+ if (now >= next_trigger) {
+ last_check = next_trigger;
+ return true;
+ } else {
+ last_check = now;
+ return false;
+ }
+ }
+ };
+
+ public:
+ struct UpdateResult {
+ int id;
+ D data;
+
+ bool operator==(const UpdateResult&) const = default;
+ };
+
+ public:
+ TimerRegistry() : next_id_(1) {}
+
+ int Add(D data, std::chrono::milliseconds interval, bool repeat,
+ std::chrono::steady_clock::time_point created =
+ std::chrono::steady_clock::now()) {
+ if (interval < std::chrono::milliseconds::zero()) {
+ throw Exception("Timer interval can't be negative.");
+ }
+ if (repeat && interval == std::chrono::milliseconds::zero()) {
+ throw Exception("Repeat timer interval can't be 0.");
+ }
+
+ std::unique_lock lock(mutex_);
+ auto id = next_id_++;
+ timers_.emplace_back(id, interval, repeat, created, std::move(data));
+ return id;
+ }
+
+ void Remove(int id) {
+ std::unique_lock lock(mutex_);
+ timers_.remove_if([id](const TimerData& timer) { return timer.id == id; });
+ }
+
+ /**
+ * Returns 0 if there is no timer.
+ */
+ std::chrono::milliseconds NextTimeout(
+ std::chrono::steady_clock::time_point now) {
+ std::unique_lock lock(mutex_);
+
+ if (timers_.empty()) return std::chrono::milliseconds::zero();
+
+ return std::ranges::min(
+ timers_ | std::views::transform([now](const TimerData& timer) {
+ return timer.NextTimeout(now);
+ }));
+ }
+
+ std::optional<UpdateResult> Update(
+ std::chrono::steady_clock::time_point new_time) {
+ std::unique_lock lock(mutex_);
+ for (auto iter = timers_.begin(); iter != timers_.end(); ++iter) {
+ auto& timer = *iter;
+ if (timer.Update(new_time)) {
+ if (timer.repeat) {
+ return UpdateResult{timer.id, timer.data};
+ } else {
+ D data(std::move(timer.data));
+ timers_.erase(iter); // We will return, so it's safe to erase here.
+ return UpdateResult{timer.id, std::move(data)};
+ }
+ }
+ }
+ return std::nullopt;
+ }
+
+ private:
+ std::mutex mutex_;
+ int next_id_;
+ std::list<TimerData> timers_;
+};
+} // namespace cru