ObjectStateHolder¶
Component that hosts a list of state holders and orchestrates capture/restore across them.
Namespace: Jungle.State
Inherits: ControllableComponent → MonoBehaviour
Overview¶
ObjectStateHolder is the MonoBehaviour you drop on a GameObject that should participate in saves. It holds a list of StateHolderBase entries — each one a topic-typed slice (Transform, Rigidbody, GameObject, …) targeting its own GameObject or component via a SmartX value.
The holder is mostly a thin shell: it iterates its entries on CaptureAll, RestoreAll, ClearAll. The real work happens inside the topic holders and their operations.
Properties¶
| Field | Description |
|---|---|
| Holders | List of StateHolderBase entries. Each topic holder targets its own GameObject or component via a SmartX picker — defaults resolve against the host GameObject (Self / sibling). |
| Capture On Begin | If true and the execution frame fires Begin, captures every holder automatically. |
| End Action | What happens on the execution frame's End: nothing, clear, restore, or restore-and-clear. |
The execution frame is inherited from ControllableComponent. By default, new ObjectStateHolders use ExternalFrame (no auto-fire) — capture/restore is opt-in via Begin/End calls or via Magpile's SceneState.
API¶
Two access modes — same per-target storage in each holder:
// All-at-once (single-slot per holder, smart picker resolves the target)
holder.CaptureAll(); // capture every holder against its smart-resolved target
holder.RestoreAll(); // restore every captured snapshot across every holder
holder.ClearAll(); // discard every captured snapshot
holder.RestoreAndClearAll(); // restore then clear
// Per-target (multi-target storage, explicit GameObject argument)
holder.CaptureFor(go); // capture state for `go` in every holder, keyed by `go`
holder.RestoreFor(go); // restore previously captured state for `go`, remove the key
holder.ClearFor(go); // discard the snapshot for `go` without restoring
bool any = holder.HasCapturedData;
bool one = holder.HasCapturedDataFor(go);
// Used by save systems such as Magpile's SceneState after reading holders back from a file
holder.ReplaceHolders(loadedHolders);
Holders maintain a Dictionary<GameObject, snapshot> internally — CaptureFor(X) and CaptureFor(Y) are independent. The all-at-once methods treat each holder as a single-entry slot (clear-then-capture / restore-everything semantics).
The per-target mode is driven by State Holder For Target Command.
Typical wiring¶
ObjectStateHolder
└─ Holders:
├─ Transform State Target: Self Operations: Position, Rotation, Scale
├─ Rigidbody State Target: sibling Operations: Velocity
└─ GameObject State Target: Self Operations: Active
Or, for a single-aspect setup:
Per-target operations¶
For per-target workflows (detector entering/leaving, scoped overrides), the existing State Holder Command hosts three additional operations that take their own capture-target picker:
- Capture State For Target →
holder.CaptureFor(target) - Restore State For Target →
holder.RestoreFor(target) - Clear State For Target →
holder.ClearFor(target)
Each carries a SmartGameObject captureTarget field. Inside a target-aware reaction (one that runs per dispatched object — GameObject Cycle Reaction, GameObject Reaction, …), the picker offers Context (ContextObjectValue) as a source — pick it, and each fire feeds the dispatched GO as the per-target argument.
Pinning the holder vs. following the dispatched target¶
The command's target says which holder it uses; nothing overrides it at runtime.
For "the holder is pinned, the dispatched GO is the snapshot key," point the SmartObjectStateHolder target at the pinned holder (Local / FromKey / …) and give each operation's captureTarget the Context source.
GameObjectCycleReaction
├── source: Detector Membership
└── onBegin:
State Holder Command
├── target: SmartObjectStateHolder
│ └── source: Local → <drag the pinned ObjectStateHolder>
└── operations:
- Capture State For Target
└── captureTarget: SmartGameObject
└── source: Context ← follows the dispatched GO
└── onEnd:
State Holder Command (same wiring)
└── operations:
- Restore State For Target
For the "holder on the collidee" pattern, set the SmartObjectStateHolder target's source to Context, so it finds the holder on the colliding GO.
Cycles¶
ObjectStateHolder implements ITargetedCycleProvider<StateHolderCycle> — reactors can hook per-(holder, target) edges via the Object State Holder Targeted Cycle frame. Each cycle fires per (holder, target), carrying the GameObject in play.
| Cycle | Begin | End | Use |
|---|---|---|---|
| Capture | before each holder's Capture() |
after | "react when this object is being saved" |
| Restore | before each holder's Restore() |
after | "react when this object is being restored" |
| Captured State | after a successful capture | before the next restore, clear, or re-capture | "while we hold an unconsumed snapshot" — tweens, UI indicators, gating |
Capture and Restore are instant (Begin and End fire back-to-back). Captured State has real duration — pair it with a Process reaction's whileRunning slot for behaviours that should last exactly as long as the snapshot.
Debug¶
Three modules, one per level of the hierarchy — click whichever entity you want to inspect:
- Object State Holder (select the GameObject) — aggregate stats only: holder count, last Capture / Restore frame+age+target, number of Captured State slots currently open.
- State Holder (click a holder inside the list) — that holder's pinned target vs. live target (with a divergence warning when a dynamic source has moved since capture), HasCapturedData flag, and the type names of its operations.
- State Operation (click an operation inside a holder) — the operation's HasCapturedData flag plus the actual captured field values (position, velocity, color, …) read via reflection.
All three are pure read-side consumers — they subscribe to the cycles or reflect serialized fields. The runtime never references the debug layer.
See Also¶
- Topic State Holders — catalog of built-in topic holders
- SceneState — drives capture/restore at scene scale and saves to file (Magpile)
- SavedRef — primitive for cross-references between saved objects