HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
sync_clock.hpp
1// Copyright Take Vos 2019-2020.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "required.hpp"
8#include "thread.hpp"
9#include "bigint.hpp"
10#include <fmt/format.h>
11#include <chrono>
12#include <algorithm>
13#include <string>
14#include <vector>
15#include <optional>
16#include <atomic>
17
18using namespace std::literals::chrono_literals;
19
20namespace tt {
21
22template<typename C1, typename C2>
24 using slow_clock = C1;
25 using fast_clock = C2;
26
27 struct time_point_pair {
28 typename slow_clock::time_point slow;
29 typename fast_clock::time_point fast;
30 };
31
32 time_point_pair first_pair;
33 time_point_pair prev_pair;
34 time_point_pair last_pair;
35 int calibration_nr = 0;
36
37 static constexpr int gainShift = 60;
38 static constexpr double gainMultiplier = static_cast<double>(1ULL << gainShift);
39
40 std::atomic<int64_t> gain = 0;
42
45 typename slow_clock::duration leapsecond_offset = 0ns;
46
47 std::string name;
48
49public:
54 name(std::move(name))
55 {
56 // Do a first calibration of the clock.
57 // Second calibration is done by the calibrate_loop thread.
58 calibrate();
59 }
60
62 }
63
64 typename slow_clock::time_point convert(typename fast_clock::time_point fast_time) const noexcept {
65 return convert(gain.load(std::memory_order_relaxed), bias.load(std::memory_order::relaxed), fast_time);
66 }
67
68 typename slow_clock::duration convert(typename fast_clock::duration fast_duration) const noexcept {
69 return convert(gain.load(std::memory_order::relaxed), fast_duration);
70 }
71
72 /* Calibrate the sync clock.
73 * Should be called from the maintenance thread every 100ms.
74 */
75 void calibrate_tick() noexcept {
76 auto backoff = 0s;
77
78 if (calibration_nr > 2) {
79 backoff = (calibration_nr - 2) * 10s;
80 }
81
82 if (backoff > 120s) {
83 backoff = 120s;
84 }
85
86 if (last_pair.slow + backoff < slow_clock::now()) {
87 calibrate();
88 }
89 }
90
91private:
92 static time_point_pair makeCalibrationPoint() noexcept {
93 // We are going to read the slow clock twice sandwiched by fast clocks,
94 // we expect that it will not be interrupted by a time-slice more than once.
95 ttlet f1 = fast_clock::now();
96 ttlet s1 = slow_clock::now();
97 ttlet f2 = fast_clock::now();
98 ttlet s2 = slow_clock::now();
99 ttlet f3 = fast_clock::now();
100
101 if ((f2 - f1) < (f3 - f2)) {
102 return {s1, f1};
103 } else {
104 return {s2, f2};
105 }
106 }
107
108 void addCalibrationPoint() noexcept {
109 auto tp = makeCalibrationPoint();
110 if (calibration_nr++ == 0) {
111 first_pair = tp;
112 }
113 prev_pair = last_pair;
114 last_pair = tp;
115 }
116
117 int64_t getGain() noexcept {
118 // Calculate the gain between the current and first calibration.
119 ttlet diff_slow = static_cast<double>((last_pair.slow - first_pair.slow).count());
120 ttlet diff_fast = static_cast<double>((last_pair.fast - first_pair.fast).count());
121
122 if (calibration_nr < 2 || diff_fast == 0.0) {
123 return static_cast<int64_t>(gainMultiplier);
124 } else {
125 auto new_gain = diff_slow / diff_fast;
126 return static_cast<int64_t>(std::round(new_gain * gainMultiplier));
127 }
128 }
129
130 typename slow_clock::duration getBias(int64_t new_gain) noexcept {
131 // Get a large integer cpu_clock_value.
132 auto tmp = static_cast<ubig128>(last_pair.fast.time_since_epoch() / 1ns);
133
134 // Multiply with the integer gain, that is pre-multiplied.
135 tmp *= new_gain;
136
137 // Add half of the lost precision for proper rounding.
138 tmp += (1LL << (gainShift - 1));
139
140 // Remove gain-pre-multiplier.
141 tmp >>= gainShift;
142
143 ttlet now_fast_after_gain = typename slow_clock::duration(static_cast<typename slow_clock::rep>(tmp));
144
145 return (last_pair.slow.time_since_epoch() + leapsecond_offset) - now_fast_after_gain;
146 }
147
148 typename slow_clock::duration getLeapAdjustment(int64_t new_gain, typename slow_clock::duration new_bias)
149 {
150 // Check and update for leap second.
151 ttlet prev_fast_as_slow = convert(last_pair.fast);
152 ttlet next_fast_as_slow = convert(new_gain, new_bias, last_pair.fast);
153 ttlet diff_fast_as_slow = prev_fast_as_slow - next_fast_as_slow;
154
155 return
156 (diff_fast_as_slow >= 999ms && diff_fast_as_slow <= 1001ms) ?
157 -1s :
158 (diff_fast_as_slow >= -1001ms && diff_fast_as_slow <= -999ms) ?
159 +1s :
160 0s;
161 }
162
166 double getDrift() noexcept {
167 // Compare the new calibration point, with the old calibration data.
168 ttlet fast_to_slow_offset = convert(last_pair.fast) - last_pair.slow;
169 ttlet fast_to_slow_offset_ns = static_cast<double>(fast_to_slow_offset / 1ns);
170
171 ttlet duration_since_calibration = last_pair.slow - prev_pair.slow;
172 ttlet duration_since_calibration_ns = static_cast<double>(duration_since_calibration / 1ns);
173 return fast_to_slow_offset_ns / duration_since_calibration_ns;
174 }
175
176 void calibrate() noexcept {
177 addCalibrationPoint();
178
179 ttlet drift = getDrift();
180
181 ttlet do_gain_calibration = calibration_nr <= 5;
182
183 ttlet new_gain = do_gain_calibration ? getGain() : gain.load(std::memory_order::relaxed);
184 ttlet new_bias = getBias(new_gain);
185 ttlet leap_adjustment = getLeapAdjustment(new_gain, new_bias);
186
187 // XXX implement leap second testing.
188 if (leap_adjustment != 0ns) {
189 tt_log_info("Clock '{}' detected leap-second {} s", name, leap_adjustment / 1s);
190 }
191
192 if (do_gain_calibration) {
193 tt_log_info("Clock '{}' calibration {}: drift={:+} ns/s gain={:+.15} ns/tick",
194 name, calibration_nr, drift * 1000000000.0, new_gain / gainMultiplier
195 );
196 } else {
197 tt_log_info("Clock '{}' calibration {}: drift={:+} ns/s",
198 name, calibration_nr, drift * 1000000000.0
199 );
200 }
201
202 if (do_gain_calibration) {
203 gain.store(new_gain, std::memory_order::relaxed);
204 }
205 bias.store(new_bias + leap_adjustment, std::memory_order::relaxed);
206 leapsecond_offset += leap_adjustment;
207 }
208
209 typename slow_clock::duration convert(int64_t new_gain, typename fast_clock::duration fast_duration) const noexcept {
210 ttlet _new_gain = static_cast<uint64_t>(new_gain);
211 ttlet _fast_duration = static_cast<uint64_t>(fast_duration.count());
212
213 ttlet [lo, hi] = wide_multiply(_new_gain, _fast_duration);
214
215 static_assert(gainShift < 64);
216 ttlet slow_duration = (lo >> gainShift) | (hi << (64 - gainShift));
217
218 return typename slow_clock::duration(static_cast<typename slow_clock::rep>(slow_duration));
219 }
220
221 typename slow_clock::time_point convert(int64_t new_gain, typename slow_clock::duration new_bias, typename fast_clock::time_point fast_time) const noexcept {
222 ttlet slow_period = convert(new_gain, fast_time.time_since_epoch());
223 return typename slow_clock::time_point(slow_period) + new_bias;
224 }
225};
226
227template<typename C1, typename C2>
228inline sync_clock_calibration_type<C1,C2> *sync_clock_calibration = nullptr;
229
240template<typename C1, typename C2>
242 using slow_clock = C1;
243 using fast_clock = C2;
244
245 using rep = typename slow_clock::rep;
246 using period = typename slow_clock::period;
247 using duration = typename slow_clock::duration;
248 using time_point = typename slow_clock::time_point;
249 static const bool is_steady = slow_clock::is_steady;
250
253 static time_point convert(typename fast_clock::time_point fast_time) noexcept {
254 return sync_clock_calibration<slow_clock,fast_clock> != nullptr ?
255 sync_clock_calibration<slow_clock,fast_clock>->convert(fast_time) :
256 time_point(duration(0ns));
257 }
258
259 static duration convert(typename fast_clock::duration fast_duration) noexcept {
260 return sync_clock_calibration<slow_clock,fast_clock> != nullptr ?
261 sync_clock_calibration<slow_clock,fast_clock>->convert(fast_duration) :
262 duration(0ns);
263 }
264
265
266 static time_point now() noexcept {
267 return convert(fast_clock::now());
268 }
269};
270
271}
STL namespace.
Definition sync_clock.hpp:23
sync_clock_calibration_type(std::string name) noexcept
Construct a sync clock.
Definition sync_clock.hpp:53
Definition sync_clock.hpp:241
static time_point convert(typename fast_clock::time_point fast_time) noexcept
Definition sync_clock.hpp:253
T count(T... args)
T load(T... args)
T move(T... args)
T round(T... args)
T store(T... args)