Skip to content

Refactored save integration to allow for arbitrary save data container objects (#317)#318

Closed
gregorhcs wants to merge 5 commits intoMothCocoon:5.xfrom
gregorhcs:feat/generalize-save-data-container
Closed

Refactored save integration to allow for arbitrary save data container objects (#317)#318
gregorhcs wants to merge 5 commits intoMothCocoon:5.xfrom
gregorhcs:feat/generalize-save-data-container

Conversation

@gregorhcs
Copy link
Copy Markdown
Contributor

@gregorhcs gregorhcs commented Dec 25, 2025

#317 Implemented generic save data container support for flow.

The Flow Subsystem now saves and loads from any UObject implementing the new IFlowSaveDataContainerInterface. The interfaces provides mutable and const access to the new FFlowSaveData struct, which wraps FFlowComponentSaveData and FFlowAssetSaveData.

To retain compatibility with UFlowSaveGame, that class now implements the new interface, thus projects can easily switch to the new API on the subsystem. I've marked the old API as deprecated.

@MothDoctor MothDoctor force-pushed the 5.x branch 2 times, most recently from 373190d to af300a9 Compare January 11, 2026 21:56
UFlowSaveGame() {}

virtual const FFlowSaveData& GetSaveData() const override { return FlowSaveData; }
virtual FFlowSaveData& GetSaveDataMutable() override { return FlowSaveData; }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I'm thinking now it would be better if the interface would also allow BP implementation. I'll add that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, so I tried bending the function signature for a while to allow implementing the interface in BP, but I think it's impossible for BP functions to return references, so I'd say we leave it like that. It's not really a regression, since previously there was no way to customize the container at all.

@MothDoctor MothDoctor force-pushed the 5.x branch 2 times, most recently from acd78b4 to 22f316a Compare January 17, 2026 14:57
@MothDoctor MothDoctor self-assigned this Jan 18, 2026
@MothDoctor
Copy link
Copy Markdown
Contributor

MothDoctor commented Mar 29, 2026

Hey, I reviewed the change. I'm against accepting this, especially in the current form.

  • There's no need to deprecate the layout of UFlowSaveGame if the project needs an arbitrary save object. Conceptually, this shouldn't require anything more than subclassing UFlowSubsystem. Obviously, a new Checkpoint node might be required, but that's common.
  • The plugin implementation should use the engine's USaveGame, which is basically an empty interface. Introducing a custom interface makes plugin code a bit convoluted.
  • This change would break already released games since it doesn't move information from deprecated fields to the new one.
image


However, I see a good reason why using UFlowSaveGame itself might be undesired. The project might want to manage the Save data differently, without dependency on this specific class. Until now, the plugin code has hardcoded the usage of UFlowSaveGame.

To resolve this, I refactored code to remove workarounds if the project doesn't use UFlowSaveGame.
1c13e70

  • Added variants of OnGameSaved and OnGameLoaded: accepting TArray<FFlowComponentSaveData> and TArray<FFlowAssetSaveData> as input parameters.
  • Instead of creating dummy FFlowSaveData (which only moved the content of UFlowSaveGame), I ended up creating a transient UFlowSaveGame object in the new OnGameLoaded variant. This way, the plugin can keep operating on UFlowSaveGame as a container for these 2 arrays, but projects can store these arrays whenever they want.
void UFlowSubsystem::OnGameLoaded(TArray<FFlowComponentSaveData>& FlowComponents, TArray<FFlowAssetSaveData>& FlowInstances)
{
	// Create an object to store Flow Save data loaded from the custom data container. 
	LoadedSaveGame = NewObject<UFlowSaveGame>(GetTransientPackage(), UFlowSaveGame::StaticClass());
	LoadedSaveGame->FlowComponents = FlowComponents;
	LoadedSaveGame->FlowInstances = FlowInstances;
}

You should be able to support your case by calling new variants of OnGameSaved and OnGameLoaded. But if this wouldn't be enough, it's now possible to override the Save/Load logic with:

  • New variant of OnGameSaved()
  • New variant of OnGameLoad()
  • New GetLoadedComponentRecord() now called by UFlowComponent::LoadInstance.
  • New GetLoadedAssetRecord() is now called by internal UFlowSubsystem methods, but can be accessed from anywhere.

Let me know if there's something I didn't cover! Didn't have a chance to test that in a real project, would be grateful for any issue reports or PRs improving this refactor!
I'm closing this PR for tracking purposes, but let's continue the conversation on Discord :)

@MothDoctor MothDoctor closed this Mar 29, 2026
@gregorhcs gregorhcs deleted the feat/generalize-save-data-container branch March 30, 2026 11:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants