A high-resolution monotonic clock, stopwatch, and frame timer for C/C++. Self-contained,
zero external dependencies, cross-platform.
Named after the shadow-caster of a sundial, the one fixed piece that turns motion into a readable time.
We felt that many game engines need some form of time calculation regarding delta time,
chrono works, but engines typically run directly on the operating system.
gnomon tries to make it a little bit easier to maintain your monotonic timings, so your timings stay consistent no
matter which rendering API you're on, specifically designed for your engine's needs.
gnomon consists of three layers, strictly separated. The clock is the source;
the timer and the frame clock are consumers built on top of it. All platform-specific
code lives in the clock and nowhere else.
- Clock (
GnoClock): reads the platform's hardware counter. It caches the counter frequency and a creation epoch once, so nothing downstream has to touch the operating system again. - Timer (
GnoTimer): a stopwatch over a clock. Start, stop, resume; it banks elapsed time across pauses. - Frame clock (
GnoFrameClock): game-loop timing over a clock. A variable delta for gameplay and input, a fixed-timestep accumulator for physics, delta clamping, and time scaling for pause and slow-mo.
Because the platform code is quarantined in the clock, adding or fixing a backend touches exactly one place, and the timer and frame clock never change.
The clock picks the best source for each platform at compile time:
| Platform | Source |
|---|---|
| Windows | QueryPerformanceCounter |
| macOS | currently not supported... |
| Linux / other | clock_gettime(CLOCK_MONOTONIC_RAW) |
Two things worth knowing. The frequency is read once at creation, so
converting ticks to seconds never costs a syscall. And gnoGetClockNow returns
ticks since the clock was created, a meaningful zero point, while intervals
stay correct, since the epoch cancels when you subtract two readings.
You need a C11 compiler and CMake 3.11+.
cmake -B build
cmake --build build
ctest --test-dir build --output-on-failureFrom your own project, just link it:
target_link_libraries(your_engine PRIVATE gnomon)The API is C, but the headers are extern "C"-guarded, so it drops straight into
a C++ engine.
#include "gnomon/gnomon.h"
GnoClockCreateInfo ci = { GNO_STRUCTURE_TYPE_CLOCK_CREATE_INFO, NULL, 0 };
GnoClock clock;
gnoCreateClock(&ci, NULL, &clock); /* NULL = default allocator */
GnoTick a = gnoGetClockNow(clock);
/* ... work ... */
GnoTick b = gnoGetClockNow(clock);
printf("%.3f ms\n", gnoTicksToMilliseconds(clock, b - a));
gnoDestroyClock(clock, NULL);Pass GNO_TIMER_CREATE_START_BIT to start on creation. Stopping banks the
elapsed time; starting again resumes and adds to it.
GnoTimerCreateInfo ti = { GNO_STRUCTURE_TYPE_TIMER_CREATE_INFO, NULL,
GNO_TIMER_CREATE_START_BIT };
GnoTimer timer;
gnoCreateTimer(clock, &ti, NULL, &timer);
/* ... work ... */
gnoStopTimer(timer);
printf("%.3f ms\n", gnoGetTimerMilliseconds(timer));
gnoDestroyTimer(timer, NULL);This is what gnomon is really for. The variable delta drives gameplay and
input; the fixed-step accumulator drives deterministic physics; alpha lets you
interpolate the render between physics steps.
GnoFrameClockCreateInfo fi = { GNO_STRUCTURE_TYPE_FRAME_CLOCK_CREATE_INFO,
NULL, 0,
60.0, /* fixedHz: physics rate */
0.25, /* maxDelta: hitch clamp (sec) */
1.0 }; /* timeScale: 1 = normal */
GnoFrameClock frame;
gnoCreateFrameClock(clock, &fi, NULL, &frame);
while (running) {
double dt = gnoTickFrameClock(frame); /* variable dt for gameplay */
process_input(dt);
while (gnoStepFrameClock(frame)) /* runs 0..N times */
physics_update(gnoGetFrameClockFixedDelta(frame));
render(gnoGetFrameClockAlpha(frame)); /* interpolate physics state */
}
gnoSetFrameClockTimeScale(frame, 0.0); /* pause */
gnoSetFrameClockTimeScale(frame, 0.5); /* half-speed slow-mo */
gnoDestroyFrameClock(frame, NULL);maxDelta clamps a single frame's delta, so a breakpoint or a long hitch can't
send your physics into a catch-up spiral.
Clock
| Function | Purpose |
|---|---|
gnoCreateClock / gnoDestroyClock |
Lifecycle. |
gnoGetClockNow |
Ticks since clock creation. |
gnoGetClockFrequency |
Counter ticks per second. |
gnoTicksToSeconds / gnoTicksToMilliseconds |
Convert a tick delta. |
gnoEnumerateClockSources |
Query available timing backends. |
Timer
| Function | Purpose |
|---|---|
gnoCreateTimer / gnoDestroyTimer |
Lifecycle. |
gnoStartTimer / gnoStopTimer / gnoResetTimer |
Control. |
gnoGetTimerMilliseconds |
Elapsed ms (safe while running). |
Frame clock
| Function | Purpose |
|---|---|
gnoCreateFrameClock / gnoDestroyFrameClock |
Lifecycle. |
gnoTickFrameClock |
Advance one frame; returns scaled variable dt. |
gnoStepFrameClock |
Consume one fixed step; loop until it returns false. |
gnoGetFrameClockAlpha |
Interpolation factor in [0, 1). |
gnoGetFrameClockFixedDelta |
The fixed physics step, in seconds. |
gnoSetFrameClockTimeScale |
1 = normal, 0 = paused, <1 = slow-mo. |
Every Create call takes a *CreateInfo whose sType is validated, plus an
optional GnoAllocationCallbacks* (pass NULL for malloc/free). Anything
that can fail returns GnoResult, where GNO_SUCCESS is 0.
This project is licensed under MIT, you can find it here.