aboutsummaryrefslogtreecommitdiff
path: root/include/cru/base/Timer.h
blob: 5508bccfcd00e4da50c93cbb28b297c96fd38374 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#pragma once

#include "Base.h"

#include <algorithm>
#include <chrono>
#include <list>
#include <mutex>
#include <optional>
#include <ranges>

namespace cru {
template <typename D>
class TimerRegistry : public Object {
 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 interval == std::chrono::milliseconds::zero()
                 ? std::chrono::milliseconds::zero()
                 : std::chrono::duration_cast<std::chrono::milliseconds>(
                       interval - (now - created) % interval);
    }

    bool Update(std::chrono::steady_clock::time_point now) {
      auto next_trigger =
          interval == std::chrono::milliseconds::zero()
              ? last_check
              : 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 nullopt if there is no timer.
   */
  std::optional<std::chrono::milliseconds> NextTimeout(
      std::chrono::steady_clock::time_point now) {
    std::unique_lock lock(mutex_);

    if (timers_.empty()) return std::nullopt;

    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 {
          UpdateResult result{timer.id, std::move(timer.data)};
          timers_.erase(iter);  // We will return, so it's safe to erase here.
          return result;
        }
      }
    }
    return std::nullopt;
  }

 private:
  std::mutex mutex_;
  int next_id_;
  std::list<TimerData> timers_;
};
}  // namespace cru