Simulation FrameworkGet v1.3 ↗

Queued native transitions — API 0.5#

Queued transitions reserve one provider plan and apply at most one member operation per unpaused world update, rotating between entities. They preserve partial progress in native journal 3 (introduced in journal 2). Existing synchronous calls remain available and keep their execution semantics.

Admission and progress#

A provider must override PawnSimulationProvider.AllowsQueuedTransitions to return true after defining how its accounting behaves while native work is pending. The default is false, independently of AllowsIntraStepTransitions. The expedition example explicitly supports both; its existing supply model already records the original request tick and an accounting cursor.

var sim = Find.World.GetComponent<SimLODWorldComponent>();
var receipt = sim.QueueMaterialization(entity, sim.Get(entity).Revision,
    "encounter-18/open", map, cells);
// receipt.Status == Pending, receipt.AppliedCount == 0, receipt.Queued == true.
// Save the key in the caller's encounter state; observe progress on later updates.

var pending = sim.GetQueuedNativeOperations(); // Saved round-robin order.
var current = sim.GetNativeOperation(entity, "encounter-18/open"); // Indexed current receipt (0.6).
var operations = sim.GetNativeOperations(entity); // Full history for inspection, not per-frame polling.

QueueMaterialization, QueueReconciliation and QueueRematerialization take the same arguments as their synchronous counterparts. Admission validates the request/provider/native preconditions, takes the entity lock, saves the complete planned payload and original EffectiveTick, and reserves population where required. It performs no Pawn generation, spawning, despawning or disposal. Normal requests remain bounded to 1–32 member items. A member item can include generation plus spawning; those native calls are not separately preempted.

Request keys retain their fingerprint/replay guarantees. Repeating a queued request returns its saved receipt without reserving again, changing queue position or performing work. Execution mode is part of the request fingerprint: the same key cannot switch between queued and synchronous execution. Reordering return selections is equivalent; reordering destination cells changes the request.

Destination cells are validated at admission and before native work; they are not locked against native movement or other requests. Providers should coordinate their own candidate cells. A conflicting occupant causes a fault instead of being displaced by queued work.

Each unpaused WorldComponentUpdate considers the first queued operation. It checks its current conditions and attempts one unapplied member. An unfinished operation rotates to the tail; a completed or faulted operation leaves the runnable queue. A missing/incompatible provider consumes a queue turn without native effects and rotates, so available providers can progress. There is no scan of completed operation history to select work, and no wall-clock timing decision controls the order or quota.

When no operation is queued, the automatic update takes a constant check path without constructing native diagnostics. LastNativeUpdateMilliseconds, LastNativeItemsApplied and NativeQueueUpdatesThisSession are session observations. Applied count measures journaled successful member items; an exception can have native side effects before that count increases. The internal pump entry used by the benchmark is test-assembly access, not a public way to bypass the quota.

Time and provisional state#

The entity remains TransitionPending and is excluded from abstract scheduled updates until its plan completes. Its native members can temporarily be split between map and holder. Member state labels describe the last completed representation transition; use per-item progress and observed native owners to understand pending work. Do not report completed conservation totals or start another operation on that entity while its lock remains.

The expedition counts reserved/deploying members as deployed from the original admission tick, and returned members as retained from the original return request tick once that plan commits. Recovery does not replan at a later tick. Scheduled catch-up then uses the saved supply cursor. This deliberately allows a temporary gap or overlap between abstract accounting and actual native presence while the operation is pending; it is an accounting convention, not atomic native time. Native food/inventory remains a separate system.

The queue operates per rendered game update, rather than per simulation tick. Pausing stops automatic progress; faster game speed can advance more native game ticks between queue turns. The persisted queue order and one-member quota are deterministic for the same admission/resume sequence and update opportunities. Frame rate can still change the native tick at which a member appears, and native Pawns can move, die or change ownership between turns. This is not cross-frame-rate native replay or Multiplayer support.

Blocked work and resume#

Before each attempt, the queue rechecks provider permission/schema/model, entity lock, all current destinations, native references/owners and already applied items. A changed owner, carried returnee, occupied destination or other native blocker faults the operation and removes it from runnable work. Other entities retain their turns. A later update does not automatically retry that fault.

Inspect with InspectNativeOperation(entity, key). After correcting a recoverable condition, call RecoverNativeOperation(entity, key). For queued operations it validates conditions, clears the entity's saved failure, returns a Pending receipt and appends the operation to the tail. It performs no member effects and preserves the original revision, payload, effective time and applied items. Repeating resume while already pending does not duplicate or reorder the queue entry. Completed recovery calls only replay their receipt.

Unavailable providers remain suspended with their ordinary pending work and order preserved in the save. Once a compatible provider returns, that pending work is eligible for automatic progression. A previously faulted operation still requires explicit recovery. A provider that withdraws its queued-transition permission blocks execution even if its schema/model otherwise match.

Any schema mismatch suspends a locked operation, including a newer provider that would normally migrate an unlocked record. The pending plan is never migrated. Restoring the matching schema restores Pending or Faulted according to the original saved failure state.

The developer recovery window shows queued/synchronous mode, progress, owners, blockers and a guarded Resume queued work action. An already pending request displays Queued; completed and blocked controls are visibly inactive. The example adds Queue latest expedition deployment and Queue latest expedition return actions. Version 0.5 adds a separate prepared settlement path to preserve observed effects and cancel provably unused claims. Force-transfer and map-retargeting APIs remain outside this adapter.

Save and version boundary#

Native journal 3 saves queue order and settlements. The reader accepts journals 1 and 2, preserving their synchronous/manual and queued semantics, and upgrades after validation. Older readers reject journal 3; do not downgrade a save after using this version. The abstract archive stays at 2, RNG at 1, and assembly identity at 0.1.0.0; framework package/file version is 0.5.0.

Ordinary saves are supported between completed queue turns, including partial generation/spawn, return and rematerialization. The native validator accepts a retained member that has already spawned as part of a pending rematerialization, while recovery still verifies its actual owner. Queue keys must match pending journal entries exactly; missing, duplicate or foreign keys disable native operations rather than silently dropping work. The native journal remains structured Scribe data, not an opaque forward-compatible archive.

One generator call, provider callback, native side effect or final commit remains synchronous and non-preemptible. Admission/validation and native saves also have costs. The queue bounds member attempts, not milliseconds or whole frames. Reentrant saves during arbitrary native callbacks, transactional rollback, arbitrary existing-pawn ingestion, native class/Def removal and map deletion remain outside the supported scope.

Use the pawn lifecycle guide for general ownership/replay rules and current status for executed tests and measurements.