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 | |
James Kuszmaul | 20dcc7c | 2023-01-20 11:06:31 -0800 | [diff] [blame] | 4 | #include <optional> |
| 5 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 6 | #include "aos/time/time.h" |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 7 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame^] | 8 | namespace aos::time { |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 9 | |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 10 | // Handles sleeping until a fixed offset from some time interval. |
| 11 | class 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 Schuh | f2a50ba | 2016-12-24 16:16:26 -0800 | [diff] [blame] | 18 | // offset must be >= chrono::seconds(0) and < interval. |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 19 | PhasedLoop( |
| 20 | const monotonic_clock::duration interval, |
Austin Schuh | d32b362 | 2019-06-23 18:49:06 -0700 | [diff] [blame] | 21 | const monotonic_clock::time_point monotonic_now, |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 22 | const monotonic_clock::duration offset = monotonic_clock::duration(0)); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 23 | |
Austin Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 24 | // Updates the offset and interval. |
James Kuszmaul | 20dcc7c | 2023-01-20 11:06:31 -0800 | [diff] [blame] | 25 | // |
| 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 Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 49 | |
| 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 Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 53 | const monotonic_clock::time_point monotonic_trigger); |
Austin Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 54 | |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 55 | // Resets the count of skipped iterations. |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 56 | // Iterate(monotonic_now) will return 1 and set sleep_time() to something |
| 57 | // within interval of monotonic_now. |
Austin Schuh | d32b362 | 2019-06-23 18:49:06 -0700 | [diff] [blame] | 58 | void Reset(const monotonic_clock::time_point monotonic_now) { |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 59 | Iterate(monotonic_now - interval_); |
| 60 | } |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 61 | |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 62 | // Calculates the next time to run after monotonic_now. |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 63 | // The result can be retrieved with sleep_time(). |
| 64 | // 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] | 65 | // often enough). This can be < 1 iff monotonic_now goes backwards between |
| 66 | // calls. |
Austin Schuh | 60e7794 | 2022-05-16 17:48:24 -0700 | [diff] [blame] | 67 | int Iterate( |
| 68 | const monotonic_clock::time_point monotonic_now = monotonic_clock::now()); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 69 | |
| 70 | // Sleeps until the next time and returns the number of iterations which have |
| 71 | // passed. |
| 72 | int SleepUntilNext() { |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 73 | const int r = Iterate(monotonic_clock::now()); |
| 74 | ::std::this_thread::sleep_until(sleep_time()); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 75 | return r; |
| 76 | } |
| 77 | |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 78 | monotonic_clock::time_point sleep_time() const { return last_time_; } |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 79 | |
Austin Schuh | de8a8ff | 2019-11-30 15:25:36 -0800 | [diff] [blame] | 80 | monotonic_clock::duration interval() const { return interval_; } |
| 81 | monotonic_clock::duration offset() const { return offset_; } |
| 82 | |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 83 | private: |
Austin Schuh | 5d4b098 | 2017-04-08 14:36:08 -0700 | [diff] [blame] | 84 | monotonic_clock::duration interval_, offset_; |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 85 | |
| 86 | // The time we most recently slept until. |
Austin Schuh | 8aec1ed | 2016-05-01 13:29:20 -0700 | [diff] [blame] | 87 | monotonic_clock::time_point last_time_ = monotonic_clock::epoch(); |
Brian Silverman | dcaa3f7 | 2015-11-29 05:32:08 +0000 | [diff] [blame] | 88 | }; |
| 89 | |
Stephan Pleines | d99b1ee | 2024-02-02 20:56:44 -0800 | [diff] [blame^] | 90 | } // namespace aos::time |
brians | 343bc11 | 2013-02-10 01:53:46 +0000 | [diff] [blame] | 91 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 92 | #endif // AOS_UTIL_PHASED_LOOP_H_ |