5 Module CLOCK_SYNC
gullradriel edited this page 2026-10-09 00:55:18 +02:00

CLOCK_SYNC: client/server clock offset estimation

Structures

Name
struct N_CLOCK_SYNC
clock synchronization estimator

Functions

Name
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_seed(N_CLOCK_SYNC * cs, double offset, double rtt)
Inject a synthetic (offset, rtt) sample, e.g.
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 server's timestamp from the response, local_now is the current local time when the response was received.
N_CLOCK_SYNC * n_clock_sync_new(void )
allocate and initialize a new clock sync estimator
void n_clock_sync_mark_sent(N_CLOCK_SYNC * cs, double local_now)
mark that a sync request was just sent
void n_clock_sync_delete(N_CLOCK_SYNC ** cs)
free a clock sync estimator
double median_of(const double * samples, int count)
compute the median of an array of doubles
int cmp_double(const void * a, const void * b)
comparison function for qsort on doubles
void clock_sync_store_sample(N_CLOCK_SYNC * cs, double offset, double rtt, double local_now)
store one (offset, rtt) sample and recompute the estimates.
double clock_sync_effective_offset(const N_CLOCK_SYNC * cs, double local_now)
effective (slewed) offset at local_now, pure function of the stored state so the const getter needs no mutation.

Defines

Name
N_CLOCK_SYNC_SLEW_SECONDS
seconds over which n_clock_sync_server_time slews from the previous offset estimate to a new one (smoothstep).
N_CLOCK_SYNC_SAMPLE_COUNT
number of samples for the median filter
N_CLOCK_SYNC_MAX_RTT
responses with an RTT above this are rejected outright (retransmit storms / suspend-resume artefacts, not usable timing data)
N_CLOCK_SYNC_INTERVAL
default interval between sync requests in seconds
N_CLOCK_SYNC_BURST_SAMPLES
number of REAL samples below which the burst interval applies
N_CLOCK_SYNC_BURST_INTERVAL
accelerated interval used until N_CLOCK_SYNC_BURST_SAMPLES real responses have been collected, a fresh estimator converges in a couple of seconds instead of minutes

Functions Documentation

function n_clock_sync_should_send

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).

Uses N_CLOCK_SYNC_BURST_INTERVAL until N_CLOCK_SYNC_BURST_SAMPLES real responses have been collected, then N_CLOCK_SYNC_INTERVAL.

function n_clock_sync_server_time

double n_clock_sync_server_time(
    const N_CLOCK_SYNC * cs,
    double local_now
)

get estimated server time given a local time value.

When the underlying offset estimate changes, the returned time SLEWS from the previous estimate to the new one over N_CLOCK_SYNC_SLEW_SECONDS (smoothstep) instead of stepping, pure function of the stored state, safe to call at any rate.

function n_clock_sync_seed

void n_clock_sync_seed(
    N_CLOCK_SYNC * cs,
    double offset,
    double rtt
)

Inject a synthetic (offset, rtt) sample, e.g.

a server-supplied clock value at login, so the estimator is usable BEFORE the first request/response round-trip completes. Pass the believed offset (server_time - local_now) and a conservative synthetic rtt. RESETS the estimator to this single sample and adopts the offset immediately (no slew, at login there is nothing to slew from). The FIRST real response flushes the seed wholesale, so a seed with a synthetic rtt lower than the link's real RTT can never pin the estimate (offset selection is best-rtt, NTP-style).

function n_clock_sync_process_response

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 server's timestamp from the response, local_now is the current local time when the response was received.

Updates estimated_offset and estimated_rtt. Returns TRUE on success, FALSE on error.

function n_clock_sync_new

N_CLOCK_SYNC * n_clock_sync_new(
    void 
)

allocate and initialize a new clock sync estimator

function n_clock_sync_mark_sent

void n_clock_sync_mark_sent(
    N_CLOCK_SYNC * cs,
    double local_now
)

mark that a sync request was just sent

function n_clock_sync_delete

void n_clock_sync_delete(
    N_CLOCK_SYNC ** cs
)

free a clock sync estimator

function median_of

static double median_of(
    const double * samples,
    int count
)

compute the median of an array of doubles

function cmp_double

static int cmp_double(
    const void * a,
    const void * b
)

comparison function for qsort on doubles

function clock_sync_store_sample

static void clock_sync_store_sample(
    N_CLOCK_SYNC * cs,
    double offset,
    double rtt,
    double local_now
)

store one (offset, rtt) sample and recompute the estimates.

Offset selection is NTP-style: the sample with the LOWEST rtt wins, because its one-way uncertainty (+/-rtt/2) is the smallest. A plain median is poisoned for minutes when the link is congested at startup (e.g. an MMO client receiving its login chunk burst: every early sample carries seconds of queueing delay, and the median needs a majority of clean samples to recover). estimated_rtt stays a median, it feeds timeout heuristics, where typical beats best.

local_now anchors the slew when the adopted offset changes: n_clock_sync_server_time blends from the previous effective offset instead of stepping.

function clock_sync_effective_offset

static double clock_sync_effective_offset(
    const N_CLOCK_SYNC * cs,
    double local_now
)

effective (slewed) offset at local_now, pure function of the stored state so the const getter needs no mutation.

Blends offset_prev -> estimated_offset with a smoothstep over N_CLOCK_SYNC_SLEW_SECONDS from the moment the estimate last changed.

Macros Documentation

define N_CLOCK_SYNC_SLEW_SECONDS

#define N_CLOCK_SYNC_SLEW_SECONDS 1.0

seconds over which n_clock_sync_server_time slews from the previous offset estimate to a new one (smoothstep).

A consumer that anchors motion on this clock sees a bounded rate change instead of a step.

define N_CLOCK_SYNC_SAMPLE_COUNT

#define N_CLOCK_SYNC_SAMPLE_COUNT 11

number of samples for the median filter

define N_CLOCK_SYNC_MAX_RTT

#define N_CLOCK_SYNC_MAX_RTT 10.0

responses with an RTT above this are rejected outright (retransmit storms / suspend-resume artefacts, not usable timing data)

define N_CLOCK_SYNC_INTERVAL

#define N_CLOCK_SYNC_INTERVAL 3.0

default interval between sync requests in seconds

define N_CLOCK_SYNC_BURST_SAMPLES

#define N_CLOCK_SYNC_BURST_SAMPLES 5

number of REAL samples below which the burst interval applies

define N_CLOCK_SYNC_BURST_INTERVAL

#define N_CLOCK_SYNC_BURST_INTERVAL 0.5

accelerated interval used until N_CLOCK_SYNC_BURST_SAMPLES real responses have been collected, a fresh estimator converges in a couple of seconds instead of minutes