blob: dc2dc967253e91d24ab7a2549c6511d3fdecb154 [file] [log] [blame]
John Park33858a32018-09-28 23:05:48 -07001#ifndef AOS_UTIL_PHASED_LOOP_H_
2#define AOS_UTIL_PHASED_LOOP_H_
brians343bc112013-02-10 01:53:46 +00003
James Kuszmaul20dcc7c2023-01-20 11:06:31 -08004#include <optional>
5
John Park33858a32018-09-28 23:05:48 -07006#include "aos/time/time.h"
Brian Silvermandcaa3f72015-11-29 05:32:08 +00007
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -08008namespace aos::time {
brians343bc112013-02-10 01:53:46 +00009
Brian Silvermandcaa3f72015-11-29 05:32:08 +000010// Handles sleeping until a fixed offset from some time interval.
11class PhasedLoop {
12 public:
13 // For example, with interval = 1s and offset = 0.1s this will fire at:
14 // 0.1s
15 // 1.1s
16 // ...
17 // 10000.1s
Austin Schuhf2a50ba2016-12-24 16:16:26 -080018 // offset must be >= chrono::seconds(0) and < interval.
Austin Schuh8aec1ed2016-05-01 13:29:20 -070019 PhasedLoop(
20 const monotonic_clock::duration interval,
Austin Schuhd32b3622019-06-23 18:49:06 -070021 const monotonic_clock::time_point monotonic_now,
Austin Schuhf257f3c2019-10-27 21:00:43 -070022 const monotonic_clock::duration offset = monotonic_clock::duration(0));
Brian Silvermandcaa3f72015-11-29 05:32:08 +000023
Austin Schuh5d4b0982017-04-08 14:36:08 -070024 // Updates the offset and interval.
James Kuszmaul20dcc7c2023-01-20 11:06:31 -080025 //
26 // After a call to set_interval_and_offset with monotonic_now = nullopt, the
27 // following will hold, for any allowed values of interval and offset:
28 // auto original_time = loop.sleep_time();
29 // loop.set_interval_and_offset(interval, offset);
30 // CHECK_LE(loop.sleep_time(), original_time);
31 // CHECK_EQ(0, loop.Iterate(original_time));
32 //
33 // Note that this will not be the behavior that all (or even necessarily most)
34 // users want, since it doesn't necessarily preserve a "keep the iteration
35 // time as consistent as possible" concept. However, it *is* better defined
36 // than the alternative, where if you decrease the offset by, e.g., 1ms on a
37 // 100ms interval, then the behavior will vary depending on whather you are
38 // going from 0ms->999ms offset or from 1ms->0ms offset.
39 //
40 // If monotonic_now is set, then the following will hold:
41 // auto original_time = loop.sleep_time();
42 // loop.set_interval_and_offset(interval, offset, monotonic_now);
43 // CHECK_LE(loop.sleep_time(), monotonic_now);
44 // CHECK_EQ(0, loop.Iterate(monotonic_now));
45 void set_interval_and_offset(
46 const monotonic_clock::duration interval,
47 const monotonic_clock::duration offset,
48 std::optional<monotonic_clock::time_point> monotonic_now = std::nullopt);
Austin Schuh5d4b0982017-04-08 14:36:08 -070049
50 // Computes the offset given an interval and a time that we should trigger.
51 static monotonic_clock::duration OffsetFromIntervalAndTime(
52 const monotonic_clock::duration interval,
Austin Schuhf257f3c2019-10-27 21:00:43 -070053 const monotonic_clock::time_point monotonic_trigger);
Austin Schuh5d4b0982017-04-08 14:36:08 -070054
Brian Silvermandcaa3f72015-11-29 05:32:08 +000055 // Resets the count of skipped iterations.
Austin Schuh8aec1ed2016-05-01 13:29:20 -070056 // Iterate(monotonic_now) will return 1 and set sleep_time() to something
57 // within interval of monotonic_now.
Austin Schuhd32b3622019-06-23 18:49:06 -070058 void Reset(const monotonic_clock::time_point monotonic_now) {
Austin Schuh8aec1ed2016-05-01 13:29:20 -070059 Iterate(monotonic_now - interval_);
60 }
Brian Silvermandcaa3f72015-11-29 05:32:08 +000061
Austin Schuh8aec1ed2016-05-01 13:29:20 -070062 // Calculates the next time to run after monotonic_now.
Brian Silvermandcaa3f72015-11-29 05:32:08 +000063 // The result can be retrieved with sleep_time().
64 // Returns the number of iterations which have passed (1 if this is called
Austin Schuh8aec1ed2016-05-01 13:29:20 -070065 // often enough). This can be < 1 iff monotonic_now goes backwards between
66 // calls.
Austin Schuh60e77942022-05-16 17:48:24 -070067 int Iterate(
68 const monotonic_clock::time_point monotonic_now = monotonic_clock::now());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000069
70 // Sleeps until the next time and returns the number of iterations which have
71 // passed.
72 int SleepUntilNext() {
Austin Schuh8aec1ed2016-05-01 13:29:20 -070073 const int r = Iterate(monotonic_clock::now());
74 ::std::this_thread::sleep_until(sleep_time());
Brian Silvermandcaa3f72015-11-29 05:32:08 +000075 return r;
76 }
77
Austin Schuh8aec1ed2016-05-01 13:29:20 -070078 monotonic_clock::time_point sleep_time() const { return last_time_; }
Brian Silvermandcaa3f72015-11-29 05:32:08 +000079
Austin Schuhde8a8ff2019-11-30 15:25:36 -080080 monotonic_clock::duration interval() const { return interval_; }
81 monotonic_clock::duration offset() const { return offset_; }
82
Brian Silvermandcaa3f72015-11-29 05:32:08 +000083 private:
Austin Schuh5d4b0982017-04-08 14:36:08 -070084 monotonic_clock::duration interval_, offset_;
Brian Silvermandcaa3f72015-11-29 05:32:08 +000085
86 // The time we most recently slept until.
Austin Schuh8aec1ed2016-05-01 13:29:20 -070087 monotonic_clock::time_point last_time_ = monotonic_clock::epoch();
Brian Silvermandcaa3f72015-11-29 05:32:08 +000088};
89
Stephan Pleinesd99b1ee2024-02-02 20:56:44 -080090} // namespace aos::time
brians343bc112013-02-10 01:53:46 +000091
John Park33858a32018-09-28 23:05:48 -070092#endif // AOS_UTIL_PHASED_LOOP_H_