Open Asterune
Instance state and lifetime

Instance state and lifetime

Examine instance identity, copy-on-write world state, revision tracking, hierarchy changes, and deferred destruction.

The DataModel stores the instance hierarchy and the typed state that engine systems consume. Its arena separates an object's identity from its storage location. Runtime snapshots can share unchanged nodes while physics, rendering, scripts, and replication track the changes that concern them.

An instance node contains its class, parent, children, metadata, and built-in component state. Game-defined components use a separate column store attached to the same arena. The hierarchy remains the authority for ancestry; component membership does not create another parent-child relationship.

Runtime identity and physical slots

An instance has a 64-bit runtime ID. Normal allocation advances a counter; reclaiming an instance frees its physical slot rather than returning its ID to a live-object pool. A later object can occupy the same slot with a different ID. A stale handle therefore does not identify the replacement object.

The arena first attempts lookup at slot id - 1, checking that the stored node actually has the requested ID. When that direct position does not apply, a mapping supplies the physical slot. This keeps the initial contiguous case short while supporting reclaimed slots and explicitly inserted replication IDs. A client's locally allocated range starts at 2^52, separating those objects from the ordinary server-issued range.

Internal batch operations can reserve or roll back allocation ranges. A checkpoint records the allocation counter and pending-event positions. If a clone or graft fails, rollback removes the allocations made by that operation and restores those positions. This is a construction rollback, rather than a transaction that reverses arbitrary edits to preexisting objects.

Runtime IDs, GUIDs, and persistent unique IDs have different purposes. A node receives a GUID at creation; persistent identity is assigned separately when needed. These identities let authored objects be matched through operations that rebuild their runtime representation. An instance's display name supplies neither runtime uniqueness nor persistent identity.

Shared snapshots and copy-on-write nodes

An arena snapshot shares each node through an atomic reference-counted pointer. Creating that snapshot copies the pointer array and arena bookkeeping instead of cloning every component payload. A later write makes the affected node private to the arena that writes it. Unchanged nodes continue to share their allocations.

The component store uses a related mechanism at component-set granularity. Editing one instance node and editing one game-defined component set therefore have different copying costs. The node copy contains that instance's built-in state; a component-set copy contains its rows and columns. A snapshot does not turn every field into an independently shared allocation.

The arena compares related snapshots by node pointer identity. Matching pointers establish that an instance stayed unchanged. Different pointers mark a possible change, even if later edits restored the same values. The comparison also reports instances live in only one snapshot. This avoids comparing every material, transform, string, and nested component merely to find candidate changes.

Shared snapshots carry IDs, revisions, and tombstones, but do not carry pending instance events or mutation journals. A snapshot is a representation of world state rather than a second consumer of the source arena's pending event stream. The Luau stack, graphics device, and audio device have their own lifetimes.

Revision counters and incremental consumers

A generic mutable node access conservatively marks the node as written before returning it. The arena does not wait to compare every field afterward. A no-op write can therefore advance a revision even though the visible scene stays the same. Specialized edit paths can compare their inputs first when avoiding invalidation matters.

The arena maintains several revision domains rather than one universal dirty flag:

Revision or journalWhat it lets a consumer detect
TopologyAllocation, destruction, and reparenting that can change membership or ancestry
Global mutation and per-node revisionsWrites that can change a node's state
Component-family revisionsChanges relevant to groups such as lights, sounds, cameras, or GUI objects
Collision and animation revisionsChanges relevant to collision scenes or authored animation data
Tag revisionChanges to named membership sets
Part-write logCandidate parts or models changed since a consumer's last revision

Built-in component-family indexes cache lists of instance IDs. Their membership is rebuilt when topology changes, then readers share the resulting lists. Updating a part's transform does not require rediscovering which nodes are parts. A consumer separately examines state revisions to decide what to upload or recompute.

The part-write log holds at most 65,536 entries before dropping its older half. Each reader supplies its previous revision. If that revision predates the retained log, the arena reports that the incremental history is unavailable. The consumer must inspect all relevant parts rather than accepting an incomplete list. Repeated writes to one part can produce repeated log entries; consumers cannot interpret the log length as a unique-object count.

Topology journaling has a similar bounded-history condition. The distinction between a revision and a complete retained journal matters: a revision can establish that something changed even after the detailed record has expired.

Hierarchy edits and dependent state

Reparenting validates the move before changing the hierarchy. It rejects cycles, missing or destroyed destinations, locked parents, and applicable class containment violations. A no-op move returns without producing another structural edit.

For a real move, the arena collects the affected subtree and the old and new ancestor chains. It updates both parent-child directions and queues events for old-ancestor removal, child removal, child addition, ancestry change, and new-ancestor addition. Descendant notifications share a collected list of moved IDs instead of copying a complete subtree into every event.

A move also updates state derived from ancestry. Parts entering or leaving Workspace change their world membership and appear in the part-write log. GUI moves invalidate the layouts of the affected layers; moving an entire layer can invalidate the broader layout memo. Component queries that depend on ancestry observe topology changes separately from ordinary field writes.

This coupling prevents a hierarchy edit from changing only the visible tree. A part moved out of the world must also stop participating in the systems that use world membership. A cached ancestry query must revisit its matches even when every component value remains identical.

Destruction, lifecycle reads, and reclamation

Destruction first detaches the root and marks the affected nodes as destroyed. It removes tag and persistent-identity index entries, removes game-defined component membership, applies reference destruction policies, and queues lifecycle events. Ordinary live lookup stops returning the destroyed nodes.

The arena retains these nodes as tombstones so lifecycle delivery can inspect the state that existed at destruction. During delivery, a scoped lifecycle subject makes that subject and its destroyed subtree readable. Ending the scope restores the preceding view. This temporary readability does not restore the object to ordinary world membership or make it writable again.

Reclamation follows event draining rather than the instant destruction occurs. The first drain returns the destruction events and moves the newly doomed IDs into a reclaimable list. The next drain releases those slots. Consumers of the first batch can therefore inspect tombstones before a later drain reclaims them. Holding an instance handle does not extend this arena lifetime.

Physical slots return to the vacant pool only after reclamation. Component storage also releases slot-related bookkeeping then. Object identity, live membership, lifecycle visibility, and physical allocation consequently have separate transitions rather than one immediate deletion step.

References during copies and destruction

Cloning a subtree first builds a mapping from source IDs to cloned IDs. It then rewrites built-in references, attribute references, and game-defined component references. References between cloned descendants point to the corresponding clones. A reference outside the cloned subtree retains its existing target in the ordinary clone path.

Grafting content from another arena can instead clear or resolve external references according to the selected graft policy. Internal references must be remapped because the destination allocates its own runtime identities. Copying the visible parent-child tree alone would leave references pointing into the source world.

Game-defined reference fields also maintain a reverse index. Destroying a target can clear a field, remove the referring component and its dependents, or destroy its holder, according to the schema. The index identifies the holders that require work without searching every component column.

The instance guide covers script-facing lookup, parenting, cloning, and destruction. The execution model explains when the engine dispatches the queued lifecycle work.