Rolly Docs

Use Rolly Save from C++

Add the plugin's modules to your code, save and load through the subsystem, read and write save files and records yourself, keep saves in storage of your own, and run the plugin's automated tests.

Sheet
53 of 64
Rev
1.0.0
Updated
On this sheet

Add the modules

Module Loads in What it holds
RollySave Games, servers and the editor The subsystem and its Blueprint nodes, actors, Save Rules, handlers, level tools, the save file format, storage, migration
RollySavePCG Games, servers and the editor Saving PCG components and their output
RollySaveStateTree Games, servers and the editor The StateTree handler
RollySaveGAS Games, servers and the editor The Gameplay Abilities handler
RollySaveUI Games and the editor, never a dedicated server The slot screen, the profiles panel, the saving indicator, notices and the loading overlay
RollySaveEditor The editor The rule editor, Pick Properties, the outliner column, markers, checks, the setup panel, Play from Slot, the Save Inspector and the automated tests
RollySaveCore Games, servers and the editor The shared foundation: setup reports, validation, the input device tracker
RollySaveCoreUI Games and the editor, never a dedicated server The Rolly UI design system the screens are built from
RollySaveCoreEditor The editor The Message Log listing, the theme editor, the core’s tests

The last three are the shared Rolly foundation, carried inside this plugin under this product’s name, so several Rolly products in one project never clash.

PrivateDependencyModuleNames.AddRange(new string[]
{
	"RollySave",   // the subsystem, files, records, rules, handlers
	"RollySaveUI", // the slot screen and the other screens
});
C#

Headers sit in feature folders and are included by path from the module’s Public folder (Game/RollySaveSubsystem.h); ARCHITECTURE.md lists the folders and the extension points, and the class reference gives every class’s header.

Save and load through the subsystem

URollySaveSubsystem (Game/RollySaveSubsystem.h), one per game instance, saves and loads the game on the server or in a standalone game: the variables flagged SaveGame of the game instance, its subsystems, the Game Mode, the Game State, the world’s subsystems and the map’s Level Blueprint, each by its role in the game, the objects registered under a name, and the world’s actors and components that save (URollySaveWorldSubsystem::ResolveActorSettings gives what one keeps; URollySaveRule data assets set it for the actors they match and save engine properties by path, RollySaveRuleExplain::Explain tells why; URollySaveComponent overrides it and fires OnPreSave, OnSaved and OnLoaded) and the local players. ForgetLevel and ForgetAllLevels reset the levels it keeps across travel. Project Settings > Plugins > Rolly Save > What Saves turns each kind off, and Game Data Version is written into every save.

#include "Game/RollySaveSubsystem.h"

URollySaveSubsystem* Save = URollySaveSubsystem::Get(this); // any object of the running game
Save->RegisterObject(QuestLog, TEXT("QuestLog"));           // an object that does not save on its own
Save->Save(TEXT("Slot 1"), [](const FRollySaveResult& Result)
{
	// On a later tick. Succeeded, or Result.Status says why and the slot keeps its previous save.
});

// Opens the map the save was made in; every saved value is set before its Begin Play.
Save->Load(TEXT("Slot 1"), ERollySaveLoadMap::OpenSavedMap, [](const FRollySaveResult& Result)
{
	// After the loaded map's Begin Play, where this object still exists, such as in the Game Instance.
});
C++
Save->ListSlots([](const FRollySaveResult& Result, const TArray<FRollySaveSlotInfo>& Slots)
{
	// Newest first, from the slot index: SlotName, SaveTime (UTC), GameDataVersion, SizeBytes and the save's metadata.
});
Save->Copy(TEXT("Slot 1"), TEXT("Slot 2")); // Slot 2 keeps what it held before as its backup
Save->Delete(TEXT("Slot 2"));               // both copies; succeeds when the slot held no save
C++

A load’s callback reaches only an object that lives through the map change. Elsewhere, bind OnLoadFinished in Begin Play, or call GetLastLoadResult there: the values are set by then. An object registered under a name after the load, such as an actor that registers in its Begin Play, takes its saved values inside that RegisterObject call. Until a load’s values are set, and during seamless travel, saves and loads return Busy; spread After Begin Play values are the exception: a save or load asked for while they are set sets the rest first. The next save writes back what the last load or save held for objects that are not there, such as another map’s Level Blueprint; call ClearLoadedGame when the player starts a new game.

Load also takes an FRollySaveLoadRequest: a slot from storage or memory (bFromMemory), the whole game or the parts of a Scope, into the saved map, the current one or GivenMap. SavePart saves the parts of an FRollySaveScope and keeps the rest of the slot, SaveToMemory, SaveRewindPoint and Rewind keep whole-game snapshots for the session, and AddSaveBlock and RemoveSaveBlock refuse saves while a block is on.

Fill what slot lists show

SetSlotLocation, SetSlotGameFields and GetPlayTime fill and read the metadata ListSlots gives, SetSlotTitle, SetNextSaveTitle and SaveToNewSlot give saves titles, and SetCurrentSlot names the current slot (see Show saves in a slot list). CaptureThumbnail, SetNextThumbnail and ClearThumbnail choose the next save’s picture, CaptureThumbnailHold, UseThumbnailHold and ReleaseThumbnailHold keep one per menu for split screen, and LoadSlotThumbnail gives a slot’s picture as a texture (see Show save thumbnails).

Follow saves, loads and the world

OnSaveStarted (with who started the save; a player’s menu saves inside FPlayerSaveScope), OnLoadStarted and OnLoadProgress report saves and loads as they run. IsWorldReady, HoldPlayer, ReleasePlayer, ReleaseHeldPlayers, IsPlayerHeld and IsPlayerWaitingForGround hold players until the ground under them has loaded, OnPlayerHeld, OnPlayerReleased and OnWorldReady report the holds and their end, and URollySaveWaitWorldReadyAction is Wait for Rolly World Ready (see Hold players until the ground has loaded). MakeDebugSnapshot and MakeDebugActor, in every build but Shipping, give the game’s and an actor’s save state as the Gameplay Debugger shows it, and FRollySaveDebugSnapshotBuild makes the full snapshot over several frames (see See what an actor saves while you play).

Run the game’s flow

RequestAutosave, SaveCheckpoint and LoadCheckpoint save and retry the game in its flow; StartRun and EndRun bind the game’s saves to one slot; GetProfile, LoadProfile, SaveProfile and ResetProfile reach each local player’s URollySaveProfile (Profile/RollySaveProfile.h), whose Blueprint Project Settings names, and with Profile Keys at Named, ListProfiles, CreateProfile, RenameProfile, DeleteProfile, UseProfile and GetProfileKey manage named profiles; StartNewGamePlus starts over with the parts of an FRollySaveScope; and RenameSlot, ExportToFile, ImportFromFile, ExportToBytes and ImportFromBytes move saves, and ExportSlotToJson, ExportSlotToJsonString and ImportSlotFromJson move them as Rolly Save JSON (see Export and import saves as JSON). Each takes an FRollySaveDone or a callback with what it gives, on a later tick.

OnBeforeLifecycleSave fires before the saves Rolly Save writes as the game leaves, OnUserSignedIn and OnUserSignedOut report platform users on storage that keeps users apart, and OnLowStorage and CheckStorageSpace tell how much space the saves have (see Save when the game closes or goes to the background and Keep saves with each player’s account). OnSaveChangedOutsideGame reports a save that save protection found changed, and a subclass of URollySaveProtectionKeyProvider gives the keys from code (see Protect saves from editing).

Extend what saves

The Blueprint nodes are URollySaveSlotAction, URollySaveLoadAction, URollySaveListAction, URollySaveImportAction, URollySaveBytesAction, URollySaveProfileAction, URollySaveThumbnailAction, URollySaveProfilesAction, URollySaveProfileInfoAction, URollySaveJsonAction and URollySaveStorageSpaceAction (Nodes/), which call these functions; ImportEngineSaveGame reads a save of the engine’s Save Game to Slot.

  • Migration: to change saved data between versions of your game, raise Game Data Version and subclass URollySaveMigrationStep (Migration/RollySaveMigrations.h), or use a built-in step, in the Migrations asset (see Keep old saves loading after an update).
  • State that is not a property: subclass URollySaveHandler (Handler/RollySaveHandler.h) and list the class in Project Settings > Plugins > Rolly Save > Global Handlers or attach it in a Save Rule (see Save what is not a variable).
  • Engine state: the handlers URollySaveMaterialHandler, URollySaveTimelineHandler, URollySaveSequenceHandler, URollySaveMontageHandler and URollySaveAudioHandler (EngineState/) subclass URollySaveEngineStateHandler, which finds an actor’s components through FRollySaveComponentFilter and notes the assets they show as their design (see Save materials, timelines, sequences, montages and sounds).
  • Gameplay Abilities: URollySaveGASHandler (RollySaveGASHandler.h, module RollySaveGAS) saves Ability System Components; a C++ subclass saves values of your effect context type through CaptureContextValues and ApplyContextValues, and a C++ handler reads IsInPlaceApply to tell a load in place from an actor the load made fresh (see Save Gameplay Ability System state).
  • Save Rules: URollySaveRule (Rules/RollySaveRule.h) is a Save Rule; ApplySavedValues gives an actor with Apply Timing When Asked its values, and ReloadSaveRules finds rules of plugins mounted during play (see Choose what saves).
  • Level tools: URollySaveTriggerComponent (Level/RollySaveTriggerComponent.h) saves when an actor enters its actor’s shape or Trigger is called, refused by IRollySaveTriggerInterface::CanTriggerRollySave on its actor; URollySaveResetAreaComponent resets the saved actors inside its shape when the level is next built, counting After Game Time on play time or a URollySaveGameClock subclass; MarkAllAreasForReset, IsRestoring and RequestSlotScreen are on the subsystem, and URollySaveSubsystem::FRestoreScope makes Is Restoring true around a teleport of your own (see Place save points, checkpoints and reset areas).
  • Instances: instanced meshes save through Instances in FRollySaveActorSettings and FRollySaveActorOverrides (FRollySaveInstanceSettings, Instances/RollySaveInstanceTypes.h). GetInstanceKey, FindInstanceByKey and EqualInstanceKeys on the subsystem turn an instance into an FRollySaveInstanceKey that names it across saves and loads, and back (see Save harvested foliage and instanced meshes).
  • Broken geometry collections: they save through Destruction (ERollySaveDestruction, Destruction/RollySaveDestructionTypes.h); RollySaveDestructionSection (Destruction/RollySaveDestructionSection.h) reads and writes their GCOL sections for tools that read saves (see Keep broken destructibles broken).

Open the screens from code

The screens are in the RollySaveUI module, on clients and standalone games: URollySaveUISubsystem (RollySaveUISubsystem.h) opens the slot screen (OpenSlotScreen) and the profiles panel (OpenProfiles), fires OnNewGameChosen, and runs the saving indicator, notices and loading overlay from the runtime’s events; URollySaveUILibrary::ShowSavingIndicatorPreview shows the indicator without a save, and FRollySaveMapLoadScreen hands the movie player the overlay’s copy (SRollySaveMapLoadScreen) during a map load. FRollySaveSlotScreenModel (Model/RollySaveSlotScreenModel.h) gives a screen of your own the slot screen’s rows, groups, order and actions (see Give players a slot screen).

Read and write save files

URollySaveStore (Store/RollySaveStore.h) keeps two copies of every file on a storage backend. Call it on the game thread; results arrive on the game thread.

  • Save, Load and Delete take an FRollySaveFileName (Storage/RollySaveFileName.h), made only by ForSlot, ForProfile and ForIndex, so a backend never receives a name of another product.
  • Inspect reads both copies and reports what a load would find, with the header only. LoadCopy reads one copy’s bytes and file, and FRollySaveFile::Diagnose reports every checksum of those bytes. List gives the slot index’s entries.
  • Save with an expected sequence number writes only while the file’s newest save still has it, and ends with Changed otherwise, as an edit of an opened save needs.
  • SetReadOptions lists your own chunk IDs, as KnownChunks does for FRollySaveFile::Read. OnWriteFailed and OnLoadedFromBackup report results to code that did not ask, such as a notice on screen. Flush blocks until the store’s operations have reported, within a time limit, saves that wait for their pictures included, which it writes at once; it may be called from a result callback.

FRollySaveFile (File/RollySaveFile.h) is the reader and writer: plain C++ without UObjects, safe on any thread. FRollySaveFile::ReadHeader reads only the prelude and the header. Chunks of your own take an ID of four printable ASCII characters that starts with a lowercase letter; IDs that start with an uppercase letter belong to Rolly Save.

FRollySaveFileReadOptions Options;
Options.KnownChunks.Add(FRollySaveFourCC("mygm")); // decode it; unknown chunks are only carried
FRollySaveFile Loaded;
const FRollySaveFileResult Result = FRollySaveFile::Read(FileBytes, Loaded, Options);
if (Result.Status == ERollySaveFileStatus::NewerVersion)
{
	// Keep the file: a newer version of the game wrote it.
}
C++

Tools that read or change saves start from FRollySaveInspectFile::Make (Inspect/RollySaveInspectFile.h), a file split into chunks and records. RollySaveValueCodec::Decode reads a record’s values by their saved types alone, without its class, and Encode writes them back byte for byte; FRollySaveStagedEdits checks each change as it is staged and rebuilds a file that passes a load’s checks; RollySaveFileDiff::Compare compares two files by key, and SetValueFrom and UseRecord stage the other file’s value or record; RollySaveJson (Json/RollySaveJson.h) writes and reads Rolly Save JSON. Decode on the game thread, since it looks up structs.

Capture and apply records yourself

A record holds one object’s saved state. FRollySaveRecordWriter::Capture (Record/RollySaveRecordWriter.h) writes every property flagged SaveGame, struct members included when they carry the flag too, property bags whole, and, for objects other than actors and scene components, what their Serialize override writes when Ar.IsSaveGame() is true. It calls OnRollySavePreSave on the object, and on each object it owns, before writing it, and the apply calls OnRollySaveLoaded once every record applied, when the object declares them (see Make your plugin’s objects save). Applying reads it back with the versions of the file it came from, so a save made by an older version of your game reads the way it was written. Enums are saved by value name, maps and sets load as exactly what was saved, and a value equal to the class default still restores over an edited instance.

A property that points at an object saves it by what the object is:

  • Another saved object: by its save ID, and the apply finds the object of that ID again. Register such objects under a name in an FRollySaveNameRegistry (Record/RollySaveNameRegistry.h) and give it to the writer and the reader, or implement IRollySaveObjectResolver to give IDs of your own. A pointer to the captured object itself needs neither.
  • An object the saved object owns (its outer is the saved object, or an object the saved object owns): inline, with its class, name and SaveGame properties. The apply reuses an object of that class and name, or creates one; nested objects, shared pointers and cycles come back as they were.
  • An asset or a type: by path. Load the records’ assets with FRollySavePreloader (Record/RollySavePreloader.h) before they apply: an apply loads nothing, and an asset that is not loaded reads as empty.
  • Anything else saves as empty and is listed in the result’s Issues, as is a pointer that loads as empty.

Soft object pointers save their path, except a pointer to the captured object or to a loaded object the resolver gives an ID, which saves as that ID. A path into a Play In Editor copy of a map is saved as the map’s own path and points into the copy the record applies in.

Checksums catch damage, not edits, so every record passes a check of its structure (RollySaveRecordCheck, Record/RollySaveRecordCheck.h) before anything of its file applies: a record whose sizes, counts, nesting, names, types or owned objects a save never writes is Damaged, and no record of that file applies. Some bounds hold for a whole file, such as its owned objects and what its texts display; capturing runs the same check and counts them across the captures you give one FRollySaveCheckBudget, so a file of your captures never holds what your game would refuse. Capture and apply on the game thread: anywhere else, and during garbage collection, they return InvalidInput and do nothing.

#include "Record/RollySaveApplySession.h"
#include "Record/RollySaveNameRegistry.h"
#include "Record/RollySavePreloader.h"
#include "Record/RollySaveRecordCheck.h"
#include "Record/RollySaveRecordCodec.h"
#include "Record/RollySaveRecordWriter.h"

FRollySaveNameRegistry Registry; // save IDs of the objects records point at
Registry.Register(TEXT("inventory"), *Inventory);
Registry.Register(TEXT("quests"), *Quests);

FRollySaveRecordWriter Writer(&Registry); // reuse it: it keeps its buffers
FRollySaveCheckBudget FileBudget; // one per file, for every capture of that file
FRollySaveRecord Record;
Writer.Capture(*Inventory, *Registry.FindId(*Inventory), Record, FileBudget);
TArray<uint8> Payload;
RollySaveRecordCodec::EncodePayload(MakeArrayView(&Record, 1), Payload);
File.AddChunk(FRollySaveFourCC("mygm"), TEXT("inventory"), MoveTemp(Payload));

// After FRollySaveFile::Read with "mygm" in KnownChunks. Loaded, Records (TArray<FRollySaveRecord>) and
// Preloader (FRollySavePreloader) are members, so they live until the records applied.
RollySaveRecordCodec::DecodePayload(Loaded.FindChunk(FRollySaveFourCC("mygm"), TEXT("inventory"))->Data, Records);
const FRollySaveRecord* const ToLoad[] = { &Records[0] };
Preloader.Start(ToLoad, FSimpleDelegate::CreateWeakLambda(this, [this]()
{
	// On a later tick, with the assets the records name loaded; Preloader.GetIssues() lists those that are not.
	FRollySaveApplySession Session(Loaded.Header.Versions, &Registry); // one per file
	Session.Prepare(Records[0], *Inventory);
	const TArray<FRollySaveRecordResult> Results = Session.Apply(); // per record: Damaged, NewerVersion or Ok with Issues
	Preloader.Release();
}));
C++

Give every record of one file to one FRollySaveApplySession (Record/RollySaveApplySession.h), each with the object it restores, then call Apply once: records that point at each other, such as an inventory and the quests that refer to it, find each other’s objects without a resolver, and a damaged record stops the whole file. FRollySaveRecordReader::Apply (Record/RollySaveRecordReader.h) applies a file’s records one at a time instead; use one reader per file, since it counts the file’s bounds and refuses every record after a damaged one.

Keep saves somewhere else

To keep saves somewhere else, such as a cloud store, subclass URollySaveBackend (Storage/RollySaveBackend.h) and override StartWrite, StartRead, StartExists, StartDelete and StartList, and StartQueryFreeSpace when the storage can tell its free bytes (by default it cannot). Each calls its callback once, from any thread, without waiting for the game thread. StartList reports every file the storage holds, or Unsupported; List passes on only Rolly Save’s. When several backend objects reach the same files, override GetStorageId to return the same text for each, so their saves share one queue per slot. Pick the class in Project Settings > Plugins > Rolly Save > Storage Backend.

Run the automated tests

The tests are Rolly.Save.* and RollySaveCore.* for the shared foundation and the design system. Run them in the editor from Tools > Session Frontend > Automation (filter Rolly), or headless:

UnrealEditor-Cmd.exe <YourProject>.uproject -ExecCmds="Automation RunTests Rolly.Save; Quit" -unattended -nullrhi
Text

They make their worlds, actors and rules in memory. Tests that draw pictures (Rolly.Save.UI.Pictures, Rolly.Save.Editor.Pictures and their kin) render only when the run can render (-RenderOffScreen instead of -nullrhi) and otherwise say what they skipped. The golden tests read fixture files from the project’s TestData/RollySaveFixtures folder, which a buyer’s project does not have; without it they have no cases. ARCHITECTURE.md lists what each area covers.