Nilorea Library
C utilities for networking, threading, graphics
Loading...
Searching...
No Matches
n_clock_sync.c
Go to the documentation of this file.
1/*
2 * Nilorea Library
3 * Copyright (C) 2005-2026 Castagnier Mickael
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
28#include "nilorea/n_log.h"
29#include "nilorea/n_common.h"
30#include <string.h>
31#include <stdlib.h>
32
39static int cmp_double(const void* a, const void* b) {
40 double da = *(const double*)a;
41 double db = *(const double*)b;
42 if (da < db) return -1;
43 if (da > db) return 1;
44 return 0;
45}
46
48static double median_of(const double* samples, int count) {
49 if (count <= 0) return 0.0;
50
51 double tmp[N_CLOCK_SYNC_SAMPLE_COUNT];
52 int n = (count < N_CLOCK_SYNC_SAMPLE_COUNT) ? count : N_CLOCK_SYNC_SAMPLE_COUNT;
53 memcpy(tmp, samples, (size_t)n * sizeof(double));
54 qsort(tmp, (size_t)n, sizeof(double), cmp_double);
55
56 if (n % 2 == 1) {
57 return tmp[n / 2];
58 }
59 return (tmp[n / 2 - 1] + tmp[n / 2]) / 2.0;
60}
61
63 N_CLOCK_SYNC* cs = NULL;
64 Malloc(cs, N_CLOCK_SYNC, 1);
65 __n_assert(cs, return NULL);
66
67 memset(cs, 0, sizeof(N_CLOCK_SYNC));
68 cs->sample_index = 0;
69 cs->sample_count = 0;
70 cs->estimated_offset = 0.0;
71 cs->estimated_rtt = 0.0;
72 /* Set last_sync_time far enough in the past so first should_send returns TRUE */
74
75 return cs;
76}
77
79 __n_assert(cs, return);
80 __n_assert(*cs, return);
81 Free(*cs);
82 *cs = NULL;
83}
84
85int n_clock_sync_process_response(N_CLOCK_SYNC* cs, double client_send_time, double server_time, double local_now) {
86 __n_assert(cs, return FALSE);
87
88 double rtt = local_now - client_send_time;
89 if (rtt < 0.0) {
90 n_log(LOG_ERR, "n_clock_sync: negative RTT (%.4f), ignoring sample", rtt);
91 return FALSE;
92 }
93
94 double one_way = rtt / 2.0;
95 double offset = server_time + one_way - local_now;
96
97 /* Store in circular buffers */
98 cs->offset_samples[cs->sample_index] = offset;
99 cs->rtt_samples[cs->sample_index] = rtt;
102 cs->sample_count++;
103 }
104
105 /* Compute medians */
108
109 n_log(LOG_DEBUG, "n_clock_sync: sample %d, offset=%.4f rtt=%.4f (median offset=%.4f rtt=%.4f)",
110 cs->sample_count, offset, rtt, cs->estimated_offset, cs->estimated_rtt);
111
112 return TRUE;
113}
114
115double n_clock_sync_server_time(const N_CLOCK_SYNC* cs, double local_now) {
116 __n_assert(cs, return local_now);
117 return local_now + cs->estimated_offset;
118}
119
120int n_clock_sync_should_send(const N_CLOCK_SYNC* cs, double local_now) {
121 __n_assert(cs, return FALSE);
122 return (local_now - cs->last_sync_time) >= N_CLOCK_SYNC_INTERVAL;
123}
124
125void n_clock_sync_mark_sent(N_CLOCK_SYNC* cs, double local_now) {
126 __n_assert(cs, return);
127 cs->last_sync_time = local_now;
128}
129
int sample_index
current write position in circular buffer
double last_sync_time
local time of last sync request sent
double estimated_offset
add to local time to get estimated server time
double estimated_rtt
current estimated round-trip time
int sample_count
number of samples collected so far
double rtt_samples[11]
circular buffer of RTT values
double offset_samples[11]
circular buffer of offset estimates
#define N_CLOCK_SYNC_SAMPLE_COUNT
number of samples for the median filter
N_CLOCK_SYNC * n_clock_sync_new(void)
allocate and initialize a new clock sync estimator
#define N_CLOCK_SYNC_INTERVAL
default interval between sync requests in seconds
static int cmp_double(const void *a, const void *b)
comparison function for qsort on doubles
int n_clock_sync_should_send(const N_CLOCK_SYNC *cs, double local_now)
check if it's time to send a new sync request (returns TRUE/FALSE)
double n_clock_sync_server_time(const N_CLOCK_SYNC *cs, double local_now)
get estimated server time given a local time value
void n_clock_sync_delete(N_CLOCK_SYNC **cs)
free a clock sync estimator
void n_clock_sync_mark_sent(N_CLOCK_SYNC *cs, double local_now)
mark that a sync request was just sent
int n_clock_sync_process_response(N_CLOCK_SYNC *cs, double client_send_time, double server_time, double local_now)
record a sync response: client_send_time is the local time the request was sent, server_time is the s...
static double median_of(const double *samples, int count)
compute the median of an array of doubles
clock synchronization estimator
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
Definition n_common.h:203
#define __n_assert(__ptr, __ret)
macro to assert things
Definition n_common.h:278
#define Free(__ptr)
Free Handler to get errors.
Definition n_common.h:262
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
Definition n_log.h:88
#define LOG_DEBUG
debug-level messages
Definition n_log.h:83
#define LOG_ERR
error conditions
Definition n_log.h:75
Clock synchronization estimator for networked games.
Common headers and low-level functions & define.
Generic log system.