State, timing and math utilities
These utilities do not choose runtime threads, input devices or graphics policy.
Their public headers state their ownership and preconditions; value types still
retain or borrow external objects according to the caller’s chosen type.
CE::State owns sampled state and publication, CE::Time owns clock/duration
helpers, and CE::Math owns mathematical representations such as packed bits.
Allocation policy and address operations belong to CE::Memory; numeric text
parsing belongs to CE::Text::Numbers, and drawable geometry to
CE::Rendering::Geometry. Header paths remain stable across these logical owners.
Sampled and synchronized state
CE::State::StateTracker<T> owns previous/current samples. It has no synchronization;
accessor references borrow the tracker. update() copies current to previous,
then assigns the new current sample. A throwing assignment follows T’s guarantee
and can leave partial state. changed() compares the samples; delta() subtracts
them in T’s units, rather than introducing a clock interval.
CE::State::VersionedVariable<T> protects one value/revision pair with a mutex. Use snapshot()
to copy them together; separate get() and revision() calls can see different
updates. Equal sets leave the revision unchanged. Comparison, assignment and
snapshot copying execute under the mutex and must not reenter the variable. Their
exceptions propagate. A throwing assignment can change T without advancing the
revision or notifying waiters; synchronized access alone promises no transactional
rollback for arbitrary T.
wait_for_change(saved_revision) blocks until the revision differs and returns
the current snapshot. Multiple updates can coalesce; it does not deliver every
intermediate value. Keep the object alive and ensure a producer can make progress
for the whole wait. There is no timeout, stop token or destruction cancellation.
CE::State::ObservedVariable<T> owns versioned storage and a fixed array of callbacks.
Every successful set invokes nonempty observers in array order on the setter’s
thread, including equal sets. Callbacks run outside the storage mutex and receive
a reference to that set’s argument, not a later concurrent snapshot. Do not retain
the reference beyond the call. Concurrent or reentrant sets can overlap callbacks;
callbacks protect their own borrowed targets. A callback exception propagates after
publication and skips remaining observers without rollback. Waiters still follow
value changes alone. wait_until_change() saves a revision at the time of that
call; retain an earlier revision explicitly when an earlier unread change matters.
Transition tables
CE::State::StateMachine<State, Trigger> owns state, rules and callback captures. Use one
owner and do not recursively trigger the same machine. state() borrows its
storage. Rules are examined in insertion order until the first matching successful
guard; no match returns false. Guards can inspect other machines or add rules;
new rules can be considered later in the same trigger.
For a selected rule the machine copies the rule and relevant hooks, then runs exit, assigns state, runs the transition action, and runs enter. Replacing hooks during exit does not replace the copied hook for that transition. Exceptions stop this sequence and propagate: exit failure leaves the old state; action/enter failure leaves the new state. Throwing state assignment follows State’s own guarantee. Callback side effects are not rolled back. Keep borrowed callback targets alive through invocation and capture destruction.
Timing and numeric helpers
Engine type introspection uses CTTI 1.1’s name_of and type_id_of APIs.
TYPENAME/TYPENAMEOF return fully qualified names as std::string_view;
the expression-name macro preserves decltype qualifiers without evaluating its
argument. Value type IDs apply decay; explicit type IDs preserve the supplied type.
CE::Time::DeltaTime samples its clock once per call, returns elapsed seconds and starts
the next interval at that sample. elapsed() only observes; checkin() resets.
It is caller-owned and unsynchronized. The default steady clock is monotonic;
a custom clock controls backward jumps and duration representation. Duration
aliases and CE::Time::duration_cast/CE::Time::to_hours/CE::Time::to_minutes/CE::Time::to_seconds/CE::Time::to_milliseconds follow standard duration_cast:
integer conversions truncate toward zero and callers ensure representability.
They add no finite-value, overflow or scheduling policy.
CE::Text::Numbers::parse_floats() requires a complete stod token, accepts its leading-space,
sign, locale and nonfinite-value behavior, and rejects trailing data. Integer
parsing uses decimal from_chars, without leading whitespace/plus; signed parsing
allows minus. Syntax errors throw invalid_args, range errors bad_request.
Integers choose the smallest fitting type of the selected signedness. The
CE::Text::Numbers::string_to_number compatibility selector uses the floating parser only when it
finds ., e or E; floating results are double. It does not auto-detect bases or
special values without those markers. Input strings are borrowed only for the call.
CE::Memory::format_bytes() formats binary byte units at powers of 1024 to one decimal;
rounding near a boundary can display 1024.0 in the preceding unit. CE::Memory::adjust_length()
uses caller-defined length units: larger adds the growth amount, greedy truncates
the scaled length then adds it, and exact/unknown policies retain the input.
The named policies are CE::Memory::FitPolicy::exact, larger, and greedy.
Greedy requires a finite positive factor; unrepresentable growth throws
bad_request. CE::Memory::reduce_fit_policy() steps greedy to larger, and larger/exact/unknown to exact.
These functions return values without allocating managed storage.
Packed bits, addresses and geometry
CE::Math::BitArray<Bits> owns packed native words with bit zero at the least-significant
position. Indices must be below Bits; debug assertions are not release-mode bounds
checks. Mutable bit proxies and word spans borrow the array. Access requires caller
synchronization even for different bits in the same word. Whole-word access may
change unused high bits; word size/byte order is not a portable serialized format.
Pointer helpers calculate byte offsets/alignment without owning storage or checking
allocation bounds, object lifetime or overflow. Supply representable addresses and
nonzero valid alignments; derived pointers stay within the backing allocation.
Pointer comparisons require comparable ranges. Null address alignment is zero,
which cannot be reused in the alignment-offset helpers. CE::Memory::Pointers::pointer_to_hash() accepts
1/2/4/8-byte widths and derives a hash from the address; smaller widths reduce modulo
the unsigned maximum. This is not a stable identity across releases or processes.
CE::Rendering::Geometry::Anchor helpers build CPU quads in local Y-up pixels from top-left source rectangles.
They require a finite normalized pivot and nonzero texture dimensions; invalid
values throw before writing. They do not check frame size/source bounds. Pointer
overloads require six writable CE::Rendering::Geometry::Vertex2D entries or thirty interleaved floats.
Case-sensitive two-letter anchor names include CL/ML and CR/MR aliases; unknown
names/enum values select center. The asset coordinate contract
defines grid geometry and normalized UV ownership.
Block range operations are caller-synchronized; manager transactions and retained
release contexts have the resource lifetime contract.
CE::Memory::Block<T>::vector(deleter) creates independent element control blocks rather than
aliasing the backing owner. The supplied deleter retains/releases backing storage
and manages constructed element lifetime; this helper does not construct elements
or retain the Block’s owner automatically.
Block stream insertion finds CE::Memory::operator<< through argument-dependent
lookup. Its free overload participates only when the target has no applicable
member insertion for that block type. A wrapper with its own member insertion
keeps that operation and can forward the block to a standard stream, where the
Memory overload supplies formatting. Custom streams without such a member can
use the same free overload; it is not restricted to standard stream classes.
std::align_val_t belongs to std, so inserting an alignment into a
standard stream does not find the Memory overload through that lookup. Call it
explicitly (or bring the overload into local scope with a using declaration):
CE::Memory::operator<<(stream, std::align_val_t{64});
CE::State::Singleton<Type> requires public construction;
CE::State::FriendSingleton<Type> supports a constructor accessible through
friendship. Both synchronize construction/publication. Returned pointers borrow
static storage and each Type governs its subsequent operations; the full contract is in
singleton.h.
File discovery
CE::Files::FileIndex indexes regular files by extension beneath explicitly
supplied directories. It is caller-owned and unsynchronized, with no refresh or
removal operation. Construct a new index to rescan an already visited root.
Paths use equality without canonicalization, so alternate paths can index the
same physical file twice. Extensions are lowercased with their leading dot;
extensionless files use file_no_ext, and enumeration is unsorted.
get_files_of_type() borrows an index-owned vector and inserts an empty bucket
for an unknown key. Further indexing can change its contents and invalidate
element references or iterators. Filesystem/allocation errors propagate and can
leave a partial index; see
file-mgr.h.