cpptime.h 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. #ifndef CPPTIME_H_
  2. #define CPPTIME_H_
  3. /**
  4. * The MIT License (MIT)
  5. *
  6. * Copyright (c) 2015 Michael Egli
  7. *
  8. * Permission is hereby granted, free of charge, to any person obtaining a copy
  9. * of this software and associated documentation files (the "Software"), to deal
  10. * in the Software without restriction, including without limitation the rights
  11. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  12. * copies of the Software, and to permit persons to whom the Software is
  13. * furnished to do so, subject to the following conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be included in
  16. * all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  19. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  20. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  21. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  22. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  23. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  24. * THE SOFTWARE.
  25. *
  26. * \author Michael Egli
  27. * \copyright Michael Egli
  28. * \date 11-Jul-2015
  29. *
  30. * \file cpptime.h
  31. *
  32. * C++11 timer component
  33. * =====================
  34. *
  35. * A portable, header-only C++11 timer component.
  36. *
  37. * Overview
  38. * --------
  39. *
  40. * This component can be used to manage a set of timeouts. It is implemented in
  41. * pure C++11. It is therefore very portable given a compliant compiler.
  42. *
  43. * A timeout can be added with one of the `add` functions, and removed with the
  44. * `remove` function. A timeout can be either one-shot or periodic. In case a
  45. * timeout is one-shot, the callback is invoked once and the timeout event is
  46. * then automatically removed. If the timer is periodic, it is never
  47. * automatically removed, but always renewed.
  48. *
  49. * Removing a timeout is possible even from within the callback.
  50. *
  51. * Timeout Units
  52. * -------------
  53. *
  54. * The preferred functions for adding timeouts are those that take a
  55. * `std::chrono::...` argument. However, for convenience, there is also an API
  56. * that takes a uint64_t. When using this API, all values are expected to be
  57. * given in microseconds (us).
  58. *
  59. * For periodic timeouts, a separate timeout can be specified for the initial
  60. * (first) timeout, and the periodicity after that.
  61. *
  62. * To avoid drifts, times are added by simply adding the period to the intially
  63. * calculated (or provided) time. Also, we use `wait until` type of API to wait
  64. * for a timeout instead of a `wait for` API.
  65. *
  66. * Data Structure
  67. * --------------
  68. *
  69. * Internally, a std::vector is used to store timeout events. The timer_id
  70. * returned from the `add` functions are used as index to this vector.
  71. *
  72. * In addition, a std::multiset is used that holds all time points when
  73. * timeouts expire.
  74. *
  75. * Using a vector to store timeout events has some implications. It is very
  76. * fast to remove an event, because the timer_id is the vector's index. On the
  77. * other hand, this makes it also more complicated to manage the timer_ids. The
  78. * current solution is to keep track of ids that are freed in order to re-use
  79. * them. A stack is used for this.
  80. *
  81. * Examples
  82. * --------
  83. *
  84. * More examples can be found in the `tests` folder.
  85. *
  86. * ~~~
  87. * CppTime::Timer t;
  88. * t.add(std::chrono::seconds(1), [](CppTime::timer_id){ std::cout << "got it!"; });
  89. * std::this_thread::sleep_for(std::chrono::seconds(2));
  90. * ~~~
  91. */
  92. // Includes
  93. #include <functional>
  94. #include <thread>
  95. #include <mutex>
  96. #include <condition_variable>
  97. #include <chrono>
  98. #include <algorithm>
  99. #include <vector>
  100. #include <stack>
  101. #include <set>
  102. namespace CppTime
  103. {
  104. // Public types
  105. using timer_id = std::size_t;
  106. using handler_t = std::function<void(timer_id)>;
  107. using clock = std::chrono::steady_clock;
  108. using timestamp = std::chrono::time_point<clock>;
  109. using duration = std::chrono::microseconds;
  110. // Private definitions. Do not rely on this namespace.
  111. namespace detail
  112. {
  113. // The event structure that holds the information about a timer.
  114. struct Event {
  115. timer_id id;
  116. timestamp start;
  117. duration period;
  118. handler_t handler;
  119. bool valid;
  120. Event()
  121. : id(0), start(duration::zero()), period(duration::zero()), handler(nullptr), valid(false)
  122. {
  123. }
  124. template <typename Func>
  125. Event(timer_id id, timestamp start, duration period, Func &&handler)
  126. : id(id), start(start), period(period), handler(std::forward<Func>(handler)), valid(true)
  127. {
  128. }
  129. Event(Event &&r) = default;
  130. Event &operator=(Event &&ev) = default;
  131. Event(const Event &r) = delete;
  132. Event &operator=(const Event &r) = delete;
  133. };
  134. // A time event structure that holds the next timeout and a reference to its
  135. // Event struct.
  136. struct Time_event {
  137. timestamp next;
  138. timer_id ref;
  139. };
  140. inline bool operator<(const Time_event &l, const Time_event &r)
  141. {
  142. return l.next < r.next;
  143. }
  144. } // end namespace detail
  145. class Timer
  146. {
  147. using scoped_m = std::unique_lock<std::mutex>;
  148. // Thread and locking variables.
  149. std::mutex m;
  150. std::condition_variable cond;
  151. std::thread worker;
  152. // Use to terminate the timer thread.
  153. bool done = false;
  154. // The vector that holds all active events.
  155. std::vector<detail::Event> events;
  156. // Sorted queue that has the next timeout at its top.
  157. std::multiset<detail::Time_event> time_events;
  158. // A list of ids to be re-used. If possible, ids are used from this pool.
  159. std::stack<CppTime::timer_id> free_ids;
  160. public:
  161. Timer() : m{}, cond{}, worker{}, events{}, time_events{}, free_ids{}
  162. {
  163. scoped_m lock(m);
  164. done = false;
  165. worker = std::thread([this]{ run(); });
  166. }
  167. ~Timer()
  168. {
  169. scoped_m lock(m);
  170. done = true;
  171. lock.unlock();
  172. cond.notify_all();
  173. worker.join();
  174. events.clear();
  175. time_events.clear();
  176. while(!free_ids.empty()) {
  177. free_ids.pop();
  178. }
  179. }
  180. /**
  181. * Add a new timer.
  182. *
  183. * \param when The time at which the handler is invoked.
  184. * \param handler The callable that is invoked when the timer fires.
  185. * \param period The periodicity at which the timer fires. Only used for periodic timers.
  186. */
  187. timer_id add(
  188. const timestamp &when, handler_t &&handler, const duration &period = duration::zero())
  189. {
  190. scoped_m lock(m);
  191. timer_id id = 0;
  192. // Add a new event. Prefer an existing and free id. If none is available, add
  193. // a new one.
  194. if(free_ids.empty()) {
  195. id = events.size();
  196. detail::Event e(id, when, period, std::move(handler));
  197. events.push_back(std::move(e));
  198. } else {
  199. id = free_ids.top();
  200. free_ids.pop();
  201. detail::Event e(id, when, period, std::move(handler));
  202. events[id] = std::move(e);
  203. }
  204. time_events.insert(detail::Time_event{when, id});
  205. lock.unlock();
  206. cond.notify_all();
  207. return id;
  208. }
  209. /**
  210. * Overloaded `add` function that uses a `std::chrono::duration` instead of a
  211. * `time_point` for the first timeout.
  212. */
  213. template <class Rep, class Period>
  214. inline timer_id add(const std::chrono::duration<Rep, Period> &when, handler_t &&handler,
  215. const duration &period = duration::zero())
  216. {
  217. return add(clock::now() + std::chrono::duration_cast<std::chrono::microseconds>(when),
  218. std::move(handler), period);
  219. }
  220. /**
  221. * Overloaded `add` function that uses a uint64_t instead of a `time_point` for
  222. * the first timeout and the period.
  223. */
  224. inline timer_id add(const uint64_t when, handler_t &&handler, const uint64_t period = 0)
  225. {
  226. return add(duration(when), std::move(handler), duration(period));
  227. }
  228. /**
  229. * Removes the timer with the given id.
  230. */
  231. bool remove(timer_id id)
  232. {
  233. scoped_m lock(m);
  234. if(events.size() == 0 || events.size() < id) {
  235. return false;
  236. }
  237. events[id].valid = false;
  238. auto it = std::find_if(time_events.begin(), time_events.end(),
  239. [&](const detail::Time_event &te) { return te.ref == id; });
  240. if(it != time_events.end()) {
  241. free_ids.push(it->ref);
  242. time_events.erase(it);
  243. }
  244. lock.unlock();
  245. cond.notify_all();
  246. return true;
  247. }
  248. private:
  249. void run()
  250. {
  251. scoped_m lock(m);
  252. while(!done) {
  253. if(time_events.empty()) {
  254. // Wait for work
  255. cond.wait(lock);
  256. } else {
  257. detail::Time_event te = *time_events.begin();
  258. if(CppTime::clock::now() >= te.next) {
  259. // Remove time event
  260. time_events.erase(time_events.begin());
  261. // Invoke the handler
  262. lock.unlock();
  263. events[te.ref].handler(te.ref);
  264. lock.lock();
  265. if(events[te.ref].valid && events[te.ref].period.count() > 0) {
  266. // The event is valid and a periodic timer.
  267. te.next += events[te.ref].period;
  268. time_events.insert(te);
  269. } else {
  270. // The event is either no longer valid because it was removed in the
  271. // callback, or it is a one-shot timer.
  272. events[te.ref].valid = false;
  273. free_ids.push(te.ref);
  274. }
  275. } else {
  276. cond.wait_until(lock, te.next);
  277. }
  278. }
  279. }
  280. }
  281. };
  282. } // end namespace CppTime
  283. #endif // CPPTIME_H_