John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 1 | #ifndef AOS_UTIL_PHASED_LOOP_H_ |
| 2 | #define AOS_UTIL_PHASED_LOOP_H_ |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 3 | |
Stephan Pleines | b117767 | 2024-05-27 17:48:32 -0700 | [diff] [blame] | 4 | #include <chrono> |
James Kuszmaul | 20dcc7c | 2023-01-20 11:06:31 -0800 | [diff] [blame] | 5 | #include <optional> |
| 6 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 7 | #include "aos/time/time.h" |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 8 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame] | 9 | namespace aos::time { |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 10 | |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 11 | // Handles sleeping until a fixed offset from some time interval. |
| 12 | class PhasedLoop { |
| 13 | public: |
| 14 | // For example, with interval = 1s and offset = 0.1s this will fire at: |
| 15 | // 0.1s |
| 16 | // 1.1s |
| 17 | // ... |
| 18 | // 10000.1s |
Austin Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 19 | // offset must be >= chrono::seconds(0) and < interval. |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 20 | PhasedLoop( |
| 21 | const monotonic_clock::duration interval, |
Austin Schuh | d32b362 | 2019-06-23 18:49:06 -0700 | [diff] [blame] | 22 | const monotonic_clock::time_point monotonic_now, |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 23 | const monotonic_clock::duration offset = monotonic_clock::duration(0)); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 24 | |
Austin Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 25 | // Updates the offset and interval. |
James Kuszmaul | 20dcc7c | 2023-01-20 11:06:31 -0800 | [diff] [blame] | 26 | // |
| 27 | // After a call to set_interval_and_offset with monotonic_now = nullopt, the |
| 28 | // following will hold, for any allowed values of interval and offset: |
| 29 | // auto original_time = loop.sleep_time(); |
| 30 | // loop.set_interval_and_offset(interval, offset); |
| 31 | // CHECK_LE(loop.sleep_time(), original_time); |
| 32 | // CHECK_EQ(0, loop.Iterate(original_time)); |
| 33 | // |
| 34 | // Note that this will not be the behavior that all (or even necessarily most) |
| 35 | // users want, since it doesn't necessarily preserve a "keep the iteration |
| 36 | // time as consistent as possible" concept. However, it *is* better defined |
| 37 | // than the alternative, where if you decrease the offset by, e.g., 1ms on a |
| 38 | // 100ms interval, then the behavior will vary depending on whather you are |
| 39 | // going from 0ms->999ms offset or from 1ms->0ms offset. |
| 40 | // |
| 41 | // If monotonic_now is set, then the following will hold: |
| 42 | // auto original_time = loop.sleep_time(); |
| 43 | // loop.set_interval_and_offset(interval, offset, monotonic_now); |
| 44 | // CHECK_LE(loop.sleep_time(), monotonic_now); |
| 45 | // CHECK_EQ(0, loop.Iterate(monotonic_now)); |
| 46 | void set_interval_and_offset( |
| 47 | const monotonic_clock::duration interval, |
| 48 | const monotonic_clock::duration offset, |
| 49 | std::optional<monotonic_clock::time_point> monotonic_now = std::nullopt); |
Austin Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 50 | |
| 51 | // Computes the offset given an interval and a time that we should trigger. |
| 52 | static monotonic_clock::duration OffsetFromIntervalAndTime( |
| 53 | const monotonic_clock::duration interval, |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 54 | const monotonic_clock::time_point monotonic_trigger); |
Austin Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 55 | |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 56 | // Resets the count of skipped iterations. |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 57 | // Iterate(monotonic_now) will return 1 and set sleep_time() to something |
| 58 | // within interval of monotonic_now. |
Austin Schuh | d32b362 | 2019-06-23 18:49:06 -0700 | [diff] [blame] | 59 | void Reset(const monotonic_clock::time_point monotonic_now) { |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 60 | Iterate(monotonic_now - interval_); |
| 61 | } |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 62 | |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 63 | // Calculates the next time to run after monotonic_now. |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 64 | // The result can be retrieved with sleep_time(). |
| 65 | // Returns the number of iterations which have passed (1 if this is called |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 66 | // often enough). This can be < 1 iff monotonic_now goes backwards between |
| 67 | // calls. |
Austin Schuh | 60e7794 | 2022-05-16 17:48:24 -0700 | [diff] [blame] | 68 | int Iterate( |
| 69 | const monotonic_clock::time_point monotonic_now = monotonic_clock::now()); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 70 | |
| 71 | // Sleeps until the next time and returns the number of iterations which have |
| 72 | // passed. |
| 73 | int SleepUntilNext() { |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 74 | const int r = Iterate(monotonic_clock::now()); |
| 75 | ::std::this_thread::sleep_until(sleep_time()); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 76 | return r; |
| 77 | } |
| 78 | |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 79 | monotonic_clock::time_point sleep_time() const { return last_time_; } |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 80 | |
Austin Schuh | de8a8ff | 2019-11-30 15:25:36 -0800 | [diff] [blame] | 81 | monotonic_clock::duration interval() const { return interval_; } |
| 82 | monotonic_clock::duration offset() const { return offset_; } |
| 83 | |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 84 | private: |
Austin Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 85 | monotonic_clock::duration interval_, offset_; |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 86 | |
| 87 | // The time we most recently slept until. |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 88 | monotonic_clock::time_point last_time_ = monotonic_clock::epoch(); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 89 | }; |
| 90 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame] | 91 | } // namespace aos::time |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 92 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 93 | #endif // AOS_UTIL_PHASED_LOOP_H_ |