Custom state and dependencies
The versioned Writer/Reader API, the IWSRFResettable callbacks, supported value types, and storing inter-Actor relationships as Stable IDs.
Automatic capture handles the things every Actor has: transform, visibility, collision, meshes. It deliberately does not try to serialise your gameplay. Custom State is the explicit, versioned channel for that.
Explicit, not reflection
Custom State is not reflection-based object serialisation. You name each value and write it with a
typed node. That is what makes restore ordering deterministic and snapshots free of UObject
references.
The interface
Implement WSRF Resettable on the Blueprint or C++ class, then set the restore strategy to
AutomaticThenInterface (default) or InterfaceOnly.
| Event | When | What to do |
|---|---|---|
WSRF Capture Custom State | When the initial state is captured | Set a version, write your values |
WSRF Prepare For Reset | Before the Actor is restored | Stop timers, abort montages, disable transient gameplay |
WSRF Restore Custom State | After automatic state and attachments | Read the values back and apply them |
WSRF Reset Completed | At the end of the Actor’s reset | Resume gameplay, notify other systems |
Writing values
UWSRFStateWriter is handed to you in the capture callback:
WSRF Capture Custom State (Writer)
-> Writer -> Set Version (2)
-> Writer -> Write Bool ("bArmed", bArmed)
-> Writer -> Write Integer ("ChargesLeft", ChargesLeft)
-> Writer -> Write Name ("Phase", CurrentPhase)
-> Writer -> Write Transform("DoorOpenTransform", DoorTransform)
Supported types:
| Writer node | Reader node | Value |
|---|---|---|
WriteBool | ReadBool | bool |
WriteInteger | ReadInteger | int64 |
WriteFloat | ReadFloat | float |
WriteDouble | ReadDouble | double |
WriteEnum | ReadEnum | enum value + enum type name |
WriteEnumByte | ReadEnumByte | byte enum value + type name |
WriteName | ReadName | FName |
WriteString | ReadString | FString |
WriteVector | ReadVector | FVector |
WriteRotator | ReadRotator | FRotator |
WriteTransform | ReadTransform | FTransform |
WriteGameplayTag | ReadGameplayTag | FGameplayTag |
WriteGameplayTags | ReadGameplayTags | FGameplayTagContainer |
WriteStableId | ReadStableId | FWSRFActorId |
Rules the API enforces: empty keys are rejected, writing the same key again replaces its previous typed value, and a read fails cleanly when the key is missing or its type does not match.
Reading values back
UWSRFStateReader is handed to you in the restore callback. Check the version first, then read:
WSRF Restore Custom State (Reader)
-> Reader -> Get Version -> Switch on Int
2: Read Bool("bArmed") -> Read Integer("ChargesLeft") -> Read Name("Phase")
1: Read Bool("bArmed") // older payload, defaults for the rest
-> apply the values to the Actor
Every read returns a success boolean alongside the value, so a missing key is a branch rather than a crash.
Restoration should assign state directly. Do not call gameplay events such as TakeDamage or
GrantLoot merely to reconstruct a value — those may trigger irreversible side effects.
Versioning your payload
SetVersion stamps the payload with your own schema version. Nothing forces you to bump it — but
when you do, old payloads keep their old number and your restore code can branch on it.
HasKey covers the simple case: a value added in a later version is absent from older payloads, and
you can default it without touching the version at all.
Your version is not WSRF's version
The version you pass to SetVersion belongs to your Actor’s contract and is preserved verbatim
through storage and migration. It never selects or triggers a WSRF snapshot migration — WSRF’s own
storage format is versioned separately (snapshot schema 3 stores Custom State as a compact stream
of key, value type and active value). Bumping your version changes only what your own restore
branch sees.
Writer and Reader are callback-scoped
Both objects are temporary helpers owned by the framework for the duration of the callback. Do not store them in a variable and use them later.
Dependencies between Actors
The rule: never store an object reference to another managed Actor. It goes stale the moment that Actor is destroyed and respawned. Store its Stable ID instead.
Capture:
Linked Lever -> Get Component (WSRF State Component) -> Get Stable Id
-> Writer -> Write Stable Id ("LinkedLever", Id)
Restore:
Reader -> Read Stable Id ("LinkedLever") -> Id
-> WSRF World State Subsystem -> Find Loaded Actor (Id)
-> Cast To BP_Lever -> store the reference for this session
This works even when both Actors were destroyed before the reset: respawn happens earlier in the
pipeline, so by the time WSRF Restore Custom State runs, the target exists again.
Still handle a null result from Find Loaded Actor and resolve again later if the target’s level or
container is not loaded. A completion callback does not guarantee that every possible related source
level has loaded or that every respawn succeeded.
Inside a Level Instance, use Find Loaded Actor In Context with a context Actor from the same
container, so two copies of the same instanced content resolve their own lever rather than each
other’s.
Restore ordering
The pipeline orders Actors before restoring anything: a parent before its attached children, then
ascending ResetPriority, then Stable ID as a deterministic tie-break. Two runs over the same world
produce the same order.
GetPredictedRestoreOrder exposes that ordering for a set of IDs, which is what the Inspector uses
to show the documented pipeline for a selected Actor.
Deliberate exclusions
There are no Writer nodes for UObject references, arbitrary object graphs, generic arrays, maps,
sets, or user-defined binary blobs.
Scope limitation
Custom State is a typed key/value payload, not a general-purpose serializer. Values and Stable IDs only. That constraint is what guarantees a snapshot can be stored in a SaveGame and applied in a later session without retaining engine objects.
Demo
The demo map’s orange fixtures are exactly this feature: a Lever and a Door that hold a Stable-ID relationship and their own custom values, and reconstruct it after both original Actors have been destroyed.
Last updated: 2026-09-04