State

Purpose

State supplies reusable sampled values, versioned publication, observation and transition tables. Applications choose when to sample or transition; these types do not create threads or automatically synchronize an entire game.

Interface

Types below are in CE::State unless another namespace is shown.

Types and operations

API and purpose

StateTracker
Retain previous/current samples and observe changed()/delta(). Sampled state.

VersionedVariable
Publish and copy a synchronized value/revision pair; wait for a changed revision. Synchronized publication.

ObservedVariable
Add immediate observers outside storage locks, on each setter’s thread. Observation and callbacks.

StateMachine
Store ordered guarded transitions and enter/exit/action hooks. Transition tables.

Singleton, FriendSingleton
Deliberately share construction where an existing service contract requires it. Singleton declarations and lifetime.

Choosing a state type

Need

Purpose and details

Compare successive samples

Use StateTracker<T> with caller synchronization; delta() uses T’s units. Sampling and assignment.

Share the latest value

Use VersionedVariable<T>; waits coalesce updates and are not event history. Value/revision snapshots.

React to setters

Use ObservedVariable<T>; equal successful sets still notify and callbacks protect their own targets. Observer execution.

Model behavior transitions

Supply application-defined State and Trigger types, often enums, to StateMachine. No engine-wide enum is prescribed. Rules, ordering and failure.

Usage

Choose a state type from the information a reader needs: successive local samples, the latest published value, immediate setter callbacks, or guarded behavior transitions. These are independent application-owned values rather than a runtime service that must be started.

        ---
config:
  theme: base
  themeVariables:
    fontFamily: system-ui, sans-serif
    fontSize: 14px
  flowchart:
    htmlLabels: true
    curve: linear
    nodeSpacing: 22
    rankSpacing: 38
    padding: 12
---
flowchart TB
    accTitle: State · application state choices
    accDescr: Local names omit State::; ambiguous nested namespaces remain qualified. Arrows point toward the API or type used, not call order or data flow. Methods beneath type names are selected application entry points; Implement hooks marks engine-invoked overrides. Dashed arrows mark optional choices.
    k0(["Application<br/>state model"]):::app
    k1["<b>StateTracker</b><br/><br/>update(T)<br/><br/>changed()<br/><br/>delta(): decltype(current() - previous())"]:::focal
    k2["<b>StateMachine</b><br/><br/>add_transition(<br/>State,<br/>Trigger,<br/>State,<br/>StateMachine&lt;State, Trigger&gt;::Guard,<br/>StateMachine&lt;State, Trigger&gt;::Action)<br/><br/>trigger(Trigger)<br/><br/>state(): State"]:::focal
    k3(["Application<br/>state producer"]):::app
    k4["<b>VersionedVariable</b><br/><br/>set(T)<br/><br/>snapshot(): VersionedSnapshot&lt;T&gt;<br/><br/>wait_for_change(since): VersionedSnapshot&lt;T&gt;"]:::focal
    k5["<b>ObservedVariable</b><br/><br/>ObservedVariable(<br/>T,<br/>ObservedVariable&lt;T, Observers&gt;::Callback[])<br/><br/>set(T)<br/><br/>snapshot(): VersionedSnapshot&lt;T&gt;"]:::focal
    k6["VersionedSnapshot"]:::focal
    k7(["Application<br/>snapshot reader"]):::app
    k8(["Application<br/>transition / observer hooks"]):::app
    k0 --> k1
    k0 --> k2
    k3 --> k4
    k3 --> k5
    k4 --> k6
    k7 --> k6
    k5 --> k8
    k2 --> k8
    k7 --> k4
    k7 -.-> k5
    k5 --> k6
    classDef focal fill:#e7f3ef,stroke:#387d6b,color:#153d33,stroke-width:1.2px
    classDef neighbor fill:#f0f3f8,stroke:#8c9bb1,color:#35445b,stroke-width:1px
    classDef app fill:#fff2df,stroke:#c09452,color:#674515,stroke-dasharray:5 3
    classDef system fill:#eaf0fc,stroke:#617fab,color:#263f69,stroke-width:1.2px
    linkStyle default stroke:#7e8fa7,stroke-width:1.2px

    

Local names omit State:: where unambiguous. Explore this diagram and its other views →

The engine dependency view also shows where existing services use shared construction.

Choose what a change means

Application question

Choose

Observation and execution

What changed since my previous update?

State::StateTracker

The caller supplies each sample; changed() compares previous/current and is not an unread-change flag.

What is the latest value and its revision?

State::VersionedVariable

snapshot() copies one synchronized pair. Equal sets keep the revision; intermediate changes can coalesce.

What should run whenever a setter succeeds?

State::ObservedVariable

Observers run on the setter’s thread, including equal sets, after the storage lock releases.

Which guarded behavior transition is permitted?

State::StateMachine

One caller triggers ordered rules; the first matching successful guard selects the transition.

Must every occurrence reach consumers?

Typed event channel

Use event delivery rather than treating the latest value/revision as occurrence history.

An observer that must mutate simulation-owned state should submit owned work to Dispatch, rather than assume the setter runs on simulation. wait_for_change() has no cancellation/timeout; keep the variable alive and arrange for its producer and waiters to finish before destroying it. The state guide owns the detailed exception and callback rules.

Uses

Consumer

Dependency supplied by the caller

Requirement

State::StateTracker<T>

Sample type T

Defines copying, assignment, comparison and optional subtraction; the caller serializes access.

State::VersionedVariable<T>

Value type T

Copies value/revision under one storage lock. That lock does not extend pointee lifetimes or promise rollback for throwing assignment.

State::ObservedVariable<T>

State::VersionedVariable<T> and observer captures

Publishes before invoking observers; borrowed targets remain alive and protect themselves against overlapping callbacks.

State::StateMachine<State, Trigger>

Application state/trigger types, guards and actions

Owns the rule table and callable captures; no runtime, input or worker service is required.

Used by

Consumer

State helper

Relationship

Application models

Sampled/publication/observer/transition types above

The application chooses ownership, sampling and trigger boundaries; no global game state machine is prescribed.

Assets::Loading::Loader and asset caches

State::Singleton

Publish deliberately shared service facades; explicit initialization must precede competing access when configuration matters. Assets.

Events::EventSystem

State::Singleton

Offers the process-default event facade; independently owned buses remain possible. Events.

Memory::Manager and Memory::Objects::Pool

State::Singleton

Share acquisition facades while handles retain independent final-release contexts. Memory.

Shared construction is a separate choice

State::Singleton<T> requires accessible public construction; State::FriendSingleton supports a constructor accessible through friendship. Both synchronize construction and publication, leaving subsequent operations to T’s own contract. get_existing() borrows a pointer and does not pin the service against static teardown. Use an existing service’s declared entry point rather than wrapping an application value in a singleton to obtain synchronization. See shared service construction.

Contracts

snapshot() copies a versioned value and revision together; separate getters may observe different updates. Equal sets do not advance a versioned revision. Waits coalesce updates and deliver current state rather than every intermediate value. Assignment/copy exceptions retain the supplied type’s guarantee, not a transactional rollback guarantee.

Observed callbacks run outside storage locks on the setter’s thread, including equal successful sets. Concurrent/reentrant setters can overlap callbacks. State-machine guards/rules have insertion order; failures stop transition steps without undoing prior callback effects. Do not recursively trigger the same machine.

Lifecycle

Own each value/machine within its application component. Keep borrowed callback targets alive through calls and capture destruction. Versioned waiters require a live variable and a producer able to make progress; destruction is not cancellation. Borrowed accessor references expire with their owner or mutation according to that type’s contract.

Execution

StateTracker and transition machines require caller synchronization. Versioned storage synchronizes its value/revision, while callbacks protect their own external targets. Select a Dispatch owner when an observer must change simulation state from another producer.

Integration

Sample an application-owned score during update:

CE::State::StateTracker<int> score{0};
score.update(10);
if (score.changed()) {
    const int gained = score.delta();
    // Use gained while preparing the next HUD content update.
    static_cast<void>(gained);
}

The tracker owns integer samples and introduces no clock. Keep it as a member to compare successive updates rather than reconstructing it every tick.

Limits and further reading

State publication is not event history, a completion barrier or automatic thread assignment. Read state and utility contracts, StateTracker, VersionedVariable, ObservedVariable and StateMachine.