Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Source/Flow/Private/FlowComponent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,10 +567,10 @@ FFlowComponentSaveData UFlowComponent::SaveInstance()

bool UFlowComponent::LoadInstance()
{
const UFlowSaveGame* SaveGame = GetFlowSubsystem()->GetLoadedSaveGame();
if (SaveGame->FlowComponents.Num() > 0)
const FFlowSaveData& SaveData = GetFlowSubsystem()->GetLoadedSaveDataContainer()->GetSaveData();
if (SaveData.FlowComponents.Num() > 0)
{
for (const FFlowComponentSaveData& ComponentRecord : SaveGame->FlowComponents)
for (const FFlowComponentSaveData& ComponentRecord : SaveData.FlowComponents)
{
if (ComponentRecord.WorldName == GetWorld()->GetName() && ComponentRecord.ActorInstanceName == GetOwner()->GetName())
{
Expand Down
48 changes: 33 additions & 15 deletions Source/Flow/Private/FlowSubsystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ FNativeFlowAssetEvent UFlowSubsystem::OnInstancedTemplateRemoved;
#define LOCTEXT_NAMESPACE "FlowSubsystem"

UFlowSubsystem::UFlowSubsystem()
: LoadedSaveGame(nullptr)
: LoadedSaveDataContainer(nullptr)
{
}

Expand Down Expand Up @@ -310,26 +310,41 @@ UWorld* UFlowSubsystem::GetWorld() const

void UFlowSubsystem::OnGameSaved(UFlowSaveGame* SaveGame)
{
// clear existing data, in case we received reused SaveGame instance
SaveFlowDataTo(SaveGame);
}

void UFlowSubsystem::OnGameLoaded(UFlowSaveGame* SaveGame)
{
LoadFlowDataFrom(SaveGame);
}

void UFlowSubsystem::SaveFlowDataTo(const TScriptInterface<IFlowSaveDataContainerInterface> SaveDataContainer)
{
if (!ensureMsgf(SaveDataContainer, TEXT("%hs expects the save data container passed in to be valid!"), __FUNCTION__))
return;

FFlowSaveData& FlowSaveData = SaveDataContainer->GetSaveDataMutable();

// clear existing data, in case we received reused save data container instance
// we only remove data for the current world + global Flow Graph instances (i.e. not bound to any world if created by UGameInstanceSubsystem)
// we keep data bound to other worlds
if (GetWorld())
{
const FString& WorldName = GetWorld()->GetName();

for (int32 i = SaveGame->FlowInstances.Num() - 1; i >= 0; i--)
for (int32 i = FlowSaveData.FlowInstances.Num() - 1; i >= 0; i--)
{
if (SaveGame->FlowInstances[i].WorldName.IsEmpty() || SaveGame->FlowInstances[i].WorldName == WorldName)
if (FlowSaveData.FlowInstances[i].WorldName.IsEmpty() || FlowSaveData.FlowInstances[i].WorldName == WorldName)
{
SaveGame->FlowInstances.RemoveAt(i);
FlowSaveData.FlowInstances.RemoveAt(i);
}
}

for (int32 i = SaveGame->FlowComponents.Num() - 1; i >= 0; i--)
for (int32 i = FlowSaveData.FlowComponents.Num() - 1; i >= 0; i--)
{
if (SaveGame->FlowComponents[i].WorldName.IsEmpty() || SaveGame->FlowComponents[i].WorldName == WorldName)
if (FlowSaveData.FlowComponents[i].WorldName.IsEmpty() || FlowSaveData.FlowComponents[i].WorldName == WorldName)
{
SaveGame->FlowComponents.RemoveAt(i);
FlowSaveData.FlowComponents.RemoveAt(i);
}
}
}
Expand All @@ -341,11 +356,11 @@ void UFlowSubsystem::OnGameSaved(UFlowSaveGame* SaveGame)
{
if (UFlowComponent* FlowComponent = Cast<UFlowComponent>(RootInstance.Value))
{
FlowComponent->SaveRootFlow(SaveGame->FlowInstances);
FlowComponent->SaveRootFlow(FlowSaveData.FlowInstances);
}
else
{
RootInstance.Key->SaveInstance(SaveGame->FlowInstances);
RootInstance.Key->SaveInstance(FlowSaveData.FlowInstances);
}
}
}
Expand All @@ -362,14 +377,17 @@ void UFlowSubsystem::OnGameSaved(UFlowSaveGame* SaveGame)
// write archives to SaveGame
for (const TWeakObjectPtr<UFlowComponent> RegisteredComponent : RegisteredComponents)
{
SaveGame->FlowComponents.Emplace(RegisteredComponent->SaveInstance());
FlowSaveData.FlowComponents.Emplace(RegisteredComponent->SaveInstance());
}
}
}

void UFlowSubsystem::OnGameLoaded(UFlowSaveGame* SaveGame)
void UFlowSubsystem::LoadFlowDataFrom(TScriptInterface<IFlowSaveDataContainerInterface> SaveDataContainer)
{
LoadedSaveGame = SaveGame;
if (!ensureMsgf(SaveDataContainer, TEXT("%hs expects the save data container passed in to be valid!"), __FUNCTION__))
return;

LoadedSaveDataContainer = SaveDataContainer;

// here's opportunity to apply loaded data to custom systems
// it's recommended to do this by overriding method in the subclass
Expand All @@ -382,7 +400,7 @@ void UFlowSubsystem::LoadRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const F
return;
}

for (const FFlowAssetSaveData& AssetRecord : LoadedSaveGame->FlowInstances)
for (const FFlowAssetSaveData& AssetRecord : LoadedSaveDataContainer->GetSaveData().FlowInstances)
{
if (AssetRecord.InstanceName == SavedAssetInstanceName
&& (FlowAsset->IsBoundToWorld() == false || AssetRecord.WorldName == GetWorld()->GetName()))
Expand All @@ -406,7 +424,7 @@ void UFlowSubsystem::LoadSubFlow(UFlowNode_SubGraph* SubGraphNode, const FString

UFlowAsset* SubGraphAsset = SubGraphNode->Asset.LoadSynchronous();

for (const FFlowAssetSaveData& AssetRecord : LoadedSaveGame->FlowInstances)
for (const FFlowAssetSaveData& AssetRecord : LoadedSaveDataContainer->GetSaveData().FlowInstances)
{
if (AssetRecord.InstanceName == SavedAssetInstanceName
&& ((SubGraphAsset && SubGraphAsset->IsBoundToWorld() == false) || AssetRecord.WorldName == GetWorld()->GetName()))
Expand Down
2 changes: 1 addition & 1 deletion Source/Flow/Private/Nodes/Graph/FlowNode_Checkpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void UFlowNode_Checkpoint::ExecuteInput(const FName& PinName)
if (GetFlowSubsystem())
{
UFlowSaveGame* NewSaveGame = Cast<UFlowSaveGame>(UGameplayStatics::CreateSaveGameObject(UFlowSaveGame::StaticClass()));
GetFlowSubsystem()->OnGameSaved(NewSaveGame);
GetFlowSubsystem()->SaveFlowDataTo(NewSaveGame);

UGameplayStatics::SaveGameToSlot(NewSaveGame, NewSaveGame->SaveSlotName, 0);
}
Expand Down
36 changes: 30 additions & 6 deletions Source/Flow/Public/FlowSave.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once

#include "GameFramework/SaveGame.h"
#include "Interfaces/FlowSaveDataContainerInterface.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
#include "FlowSave.generated.h"

Expand Down Expand Up @@ -66,6 +67,23 @@ struct FLOW_API FFlowComponentSaveData
}
};

USTRUCT(BlueprintType)
struct FLOW_API FFlowSaveData
{
GENERATED_USTRUCT_BODY()

UPROPERTY(VisibleAnywhere, Category = "Flow")
TArray<FFlowComponentSaveData> FlowComponents;

UPROPERTY(VisibleAnywhere, Category = "Flow")
TArray<FFlowAssetSaveData> FlowInstances;

friend FArchive& operator<<(FArchive& Ar, FFlowSaveData& InData)
{
return Ar;
}
};

struct FLOW_API FFlowArchive : public FObjectAndNameAsStringProxyArchive
{
FFlowArchive(FArchive& InInnerArchive) : FObjectAndNameAsStringProxyArchive(InInnerArchive, true)
Expand All @@ -75,26 +93,32 @@ struct FLOW_API FFlowArchive : public FObjectAndNameAsStringProxyArchive
};

UCLASS(BlueprintType)
class FLOW_API UFlowSaveGame : public USaveGame
class FLOW_API UFlowSaveGame : public USaveGame, public IFlowSaveDataContainerInterface
{
GENERATED_BODY()

public:
UFlowSaveGame() {};
UFlowSaveGame() {}

virtual const FFlowSaveData& GetSaveData() const override { return FlowSaveData; }
virtual FFlowSaveData& GetSaveDataMutable() override { return FlowSaveData; }

UPROPERTY(VisibleAnywhere, Category = "SaveGame")
FString SaveSlotName = TEXT("FlowSave");

UPROPERTY(VisibleAnywhere, Category = "Flow")
UPROPERTY(VisibleAnywhere, Category = "Flow", meta=(Deprecated, DeprecationMessage="Use GetSaveData()/GetSaveDataMutable() for data access."))
TArray<FFlowComponentSaveData> FlowComponents;

UPROPERTY(VisibleAnywhere, Category = "Flow")
UPROPERTY(VisibleAnywhere, Category = "Flow", meta=(Deprecated, DeprecationMessage="Use GetSaveData()/GetSaveDataMutable() for data access."))
TArray<FFlowAssetSaveData> FlowInstances;

UPROPERTY(VisibleAnywhere, Category = "Flow")
FFlowSaveData FlowSaveData;

friend FArchive& operator<<(FArchive& Ar, UFlowSaveGame& SaveGame)
{
Ar << SaveGame.FlowComponents;
Ar << SaveGame.FlowInstances;
Ar << SaveGame.FlowSaveData.FlowComponents;
Ar << SaveGame.FlowSaveData.FlowInstances;
return Ar;
}
};
29 changes: 25 additions & 4 deletions Source/Flow/Public/FlowSubsystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem

protected:
UPROPERTY()
TObjectPtr<UFlowSaveGame> LoadedSaveGame;
TScriptInterface<IFlowSaveDataContainerInterface> LoadedSaveDataContainer;

public:
virtual bool ShouldCreateSubsystem(UObject* Outer) const override;
Expand Down Expand Up @@ -124,11 +124,29 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem
UPROPERTY(BlueprintAssignable, Category = "FlowSubsystem")
FSimpleFlowEvent OnSaveGame;

UFUNCTION(BlueprintCallable, Category = "FlowSubsystem")
UFUNCTION(BlueprintCallable, Category = "FlowSubsystem", meta=(DeprecatedFunction, DeprecationMessage="Use SaveFlowDataTo(..) instead"))
virtual void OnGameSaved(UFlowSaveGame* SaveGame);

UFUNCTION(BlueprintCallable, Category = "FlowSubsystem", meta=(DeprecatedFunction, DeprecationMessage="Use LoadFlowDataFrom(..) instead"))
virtual void OnGameLoaded(UFlowSaveGame* SaveGame);

UFUNCTION(BlueprintCallable, Category = "FlowSubsystem")
virtual void SaveFlowDataTo(TScriptInterface<IFlowSaveDataContainerInterface> SaveDataContainer);

UFUNCTION(BlueprintCallable, Category = "FlowSubsystem")
virtual void OnGameLoaded(UFlowSaveGame* SaveGame);
virtual void LoadFlowDataFrom(TScriptInterface<IFlowSaveDataContainerInterface> SaveDataContainer);

void SaveFlowDataTo(IFlowSaveDataContainerInterface* SaveDataContainer)
{
const TScriptInterface<IFlowSaveDataContainerInterface> SaveDataContainerScript = Cast<UObject>(SaveDataContainer);
SaveFlowDataTo(SaveDataContainerScript);
}

void LoadFlowDataFrom(IFlowSaveDataContainerInterface* SaveDataContainer)
{
const TScriptInterface<IFlowSaveDataContainerInterface> SaveDataContainerScript = Cast<UObject>(SaveDataContainer);
LoadFlowDataFrom(SaveDataContainerScript);
}

UFUNCTION(BlueprintCallable, Category = "FlowSubsystem")
virtual void LoadRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const FString& SavedAssetInstanceName, const bool bAllowMultipleInstances);
Expand All @@ -137,7 +155,10 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem
virtual void LoadSubFlow(UFlowNode_SubGraph* SubGraphNode, const FString& SavedAssetInstanceName);

UFUNCTION(BlueprintPure, Category = "FlowSubsystem")
UFlowSaveGame* GetLoadedSaveGame() const { return LoadedSaveGame; }
TScriptInterface<IFlowSaveDataContainerInterface> GetLoadedSaveDataContainer() const { return LoadedSaveDataContainer; }

UFUNCTION(BlueprintPure, Category = "FlowSubsystem")
UFlowSaveGame* GetLoadedSaveGame() const { return Cast<UFlowSaveGame>(LoadedSaveDataContainer.GetObject()); }

//////////////////////////////////////////////////////////////////////////
// Component Registry
Expand Down
28 changes: 28 additions & 0 deletions Source/Flow/Public/Interfaces/FlowSaveDataContainerInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright https://github.com/MothCocoon/FlowGraph/graphs/contributors

#pragma once

#include "UObject/Interface.h"

#include "FlowSaveDataContainerInterface.generated.h"

struct FFlowSaveData;

// (optional) interface to allow for save data containers that are not USaveGame
UINTERFACE(MinimalAPI, BlueprintType)
class UFlowSaveDataContainerInterface : public UInterface
{
GENERATED_BODY()
};

class IFlowSaveDataContainerInterface
{
GENERATED_BODY()

public:
/** Get flow save data for modification. */
virtual FFlowSaveData& GetSaveDataMutable() = 0;

/** Get flow save data for reading. */
virtual const FFlowSaveData& GetSaveData() const = 0;
};