Skip to content

Latest commit

 

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gnomon

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.

Why did we make gnomon?

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.

Design

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.

Backends

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.

Building

You need a C11 compiler and CMake 3.11+.

cmake -B build
cmake --build build
ctest --test-dir build --output-on-failure

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

Usage

Reading time

#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);

Stopwatch

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

The game loop

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.

API reference

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.

License

This project is licensed under MIT, you can find it here.

About

High-resolution monotonic clock, stopwatch, and frame timer for C/C++ game engines.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages