Models and collectors
UPulseSaveModelBase
Base lifecycle hooks:
InitializeNewModel()InitializeFromRecord(const FPulseSaveModelRecord&)— loads the record, or migrates it if it was saved with another class or model version (see Save migration).OnPreSave()OnPostSave()OnPreLoad()OnPostLoad()GetIdentifier()GetModelVersion()
The save manager and the processors ask every model the same questions, so they need no special cases for object models. The defaults fit a custom model of a collector that is not bound to an object:
| Function | Default | Meaning |
|---|---|---|
GetRecordType() | Collected | Which kind of record the model is saved in. |
IsPersistent() | true | Whether the record survives PrepareForSave(). |
GetLoadPriority() | MIN_int32 | Higher values load first. Models with the default load after all object models. |
GetTimeoutOverride(float&) | false | Override of DefaultProcessTimeout. |
IsReadyToSave() / IsReadyToLoad() | false | Polled every tick. false means the model calls ReadyToSave() / ReadyToLoad() itself. |
PrepareLoadTarget() | true | Called before OnPreLoad. false fails the load of the model. |
NotifyLoadFailed(...) | no-op | Object models forward it to their object. |
UPulseSaveModel overrides these to use its bound object.
UPulseSaveModel
UPulseSaveModel adds object-bound save behavior. This is the class you subclass for the “model instead of object” pattern described in Core concepts.
Key functions:
SetSaveObject(UObject* ObjectToSave)InitializeWithoutSaveObject()— called when loading a record with no matching live object.IsReadyToSave()/IsReadyToLoad()— polled every tick until ready or timed out.GetTimeoutOverride(float& OutTimeout)IsPersistent()GetLoadPriority()HasSaveObject()NotifyLoadFailed(EPulseSaveLoadFailureType)
Important properties:
SaveObject(BlueprintReadOnly) — the currently bound live object, ornullptr.Identifier(SaveGame) — unique identifier of the model, matches the bound object’sGetUniqueSaveIdentifier().LoadPriority(SaveGame) — higher values load first.bSerializeAllSaveGameProperties—falseby default (recommended); settrueto opt into automatically serializing everySaveGameproperty of the bound live object instead of declaring fields explicitly on the model.
Blueprint extension points (BlueprintImplementableEvent):
Is Ready to Save(K2_IsReadyToSave)Is Ready to Load(K2_IsReadyToLoad)
UPulseSaveActorModel / UPulseSaveActorModelBase
Built-in actor model, registered automatically for AActor objects (see Model factories and settings):
- Saves actor class path, location, rotation, scale.
- Supports spawn-on-load via
LoadType(EPulseSaveActorLoadType):ExistingOnly,ExistingOrSpawn,AlwaysSpawn. - Applies transform according to
TransformSaveFlags(EPulseActorTransformSaveFlags, bitmask ofLocation/Rotation/Scale) andTeleportType.
UPulseSaveActorModelBase provides the spawn/transform plumbing (HandleSpawnActor, GetSpawnTransform, BuildSpawnParameters) that UPulseSaveActorModel builds on; subclass UPulseSaveActorModelBase directly if you need custom spawn behavior without the built-in transform save flags.
UPulseSaveActorCollector
Default actor collector; scans world actors and includes those that:
- Implement
IPulseSaveInterface. - Pass
CollectActorForSave(AActor*)/CollectActorForLoad(AActor*)(bothBlueprintNativeEvent, defaulttrue).
Override ShouldCollectActor(AActor*) in C++ for additional filtering shared by both save and load.
UPulseSaveRegisterCollector
Manual collector for objects that aren’t reachable by iterating world actors — dynamically created objects, subsystems, non-actor UObjects, etc.
UPulseSaveRegisterCollector* Collector = Cast<UPulseSaveRegisterCollector>(
SaveManager->AddCollectorByClass(UPulseSaveRegisterCollector::StaticClass()));
Collector->RegisterObject(MyObject, FPulseSaveRegisterCollectorParams()
.CollectForSave()
.CollectForLoad()
.LoadOnRegister()
.RemoveIfDestroyed());
FPulseSaveRegisterCollectorParams options:
bCollectForSave(defaulttrue)bCollectForLoad(defaulttrue)bLoadOnRegister(defaulttrue) — immediately callsLoadSingleObjecton the object when registered.bRemoveIfDestroyed(defaulttrue) — auto-unregisters actors when destroyed.OnLoadedOnRegister— delegate fired once thebLoadOnRegisterload completes (resolved through the manager’sOnProcessedSingle()), set via.OnLoadedOnRegisterDelegate(...).
Call UnregisterObject(Object) to remove an object from the collector manually.
Static models (no live object)
For simple global/save-slot metadata that isn’t bound to any live object (save name, playtime, chosen difficulty, …), derive from UPulseStaticSaveModelBase and use the manager’s static model API instead of a collector:
UCLASS()
class UMySaveMetaModel : public UPulseStaticSaveModelBase
{
GENERATED_BODY()
public:
virtual FName GetIdentifier() const override { return TEXT("SaveMeta"); }
UPROPERTY() float PlaytimeSeconds = 0.f;
};
UMySaveMetaModel* Meta = SaveManager->GetOrCreateStaticModel<UMySaveMetaModel>(TEXT("SaveMeta"));
Meta->PlaytimeSeconds += DeltaSeconds;
// Optional — writes straight into the manager's current save game right now.
SaveManager->SaveStaticModel(Meta);
In Blueprint, create a Blueprint class from PulseStaticSaveModelBase, add your data as variables, and implement the event Get Identifier to return the identifier. Request it with the node Get Or Create Static Model using the same identifier. The result is typed by the class you pass in.
Static models are a separate path from the models that collectors produce:
- They are stored in records of type
Static(see Core concepts), always persistent, and have their own identifier space. An object and a static model may use the same identifier. - They are loaded and saved synchronously. They are not part of the chunks, processors, timeouts or the collected counters of the report. Their lifecycle is
InitializeNewModel(no record yet) orInitializeFromRecord+OnPreLoad+OnPostLoad(record found), andOnPreSave+OnPostSavewhen saved. GetOrCreateStaticModelreturns the same instance for repeated calls with an identifier. The instance is registered with the manager, and the manager does not own it: keep a reference as long as you need it.- Every
Save()writes all registered static models into the save game before collectors run, so you do not have to callSaveStaticModelbefore saving. - Every
Load()refreshes all registered static models in place (or resets them withInitializeNewModelif there is no record) before collectors run, so existing references reflect the loaded data. This is counted inFPulseSaveProcessReport::StaticModelsLoadedCount, failures are listed inFailedModels. - A static model that was not requested is not touched by
Load(). Its record stays in the save game. - Records of another class or model version are migrated when the model is loaded (see Save migration).
Calling SaveStaticModel manually is still useful when you want a static model persisted immediately (e.g. right before writing the save game to disk), independent of the manager’s Save()/Load() cycle.
Cached models and SetSaveGame/ClearSaveGame
Cached object models and registered static models were created against a particular save game’s records, so SetSaveGame and ClearSaveGame take an EPulseSaveModelRetentionPolicy to control what happens to both model stores when the manager’s save game changes:
Reset(default) — clears all cached object models and unregisters all static models. Safest option; externally held model references remain valid but are no longer managed by the save manager.RemoveMissing— removes only cached/static models whose identifier has no matching record of their type (Collectedfor object models,Staticfor static models) in the new save game.KeepAll— keeps every cached and static model, regardless of whether it has a matching record in the new save game. Useful when swapping save games known to share the same model identifiers.
// Clear cached and static models tied to the old save game (default behavior).
SaveManager->SetSaveGame(NewSaveGame);
// Keep cached/static models whose identifier still has a record in the new save game.
SaveManager->SetSaveGame(NewSaveGame, EPulseSaveModelRetentionPolicy::RemoveMissing);
To read a static model straight from disk without setting up a manager at all (e.g. a save-slot picker UI), use UPulseSaveUtils::LoadStaticModelFromSlot(SlotName, Identifier, ModelClass, UserIndex).