Timing

#include "libtini/timing.h"

This header gives fixed-rate game updates from a device clock, and sampled display edges. The module uses no hardware. The clock comes from a source function, usually ti_audio_ticks from the audio driver.

FunctionPurpose
ti_steps_beginStart to count fixed-rate updates from a clock
ti_steps_dueGet the updates that are due since the last call
ti_steps_resetStart again from the current time and drop the backlog
ti_time_beginStart to observe elapsed time and display edges
ti_time_pollSample the elapsed ticks and count display edges
ti_time_note_gapRecord that work can hide display edges

Types

ti_tick_source

typedef uint32_t (*ti_tick_source)(void);

A clock is a function that returns a count. A real device increases the count at a fixed rate that does not change with the CPU speed. The count is modulo 232.

The function must return an atomic snapshot in the foreground. It must not change mappings or call libtini. ti_audio_ticks is such a function, at a nominal 100 ticks each second. A CPU delay loop is not a valid source.

ti_time_sample

#define TI_TIME_ELAPSED_VALID 1
#define TI_TIME_GAP           2
typedef struct ti_time_sample {
    uint32_t display_edges; /* observed 0->1 $C019 edges, a lower bound */
    uint32_t elapsed_ticks; /* modulo difference since ti_time_begin */
    uint8_t flags;
} ti_time_sample;
display_edges
The blank-to-active changes of the //e flag at $C019 that ti_time_poll saw. The poll cannot see full frames between two polls. Thus this value is a lower limit, not a frame count.
elapsed_ticks
The source ticks since ti_time_begin, as an unsigned modulo difference. Zero when there is no source.
flags
TI_TIME_ELAPSED_VALID when a source is set. TI_TIME_GAP when a call to ti_time_note_gap occurred since begin. Memory, graphics and storage calls set this flag, because their work can hide edges.

Fixed-rate steps

A game simulates at a fixed rate and draws as frequently as it can. These functions change device ticks into a number of simulation steps. They keep the part of a step that is left for the next call. They limit the catch-up after a slow frame. They report long gaps and do not add time that did not occur.

The functions keep their state in static variables and are not reentrant. Game loop and timing shows a full loop.

ti_steps_begin

#define TI_STEPS_GAP 255
ti_error ti_steps_begin(ti_tick_source source, uint16_t ticks_per_second,
                        uint8_t steps_per_second, uint8_t max_steps);

This function starts to count steps_per_second updates each second. It uses source, which increases ticks_per_second times each second. The count starts at the current time.

source
The device clock, for example ti_audio_ticks.
ticks_per_second
The rate of the clock, for example TI_AUDIO_TICKS_PER_SECOND.
steps_per_second
Updates each second, 1 or more. ticks_per_second * (steps_per_second + 1) must be 65,535 or less. With the 100 Hz audio clock, all rates to 255 are correct.
max_steps
The maximum number of steps that one call returns, 1 to 254. The function drops the time above this limit. Thus a slow frame makes the game slower and does not add a large number of updates.

Returns

TI_OK; TI_E_ARGUMENT for a NULL source, a zero rate, or a max_steps of 0 or 255; TI_E_RANGE when the product above is more than 65,535.

ti_steps_begin(ti_audio_ticks, TI_AUDIO_TICKS_PER_SECOND, 30, 30);  /* 30 updates each second */

ti_steps_due

uint8_t ti_steps_due(void);

This function returns the number of updates that are due since the last call, 0 to max_steps. The part of a step that is left stays for the next call. Before ti_steps_begin, the function returns 0.

When more than one second of ticks occurs between two calls, the function returns TI_STEPS_GAP (255) and starts the time again from the current tick. A game usually pauses then. A paused audio clock does not increase, for example during a file call. Thus that pause is not a gap.

The function keeps only the low 16 bits of the clock between calls. A pause of 65,536 ticks or more is approximately eleven minutes at 100 Hz. After such a pause, call ti_steps_reset before you continue.

ti_steps_reset

void ti_steps_reset(void);

This function starts the count again from the current value of the clock and drops the partial step. Use it, for example, when a game closes its pause screen.

Elapsed time and display edges

ti_time_begin

ti_error ti_time_begin(ti_tick_source source, uint16_t ticks_per_second);

This function starts the observation. With a source, the elapsed ticks start at the current value of the source. ti_time_begin(NULL, 0) selects display observation without elapsed time. The function clears the edge count and the gap flag.

Returns

TI_OK, or TI_E_ARGUMENT when only one of source and ticks_per_second is set.

ti_time_poll

ti_error ti_time_poll(ti_time_sample *sample);

This function samples the display flag one time. When the flag changed from blank to active since the last poll, the function counts one edge. Then it fills *sample. The elapsed ticks accept one wrap of the 32-bit source. Call begin again before 232 ticks occur.

Returns

TI_OK; TI_E_UNAVAILABLE when there is no source (the sample is filled, the elapsed value is zero and TI_TIME_ELAPSED_VALID is clear); TI_E_ARGUMENT when sample is not writable near memory.

ti_time_note_gap

void ti_time_note_gap(void);

This function sets TI_TIME_GAP until the next ti_time_begin. The library calls it after memory transfers, draw calls, publication, storage and audio gaps. A game can call it around its own long work. It does not count missed frames.