diff --git a/Source/Flow/Private/FlowComponent.cpp b/Source/Flow/Private/FlowComponent.cpp index fc1dc359e..0b0fd8392 100644 --- a/Source/Flow/Private/FlowComponent.cpp +++ b/Source/Flow/Private/FlowComponent.cpp @@ -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()) { diff --git a/Source/Flow/Private/FlowSubsystem.cpp b/Source/Flow/Private/FlowSubsystem.cpp index 946610632..2bb1533bb 100644 --- a/Source/Flow/Private/FlowSubsystem.cpp +++ b/Source/Flow/Private/FlowSubsystem.cpp @@ -25,7 +25,7 @@ FNativeFlowAssetEvent UFlowSubsystem::OnInstancedTemplateRemoved; #define LOCTEXT_NAMESPACE "FlowSubsystem" UFlowSubsystem::UFlowSubsystem() - : LoadedSaveGame(nullptr) + : LoadedSaveDataContainer(nullptr) { } @@ -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 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); } } } @@ -341,11 +356,11 @@ void UFlowSubsystem::OnGameSaved(UFlowSaveGame* SaveGame) { if (UFlowComponent* FlowComponent = Cast(RootInstance.Value)) { - FlowComponent->SaveRootFlow(SaveGame->FlowInstances); + FlowComponent->SaveRootFlow(FlowSaveData.FlowInstances); } else { - RootInstance.Key->SaveInstance(SaveGame->FlowInstances); + RootInstance.Key->SaveInstance(FlowSaveData.FlowInstances); } } } @@ -362,14 +377,17 @@ void UFlowSubsystem::OnGameSaved(UFlowSaveGame* SaveGame) // write archives to SaveGame for (const TWeakObjectPtr RegisteredComponent : RegisteredComponents) { - SaveGame->FlowComponents.Emplace(RegisteredComponent->SaveInstance()); + FlowSaveData.FlowComponents.Emplace(RegisteredComponent->SaveInstance()); } } } -void UFlowSubsystem::OnGameLoaded(UFlowSaveGame* SaveGame) +void UFlowSubsystem::LoadFlowDataFrom(TScriptInterface 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 @@ -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())) @@ -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())) diff --git a/Source/Flow/Private/Nodes/Graph/FlowNode_Checkpoint.cpp b/Source/Flow/Private/Nodes/Graph/FlowNode_Checkpoint.cpp index 6b51e9074..a002c5c9f 100644 --- a/Source/Flow/Private/Nodes/Graph/FlowNode_Checkpoint.cpp +++ b/Source/Flow/Private/Nodes/Graph/FlowNode_Checkpoint.cpp @@ -20,7 +20,7 @@ void UFlowNode_Checkpoint::ExecuteInput(const FName& PinName) if (GetFlowSubsystem()) { UFlowSaveGame* NewSaveGame = Cast(UGameplayStatics::CreateSaveGameObject(UFlowSaveGame::StaticClass())); - GetFlowSubsystem()->OnGameSaved(NewSaveGame); + GetFlowSubsystem()->SaveFlowDataTo(NewSaveGame); UGameplayStatics::SaveGameToSlot(NewSaveGame, NewSaveGame->SaveSlotName, 0); } diff --git a/Source/Flow/Public/FlowSave.h b/Source/Flow/Public/FlowSave.h index f79711f9c..b3b9bdd54 100644 --- a/Source/Flow/Public/FlowSave.h +++ b/Source/Flow/Public/FlowSave.h @@ -3,6 +3,7 @@ #pragma once #include "GameFramework/SaveGame.h" +#include "Interfaces/FlowSaveDataContainerInterface.h" #include "Serialization/ObjectAndNameAsStringProxyArchive.h" #include "FlowSave.generated.h" @@ -66,6 +67,23 @@ struct FLOW_API FFlowComponentSaveData } }; +USTRUCT(BlueprintType) +struct FLOW_API FFlowSaveData +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(VisibleAnywhere, Category = "Flow") + TArray FlowComponents; + + UPROPERTY(VisibleAnywhere, Category = "Flow") + TArray FlowInstances; + + friend FArchive& operator<<(FArchive& Ar, FFlowSaveData& InData) + { + return Ar; + } +}; + struct FLOW_API FFlowArchive : public FObjectAndNameAsStringProxyArchive { FFlowArchive(FArchive& InInnerArchive) : FObjectAndNameAsStringProxyArchive(InInnerArchive, true) @@ -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 FlowComponents; - UPROPERTY(VisibleAnywhere, Category = "Flow") + UPROPERTY(VisibleAnywhere, Category = "Flow", meta=(Deprecated, DeprecationMessage="Use GetSaveData()/GetSaveDataMutable() for data access.")) TArray 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; } }; diff --git a/Source/Flow/Public/FlowSubsystem.h b/Source/Flow/Public/FlowSubsystem.h index 4852cb519..dd86e7c5e 100644 --- a/Source/Flow/Public/FlowSubsystem.h +++ b/Source/Flow/Public/FlowSubsystem.h @@ -60,7 +60,7 @@ class FLOW_API UFlowSubsystem : public UGameInstanceSubsystem protected: UPROPERTY() - TObjectPtr LoadedSaveGame; + TScriptInterface LoadedSaveDataContainer; public: virtual bool ShouldCreateSubsystem(UObject* Outer) const override; @@ -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 SaveDataContainer); UFUNCTION(BlueprintCallable, Category = "FlowSubsystem") - virtual void OnGameLoaded(UFlowSaveGame* SaveGame); + virtual void LoadFlowDataFrom(TScriptInterface SaveDataContainer); + + void SaveFlowDataTo(IFlowSaveDataContainerInterface* SaveDataContainer) + { + const TScriptInterface SaveDataContainerScript = Cast(SaveDataContainer); + SaveFlowDataTo(SaveDataContainerScript); + } + + void LoadFlowDataFrom(IFlowSaveDataContainerInterface* SaveDataContainer) + { + const TScriptInterface SaveDataContainerScript = Cast(SaveDataContainer); + LoadFlowDataFrom(SaveDataContainerScript); + } UFUNCTION(BlueprintCallable, Category = "FlowSubsystem") virtual void LoadRootFlow(UObject* Owner, UFlowAsset* FlowAsset, const FString& SavedAssetInstanceName, const bool bAllowMultipleInstances); @@ -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 GetLoadedSaveDataContainer() const { return LoadedSaveDataContainer; } + + UFUNCTION(BlueprintPure, Category = "FlowSubsystem") + UFlowSaveGame* GetLoadedSaveGame() const { return Cast(LoadedSaveDataContainer.GetObject()); } ////////////////////////////////////////////////////////////////////////// // Component Registry diff --git a/Source/Flow/Public/Interfaces/FlowSaveDataContainerInterface.h b/Source/Flow/Public/Interfaces/FlowSaveDataContainerInterface.h new file mode 100644 index 000000000..aad287c36 --- /dev/null +++ b/Source/Flow/Public/Interfaces/FlowSaveDataContainerInterface.h @@ -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; +};