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