Ownership and Cleanup

Decide what an application component owns, what it borrows, and which work must finish before its dependencies can be destroyed. A C++ handle’s lifetime and a native backend’s usable lifetime are separate obligations.

Choose what to retain

Relationship

Application responsibility

Cheryl examples

Own an object

Control destruction and finish its callers before releasing it.

A Startup result owns its context; the application owns its game, GUI session and audio system.

Borrow an object

Keep its owner alive and obey the object’s access rules.

A runtime borrows its context/game; context accessors and display-owned windows return borrowed access.

Retain an immutable result

Keep a strong handle for as long as that version is needed.

Assets, input poll snapshots, uploaded text and frame packets retain their selected data.

Hold a scoped request

Retain the lease while the request is wanted; release it when that consumer finishes.

Input Events/Text capture and keyboard focus.

Save a submission endpoint

Own callback inputs or keep borrowed captures alive through completion or cancellation.

Dispatcher submissions remain safe to call after closure, but reject new work and do not retain the runtime.

For example, two game objects can retain the same sprite while keeping independent positions and animation cursors. Dropping one object’s handle releases only that owner. A manager cache or published frame can still retain the sprite’s resources.

        flowchart LR
    accTitle: Independent owners of shared appearance
    accDescr: A cache, two actors and a published frame can all retain the same immutable appearance resources. Releasing one owner leaves the others intact.
    cache["Asset cache"] -->|retains| appearance["Immutable appearance resources"]
    first["First actor"] -->|retains| appearance
    second["Second actor"] -->|retains| appearance
    frame["Published frame packets"] -->|retain| appearance
    

The resource guide explains which owners to inspect before treating continued residency as a leak.

Use a lease for a temporary input request

With a live input system and an application-selected nonzero editor_target:

auto events = input.capture(CE::Input::Capture::Channel::Events);
auto text = input.capture(CE::Input::Capture::Channel::Text);
auto focus = input.routing().focus(editor_target);
// Retain these handles while the editor receives input.
// During update, read tick.input.records_for(editor_target) in order.
focus.reset();
text.reset();
events.reset();

Keep the handles as editor members when the request spans updates. Their destructors also release the requests. An older focus lease cannot clear a newer request. These leases retain request state, not an input adapter, window or widget; capture and focus take effect at a subsequent poll. Toolkit sessions manage their own leases through their focus methods. The input guide defines poll epochs and already captured records.

Keep native domains alive for native use

A retained draw packet keeps geometry, material and image generations alive. It does not keep a destroyed graphics context usable. Likewise, an audio voice handle can survive system closure but then reports Closed; controls reject.

CPU release state can have a different lifetime. Pooled object handles retain their return/construction contexts, allowing final release after a pool facade dies. Their own callers still need to finish before that facade is destroyed. Use CPU memory lifetime when allocating application objects, and native retirement and maintenance for GPU handles.

Shut down in dependency order

The runtime provides the sequence below for context-owned work. Application-owned services join that sequence through the game’s hooks:

Boundary

What the application does

Request stop

Return from the current update when no further game work is needed. A stop request does not interrupt an executing callback.

Simulation stops and joins

Close simulation dispatch and call deinit_simulation() on its owner before it exits. Retain state still used by external producers or accepted work; transfer final UI teardown to platform.

quiesce()

Stop external producers and invalidate borrowed event listeners while their targets remain alive. Avoid waiting for work that still needs platform dispatch.

Accepted work settles

Keep worker inputs, upload dependencies and callback targets alive while platform continues servicing accepted dependencies.

Frames recycle and deinit_platform() runs

Release game resources and application-held toolkit objects; close GUI sessions after UI quiescence. Settle and close separately owned audio services.

Adapters close

Input detaches from a live window; native resources retire before graphics/window destruction.

Cleanup pairs with attempted initialization, so retain only successfully acquired handles and make cleanup tolerate partial startup. For borrowed [this] event callbacks, use unregister_and_wait() before destroying the target; ordinary unregister prevents new entry but permits an already running callback to finish.

The runtime lifecycle owns the exact shutdown and failure order. Threads and Data Flow explains which dependencies require continued owner-thread progress.