diff --git a/SaveExtension.uplugin b/SaveExtension.uplugin index 3a1ba45..3db46d6 100644 --- a/SaveExtension.uplugin +++ b/SaveExtension.uplugin @@ -10,7 +10,7 @@ "CreatedByURL": "https://piperift.com", "DocsURL": "https://piperift.com/SaveExtension/", "SupportURL": "info@piperift.com", - "EngineVersion": "4.26", + "EngineVersion": "5.2", "EnabledByDefault": true, "CanContainContent": false, "IsBetaVersion": false, @@ -21,13 +21,10 @@ "LoadingPhase": "PreDefault", "WhitelistPlatforms": [ "Win64", - "Win32", - "Linux", + "Mac", + "IOS", "Android", - "PS5", - "XboxOne", - "Switch", - "Mac" + "Linux" ] }, { @@ -36,13 +33,10 @@ "LoadingPhase": "PostEngineInit", "WhitelistPlatforms": [ "Win64", - "Win32", - "Linux", + "Mac", + "IOS", "Android", - "PS5", - "XboxOne", - "Switch", - "Mac" + "Linux" ] }, { @@ -51,7 +45,6 @@ "LoadingPhase": "PreDefault", "WhitelistPlatforms": [ "Win64", - "Win32", "Linux", "Mac" ] diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SavePreset.cpp b/Source/Editor/Private/Asset/AssetTypeAction_SavePreset.cpp deleted file mode 100644 index c6c5233..0000000 --- a/Source/Editor/Private/Asset/AssetTypeAction_SavePreset.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "AssetTypeAction_SavePreset.h" - -#define LOCTEXT_NAMESPACE "AssetTypeActions" - - -////////////////////////////////////////////////////////////////////////// -// FAssetTypeAction_SavePreset - -FText FAssetTypeAction_SavePreset::GetName() const -{ - return LOCTEXT("FAssetTypeAction_SavePresetName", "Save Preset"); -} - -FColor FAssetTypeAction_SavePreset::GetTypeColor() const -{ - return FColor(63, 126, 255); -} - -////////////////////////////////////////////////////////////////////////// - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SavePreset.h b/Source/Editor/Private/Asset/AssetTypeAction_SavePreset.h deleted file mode 100644 index 09cf3a7..0000000 --- a/Source/Editor/Private/Asset/AssetTypeAction_SavePreset.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "SaveExtensionEditor.h" - -#include "AssetTypeActions_Base.h" - -#include "SavePreset.h" - -#define LOCTEXT_NAMESPACE "SaveExtensionEditor" - - -class FAssetTypeAction_SavePreset : public FAssetTypeActions_Base -{ -public: - - virtual uint32 GetCategories() override { - return FSaveExtensionEditor::Get().AssetCategory; - } - - virtual FText GetName() const override; - virtual FColor GetTypeColor() const override; - - virtual UClass* GetSupportedClass() const override { return USavePreset::StaticClass(); } -}; - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SaveSlot.cpp b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlot.cpp new file mode 100644 index 0000000..3c29130 --- /dev/null +++ b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlot.cpp @@ -0,0 +1,23 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "AssetTypeAction_SaveSlot.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + + +////////////////////////////////////////////////////////////////////////// +// FAssetTypeAction_SaveSlot + +FText FAssetTypeAction_SaveSlot::GetName() const +{ + return LOCTEXT("FAssetTypeAction_SaveSlotName", "Save Slot"); +} + +FColor FAssetTypeAction_SaveSlot::GetTypeColor() const +{ + return FColor(63, 126, 255); +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SaveSlot.h b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlot.h new file mode 100644 index 0000000..eb8cb58 --- /dev/null +++ b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlot.h @@ -0,0 +1,28 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "SaveExtensionEditor.h" + +#include +#include + + +class FAssetTypeAction_SaveSlot : public FAssetTypeActions_Base +{ +public: + virtual uint32 GetCategories() override + { + return FSaveExtensionEditor::Get().AssetCategory; + } + + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + + virtual UClass* GetSupportedClass() const override + { + return USaveSlot::StaticClass(); + } +}; + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SaveSlotData.cpp b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlotData.cpp new file mode 100644 index 0000000..d953edd --- /dev/null +++ b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlotData.cpp @@ -0,0 +1,23 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "AssetTypeAction_SaveSlotData.h" + +#define LOCTEXT_NAMESPACE "AssetTypeActions" + + +////////////////////////////////////////////////////////////////////////// +// FAssetTypeAction_SaveSlotData + +FText FAssetTypeAction_SaveSlotData::GetName() const +{ + return LOCTEXT("FAssetTypeAction_SaveSlotDataName", "Save Slot Data"); +} + +FColor FAssetTypeAction_SaveSlotData::GetTypeColor() const +{ + return FColor(63, 126, 255); +} + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SaveSlotData.h b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlotData.h new file mode 100644 index 0000000..d8caa25 --- /dev/null +++ b/Source/Editor/Private/Asset/AssetTypeAction_SaveSlotData.h @@ -0,0 +1,28 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "SaveExtensionEditor.h" + +#include +#include + + +class FAssetTypeAction_SaveSlotData : public FAssetTypeActions_Base +{ +public: + virtual uint32 GetCategories() override + { + return FSaveExtensionEditor::Get().AssetCategory; + } + + virtual FText GetName() const override; + virtual FColor GetTypeColor() const override; + + virtual UClass* GetSupportedClass() const override + { + return USaveSlotData::StaticClass(); + } +}; + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SlotData.cpp b/Source/Editor/Private/Asset/AssetTypeAction_SlotData.cpp deleted file mode 100644 index 77ecde3..0000000 --- a/Source/Editor/Private/Asset/AssetTypeAction_SlotData.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "AssetTypeAction_SlotData.h" - -#define LOCTEXT_NAMESPACE "AssetTypeActions" - - -////////////////////////////////////////////////////////////////////////// -// FAssetTypeAction_SavePreset - -FText FAssetTypeAction_SlotData::GetName() const -{ - return LOCTEXT("FAssetTypeAction_SlotDataName", "Save Data"); -} - -FColor FAssetTypeAction_SlotData::GetTypeColor() const -{ - return FColor(63, 126, 255); -} - -////////////////////////////////////////////////////////////////////////// - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SlotData.h b/Source/Editor/Private/Asset/AssetTypeAction_SlotData.h deleted file mode 100644 index a65877b..0000000 --- a/Source/Editor/Private/Asset/AssetTypeAction_SlotData.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "SaveExtensionEditor.h" - -#include "AssetTypeActions_Base.h" - -#include "SlotData.h" - -#define LOCTEXT_NAMESPACE "SaveExtensionEditor" - - -class FAssetTypeAction_SlotData : public FAssetTypeActions_Base -{ -public: - - virtual uint32 GetCategories() override { - return FSaveExtensionEditor::Get().AssetCategory; - } - - virtual FText GetName() const override; - virtual FColor GetTypeColor() const override; - - virtual UClass* GetSupportedClass() const override { return USlotData::StaticClass(); } -}; - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SlotInfo.cpp b/Source/Editor/Private/Asset/AssetTypeAction_SlotInfo.cpp deleted file mode 100644 index 1da57f5..0000000 --- a/Source/Editor/Private/Asset/AssetTypeAction_SlotInfo.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "AssetTypeAction_SlotInfo.h" - -#define LOCTEXT_NAMESPACE "AssetTypeActions" - - -////////////////////////////////////////////////////////////////////////// -// FAssetTypeAction_SavePreset - -FText FAssetTypeAction_SlotInfo::GetName() const -{ - return LOCTEXT("FAssetTypeAction_SlotInfoName", "Save Info"); -} - -FColor FAssetTypeAction_SlotInfo::GetTypeColor() const -{ - return FColor(63, 126, 255); -} - -////////////////////////////////////////////////////////////////////////// - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/AssetTypeAction_SlotInfo.h b/Source/Editor/Private/Asset/AssetTypeAction_SlotInfo.h deleted file mode 100644 index f56b3d9..0000000 --- a/Source/Editor/Private/Asset/AssetTypeAction_SlotInfo.h +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "SaveExtensionEditor.h" - -#include "AssetTypeActions_Base.h" - -#include "SlotInfo.h" - -#define LOCTEXT_NAMESPACE "SaveExtensionEditor" - - -class FAssetTypeAction_SlotInfo : public FAssetTypeActions_Base -{ -public: - - virtual uint32 GetCategories() override { - return FSaveExtensionEditor::Get().AssetCategory; - } - - virtual FText GetName() const override; - virtual FColor GetTypeColor() const override; - - virtual UClass* GetSupportedClass() const override { return USlotInfo::StaticClass(); } -}; - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Asset/SavePresetFactory.cpp b/Source/Editor/Private/Asset/SavePresetFactory.cpp deleted file mode 100644 index c33ef3c..0000000 --- a/Source/Editor/Private/Asset/SavePresetFactory.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "SavePresetFactory.h" -#include "Kismet2/KismetEditorUtilities.h" - - -USavePresetFactory::USavePresetFactory() : Super() { - bCreateNew = true; - bEditAfterNew = true; - SupportedClass = USavePreset::StaticClass(); -} - -UObject* USavePresetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) { - check(Class->IsChildOf(USavePreset::StaticClass())); - - if (!FKismetEditorUtilities::CanCreateBlueprintOfClass(Class)) - { - return nullptr; - } - return FKismetEditorUtilities::CreateBlueprint(Class, InParent, Name, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(),TEXT("AssetTypeActions")); -} diff --git a/Source/Editor/Private/Asset/SavePresetFactory.h b/Source/Editor/Private/Asset/SavePresetFactory.h deleted file mode 100644 index 55f5edf..0000000 --- a/Source/Editor/Private/Asset/SavePresetFactory.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "SavePreset.h" - -#include "AssetTypeActions_Base.h" -#include "Factories/Factory.h" - -#include "SavePresetFactory.generated.h" - - -UCLASS() -class SAVEEXTENSIONEDITOR_API USavePresetFactory : public UFactory -{ - GENERATED_BODY() - -public: - - USavePresetFactory(); - - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; -}; diff --git a/Source/Editor/Private/Asset/SaveSlotDataFactory.cpp b/Source/Editor/Private/Asset/SaveSlotDataFactory.cpp new file mode 100644 index 0000000..04cea42 --- /dev/null +++ b/Source/Editor/Private/Asset/SaveSlotDataFactory.cpp @@ -0,0 +1,26 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "Asset/SaveSlotDataFactory.h" + +#include + + +USaveSaveSlotDataFactory::USaveSaveSlotDataFactory() : Super() +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = USaveSlotData::StaticClass(); +} + +UObject* USaveSaveSlotDataFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, + EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + check(Class->IsChildOf(USaveSlotData::StaticClass())); + + if (!FKismetEditorUtilities::CanCreateBlueprintOfClass(Class)) + { + return nullptr; + } + return FKismetEditorUtilities::CreateBlueprint(Class, InParent, Name, BPTYPE_Normal, + UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), TEXT("AssetTypeActions")); +} diff --git a/Source/Editor/Private/Asset/SaveSlotDataFactory.h b/Source/Editor/Private/Asset/SaveSlotDataFactory.h new file mode 100644 index 0000000..818f15a --- /dev/null +++ b/Source/Editor/Private/Asset/SaveSlotDataFactory.h @@ -0,0 +1,22 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "AssetTypeActions_Base.h" +#include "Factories/Factory.h" +#include "SaveSlotData.h" + +#include "SaveSlotDataFactory.generated.h" + + +UCLASS() +class SAVEEXTENSIONEDITOR_API USaveSaveSlotDataFactory : public UFactory +{ + GENERATED_BODY() + +public: + USaveSaveSlotDataFactory(); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, + UObject* Context, FFeedbackContext* Warn) override; +}; diff --git a/Source/Editor/Private/Asset/SaveSlotFactory.cpp b/Source/Editor/Private/Asset/SaveSlotFactory.cpp new file mode 100644 index 0000000..3ad9e40 --- /dev/null +++ b/Source/Editor/Private/Asset/SaveSlotFactory.cpp @@ -0,0 +1,26 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "Asset/SaveSlotFactory.h" + +#include + + +USaveSlotFactory::USaveSlotFactory() : Super() +{ + bCreateNew = true; + bEditAfterNew = true; + SupportedClass = USaveSlot::StaticClass(); +} + +UObject* USaveSlotFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, + UObject* Context, FFeedbackContext* Warn) +{ + check(Class->IsChildOf(USaveSlot::StaticClass())); + + if (!FKismetEditorUtilities::CanCreateBlueprintOfClass(Class)) + { + return nullptr; + } + return FKismetEditorUtilities::CreateBlueprint(Class, InParent, Name, BPTYPE_Normal, + UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), TEXT("AssetTypeActions")); +} diff --git a/Source/Editor/Private/Asset/SaveSlotFactory.h b/Source/Editor/Private/Asset/SaveSlotFactory.h new file mode 100644 index 0000000..dbf1df2 --- /dev/null +++ b/Source/Editor/Private/Asset/SaveSlotFactory.h @@ -0,0 +1,22 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "AssetTypeActions_Base.h" +#include "Factories/Factory.h" +#include "SaveSlot.h" + +#include "SaveSlotFactory.generated.h" + + +UCLASS() +class SAVEEXTENSIONEDITOR_API USaveSlotFactory : public UFactory +{ + GENERATED_BODY() + +public: + USaveSlotFactory(); + + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, + UObject* Context, FFeedbackContext* Warn) override; +}; diff --git a/Source/Editor/Private/Asset/SlotDataFactory.cpp b/Source/Editor/Private/Asset/SlotDataFactory.cpp deleted file mode 100644 index c37c8cf..0000000 --- a/Source/Editor/Private/Asset/SlotDataFactory.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "SlotDataFactory.h" -#include "Kismet2/KismetEditorUtilities.h" - - -USlotDataFactory::USlotDataFactory() : Super() { - bCreateNew = true; - bEditAfterNew = true; - SupportedClass = USlotData::StaticClass(); -} - -UObject* USlotDataFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) { - check(Class->IsChildOf(USlotData::StaticClass())); - - if (!FKismetEditorUtilities::CanCreateBlueprintOfClass(Class)) - { - return nullptr; - } - return FKismetEditorUtilities::CreateBlueprint(Class, InParent, Name, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), TEXT("AssetTypeActions")); -} diff --git a/Source/Editor/Private/Asset/SlotDataFactory.h b/Source/Editor/Private/Asset/SlotDataFactory.h deleted file mode 100644 index fcd340f..0000000 --- a/Source/Editor/Private/Asset/SlotDataFactory.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "SlotData.h" - -#include "AssetTypeActions_Base.h" -#include "Factories/Factory.h" - -#include "SlotDataFactory.generated.h" - - -UCLASS() -class SAVEEXTENSIONEDITOR_API USlotDataFactory : public UFactory { - GENERATED_BODY() - -public: - - USlotDataFactory(); - - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; -}; diff --git a/Source/Editor/Private/Asset/SlotInfoFactory.cpp b/Source/Editor/Private/Asset/SlotInfoFactory.cpp deleted file mode 100644 index 0bdfa8e..0000000 --- a/Source/Editor/Private/Asset/SlotInfoFactory.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "SlotInfoFactory.h" -#include "Kismet2/KismetEditorUtilities.h" - - -USlotInfoFactory::USlotInfoFactory() : Super() { - bCreateNew = true; - bEditAfterNew = true; - SupportedClass = USlotInfo::StaticClass(); -} - -UObject* USlotInfoFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) { - check(Class->IsChildOf(USlotInfo::StaticClass())); - - if (!FKismetEditorUtilities::CanCreateBlueprintOfClass(Class)) - { - return nullptr; - } - return FKismetEditorUtilities::CreateBlueprint(Class, InParent, Name, BPTYPE_Normal, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass(), TEXT("AssetTypeActions")); -} diff --git a/Source/Editor/Private/Asset/SlotInfoFactory.h b/Source/Editor/Private/Asset/SlotInfoFactory.h deleted file mode 100644 index 60783ce..0000000 --- a/Source/Editor/Private/Asset/SlotInfoFactory.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "SlotInfo.h" - -#include "AssetTypeActions_Base.h" -#include "Factories/Factory.h" - -#include "SlotInfoFactory.generated.h" - - -UCLASS() -class SAVEEXTENSIONEDITOR_API USlotInfoFactory : public UFactory { - GENERATED_BODY() - -public: - - USlotInfoFactory(); - - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; -}; diff --git a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.cpp b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.cpp index 5b273ea..f848237 100644 --- a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.cpp +++ b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.cpp @@ -1,12 +1,14 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "ClassFilterHelpers.h" -#include + +#include "UnloadedBlueprintData.h" + +#include #include +#include #include -#include -#include "UnloadedBlueprintData.h" namespace ClassFilter @@ -19,8 +21,11 @@ namespace ClassFilter FClassHierarchy::FClassHierarchy() { // Register with the Asset Registry to be informed when it is done loading up files. - FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); - OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle = AssetRegistryModule.Get().OnFilesLoaded().AddStatic(ClassFilter::Helpers::RequestPopulateClassHierarchy); + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); + OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle = + AssetRegistryModule.Get().OnFilesLoaded().AddStatic( + ClassFilter::Helpers::RequestPopulateClassHierarchy); AssetRegistryModule.Get().OnAssetAdded().AddRaw(this, &FClassHierarchy::AddAsset); AssetRegistryModule.Get().OnAssetRemoved().AddRaw(this, &FClassHierarchy::RemoveAsset); @@ -28,8 +33,11 @@ namespace ClassFilter FCoreUObjectDelegates::ReloadCompleteDelegate.AddRaw(this, &FClassHierarchy::OnReloadComplete); // Register to have Populate called when a Blueprint is compiled. - OnBlueprintCompiledRequestPopulateClassHierarchyDelegateHandle = GEditor->OnBlueprintCompiled().AddStatic(ClassFilter::Helpers::RequestPopulateClassHierarchy); - OnClassPackageLoadedOrUnloadedRequestPopulateClassHierarchyDelegateHandle = GEditor->OnClassPackageLoadedOrUnloaded().AddStatic(ClassFilter::Helpers::RequestPopulateClassHierarchy); + OnBlueprintCompiledRequestPopulateClassHierarchyDelegateHandle = + GEditor->OnBlueprintCompiled().AddStatic(ClassFilter::Helpers::RequestPopulateClassHierarchy); + OnClassPackageLoadedOrUnloadedRequestPopulateClassHierarchyDelegateHandle = + GEditor->OnClassPackageLoadedOrUnloaded().AddStatic( + ClassFilter::Helpers::RequestPopulateClassHierarchy); FModuleManager::Get().OnModulesChanged().AddStatic(&OnModulesChanged); } @@ -39,8 +47,10 @@ namespace ClassFilter // Unregister with the Asset Registry to be informed when it is done loading up files. if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry"))) { - FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); - AssetRegistryModule.Get().OnFilesLoaded().Remove(OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle); + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); + AssetRegistryModule.Get().OnFilesLoaded().Remove( + OnFilesLoadedRequestPopulateClassHierarchyDelegateHandle); AssetRegistryModule.Get().OnAssetAdded().RemoveAll(this); AssetRegistryModule.Get().OnAssetRemoved().RemoveAll(this); @@ -49,18 +59,23 @@ namespace ClassFilter if (GEditor) { // Unregister to have Populate called when a Blueprint is compiled. - GEditor->OnBlueprintCompiled().Remove(OnBlueprintCompiledRequestPopulateClassHierarchyDelegateHandle); - GEditor->OnClassPackageLoadedOrUnloaded().Remove(OnClassPackageLoadedOrUnloadedRequestPopulateClassHierarchyDelegateHandle); + GEditor->OnBlueprintCompiled().Remove( + OnBlueprintCompiledRequestPopulateClassHierarchyDelegateHandle); + GEditor->OnClassPackageLoadedOrUnloaded().Remove( + OnClassPackageLoadedOrUnloadedRequestPopulateClassHierarchyDelegateHandle); } } FModuleManager::Get().OnModulesChanged().RemoveAll(this); } - static FSEClassFilterNodePtr CreateNodeForClass(UClass* Class, const TMultiMap& BlueprintPackageToAssetDataMap) + static FSEClassFilterNodePtr CreateNodeForClass( + UClass* Class, const TMultiMap& BlueprintPackageToAssetDataMap) { - // Create the new node so it can be passed to AddChildren, fill it in with if it is placeable, abstract, and/or a brush. - TSharedPtr NewNode = MakeShared(Class->GetName(), Class->GetDisplayNameText().ToString()); + // Create the new node so it can be passed to AddChildren, fill it in with if it is placeable, + // abstract, and/or a brush. + TSharedPtr NewNode = + MakeShared(Class->GetName(), Class->GetDisplayNameText().ToString()); NewNode->Blueprint = ClassFilter::Helpers::GetBlueprint(Class); NewNode->Class = Class; NewNode->ClassPath = Class->GetClassPathName(); @@ -77,11 +92,13 @@ namespace ClassFilter ClassFilter::Helpers::RequestPopulateClassHierarchy(); } - void FClassHierarchy::AddChildren_NoFilter(FSEClassFilterNodePtr& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap) + void FClassHierarchy::AddChildren_NoFilter(FSEClassFilterNodePtr& InOutRootNode, + const TMultiMap& BlueprintPackageToAssetDataMap) { UClass* RootClass = UObject::StaticClass(); - ObjectClassRoot = MakeShared(RootClass->GetName(), RootClass->GetDisplayNameText().ToString()); + ObjectClassRoot = + MakeShared(RootClass->GetName(), RootClass->GetDisplayNameText().ToString()); ObjectClassRoot->Class = RootClass; TMap Nodes; @@ -115,7 +132,8 @@ namespace ClassFilter FSEClassFilterNodePtr& ParentEntry = Nodes.FindOrAdd(CurrentClass->GetSuperClass()); if (!ParentEntry.IsValid()) { - ParentEntry = CreateNodeForClass(CurrentClass->GetSuperClass(), BlueprintPackageToAssetDataMap); + ParentEntry = + CreateNodeForClass(CurrentClass->GetSuperClass(), BlueprintPackageToAssetDataMap); } FSEClassFilterNodePtr& MyEntry = Nodes.FindOrAdd(CurrentClass); @@ -136,7 +154,8 @@ namespace ClassFilter } } - FSEClassFilterNodePtr FClassHierarchy::FindParent(const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InParentClassPath, const UClass* InParentClass) + FSEClassFilterNodePtr FClassHierarchy::FindParent(const FSEClassFilterNodePtr& InRootNode, + FTopLevelAssetPath InParentClassPath, const UClass* InParentClass) { // Check if the current node is the parent class name that is being searched for. if (InRootNode->ClassPath == InParentClassPath) @@ -149,21 +168,21 @@ namespace ClassFilter // If a class does not have a generated class name, we look up the parent class and compare. const UClass* ParentClass = InParentClass; - if (const UClass * RootClass = InRootNode->Class.Get()) + if (const UClass* RootClass = InRootNode->Class.Get()) { if (ParentClass == RootClass) { return InRootNode; } } - } // Search the children recursively, one of them might have the parent. FSEClassFilterNodePtr ReturnNode; for (const auto& Child : InRootNode->GetChildrenList()) { - // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. + // Check the child, then check the return to see if it is valid. If it is valid, end the + // recursion. ReturnNode = FindParent(Child, InParentClassPath, InParentClass); if (ReturnNode.IsValid()) { @@ -173,7 +192,8 @@ namespace ClassFilter return {}; } - FSEClassFilterNodePtr FClassHierarchy::FindNodeByClassName(const FSEClassFilterNodePtr& InRootNode, const FString& InClassName) + FSEClassFilterNodePtr FClassHierarchy::FindNodeByClassName( + const FSEClassFilterNodePtr& InRootNode, const FString& InClassName) { FString NodeClassName = InRootNode->Class.IsValid() ? InRootNode->Class->GetPathName() : FString(); if (NodeClassName == InClassName) @@ -185,7 +205,8 @@ namespace ClassFilter FSEClassFilterNodePtr ReturnNode; for (const auto& Child : InRootNode->GetChildrenList()) { - // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. + // Check the child, then check the return to see if it is valid. If it is valid, end the + // recursion. ReturnNode = FindNodeByClassName(Child, InClassName); if (ReturnNode.IsValid()) { @@ -195,7 +216,8 @@ namespace ClassFilter return {}; } - FSEClassFilterNodePtr FClassHierarchy::FindNodeByClass(const FSEClassFilterNodePtr& InRootNode, const UClass* Class) + FSEClassFilterNodePtr FClassHierarchy::FindNodeByClass( + const FSEClassFilterNodePtr& InRootNode, const UClass* Class) { if (InRootNode->Class.IsValid() && InRootNode->Class == Class) { @@ -206,7 +228,8 @@ namespace ClassFilter FSEClassFilterNodePtr ReturnNode; for (const auto& Child : InRootNode->GetChildrenList()) { - // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. + // Check the child, then check the return to see if it is valid. If it is valid, end the + // recursion. ReturnNode = FindNodeByClass(Child, Class); if (ReturnNode.IsValid()) { @@ -216,7 +239,8 @@ namespace ClassFilter return {}; } - FSEClassFilterNodePtr FClassHierarchy::FindNodeByGeneratedClassPath(const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InGeneratedClassPath) + FSEClassFilterNodePtr FClassHierarchy::FindNodeByGeneratedClassPath( + const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InGeneratedClassPath) { if (InRootNode->ClassPath == InGeneratedClassPath) { @@ -227,7 +251,8 @@ namespace ClassFilter FSEClassFilterNodePtr ReturnNode; for (const auto& Child : InRootNode->GetChildrenList()) { - // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. + // Check the child, then check the return to see if it is valid. If it is valid, end the + // recursion. ReturnNode = FindNodeByGeneratedClassPath(Child, InGeneratedClassPath); if (ReturnNode.IsValid()) { @@ -237,7 +262,8 @@ namespace ClassFilter return {}; } - void FClassHierarchy::UpdateClassInNode(FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint) + void FClassHierarchy::UpdateClassInNode( + FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint) { FSEClassFilterNodePtr Node = FindNodeByGeneratedClassPath(ObjectClassRoot, InGeneratedClassPath); @@ -248,7 +274,8 @@ namespace ClassFilter } } - bool FClassHierarchy::FindAndRemoveNodeByClassPath(const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InClassPath) + bool FClassHierarchy::FindAndRemoveNodeByClassPath( + const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InClassPath) { bool bReturnValue = false; @@ -263,7 +290,8 @@ namespace ClassFilter return true; } - // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. + // Check the child, then check the return to see if it is valid. If it is valid, end the + // recursion. bReturnValue |= FindAndRemoveNodeByClassPath(Child, InClassPath); if (bReturnValue) { @@ -290,11 +318,13 @@ namespace ClassFilter void FClassHierarchy::AddAsset(const FAssetData& InAddedAssetData) { - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); if (!AssetRegistryModule.Get().IsLoadingAssets()) { TArray AncestorClassPaths; - AssetRegistryModule.Get().GetAncestorClassNames(InAddedAssetData.AssetClassPath, AncestorClassPaths); + AssetRegistryModule.Get().GetAncestorClassNames( + InAddedAssetData.AssetClassPath, AncestorClassPaths); if (AncestorClassPaths.Contains(UBlueprintCore::StaticClass()->GetClassPathName())) { @@ -304,7 +334,8 @@ namespace ClassFilter ClassObjectPath = FPackageName::ExportTextPathToObjectPath(ClassObjectPath); } - // Make sure that the node does not already exist. There is a bit of double adding going on at times and this prevents it. + // Make sure that the node does not already exist. There is a bit of double adding going on at + // times and this prevents it. if (!FindNodeByGeneratedClassPath(ObjectClassRoot, FTopLevelAssetPath{ClassObjectPath}) .IsValid()) { @@ -317,7 +348,8 @@ namespace ClassFilter // Resolve the parent's class name locally and use it to find the parent's class. FString ParentClassPath = NewNode->ParentClassPath.ToString(); UClass* ParentClass = FindObject(nullptr, *ParentClassPath); - FSEClassFilterNodePtr ParentNode = FindParent(ObjectClassRoot, NewNode->ParentClassPath, ParentClass); + FSEClassFilterNodePtr ParentNode = + FindParent(ObjectClassRoot, NewNode->ParentClassPath, ParentClass); if (ParentNode.IsValid()) { ParentNode->AddChild(NewNode); @@ -347,13 +379,14 @@ namespace ClassFilter void FClassHierarchy::SortChildren(FSEClassFilterNodePtr& InRootNode) { - TArray< FSEClassFilterNodePtr >& ChildList = InRootNode->GetChildrenList(); + TArray& ChildList = InRootNode->GetChildrenList(); for (auto& Child : InRootNode->GetChildrenList()) { // Setup the parent weak pointer, useful for going up the tree for unloaded blueprints. Child->ParentNode = InRootNode; - // Check the child, then check the return to see if it is valid. If it is valid, end the recursion. + // Check the child, then check the return to see if it is valid. If it is valid, end the + // recursion. SortChildren(Child); } @@ -372,7 +405,8 @@ namespace ClassFilter } } - void FClassHierarchy::LoadUnloadedTagData(FSEClassFilterNodePtr& InOutClassFilterNode, const FAssetData& InAssetData) + void FClassHierarchy::LoadUnloadedTagData( + FSEClassFilterNodePtr& InOutClassFilterNode, const FAssetData& InAssetData) { const FString ClassName = InAssetData.AssetName.ToString(); FString ClassDisplayName = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintDisplayName); @@ -394,20 +428,24 @@ namespace ClassFilter FString ParentClassPathString; if (InAssetData.GetTagValue(FBlueprintTags::ParentClassPath, ParentClassPathString)) { - InOutClassFilterNode->ParentClassPath = FPackageName::ExportTextPathToObjectPath(ParentClassPathString); + InOutClassFilterNode->ParentClassPath = + FPackageName::ExportTextPathToObjectPath(ParentClassPathString); } - InOutClassFilterNode->bIsBPNormalType = InAssetData.GetTagValueRef(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal"); + InOutClassFilterNode->bIsBPNormalType = + InAssetData.GetTagValueRef(FBlueprintTags::BlueprintType) == TEXT("BPType_Normal"); // It is an unloaded blueprint, so we need to create the structure that will hold the data. - TSharedPtr UnloadedBlueprintData = MakeShared(InOutClassFilterNode); + TSharedPtr UnloadedBlueprintData = + MakeShared(InOutClassFilterNode); InOutClassFilterNode->UnloadedBlueprintData = UnloadedBlueprintData; // Get the class flags. const uint32 ClassFlags = InAssetData.GetTagValueRef(FBlueprintTags::ClassFlags); InOutClassFilterNode->UnloadedBlueprintData->SetClassFlags(ClassFlags); - const FString ImplementedInterfaces = InAssetData.GetTagValueRef(FBlueprintTags::ImplementedInterfaces); + const FString ImplementedInterfaces = + InAssetData.GetTagValueRef(FBlueprintTags::ImplementedInterfaces); if (!ImplementedInterfaces.IsEmpty()) { FString FullInterface; @@ -418,13 +456,16 @@ namespace ClassFilter { if (!CurrentString.StartsWith(TEXT("Graphs=("))) { - if (FullInterface.Split(TEXT("\""), &CurrentString, &InterfacePath, ESearchCase::CaseSensitive)) + if (FullInterface.Split( + TEXT("\""), &CurrentString, &InterfacePath, ESearchCase::CaseSensitive)) { // The interface paths in metadata end with "', so remove those InterfacePath.RemoveFromEnd(TEXT("\"'")); - FCoreRedirectObjectName ResolvedInterfaceName = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Class, FCoreRedirectObjectName(InterfacePath)); - UnloadedBlueprintData->AddImplementedInterface(ResolvedInterfaceName.ObjectName.ToString()); + FCoreRedirectObjectName ResolvedInterfaceName = FCoreRedirects::GetRedirectedName( + ECoreRedirectFlags::Type_Class, FCoreRedirectObjectName(InterfacePath)); + UnloadedBlueprintData->AddImplementedInterface( + ResolvedInterfaceName.ObjectName.ToString()); } } @@ -435,9 +476,10 @@ namespace ClassFilter void FClassHierarchy::PopulateClassHierarchy() { - TArray< FSEClassFilterNodePtr > RootLevelClasses; + TArray RootLevelClasses; - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + FAssetRegistryModule& AssetRegistryModule = + FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); // Retrieve all blueprint classes TArray BlueprintList; @@ -447,7 +489,8 @@ namespace ClassFilter Filter.ClassPaths.Add(UAnimBlueprint::StaticClass()->GetClassPathName()); Filter.ClassPaths.Add(UBlueprintGeneratedClass::StaticClass()->GetClassPathName()); - // Include any Blueprint based objects as well, this includes things like Blutilities, UMG, and GameplayAbility objects + // Include any Blueprint based objects as well, this includes things like Blutilities, UMG, and + // GameplayAbility objects Filter.bRecursiveClasses = true; AssetRegistryModule.Get().GetAssets(Filter, BlueprintList); @@ -480,17 +523,18 @@ namespace ClassFilter for (int32 SearchNodeIdx = 0; SearchNodeIdx < RootLevelClasses.Num(); ++SearchNodeIdx) { - FSEClassFilterNodePtr ParentNode = FindParent(RootLevelClasses[SearchNodeIdx], RootLevelClasses[CurrentNodeIdx]->ParentClassPath, ParentClass); + FSEClassFilterNodePtr ParentNode = FindParent(RootLevelClasses[SearchNodeIdx], + RootLevelClasses[CurrentNodeIdx]->ParentClassPath, ParentClass); if (ParentNode.IsValid()) { - // AddUniqueChild makes sure that when a node was generated one by EditorClassHierarchy and one from LoadUnloadedTagData - the proper one is selected + // AddUniqueChild makes sure that when a node was generated one by + // EditorClassHierarchy and one from LoadUnloadedTagData - the proper one is selected ParentNode->AddUniqueChild(RootLevelClasses[CurrentNodeIdx]); RootLevelClasses.RemoveAtSwap(CurrentNodeIdx); --CurrentNodeIdx; break; } } - } } @@ -500,4 +544,4 @@ namespace ClassFilter // All viewers must refresh. ClassFilter::Helpers::RefreshAll(); } -} +} // namespace ClassFilter diff --git a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.h b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.h index 0468dcf..41ac0ca 100644 --- a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.h +++ b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterHelpers.h @@ -1,26 +1,28 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "ClassFilterNode.h" +#include "Misc/ClassFilter.h" + #include -#include -#include -#include -#include -#include #include -#include -#include #include -#include #include +#include +#include +#include +#include +#include #include +#include +#include #include -#include -#include -#include #include -#include "Misc/ClassFilter.h" +#include +#include +#include +#include + #define LOCTEXT_NAMESPACE "ClassFilterHelpers" @@ -47,7 +49,8 @@ namespace ClassFilter FClassHierarchy(); ~FClassHierarchy(); - /** Populates the class hierarchy tree, pulling all the loaded and unloaded classes into a master tree. */ + /** Populates the class hierarchy tree, pulling all the loaded and unloaded classes into a master + * tree. */ void PopulateClassHierarchy(); /** Recursive function to sort a tree. @@ -63,7 +66,7 @@ namespace ClassFilter // This node should always be valid. check(ObjectClassRoot.IsValid()) - return ObjectClassRoot; + return ObjectClassRoot; } /** Finds the parent of a node, recursively going deeper into the hierarchy. @@ -79,40 +82,47 @@ namespace ClassFilter /** Updates the Class of a node. Uses the generated class package name to find the node. * @param InGeneratedClassPath The path of the generated class to find the node for. * @param InNewClass The class to update the node with. - */ - void UpdateClassInNode(FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint); + */ + void UpdateClassInNode( + FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint); /** Finds the node, recursively going deeper into the hierarchy. Does so by comparing class names. - * @param InClassName The name of the generated class package to find the node for. - * - * @return The node. - */ - FSEClassFilterNodePtr FindNodeByClassName(const FSEClassFilterNodePtr& InRootNode, const FString& InClassName); + * @param InClassName The name of the generated class package to find the node for. + * + * @return The node. + */ + FSEClassFilterNodePtr FindNodeByClassName( + const FSEClassFilterNodePtr& InRootNode, const FString& InClassName); /** Finds the node, recursively going deeper into the hierarchy. Does so by comparing class names. - * @param InClass The pointer of the class to find the node for. - * - * @return The node. - */ + * @param InClass The pointer of the class to find the node for. + * + * @return The node. + */ FSEClassFilterNodePtr FindNodeByClass(const FSEClassFilterNodePtr& InRootNode, const UClass* Class); private: /** Recursive function to build a tree, will not filter. - * @param InOutRootNode The node that this function will add the children of to the tree. - * @param PackageNameToAssetDataMap The asset registry map of blueprint package names to blueprint data + * @param InOutRootNode The node that this function will add the children of to the + *tree. + * @param PackageNameToAssetDataMap The asset registry map of blueprint package names to + *blueprint data */ - void AddChildren_NoFilter(FSEClassFilterNodePtr& InOutRootNode, const TMultiMap& BlueprintPackageToAssetDataMap); + void AddChildren_NoFilter(FSEClassFilterNodePtr& InOutRootNode, + const TMultiMap& BlueprintPackageToAssetDataMap); /** Called when hot reload has finished */ void OnReloadComplete(EReloadCompleteReason Reason); - /** Finds the node, recursively going deeper into the hierarchy. Does so by comparing generated class package names. + /** Finds the node, recursively going deeper into the hierarchy. Does so by comparing generated class + *package names. * @param InGeneratedClassPath The path of the generated class to find the node for. * * @return The node. */ - FSEClassFilterNodePtr FindNodeByGeneratedClassPath(const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InGeneratedClassPath); + FSEClassFilterNodePtr FindNodeByGeneratedClassPath( + const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InGeneratedClassPath); /** * Loads the tag data for an unloaded blueprint asset. @@ -137,7 +147,8 @@ namespace ClassFilter * * @return Returns true if the asset was found and deleted successfully. */ - bool FindAndRemoveNodeByClassPath(const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InClassPath); + bool FindAndRemoveNodeByClassPath( + const FSEClassFilterNodePtr& InRootNode, FTopLevelAssetPath InClassPath); /** Callback registered to the Asset Registry to be notified when an asset is added. */ void AddAsset(const FAssetData& InAddedAssetData); @@ -149,10 +160,10 @@ namespace ClassFilter namespace Helpers { - DECLARE_MULTICAST_DELEGATE( FPopulateClassFilter ); + DECLARE_MULTICAST_DELEGATE(FPopulateClassFilter); /** The class hierarchy that manages the unfiltered class tree for the Class Viewer. */ - static TSharedPtr< FClassHierarchy > ClassHierarchy; + static TSharedPtr ClassHierarchy; /** Used to inform any registered Class Viewers to refresh. */ static FPopulateClassFilter PopulateClassFilterDelegate; @@ -161,8 +172,8 @@ namespace ClassFilter static bool bPopulateClassHierarchy; // Pre-declare these functions. - static bool CheckIfBlueprintBase( FSEClassFilterNodePtr InNode ); - static UBlueprint* GetBlueprint( UClass* InClass ); + static bool CheckIfBlueprintBase(FSEClassFilterNodePtr InNode); + static UBlueprint* GetBlueprint(UClass* InClass); static void UpdateClassInNode( FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint); @@ -177,10 +188,10 @@ namespace ClassFilter bool bIsClassDeprecated = InClass->HasAnyClassFlags(CLASS_Deprecated); InClass->ClassFlags &= ~CLASS_Deprecated; - bool bCanCreateBlueprintOfClass = FKismetEditorUtilities::CanCreateBlueprintOfClass( InClass ); + bool bCanCreateBlueprintOfClass = FKismetEditorUtilities::CanCreateBlueprintOfClass(InClass); // Reassign the deprecated flag if it was previously assigned - if(bIsClassDeprecated) + if (bIsClassDeprecated) { InClass->ClassFlags |= CLASS_Deprecated; } @@ -210,12 +221,12 @@ namespace ClassFilter /** Will create the instance of FClassHierarchy and populate the class hierarchy tree. */ static void ConstructClassHierarchy() { - if(!ClassHierarchy.IsValid()) + if (!ClassHierarchy.IsValid()) { ClassHierarchy = MakeShared(); // When created, populate the hierarchy. - GWarn->BeginSlowTask( LOCTEXT("RebuildingClassHierarchy", "Rebuilding Class Hierarchy"), true ); + GWarn->BeginSlowTask(LOCTEXT("RebuildingClassHierarchy", "Rebuilding Class Hierarchy"), true); ClassHierarchy->PopulateClassHierarchy(); GWarn->EndSlowTask(); } @@ -230,11 +241,11 @@ namespace ClassFilter /** Will populate the class hierarchy tree if previously requested. */ static void PopulateClassHierarchy() { - if(bPopulateClassHierarchy) + if (bPopulateClassHierarchy) { bPopulateClassHierarchy = false; - GWarn->BeginSlowTask( LOCTEXT("RebuildingClassHierarchy", "Rebuilding Class Hierarchy"), true ); + GWarn->BeginSlowTask(LOCTEXT("RebuildingClassHierarchy", "Rebuilding Class Hierarchy"), true); ClassHierarchy->PopulateClassHierarchy(); GWarn->EndSlowTask(); } @@ -252,12 +263,16 @@ namespace ClassFilter ClassFilter::Helpers::PopulateClassFilterDelegate.Broadcast(); } - /** Recursive function to build a tree, filtering out nodes based on the InitOptions and filter search terms. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. - * @param InOutRootNode The node that this function will add the children of to the tree. + /** Recursive function to build a tree, filtering out nodes based on the InitOptions and filter search + *terms. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and + *DisallowedClasses. + * @param InOutRootNode The node that this function will add the children of to the + *tree. * @param InRootClassIndex The index of the root node. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. + * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class + *filter options. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. @@ -265,20 +280,21 @@ namespace ClassFilter * @return Returns true if the child passed the filter. */ static bool AddChildren_Tree(const FSEClassFilter& Filter, FSEClassFilterNodePtr& InOutRootNode, - const FSEClassFilterNodePtr& InOriginalRootNode, - bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, bool bInInternalClasses, - const TArray& InternalClasses, const TArray& InternalPaths) + const FSEClassFilterNodePtr& InOriginalRootNode, bool bInOnlyBlueprintBases, + bool bInShowUnloadedBlueprints, bool bInInternalClasses, const TArray& InternalClasses, + const TArray& InternalPaths) { - bool bChildrenPassesFilter= false; + bool bChildrenPassesFilter = false; bool bReturnPassesFilter = false; - bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); + bool bPassesBlueprintBaseFilter = + !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); - // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? - // INI path: /Script/ClassFilter.ClassFilterProjectSettings + // The INI files declare classes and folders that are considered internal only. Does this class + // match any of those patterns? INI path: /Script/ClassFilter.ClassFilterProjectSettings bool bPassesInternalFilter = true; if (!bInInternalClasses && InternalPaths.Num() > 0) { @@ -291,7 +307,8 @@ namespace ClassFilter } } } - if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter && InOriginalRootNode->Class.IsValid()) + if (!bInInternalClasses && InternalClasses.Num() > 0 && bPassesInternalFilter && + InOriginalRootNode->Class.IsValid()) { for (int i = 0; i < InternalClasses.Num(); i++) { @@ -303,27 +320,33 @@ namespace ClassFilter } } - // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. - if(bIsUnloadedBlueprint) + // There are few options for filtering an unloaded blueprint, if it matches with this filter, it + // passes. + if (bIsUnloadedBlueprint) { - if(bInShowUnloadedBlueprints) + if (bInShowUnloadedBlueprints) { - bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesBlueprintBaseFilter && bPassesInternalFilter && PassesFilter(*InOriginalRootNode->GetClassName()); + bReturnPassesFilter = InOutRootNode->bPassesFilter = + bPassesBlueprintBaseFilter && bPassesInternalFilter && + PassesFilter(*InOriginalRootNode->GetClassName()); } } else { - bReturnPassesFilter = InOutRootNode->bPassesFilter = bPassesBlueprintBaseFilter && bPassesInternalFilter && PassesFilter(*InOriginalRootNode->GetClassName()); + bReturnPassesFilter = InOutRootNode->bPassesFilter = + bPassesBlueprintBaseFilter && bPassesInternalFilter && + PassesFilter(*InOriginalRootNode->GetClassName()); } - for(const auto& Child : InOriginalRootNode->GetChildrenList()) + for (const auto& Child : InOriginalRootNode->GetChildrenList()) { - FSEClassFilterNodePtr NewNode = MakeShared( *Child.Get() ); + FSEClassFilterNodePtr NewNode = MakeShared(*Child.Get()); NewNode->SetStateFromFilter(Filter); - bChildrenPassesFilter = AddChildren_Tree(Filter, NewNode, Child, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, bInInternalClasses, InternalClasses, InternalPaths); - if(bChildrenPassesFilter) + bChildrenPassesFilter = AddChildren_Tree(Filter, NewNode, Child, bInOnlyBlueprintBases, + bInShowUnloadedBlueprints, bInInternalClasses, InternalClasses, InternalPaths); + if (bChildrenPassesFilter) { InOutRootNode->AddChild(NewNode); } @@ -333,26 +356,30 @@ namespace ClassFilter } /** Builds the class tree. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and + *DisallowedClasses. * @param InOutRootNode The node to root the tree to. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. + * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class + *filter options. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. * @return A fully built tree. */ - static void GetClassTree(const FSEClassFilter& Filter, FSEClassFilterNodePtr& InOutRootNode, bool bInOnlyBlueprintBases, - bool bInShowUnloadedBlueprints, bool bInInternalClasses = true, - const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) + static void GetClassTree(const FSEClassFilter& Filter, FSEClassFilterNodePtr& InOutRootNode, + bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, bool bInInternalClasses = true, + const TArray& InternalClasses = TArray(), + const TArray& InternalPaths = TArray()) { // Use BaseClass as root FSEClassFilterNodePtr RootNode; if (Filter.GetBaseClass()) { - RootNode = ClassHierarchy->FindNodeByClass(ClassHierarchy->GetObjectRootNode(), Filter.GetBaseClass()); + RootNode = ClassHierarchy->FindNodeByClass( + ClassHierarchy->GetObjectRootNode(), Filter.GetBaseClass()); } - else // Use UObject as root + else // Use UObject as root { RootNode = ClassHierarchy->GetObjectRootNode(); } @@ -360,34 +387,39 @@ namespace ClassFilter // Duplicate the node, it will have no children. InOutRootNode = MakeShared(*RootNode); - AddChildren_Tree(Filter, InOutRootNode, RootNode, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, bInInternalClasses, InternalClasses, InternalPaths); + AddChildren_Tree(Filter, InOutRootNode, RootNode, bInOnlyBlueprintBases, + bInShowUnloadedBlueprints, bInInternalClasses, InternalClasses, InternalPaths); } - /** Recursive function to build the list, filtering out nodes based on the InitOptions and filter search terms. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. - * @param InOutRootNode The node that this function will add the children of to the tree. + /** Recursive function to build the list, filtering out nodes based on the InitOptions and filter + *search terms. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and + *DisallowedClasses. + * @param InOutRootNode The node that this function will add the children of to the + *tree. * @param InRootClassIndex The index of the root node. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. + * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class + *filter options. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. * * @return Returns true if the child passed the filter. */ - static void AddChildren_List(TArray< FSEClassFilterNodePtr >& InOutNodeList, - const FSEClassFilterNodePtr& InOriginalRootNode, - bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, - bool bInInternalClasses, - const TArray& InternalClasses, const TArray& InternalPaths) + static void AddChildren_List(TArray& InOutNodeList, + const FSEClassFilterNodePtr& InOriginalRootNode, bool bInOnlyBlueprintBases, + bool bInShowUnloadedBlueprints, bool bInInternalClasses, const TArray& InternalClasses, + const TArray& InternalPaths) { - bool bPassesBlueprintBaseFilter = !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); + bool bPassesBlueprintBaseFilter = + !bInOnlyBlueprintBases || CheckIfBlueprintBase(InOriginalRootNode); bool bIsUnloadedBlueprint = !InOriginalRootNode->Class.IsValid(); FString GeneratedClassPathString = InOriginalRootNode->ClassPath.ToString(); - // The INI files declare classes and folders that are considered internal only. Does this class match any of those patterns? - // INI path: /Script/ClassFilter.ClassFilterProjectSettings + // The INI files declare classes and folders that are considered internal only. Does this class + // match any of those patterns? INI path: /Script/ClassFilter.ClassFilterProjectSettings bool bPassesInternalFilter = true; if (!bInInternalClasses && InternalPaths.Num() > 0) { @@ -414,52 +446,58 @@ namespace ClassFilter FSEClassFilterNodePtr NewNode = MakeShared(*InOriginalRootNode.Get()); - // There are few options for filtering an unloaded blueprint, if it matches with this filter, it passes. - if(bIsUnloadedBlueprint) + // There are few options for filtering an unloaded blueprint, if it matches with this filter, it + // passes. + if (bIsUnloadedBlueprint) { - if(bInShowUnloadedBlueprints) + if (bInShowUnloadedBlueprints) { - NewNode->bPassesFilter = bPassesBlueprintBaseFilter && bPassesInternalFilter && PassesFilter(*InOriginalRootNode->GetClassName()); + NewNode->bPassesFilter = bPassesBlueprintBaseFilter && bPassesInternalFilter && + PassesFilter(*InOriginalRootNode->GetClassName()); } } else { - NewNode->bPassesFilter = bPassesBlueprintBaseFilter && bPassesInternalFilter && PassesFilter(*InOriginalRootNode->GetClassName()); + NewNode->bPassesFilter = bPassesBlueprintBaseFilter && bPassesInternalFilter && + PassesFilter(*InOriginalRootNode->GetClassName()); } - if(NewNode->bPassesFilter) + if (NewNode->bPassesFilter) { InOutNodeList.Add(NewNode); } for (const auto& Child : InOriginalRootNode->GetChildrenList()) { - AddChildren_List(InOutNodeList, Child, bInOnlyBlueprintBases, - bInShowUnloadedBlueprints, bInInternalClasses, InternalClasses, InternalPaths); + AddChildren_List(InOutNodeList, Child, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, + bInInternalClasses, InternalClasses, InternalPaths); } } /** Builds the class list. - * @param InInitOptions The class viewer's options, holds the AllowedClasses and DisallowedClasses. + * @param InInitOptions The class viewer's options, holds the AllowedClasses and + *DisallowedClasses. * @param InOutNodeList The list to add all the nodes to. * @param bInOnlyBlueprintBases Filter option to remove non-blueprint base classes. - * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class filter options. + * @param bInShowUnloadedBlueprints Filter option to not remove unloaded blueprints due to class + *filter options. * @param bInInternalClasses Filter option for showing internal classes. * @param InternalClasses The classes that have been marked as Internal Only. * @param InternalPaths The paths that have been marked Internal Only. * * @return A fully built list. */ - static void GetClassList(TArray< FSEClassFilterNodePtr >& InOutNodeList, - bool bInOnlyBlueprintBases, bool bInShowUnloadedBlueprints, - bool bInInternalClasses = true, - const TArray& InternalClasses = TArray(), const TArray& InternalPaths = TArray()) + static void GetClassList(TArray& InOutNodeList, bool bInOnlyBlueprintBases, + bool bInShowUnloadedBlueprints, bool bInInternalClasses = true, + const TArray& InternalClasses = TArray(), + const TArray& InternalPaths = TArray()) { const FSEClassFilterNodePtr ObjectClassRoot = ClassHierarchy->GetObjectRootNode(); for (const auto& Child : ObjectClassRoot->GetChildrenList()) { - AddChildren_List(InOutNodeList, Child, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, bInInternalClasses, InternalClasses, InternalPaths); + AddChildren_List(InOutNodeList, Child, bInOnlyBlueprintBases, bInShowUnloadedBlueprints, + bInInternalClasses, InternalClasses, InternalPaths); } } @@ -468,9 +506,9 @@ namespace ClassFilter * * @return The blueprint associated with the class index. */ - static UBlueprint* GetBlueprint( UClass* InClass ) + static UBlueprint* GetBlueprint(UClass* InClass) { - if( InClass->ClassGeneratedBy && InClass->ClassGeneratedBy->IsA(UBlueprint::StaticClass()) ) + if (InClass->ClassGeneratedBy && InClass->ClassGeneratedBy->IsA(UBlueprint::StaticClass())) { return Cast(InClass->ClassGeneratedBy); } @@ -485,11 +523,12 @@ namespace ClassFilter * * @return The blueprint associated with the class index. */ - static void GetClassInfo( TWeakObjectPtr InClass, bool& bInOutIsBlueprintBase, bool& bInOutHasBlueprint ) + static void GetClassInfo( + TWeakObjectPtr InClass, bool& bInOutIsBlueprintBase, bool& bInOutHasBlueprint) { if (UClass* Class = InClass.Get()) { - bInOutIsBlueprintBase = CanCreateBlueprintOfClass_IgnoreDeprecation( Class ); + bInOutIsBlueprintBase = CanCreateBlueprintOfClass_IgnoreDeprecation(Class); bInOutHasBlueprint = Class->ClassGeneratedBy != nullptr; } else @@ -504,17 +543,18 @@ namespace ClassFilter * * @return true if the class is a blueprint. */ - static bool CheckIfBlueprintBase( TSharedPtr< FSEClassFilterNode> InNode ) + static bool CheckIfBlueprintBase(TSharedPtr InNode) { // If there is no class, it may be an unloaded blueprint. - if(UClass* Class = InNode->Class.Get()) + if (UClass* Class = InNode->Class.Get()) { return CanCreateBlueprintOfClass_IgnoreDeprecation(Class); } - else if(InNode->bIsBPNormalType) + else if (InNode->bIsBPNormalType) { bool bAllowDerivedBlueprints = false; - GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), /*out*/ bAllowDerivedBlueprints, GEngineIni); + GConfig->GetBool(TEXT("Kismet"), TEXT("AllowDerivedBlueprints"), + /*out*/ bAllowDerivedBlueprints, GEngineIni); return bAllowDerivedBlueprints; } @@ -525,7 +565,8 @@ namespace ClassFilter /** * Creates a blueprint from a class. * - * @param InOutClassNode Class node to pull what class to load and to update information in. + * @param InOutClassNode Class node to pull what class to load and to update information + * in. */ static void LoadClass(FSEClassFilterNodePtr InOutClassNode) { @@ -538,28 +579,31 @@ namespace ClassFilter InOutClassNode->Blueprint = Cast(Class->ClassGeneratedBy); InOutClassNode->Class = Class; - // Tell the original node to update so when a refresh happens it will still know about the newly loaded class. - ClassFilter::Helpers::UpdateClassInNode(InOutClassNode->ClassPath, InOutClassNode->Class.Get(), InOutClassNode->Blueprint.Get() ); + // Tell the original node to update so when a refresh happens it will still know about the + // newly loaded class. + ClassFilter::Helpers::UpdateClassInNode( + InOutClassNode->ClassPath, InOutClassNode->Class.Get(), InOutClassNode->Blueprint.Get()); } else { FMessageLog EditorErrors("EditorErrors"); FFormatNamedArguments Arguments; Arguments.Add(TEXT("ObjectName"), FText::FromString(InOutClassNode->ClassPath.ToString())); - EditorErrors.Error(FText::Format(LOCTEXT("PackageLoadFail", "Failed to load class {ObjectName}"), Arguments)); + EditorErrors.Error(FText::Format( + LOCTEXT("PackageLoadFail", "Failed to load class {ObjectName}"), Arguments)); } } /** Updates the Class of a node. Uses the generated class package name to find the node. - * @param InGeneratedClassPath The name of the generated class to find the node for. - * @param InNewClass The class to update the node with. - */ + * @param InGeneratedClassPath The name of the generated class to find the node for. + * @param InNewClass The class to update the node with. + */ static void UpdateClassInNode( FTopLevelAssetPath InGeneratedClassPath, UClass* InNewClass, UBlueprint* InNewBluePrint) { - ClassHierarchy->UpdateClassInNode(InGeneratedClassPath, InNewClass, InNewBluePrint ); + ClassHierarchy->UpdateClassInNode(InGeneratedClassPath, InNewClass, InNewBluePrint); } - } // namespace Helpers -} // namespace ClassFilter + } // namespace Helpers +} // namespace ClassFilter #undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.cpp b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.cpp index d0e0a10..9d72998 100644 --- a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.cpp +++ b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.cpp @@ -1,10 +1,12 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "ClassFilterNode.h" + +#include "Misc/ClassFilter.h" + #include #include -#include "Misc/ClassFilter.h" FSEClassFilterNode::FSEClassFilterNode(const FString& InClassName, const FString& InClassDisplayName) @@ -18,7 +20,7 @@ FSEClassFilterNode::FSEClassFilterNode(const FString& InClassName, const FString Blueprint = nullptr; } -FSEClassFilterNode::FSEClassFilterNode( const FSEClassFilterNode& InCopyObject) +FSEClassFilterNode::FSEClassFilterNode(const FSEClassFilterNode& InCopyObject) { ClassName = InCopyObject.ClassName; ClassDisplayName = InCopyObject.ClassDisplayName; @@ -47,7 +49,7 @@ FSEClassFilterNode::FSEClassFilterNode( const FSEClassFilterNode& InCopyObject) void FSEClassFilterNode::AddChild(FSEClassFilterNodePtr& Child) { ChildrenList.Add(Child); - Child->ParentNode = TSharedRef{ AsShared() }; + Child->ParentNode = TSharedRef{AsShared()}; } void FSEClassFilterNode::AddUniqueChild(FSEClassFilterNodePtr& Child) @@ -65,7 +67,8 @@ void FSEClassFilterNode::AddUniqueChild(FSEClassFilterNodePtr& Child) if (bNewChildHasMoreInfo && !bOldChildHasMoreInfo) { // make sure, that new child has all needed children - for (int OldChildIndex = 0; OldChildIndex < CurrentChild->ChildrenList.Num(); ++OldChildIndex) + for (int OldChildIndex = 0; OldChildIndex < CurrentChild->ChildrenList.Num(); + ++OldChildIndex) { Child->AddUniqueChild(CurrentChild->ChildrenList[OldChildIndex]); } @@ -85,27 +88,28 @@ FString FSEClassFilterNode::GetClassName(EClassViewerNameTypeToDisplay NameType) { switch (NameType) { - case EClassViewerNameTypeToDisplay::ClassName: - return ClassName; + case EClassViewerNameTypeToDisplay::ClassName: + return ClassName; - case EClassViewerNameTypeToDisplay::DisplayName: - return ClassDisplayName; + case EClassViewerNameTypeToDisplay::DisplayName: + return ClassDisplayName; - case EClassViewerNameTypeToDisplay::Dynamic: - FString CombinedName; - FString SanitizedName = FName::NameToDisplayString(ClassName, false); - if (ClassDisplayName.IsEmpty() && !ClassDisplayName.Equals(SanitizedName) && !ClassDisplayName.Equals(ClassName)) - { - TArray Args; - Args.Add(ClassName); - Args.Add(ClassDisplayName); - CombinedName = FString::Format(TEXT("{0} ({1})"), Args); - } - else - { - CombinedName = ClassName; - } - return MoveTemp(CombinedName); + case EClassViewerNameTypeToDisplay::Dynamic: + FString CombinedName; + FString SanitizedName = FName::NameToDisplayString(ClassName, false); + if (ClassDisplayName.IsEmpty() && !ClassDisplayName.Equals(SanitizedName) && + !ClassDisplayName.Equals(ClassName)) + { + TArray Args; + Args.Add(ClassName); + Args.Add(ClassDisplayName); + CombinedName = FString::Format(TEXT("{0} ({1})"), Args); + } + else + { + CombinedName = ClassName; + } + return MoveTemp(CombinedName); } ensureMsgf(false, TEXT("FSEClassFilterNode::GetClassName called with invalid name type.")); @@ -139,7 +143,7 @@ void FSEClassFilterNode::SetOwnFilterState(EClassFilterState State) void FSEClassFilterNode::SetStateFromFilter(const FSEClassFilter& Filter) { - const TSoftClassPtr<> ClassAsset{ ClassPath.ToString() }; + const TSoftClassPtr<> ClassAsset{ClassPath.ToString()}; if (Filter.AllowedClasses.Contains(ClassAsset)) { diff --git a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.h b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.h index 039769d..5880141 100644 --- a/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.h +++ b/Source/Editor/Private/Customizations/ClassFilter/ClassFilterNode.h @@ -1,10 +1,11 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include #include -#include +#include #include +#include + class IPropertyHandle; class IUnloadedBlueprintData; @@ -22,7 +23,6 @@ enum class EClassFilterState : uint8 class FSEClassFilterNode : public TSharedFromThis { public: - using FPtr = TSharedPtr; /** @@ -32,9 +32,9 @@ class FSEClassFilterNode : public TSharedFromThis * @param InClassDisplayName The display name of the class this node represents * @param bInIsPlaceable true if the class is a placeable class. */ - FSEClassFilterNode( const FString& InClassName, const FString& InClassDisplayName ); + FSEClassFilterNode(const FString& InClassName, const FString& InClassDisplayName); - FSEClassFilterNode( const FSEClassFilterNode& InCopyObject); + FSEClassFilterNode(const FSEClassFilterNode& InCopyObject); /** * Adds the specified child to the node. @@ -45,7 +45,8 @@ class FSEClassFilterNode : public TSharedFromThis void AddUniqueChild(FPtr& Child); /** - * Retrieves the class name this node is associated with. This is not the literal UClass name as it is missing the _C for blueprints + * Retrieves the class name this node is associated with. This is not the literal UClass name as it is + * missing the _C for blueprints * @param bUseDisplayName Whether to use the display name or class name */ const FString& GetClassName(bool bUseDisplayName = false) const @@ -54,7 +55,8 @@ class FSEClassFilterNode : public TSharedFromThis } /** - * Retrieves the class name this node is associated with. This is not the literal UClass name as it is missing the _C for blueprints + * Retrieves the class name this node is associated with. This is not the literal UClass name as it is + * missing the _C for blueprints * @param NameType Whether to use the display name or class name */ FString GetClassName(EClassViewerNameTypeToDisplay NameType) const; @@ -74,12 +76,15 @@ class FSEClassFilterNode : public TSharedFromThis /** Filter states */ void SetOwnFilterState(EClassFilterState State); void SetStateFromFilter(const struct FSEClassFilter& Filter); - EClassFilterState GetOwnFilterState() const { return FilterState; } + EClassFilterState GetOwnFilterState() const + { + return FilterState; + } EClassFilterState GetParentFilterState() const; private: - - /** The non-translated internal name for this class. This is not necessarily the UClass's name, as that may have _C for blueprints */ + /** The non-translated internal name for this class. This is not necessarily the UClass's name, as that + * may have _C for blueprints */ FString ClassName; /** The translated display name for this class */ @@ -91,7 +96,6 @@ class FSEClassFilterNode : public TSharedFromThis EClassFilterState FilterState = EClassFilterState::None; public: - TWeakPtr ParentNode; /** The class this node is associated with. */ @@ -112,7 +116,8 @@ class FSEClassFilterNode : public TSharedFromThis /** true if the class passed the filter. */ bool bPassesFilter; - /** true if the class is a "normal type", this is used to identify unloaded blueprints as blueprint bases. */ + /** true if the class is a "normal type", this is used to identify unloaded blueprints as blueprint bases. + */ bool bIsBPNormalType; /** Data for unloaded blueprints, only valid if the class is unloaded. */ diff --git a/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.cpp b/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.cpp index 68f0dd6..156932e 100644 --- a/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.cpp +++ b/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.cpp @@ -1,40 +1,43 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SClassFilter.h" + +#include "AssetRegistry/AssetData.h" +#include "ClassFilterHelpers.h" +#include "Dialogs/Dialogs.h" +#include "Editor.h" +#include "EditorStyleSet.h" +#include "Framework/Application/SlateApplication.h" +#include "Framework/Commands/UIAction.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Framework/Notifications/NotificationManager.h" +#include "GameplayTagsModule.h" +#include "GameplayTagsSettings.h" +#include "Layout/WidgetPath.h" #include "Misc/ConfigCacheIni.h" +#include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SComboButton.h" -#include "Widgets/Images/SImage.h" -#include "EditorStyleSet.h" #include "Widgets/SWindow.h" -#include "Dialogs/Dialogs.h" -#include "GameplayTagsModule.h" + +#include +#include +#include #include #include -#include -#include #include #include -#include #include +#include #include -#include "Framework/Notifications/NotificationManager.h" -#include "GameplayTagsSettings.h" -#include "Layout/WidgetPath.h" -#include "Framework/Application/SlateApplication.h" -#include "AssetRegistry/AssetData.h" -#include "Editor.h" -#include "Framework/Commands/UIAction.h" -#include "Framework/MultiBox/MultiBoxBuilder.h" -#include "ClassFilterHelpers.h" -#include #define LOCTEXT_NAMESPACE "GameplayTagWidget" -void SClassFilter::Construct(const FArguments& InArgs, const TArray& EditableFilters) +void SClassFilter::Construct( + const FArguments& InArgs, const TArray& EditableFilters) { bNeedsRefresh = true; @@ -74,76 +77,53 @@ void SClassFilter::Construct(const FArguments& InArgs, const TArray) - .TreeItemsSource(&RootClasses) - .OnGenerateRow(this, &SClassFilter::OnGenerateRow) - .OnGetChildren(this, &SClassFilter::OnGetChildren) - .OnExpansionChanged( this, &SClassFilter::OnExpansionChanged) - .SelectionMode(ESelectionMode::Multi) - ] - ] - ] - ]; + [SNew(SBorder).BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + [SNew(SVerticalBox) + + // Gameplay Tag Tree controls + + + SVerticalBox::Slot().AutoHeight().VAlign(VAlign_Top) + [SNew(SHorizontalBox) + + // Search + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .FillWidth(1.f) + .Padding(5, 1, 5, + 1)[SAssignNew(SearchBox, SSearchBox) + .HintText(LOCTEXT("ClassFilter_SearchBoxHint", "Search Classes")) + .OnTextChanged(this, &SClassFilter::OnSearchTextChanged)] + + // Expand All nodes + + SHorizontalBox::Slot() + .AutoWidth()[SNew(SButton) + .OnClicked(this, &SClassFilter::OnClickedExpandAll) + .Text(LOCTEXT("ClassFilter_ExpandAll", "Expand All"))] + + // Collapse All nodes + + SHorizontalBox::Slot() + .AutoWidth()[SNew(SButton) + .OnClicked(this, &SClassFilter::OnClickedCollapseAll) + .Text(LOCTEXT("ClassFilter_CollapseAll", "Collapse All"))] + + // Clear selections + + SHorizontalBox::Slot() + .AutoWidth()[SNew(SButton) + .OnClicked(this, &SClassFilter::OnClickedClearAll) + .Text(LOCTEXT("ClassFilter_ClearAll", "Clear All")) + .Visibility(this, + &SClassFilter::DetermineClearSelectionVisibility)]] + + // Classes tree + + SVerticalBox::Slot().MaxHeight(MaxHeight) + [SAssignNew(TreeContainerWidget, SBorder) + .Padding(FMargin( + 4.f))[SAssignNew(TreeWidget, STreeView) + .TreeItemsSource(&RootClasses) + .OnGenerateRow(this, &SClassFilter::OnGenerateRow) + .OnGetChildren(this, &SClassFilter::OnGetChildren) + .OnExpansionChanged(this, &SClassFilter::OnExpansionChanged) + .SelectionMode(ESelectionMode::Multi)]]]]; // Construct the class hierarchy. ClassFilter::Helpers::ConstructClassHierarchy(); @@ -155,7 +135,7 @@ void SClassFilter::Construct(const FArguments& InArgs, const TArrayGetDesiredSize(); @@ -168,7 +148,7 @@ FVector2D SClassFilter::ComputeDesiredSize(float LayoutScaleMultiplier) const return WidgetSize; } -void SClassFilter::OnSearchTextChanged( const FText& SearchText ) +void SClassFilter::OnSearchTextChanged(const FText& SearchText) { SearchString = SearchText.ToString(); @@ -230,53 +210,39 @@ bool SClassFilter::FilterChildrenCheck(const FSEClassFilterNodePtr& Class) return false; } -TSharedRef SClassFilter::OnGenerateRow(FSEClassFilterNodePtr Class, const TSharedRef& OwnerTable) +TSharedRef SClassFilter::OnGenerateRow( + FSEClassFilterNodePtr Class, const TSharedRef& OwnerTable) { return SNew(STableRow, OwnerTable) - .Style(FAppStyle::Get(), "GameplayTagTreeView") - [ - SNew(SBorder) - .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) - .BorderBackgroundColor(this, &SClassFilter::GetClassBackgroundColor, Class) - .Padding(0) - .Content() - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Left) - [ - SNew(SButton) - .ButtonStyle(FAppStyle::Get(), "FlatButton") - .OnClicked(this, &SClassFilter::OnClassClicked, Class) - .ForegroundColor(this, &SClassFilter::GetClassIconColor, Class) - .ContentPadding(0) - .IsEnabled(this, &SClassFilter::CanSelectClasses) - [ - SNew(STextBlock) - .Font(FAppStyle::Get().GetFontStyle("FontAwesome.8")) - .Text(this, &SClassFilter::GetClassIconText, Class) - ] - ] - // Tag Selection (selection mode only) - +SHorizontalBox::Slot() - .FillWidth(1.0f) - .HAlign(HAlign_Left) - [ - SNew(STextBlock) - .Text(FText::FromString(Class->GetClassName(true))) - .ToolTipText(Class->GetClassTooltip()) - .IsEnabled(this, &SClassFilter::CanSelectClasses) - ] - ] - ]; + .Style(FAppStyle::Get(), "GameplayTagTreeView") + [SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .BorderBackgroundColor(this, &SClassFilter::GetClassBackgroundColor, Class) + .Padding(0) + .Content()[SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().HAlign(HAlign_Left) + [SNew(SButton) + .ButtonStyle(FAppStyle::Get(), "FlatButton") + .OnClicked(this, &SClassFilter::OnClassClicked, Class) + .ForegroundColor(this, &SClassFilter::GetClassIconColor, Class) + .ContentPadding(0) + .IsEnabled(this, &SClassFilter::CanSelectClasses) + [SNew(STextBlock) + .Font(FAppStyle::Get().GetFontStyle("FontAwesome.8")) + .Text(this, &SClassFilter::GetClassIconText, Class)]] + // Tag Selection (selection mode only) + + SHorizontalBox::Slot().FillWidth(1.0f).HAlign( + HAlign_Left)[SNew(STextBlock) + .Text(FText::FromString(Class->GetClassName(true))) + .ToolTipText(Class->GetClassTooltip()) + .IsEnabled(this, &SClassFilter::CanSelectClasses)]]]; } void SClassFilter::OnGetChildren(FSEClassFilterNodePtr Class, TArray& OutChildren) { - for(auto& ChildClass : Class->GetChildrenList()) + for (auto& ChildClass : Class->GetChildrenList()) { - if(FilterChildrenCheck(ChildClass)) + if (FilterChildrenCheck(ChildClass)) { OutChildren.Add(ChildClass); } @@ -309,11 +275,11 @@ FText SClassFilter::GetClassIconText(FSEClassFilterNodePtr Class) const { switch (Class->GetOwnFilterState()) { - case EClassFilterState::Allowed: - return FText::FromString(FString(TEXT("\xf00c"))) /*fa-check*/; + case EClassFilterState::Allowed: + return FText::FromString(FString(TEXT("\xf00c"))) /*fa-check*/; - case EClassFilterState::Denied: - return FText::FromString(FString(TEXT("\xf00d"))) /*fa-times*/; + case EClassFilterState::Denied: + return FText::FromString(FString(TEXT("\xf00d"))) /*fa-times*/; } return FText::FromString(FString(TEXT("\xf096"))) /*fa-square-o*/; @@ -323,11 +289,11 @@ FSlateColor SClassFilter::GetClassIconColor(FSEClassFilterNodePtr Class) const { switch (Class->GetOwnFilterState()) { - case EClassFilterState::Allowed: - return { FLinearColor::Green }; + case EClassFilterState::Allowed: + return {FLinearColor::Green}; - case EClassFilterState::Denied: - return { FLinearColor::Red }; + case EClassFilterState::Denied: + return {FLinearColor::Red}; } return FSlateColor::UseForeground(); @@ -335,69 +301,71 @@ FSlateColor SClassFilter::GetClassIconColor(FSEClassFilterNodePtr Class) const void SClassFilter::MarkClass(FSEClassFilterNodePtr Class, EClassFilterState State) { - const TSoftClassPtr<> ClassAsset{ Class->ClassPath.ToString() }; + const TSoftClassPtr<> ClassAsset{Class->ClassPath.ToString()}; switch (State) { - case EClassFilterState::Allowed: - { - FScopedTransaction Transaction(LOCTEXT("ClassFilter_AllowClass", "Allow Class")); - if (Class) - { - Class->SetOwnFilterState(State); - - if(PropertyHandle) PropertyHandle->NotifyPreChange(); - for (const auto& Filter : Filters) + case EClassFilterState::Allowed: { + FScopedTransaction Transaction(LOCTEXT("ClassFilter_AllowClass", "Allow Class")); + if (Class) { - Filter.Filter->AllowedClasses.Add(ClassAsset); - Filter.Filter->IgnoredClasses.Remove(ClassAsset); + Class->SetOwnFilterState(State); + + if (PropertyHandle) + PropertyHandle->NotifyPreChange(); + for (const auto& Filter : Filters) + { + Filter.Filter->AllowedClasses.Add(ClassAsset); + Filter.Filter->IgnoredClasses.Remove(ClassAsset); + } + if (PropertyHandle) + PropertyHandle->NotifyPostChange(EPropertyChangeType::Unspecified); } - if (PropertyHandle) PropertyHandle->NotifyPostChange(EPropertyChangeType::Unspecified); + break; } - break; - } - case EClassFilterState::Denied: - { - FScopedTransaction Transaction(LOCTEXT("ClassFilter_DeniedClass", "Deny Class")); - if (Class) - { - Class->SetOwnFilterState(State); - - if (PropertyHandle) PropertyHandle->NotifyPreChange(); - for (const auto& Filter : Filters) + case EClassFilterState::Denied: { + FScopedTransaction Transaction(LOCTEXT("ClassFilter_DeniedClass", "Deny Class")); + if (Class) { - Filter.Filter->IgnoredClasses.Add(ClassAsset); - Filter.Filter->AllowedClasses.Remove(ClassAsset); + Class->SetOwnFilterState(State); + + if (PropertyHandle) + PropertyHandle->NotifyPreChange(); + for (const auto& Filter : Filters) + { + Filter.Filter->IgnoredClasses.Add(ClassAsset); + Filter.Filter->AllowedClasses.Remove(ClassAsset); + } + if (PropertyHandle) + PropertyHandle->NotifyPostChange(EPropertyChangeType::Unspecified); } - if (PropertyHandle) - PropertyHandle->NotifyPostChange(EPropertyChangeType::Unspecified); + break; } - break; - } - case EClassFilterState::None: - { - FScopedTransaction Transaction(LOCTEXT("ClassFilter_UnmarkClass", "Unmark Class")); - if (Class) - { - Class->SetOwnFilterState(State); - - if (PropertyHandle) PropertyHandle->NotifyPreChange(); - for (const auto& Filter : Filters) + case EClassFilterState::None: { + FScopedTransaction Transaction(LOCTEXT("ClassFilter_UnmarkClass", "Unmark Class")); + if (Class) { - Filter.Filter->IgnoredClasses.Remove(ClassAsset); - Filter.Filter->AllowedClasses.Remove(ClassAsset); + Class->SetOwnFilterState(State); + + if (PropertyHandle) + PropertyHandle->NotifyPreChange(); + for (const auto& Filter : Filters) + { + Filter.Filter->IgnoredClasses.Remove(ClassAsset); + Filter.Filter->AllowedClasses.Remove(ClassAsset); + } + if (PropertyHandle) + PropertyHandle->NotifyPostChange(EPropertyChangeType::Unspecified); } - if (PropertyHandle) - PropertyHandle->NotifyPostChange(EPropertyChangeType::Unspecified); + break; } - break; - }} + } OnFilterChanged.ExecuteIfBound(); } FReply SClassFilter::OnClickedClearAll() { - FScopedTransaction Transaction( LOCTEXT("GameplayTagWidget_RemoveAllTags", "Remove All Gameplay Tags") ); + FScopedTransaction Transaction(LOCTEXT("GameplayTagWidget_RemoveAllTags", "Remove All Gameplay Tags")); for (int32 ContainerIdx = 0; ContainerIdx < Filters.Num(); ++ContainerIdx) { @@ -426,7 +394,7 @@ FSlateColor SClassFilter::GetClassBackgroundColor(FSEClassFilterNodePtr Class) c { Color = FLinearColor::Red; } - else if(ParentState == EClassFilterState::Allowed) + else if (ParentState == EClassFilterState::Allowed) { Color = FLinearColor::Green; } @@ -436,10 +404,10 @@ FSlateColor SClassFilter::GetClassBackgroundColor(FSEClassFilterNodePtr Class) c } else { - return { FLinearColor::Transparent }; + return {FLinearColor::Transparent}; } Color.A = 0.3f; - return { Color }; + return {Color}; } FReply SClassFilter::OnClickedExpandAll() @@ -499,19 +467,20 @@ void SClassFilter::SetFilter(FSEClassFilter* OriginalFilter, FSEClassFilter* Edi if (PropertyHandle.IsValid() && bMultiSelect) { // Case for a tag container - //PropertyHandle->SetValueFromFormattedString(EditedFilter->ToString()); + // PropertyHandle->SetValueFromFormattedString(EditedFilter->ToString()); } else if (PropertyHandle.IsValid() && !bMultiSelect) { // Case for a single Tag - //FString FormattedString = TEXT("(TagName=\""); - //FormattedString += EditedFilter.First().GetTagName().ToString(); - //FormattedString += TEXT("\")"); - //PropertyHandle->SetValueFromFormattedString(FormattedString); + // FString FormattedString = TEXT("(TagName=\""); + // FormattedString += EditedFilter.First().GetTagName().ToString(); + // FormattedString += TEXT("\")"); + // PropertyHandle->SetValueFromFormattedString(FormattedString); } else { - // Not sure if we should get here, means the property handle hasn't been setup which could be right or wrong. + // Not sure if we should get here, means the property handle hasn't been setup which could be right or + // wrong. if (OwnerObj) { OwnerObj->PreEditChange(PropertyHandle.IsValid() ? PropertyHandle->GetProperty() : nullptr); @@ -536,7 +505,8 @@ void SClassFilter::Refresh() bNeedsRefresh = true; } -void SClassFilter::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +void SClassFilter::Tick( + const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) { // Will populate the class hierarchy if needed ClassFilter::Helpers::PopulateClassHierarchy(); @@ -570,7 +540,8 @@ void SClassFilter::Populate() TArray InternalClasses; TArray InternalPaths; - // We aren't showing the internal classes, then we need to know what classes to consider Internal Only, so let's gather them up from the settings object. + // We aren't showing the internal classes, then we need to know what classes to consider Internal Only, so + // let's gather them up from the settings object. GetInternalOnlyPaths(InternalPaths); GetInternalOnlyClasses(InternalClassNames); @@ -579,8 +550,7 @@ void SClassFilter::Populate() { FString PackageClassName = InternalClassNames[i].ToString(); const FSEClassFilterNodePtr ClassNode = ClassFilter::Helpers::ClassHierarchy->FindNodeByClassName( - ClassFilter::Helpers::ClassHierarchy->GetObjectRootNode(), PackageClassName - ); + ClassFilter::Helpers::ClassHierarchy->GetObjectRootNode(), PackageClassName); if (ClassNode.IsValid()) { @@ -593,7 +563,8 @@ void SClassFilter::Populate() FSEClassFilterNodePtr RootNode; // Get the class tree, passing in certain filter options. - ClassFilter::Helpers::GetClassTree(*Filters[0].Filter, RootNode, false, true, false, InternalClasses, InternalPaths); + ClassFilter::Helpers::GetClassTree( + *Filters[0].Filter, RootNode, false, true, false, InternalClasses, InternalPaths); // Add all the children of the "Object" root. for (const auto& Child : RootNode->GetChildrenList()) @@ -620,10 +591,7 @@ EVisibility SClassFilter::DetermineClearSelectionVisibility() const return CanSelectClasses() ? EVisibility::Visible : EVisibility::Collapsed; } -void SClassFilter::OnExpansionChanged(FSEClassFilterNodePtr Class, bool bIsExpanded) -{ - -} +void SClassFilter::OnExpansionChanged(FSEClassFilterNodePtr Class, bool bIsExpanded) {} bool SClassFilter::CanSelectClasses() const { diff --git a/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.h b/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.h index bd2aca7..bb566cb 100644 --- a/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.h +++ b/Source/Editor/Private/Customizations/ClassFilter/SClassFilter.h @@ -1,21 +1,22 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "ClassFilterNode.h" #include "CoreMinimal.h" -#include +#include "Input/Reply.h" +#include "Layout/Visibility.h" +#include "Misc/ClassFilter.h" #include "SlateFwd.h" #include "UObject/Object.h" -#include "Layout/Visibility.h" #include "Widgets/DeclarativeSyntaxSupport.h" -#include "Input/Reply.h" -#include "Widgets/SWidget.h" #include "Widgets/SCompoundWidget.h" -#include "Widgets/Views/STableViewBase.h" +#include "Widgets/SWidget.h" #include "Widgets/Views/STableRow.h" +#include "Widgets/Views/STableViewBase.h" #include "Widgets/Views/STreeView.h" -#include "Misc/ClassFilter.h" -#include "ClassFilterNode.h" +#include + class IPropertyHandle; @@ -23,7 +24,6 @@ class IPropertyHandle; class SClassFilter : public SCompoundWidget { public: - /** Called when the filter is changed */ DECLARE_DELEGATE(FOnFilterChanged) @@ -33,15 +33,15 @@ class SClassFilter : public SCompoundWidget , _PropertyHandle(nullptr) , _MaxHeight(260.0f) {} - SLATE_ARGUMENT(bool, ReadOnly) // Flag to set if the list is read only - SLATE_ARGUMENT(bool, MultiSelect) // If we can select multiple entries - SLATE_ARGUMENT(TSharedPtr, PropertyHandle) - SLATE_EVENT(FOnFilterChanged, OnFilterChanged) // Called when a tag status changes - SLATE_ARGUMENT(float, MaxHeight) // caps the height of the gameplay tag tree - SLATE_END_ARGS() - - /** Simple struct holding a tag container and its owner for generic re-use of the widget */ - struct FEditableClassFilterDatum + SLATE_ARGUMENT(bool, ReadOnly) // Flag to set if the list is read only + SLATE_ARGUMENT(bool, MultiSelect) // If we can select multiple entries + SLATE_ARGUMENT(TSharedPtr, PropertyHandle) + SLATE_EVENT(FOnFilterChanged, OnFilterChanged) // Called when a tag status changes + SLATE_ARGUMENT(float, MaxHeight) // caps the height of the gameplay tag tree + SLATE_END_ARGS() + + /** Simple struct holding a tag container and its owner for generic re-use of the widget */ + struct FEditableClassFilterDatum { /** Constructor */ FEditableClassFilterDatum(class UObject* InOwner, struct FSEClassFilter* InFilter) @@ -59,7 +59,8 @@ class SClassFilter : public SCompoundWidget /** Construct the actual widget */ void Construct(const FArguments& InArgs, const TArray& EditableFilters); - virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + virtual void Tick( + const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; /** Ensures that this widget will always account for the MaxHeight if it's specified */ virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override; @@ -75,7 +76,6 @@ class SClassFilter : public SCompoundWidget private: - FString SearchString; bool bReadOnly; @@ -86,7 +86,8 @@ class SClassFilter : public SCompoundWidget /** The maximum height of the gameplay tag tree. If 0, the height is unbound. */ float MaxHeight; - /** True if the Class Filter needs to be repopulated at the next appropriate opportunity, occurs whenever classes are added, removed, renamed, etc. */ + /** True if the Class Filter needs to be repopulated at the next appropriate opportunity, occurs whenever + * classes are added, removed, renamed, etc. */ bool bNeedsRefresh; /* Array of tags to be displayed in the TreeView */ @@ -121,7 +122,8 @@ class SClassFilter : public SCompoundWidget * * @return Generated row widget for the item node */ - TSharedRef OnGenerateRow(FSEClassFilterNodePtr Class, const TSharedRef& OwnerTable); + TSharedRef OnGenerateRow( + FSEClassFilterNodePtr Class, const TSharedRef& OwnerTable); /** * Get children nodes of the specified node diff --git a/Source/Editor/Private/Customizations/SClassFilterGraphPin.cpp b/Source/Editor/Private/Customizations/SClassFilterGraphPin.cpp index 347bffe..930e59a 100644 --- a/Source/Editor/Private/Customizations/SClassFilterGraphPin.cpp +++ b/Source/Editor/Private/Customizations/SClassFilterGraphPin.cpp @@ -1,40 +1,34 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SClassFilterGraphPin.h" -#include "Widgets/Input/SComboButton.h" + #include "GameplayTagsModule.h" +#include "Widgets/Input/SComboButton.h" #include "Widgets/Layout/SScaleBox.h" + #define LOCTEXT_NAMESPACE "GameplayTagGraphPin" void SClassFilterGraphPin::Construct(const FArguments& InArgs, UEdGraphPin* InGraphPinObj) { - SGraphPin::Construct( SGraphPin::FArguments(), InGraphPinObj ); + SGraphPin::Construct(SGraphPin::FArguments(), InGraphPinObj); } -TSharedRef SClassFilterGraphPin::GetDefaultValueWidget() +TSharedRef SClassFilterGraphPin::GetDefaultValueWidget() { ParseDefaultValueData(); - //Create widget - return SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Fill) - [ - SAssignNew( ComboButton, SComboButton ) - .OnGetMenuContent(this, &SClassFilterGraphPin::GetListContent) - .ButtonStyle(FAppStyle::Get(), "FlatButton") - .ForegroundColor(FSlateColor::UseForeground()) - .ContentPadding(FMargin(0.0f, 2.0f)) - .MenuPlacement(MenuPlacement_BelowAnchor) - .Visibility( this, &SGraphPin::GetDefaultValueVisibility ) - ] - +SHorizontalBox::Slot() - .AutoWidth() - [ - SelectedTags() - ]; + // Create widget + return SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth().VAlign( + VAlign_Fill)[SAssignNew(ComboButton, SComboButton) + .OnGetMenuContent(this, &SClassFilterGraphPin::GetListContent) + .ButtonStyle(FAppStyle::Get(), "FlatButton") + .ForegroundColor(FSlateColor::UseForeground()) + .ContentPadding(FMargin(0.0f, 2.0f)) + .MenuPlacement(MenuPlacement_BelowAnchor) + .Visibility(this, &SGraphPin::GetDefaultValueVisibility)] + + SHorizontalBox::Slot().AutoWidth()[SelectedTags()]; } void SClassFilterGraphPin::ParseDefaultValueData() @@ -45,24 +39,19 @@ void SClassFilterGraphPin::ParseDefaultValueData() TSharedRef SClassFilterGraphPin::GetListContent() { EditableFilters.Empty(); - EditableFilters.Add( SClassFilter::FEditableClassFilterDatum( GraphPinObj->GetOwningNode(), &Filter ) ); - - return SNew( SVerticalBox ) - +SVerticalBox::Slot() - .AutoHeight() - .MaxHeight( 400 ) - [ - SNew( SClassFilter, EditableFilters ) - .OnFilterChanged(this, &SClassFilterGraphPin::RefreshPreviewList) - .Visibility( this, &SGraphPin::GetDefaultValueVisibility ) - ]; + EditableFilters.Add(SClassFilter::FEditableClassFilterDatum(GraphPinObj->GetOwningNode(), &Filter)); + + return SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().MaxHeight( + 400)[SNew(SClassFilter, EditableFilters) + .OnFilterChanged(this, &SClassFilterGraphPin::RefreshPreviewList) + .Visibility(this, &SGraphPin::GetDefaultValueVisibility)]; } TSharedRef SClassFilterGraphPin::SelectedTags() { RefreshPreviewList(); - SAssignNew( PreviewList, SListView> ) + SAssignNew(PreviewList, SListView>) .ListItemsSource(&PreviewClasses) .SelectionMode(ESelectionMode::None) .OnGenerateRow(this, &SClassFilterGraphPin::OnGeneratePreviewRow); @@ -70,7 +59,8 @@ TSharedRef SClassFilterGraphPin::SelectedTags() return PreviewList->AsShared(); } -TSharedRef SClassFilterGraphPin::OnGeneratePreviewRow(TSharedPtr Class, const TSharedRef& OwnerTable) +TSharedRef SClassFilterGraphPin::OnGeneratePreviewRow( + TSharedPtr Class, const TSharedRef& OwnerTable) { FLinearColor StateColor; FText StateText; @@ -85,26 +75,17 @@ TSharedRef SClassFilterGraphPin::OnGeneratePreviewRow(TSharedPtr>, OwnerTable) - [ - SNew(SHorizontalBox) - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - .Padding(0, 0, 2, 0) - [ - SNew(STextBlock) - .ColorAndOpacity(StateColor) - .Font(FAppStyle::Get().GetFontStyle("FontAwesome.8")) - .Text(StateText) - ] - + SHorizontalBox::Slot() - .AutoWidth() - .VAlign(VAlign_Center) - [ - SNew(STextBlock).Text(FText::FromString(Class->ClassName)) - ] - ]; + return SNew(STableRow>, + OwnerTable)[SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(0, 0, 2, 0)[SNew(STextBlock) + .ColorAndOpacity(StateColor) + .Font(FAppStyle::Get().GetFontStyle("FontAwesome.8")) + .Text(StateText)] + + SHorizontalBox::Slot().AutoWidth().VAlign( + VAlign_Center)[SNew(STextBlock).Text(FText::FromString(Class->ClassName))]]; } void SClassFilterGraphPin::RefreshPreviewList() diff --git a/Source/Editor/Private/Customizations/SClassFilterGraphPin.h b/Source/Editor/Private/Customizations/SClassFilterGraphPin.h index 170532a..3d95914 100644 --- a/Source/Editor/Private/Customizations/SClassFilterGraphPin.h +++ b/Source/Editor/Private/Customizations/SClassFilterGraphPin.h @@ -1,14 +1,16 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "ClassFilter/SClassFilter.h" +#include "SEClassFilterCustomization.h" +#include "SGraphPin.h" + #include #include #include -#include #include -#include "ClassFilter/SClassFilter.h" -#include "SGraphPin.h" -#include "SEClassFilterCustomization.h" +#include + class SComboButton; @@ -22,11 +24,10 @@ class SClassFilterGraphPin : public SGraphPin void Construct(const FArguments& InArgs, UEdGraphPin* InGraphPinObj); //~ Begin SGraphPin Interface - virtual TSharedRef GetDefaultValueWidget() override; + virtual TSharedRef GetDefaultValueWidget() override; //~ End SGraphPin Interface private: - /** Refreshes the list of tags displayed on the node. */ void RefreshPreviewList(); @@ -46,10 +47,10 @@ class SClassFilterGraphPin : public SGraphPin * Callback for populating rows of the SelectedTags List View. * @return widget that contains the name of a tag. */ - TSharedRef OnGeneratePreviewRow(TSharedPtr Class, const TSharedRef& OwnerTable); + TSharedRef OnGeneratePreviewRow( + TSharedPtr Class, const TSharedRef& OwnerTable); private: - // Combo Button for the drop down list. TSharedPtr ComboButton; diff --git a/Source/Editor/Private/Customizations/SEActorClassFilterCustomization.cpp b/Source/Editor/Private/Customizations/SEActorClassFilterCustomization.cpp deleted file mode 100644 index fdcafed..0000000 --- a/Source/Editor/Private/Customizations/SEActorClassFilterCustomization.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Customizations/SEActorClassFilterCustomization.h" - -#include -#include - -#define LOCTEXT_NAMESPACE "FSEActorClassFilterCustomization" - - -TSharedPtr FSEActorClassFilterCustomization::GetFilterHandle(TSharedRef StructPropertyHandle) -{ - return StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSEActorClassFilter, ClassFilter));; -} - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Customizations/SEActorClassFilterCustomization.h b/Source/Editor/Private/Customizations/SEActorClassFilterCustomization.h deleted file mode 100644 index 069cb9f..0000000 --- a/Source/Editor/Private/Customizations/SEActorClassFilterCustomization.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. -#pragma once - -#include "SEClassFilterCustomization.h" - - -class IPropertyHandle; - -class FSEActorClassFilterCustomization : public FSEClassFilterCustomization -{ -public: - - /** - * Creates a new instance. - * - * @return A new struct customization for Factions. - */ - static TSharedRef MakeInstance() - { - return MakeShared(); - } - -protected: - - virtual TSharedPtr GetFilterHandle(TSharedRef StructPropertyHandle) override; -}; - diff --git a/Source/Editor/Private/Customizations/SEClassFilterCustomization.cpp b/Source/Editor/Private/Customizations/SEClassFilterCustomization.cpp index 43a275b..b9d0980 100644 --- a/Source/Editor/Private/Customizations/SEClassFilterCustomization.cpp +++ b/Source/Editor/Private/Customizations/SEClassFilterCustomization.cpp @@ -1,15 +1,18 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Customizations/SEClassFilterCustomization.h" #include -#include -#include -#include #include +#include +#include +#include #include +#include +#include #include + #define LOCTEXT_NAMESPACE "FSEClassFilterCustomization" @@ -18,13 +21,14 @@ FSEClassFilterCustomization::~FSEClassFilterCustomization() GEditor->UnregisterForUndo(this); } -void FSEClassFilterCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +void FSEClassFilterCustomization::CustomizeHeader(TSharedRef StructPropertyHandle, + FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { StructHandle = StructPropertyHandle; - FilterHandle = GetFilterHandle(StructPropertyHandle); BuildEditableFilterList(); + // clang-format off HeaderRow .NameContent() [ @@ -59,65 +63,54 @@ void FSEClassFilterCustomization::CustomizeHeader(TSharedRefRegisterForUndo(this); } -void FSEClassFilterCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) +void FSEClassFilterCustomization::CustomizeChildren(TSharedRef StructPropertyHandle, + class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) {} void FSEClassFilterCustomization::BuildEditableFilterList() { EditableFilters.Empty(); - if (FilterHandle.IsValid()) + if (StructHandle.IsValid()) { TArray RawStructData; - FilterHandle->AccessRawData(RawStructData); + StructHandle->AccessRawData(RawStructData); TArray Outers; - FilterHandle->GetOuterObjects(Outers); + StructHandle->GetOuterObjects(Outers); UObject* FirstOuter = Outers.Num() ? Outers[0] : nullptr; for (int32 ContainerIdx = 0; ContainerIdx < RawStructData.Num(); ++ContainerIdx) { - EditableFilters.Add(SClassFilter::FEditableClassFilterDatum(FirstOuter, (FSEClassFilter*)RawStructData[ContainerIdx])); + EditableFilters.Add(SClassFilter::FEditableClassFilterDatum( + FirstOuter, (FSEClassFilter*) RawStructData[ContainerIdx])); } } } TSharedRef FSEClassFilterCustomization::GetListContent() { - if (!FilterHandle.IsValid() || FilterHandle->GetProperty() == nullptr) + if (!StructHandle.IsValid() || StructHandle->GetProperty() == nullptr) { return SNullWidget::NullWidget; } - bool bReadOnly = FilterHandle->IsEditConst(); - - TSharedRef EditPopup = SNew(SClassFilter, EditableFilters) - .ReadOnly(bReadOnly) - .OnFilterChanged(this, &FSEClassFilterCustomization::RefreshClassList) - .PropertyHandle(FilterHandle); + bool bReadOnly = StructHandle->IsEditConst(); + // clang-format off + TSharedRef EditPopup = + SNew(SClassFilter, EditableFilters) + .ReadOnly(bReadOnly) + .OnFilterChanged(this, &FSEClassFilterCustomization::RefreshClassList) + .PropertyHandle(StructHandle); LastFilterPopup = EditPopup; - return SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() @@ -125,6 +118,7 @@ TSharedRef FSEClassFilterCustomization::GetListContent() [ EditPopup ]; + // clang-format on } void FSEClassFilterCustomization::OnPopupStateChanged(bool bIsOpened) @@ -139,22 +133,16 @@ void FSEClassFilterCustomization::OnPopupStateChanged(bool bIsOpened) } } -FReply FSEClassFilterCustomization::OnClearClicked() +void FSEClassFilterCustomization::OnClearClicked() { FScopedTransaction Transaction(LOCTEXT("ClassFilter_Filter", "Clear Filter")); + StructHandle->NotifyPreChange(); for (auto& Filter : EditableFilters) { - // Reset Filter - if (Filter.Owner.IsValid()) - { - Filter.Owner->Modify(); - } - *Filter.Filter = {}; } - + StructHandle->NotifyPostChange(EPropertyChangeType::ValueSet); RefreshClassList(); - return FReply::Handled(); } EVisibility FSEClassFilterCustomization::GetClassPreviewVisibility() const @@ -170,6 +158,7 @@ TSharedRef FSEClassFilterCustomization::GetClassPreview() { RefreshClassList(); + // clang-format off return SNew(SBox) .MinDesiredWidth(100.f) [ @@ -178,9 +167,11 @@ TSharedRef FSEClassFilterCustomization::GetClassPreview() .ListItemsSource(&PreviewClasses) .OnGenerateRow(this, &FSEClassFilterCustomization::OnGeneratePreviewRow) ]; + // clang-format on } -TSharedRef FSEClassFilterCustomization::OnGeneratePreviewRow(TSharedPtr Class, const TSharedRef& OwnerTable) +TSharedRef FSEClassFilterCustomization::OnGeneratePreviewRow( + TSharedPtr Class, const TSharedRef& OwnerTable) { FLinearColor StateColor; FText StateText; @@ -195,6 +186,7 @@ TSharedRef FSEClassFilterCustomization::OnGeneratePreviewRow(TSharedP StateText = FText::FromString(FString(TEXT("\xf00d"))) /*fa-times*/; } + // clang-format off return SNew(STableRow>, OwnerTable) [ SNew(SHorizontalBox) @@ -215,6 +207,7 @@ TSharedRef FSEClassFilterCustomization::OnGeneratePreviewRow(TSharedP SNew(STextBlock).Text(FText::FromString(Class->ClassName)) ] ]; + // clang-format on } void FSEClassFilterCustomization::PostUndo(bool bSuccess) diff --git a/Source/Editor/Private/Customizations/SEClassFilterCustomization.h b/Source/Editor/Private/Customizations/SEClassFilterCustomization.h index 0a8bfda..fb9f20d 100644 --- a/Source/Editor/Private/Customizations/SEClassFilterCustomization.h +++ b/Source/Editor/Private/Customizations/SEClassFilterCustomization.h @@ -1,33 +1,31 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include -#include #include "ClassFilter/SClassFilter.h" +#include +#include + class IPropertyHandle; -struct FSEClassFilterItem { +struct FSEClassFilterItem +{ FString ClassName; bool bAllowed; - FSEClassFilterItem(FString ClassName, bool bAllowed) - : ClassName{ClassName}, bAllowed{bAllowed} - {} + FSEClassFilterItem(FString ClassName, bool bAllowed) : ClassName{ClassName}, bAllowed{bAllowed} {} }; class FSEClassFilterCustomization : public IPropertyTypeCustomization, public FEditorUndoClient { protected: - /** Filters this customization edits */ TArray EditableFilters; TArray> PreviewClasses; TSharedPtr StructHandle; - TSharedPtr FilterHandle; TSharedPtr EditButton; @@ -36,12 +34,11 @@ class FSEClassFilterCustomization : public IPropertyTypeCustomization, public FE TWeakPtr LastFilterPopup; public: - /** - * Creates a new instance. - * - * @return A new struct customization for Factions. - */ + * Creates a new instance. + * + * @return A new struct customization for Factions. + */ static TSharedRef MakeInstance() { return MakeShared(); @@ -50,21 +47,19 @@ class FSEClassFilterCustomization : public IPropertyTypeCustomization, public FE ~FSEClassFilterCustomization(); protected: - /** IPropertyTypeCustomization interface */ - virtual void CustomizeHeader(TSharedRef StructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; - virtual void CustomizeChildren(TSharedRef StructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + virtual void CustomizeHeader(TSharedRef StructPropertyHandle, + class FDetailWidgetRow& HeaderRow, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; + virtual void CustomizeChildren(TSharedRef StructPropertyHandle, + class IDetailChildrenBuilder& StructBuilder, + IPropertyTypeCustomizationUtils& StructCustomizationUtils) override; //~ Begin FEditorUndoClient Interface virtual void PostUndo(bool bSuccess) override; virtual void PostRedo(bool bSuccess) override; //~ End FEditorUndoClient Interface - virtual TSharedPtr GetFilterHandle(TSharedRef StructPropertyHandle) - { - return StructHandle; - } - /** Build List of Editable Containers */ void BuildEditableFilterList(); @@ -73,16 +68,16 @@ class FSEClassFilterCustomization : public IPropertyTypeCustomization, public FE void OnPopupStateChanged(bool bIsOpened); - FReply OnClearClicked(); + void OnClearClicked(); EVisibility GetClassPreviewVisibility() const; TSharedRef GetClassPreview(); - TSharedRef OnGeneratePreviewRow(TSharedPtr Class, const TSharedRef& OwnerTable); + TSharedRef OnGeneratePreviewRow( + TSharedPtr Class, const TSharedRef& OwnerTable); void RefreshClassList(); }; - diff --git a/Source/Editor/Private/Customizations/SEClassFilterGraphPanelPinFactory.h b/Source/Editor/Private/Customizations/SEClassFilterGraphPanelPinFactory.h index 91f3a1d..e76d367 100644 --- a/Source/Editor/Private/Customizations/SEClassFilterGraphPanelPinFactory.h +++ b/Source/Editor/Private/Customizations/SEClassFilterGraphPanelPinFactory.h @@ -1,17 +1,18 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "CoreMinimal.h" -#include "Widgets/DeclarativeSyntaxSupport.h" +#include "EdGraphSchema_K2.h" #include "EdGraphUtilities.h" #include "GameplayTagContainer.h" -#include "EdGraphSchema_K2.h" -#include "SGraphPin.h" -#include "SClassFilterGraphPin.h" #include "Misc/ClassFilter.h" +#include "SClassFilterGraphPin.h" +#include "SGraphPin.h" +#include "Widgets/DeclarativeSyntaxSupport.h" + -class FSEClassFilterGraphPanelPinFactory: public FGraphPanelPinFactory +class FSEClassFilterGraphPanelPinFactory : public FGraphPanelPinFactory { virtual TSharedPtr CreatePin(class UEdGraphPin* InPin) const override { diff --git a/Source/Editor/Private/Customizations/SEComponentClassFilterCustomization.cpp b/Source/Editor/Private/Customizations/SEComponentClassFilterCustomization.cpp deleted file mode 100644 index 58c4e5a..0000000 --- a/Source/Editor/Private/Customizations/SEComponentClassFilterCustomization.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Customizations/SEComponentClassFilterCustomization.h" -#include "PropertyHandle.h" - -#define LOCTEXT_NAMESPACE "FSEComponentClassFilterCustomization" - - -TSharedPtr FSEComponentClassFilterCustomization::GetFilterHandle(TSharedRef StructPropertyHandle) -{ - return StructHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FSEComponentClassFilter, ClassFilter));; -} - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Customizations/SEComponentClassFilterCustomization.h b/Source/Editor/Private/Customizations/SEComponentClassFilterCustomization.h deleted file mode 100644 index a3324fa..0000000 --- a/Source/Editor/Private/Customizations/SEComponentClassFilterCustomization.h +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. -#pragma once - -#include "SEClassFilterCustomization.h" - - -class IPropertyHandle; - -class FSEComponentClassFilterCustomization : public FSEClassFilterCustomization -{ -public: - - /** - * Creates a new instance. - * - * @return A new struct customization for Factions. - */ - static TSharedRef MakeInstance() - { - return MakeShared(); - } - -protected: - - virtual TSharedPtr GetFilterHandle(TSharedRef StructPropertyHandle) override; -}; - diff --git a/Source/Editor/Private/Customizations/SavePresetDetails.cpp b/Source/Editor/Private/Customizations/SavePresetDetails.cpp deleted file mode 100644 index 163306f..0000000 --- a/Source/Editor/Private/Customizations/SavePresetDetails.cpp +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "SavePresetDetails.h" - -#include "DetailLayoutBuilder.h" -#include "DetailCategoryBuilder.h" -#include "DetailWidgetRow.h" -#include -#include - -#include "SavePreset.h" - -#define LOCTEXT_NAMESPACE "FSavePresetDetails" - - -/************************************************************************ - * FSavePresetDetails - */ - -TSharedRef FSavePresetDetails::MakeInstance() -{ - return MakeShareable(new FSavePresetDetails); -} - -void FSavePresetDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) -{ - TArray> Objects; - DetailBuilder.GetObjectsBeingCustomized(Objects); - - if (Objects.Num() && Objects[0] != nullptr) - { - Settings = CastChecked(Objects[0].Get()); - - DetailBuilder.EditCategory(TEXT("Gameplay")); - DetailBuilder.EditCategory(TEXT("Serialization")); - - IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("Asynchronous")); - { - Category.AddProperty(TEXT("MultithreadedSerialization")); - Category.AddProperty(TEXT("FrameSplittedSerialization")); - Category.AddCustomRow(LOCTEXT("AsyncWarning", "Asynchronous Warning")) - .Visibility({ this, &FSavePresetDetails::GetWarningVisibility }) - .ValueContent() - .MinDesiredWidth(300.f) - .MaxDesiredWidth(400.f) - [ - SNew(SBorder) - .Padding(2.f) - .BorderImage(FAppStyle::GetBrush("ErrorReporting.EmptyBox")) - .BorderBackgroundColor(this, &FSavePresetDetails::GetWarningColor) - [ - SNew(STextBlock) - .Text(LOCTEXT("AsyncWarningText", "WARNING: Frame-splitted loading or saving is not recommended while using Level Streaming or World Composition")) - .AutoWrapText(true) - ] - ]; - - Category.AddProperty(TEXT("MaxFrameMs")).EditCondition({ this, &FSavePresetDetails::CanEditAsynchronous }, nullptr); - } - - DetailBuilder.EditCategory(TEXT("Level Streaming")); - } -} - -FSlateColor FSavePresetDetails::GetWarningColor() const -{ - return FLinearColor{ FColor{ 234, 220, 25, 128 } }; -} - -EVisibility FSavePresetDetails::GetWarningVisibility() const -{ - if (Settings.IsValid()) - { - return Settings->GetFrameSplitSerialization() == ESaveASyncMode::OnlySync ? EVisibility::Collapsed : EVisibility::Visible; - } - return EVisibility::Collapsed; -} - -bool FSavePresetDetails::CanEditAsynchronous() const -{ - if (Settings.IsValid()) - { - return Settings->GetFrameSplitSerialization() != ESaveASyncMode::OnlySync; - } - return true; -} - -#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Customizations/SaveSlotDetails.cpp b/Source/Editor/Private/Customizations/SaveSlotDetails.cpp new file mode 100644 index 0000000..7133195 --- /dev/null +++ b/Source/Editor/Private/Customizations/SaveSlotDetails.cpp @@ -0,0 +1,89 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "SaveSlotDetails.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" + +#include +#include +#include + + +#define LOCTEXT_NAMESPACE "FSaveSlotDetails" + + +/************************************************************************ + * FSaveSlotDetails + */ + +TSharedRef FSaveSlotDetails::MakeInstance() +{ + return MakeShareable(new FSaveSlotDetails); +} + +void FSaveSlotDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + TArray> Objects; + DetailBuilder.GetObjectsBeingCustomized(Objects); + + if (Objects.Num() && Objects[0] != nullptr) + { + Slot = CastChecked(Objects[0].Get()); + + DetailBuilder.EditCategory(TEXT("Gameplay")); + DetailBuilder.EditCategory(TEXT("Serialization")); + + IDetailCategoryBuilder& Category = DetailBuilder.EditCategory(TEXT("Asynchronous")); + { + Category.AddProperty(TEXT("MultithreadedSerialization")); + Category.AddProperty(TEXT("FrameSplittedSerialization")); + Category.AddCustomRow(LOCTEXT("AsyncWarning", "Asynchronous Warning")) + .Visibility({this, &FSaveSlotDetails::GetWarningVisibility}) + .ValueContent() + .MinDesiredWidth(300.f) + .MaxDesiredWidth( + 400.f)[SNew(SBorder) + .Padding(2.f) + .BorderImage(FAppStyle::GetBrush("ErrorReporting.EmptyBox")) + .BorderBackgroundColor(this, &FSaveSlotDetails::GetWarningColor) + [SNew(STextBlock) + .Text(LOCTEXT("AsyncWarningText", + "WARNING: Frame-splitted loading or saving is not recommended " + "while using Level Streaming or World Composition")) + .AutoWrapText(true)]]; + + Category.AddProperty(TEXT("MaxFrameMs")) + .EditCondition({this, &FSaveSlotDetails::CanEditAsynchronous}, nullptr); + } + + DetailBuilder.EditCategory(TEXT("Level Streaming")); + } +} + +FSlateColor FSaveSlotDetails::GetWarningColor() const +{ + return FLinearColor{FColor{234, 220, 25, 128}}; +} + +EVisibility FSaveSlotDetails::GetWarningVisibility() const +{ + if (Slot.IsValid()) + { + return Slot->GetFrameSplitSerialization() == ESEAsyncMode::SaveAndLoadSync ? EVisibility::Collapsed + : EVisibility::Visible; + } + return EVisibility::Collapsed; +} + +bool FSaveSlotDetails::CanEditAsynchronous() const +{ + if (Slot.IsValid()) + { + return Slot->GetFrameSplitSerialization() != ESEAsyncMode::SaveAndLoadSync; + } + return true; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Editor/Private/Customizations/SavePresetDetails.h b/Source/Editor/Private/Customizations/SaveSlotDetails.h similarity index 81% rename from Source/Editor/Private/Customizations/SavePresetDetails.h rename to Source/Editor/Private/Customizations/SaveSlotDetails.h index 7bcc334..65ec884 100644 --- a/Source/Editor/Private/Customizations/SavePresetDetails.h +++ b/Source/Editor/Private/Customizations/SaveSlotDetails.h @@ -1,30 +1,29 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "CoreMinimal.h" +#include "EditorUndoClient.h" #include "IDetailCustomization.h" -#include "Layout/Visibility.h" -#include "Widgets/Views/SListView.h" -#include "Widgets/Input/SEditableTextBox.h" #include "Input/Reply.h" +#include "Layout/Visibility.h" #include "PropertyHandle.h" -#include "EditorUndoClient.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Views/SListView.h" + class IDetailsView; class IDetailLayoutBuilder; -class USavePreset; +class USaveSlot; -class FSavePresetDetails : public IDetailCustomization +class FSaveSlotDetails : public IDetailCustomization { public: - /** Makes a new instance of this detail layout class for a specific detail view requesting it */ static TSharedRef MakeInstance(); private: - /** IDetailCustomization interface */ virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; @@ -33,5 +32,5 @@ class FSavePresetDetails : public IDetailCustomization bool CanEditAsynchronous() const; - TWeakObjectPtr Settings; + TWeakObjectPtr Slot; }; diff --git a/Source/Editor/Private/SaveExtensionEditor.cpp b/Source/Editor/Private/SaveExtensionEditor.cpp index 016708a..9f648e0 100644 --- a/Source/Editor/Private/SaveExtensionEditor.cpp +++ b/Source/Editor/Private/SaveExtensionEditor.cpp @@ -1,18 +1,14 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SaveExtensionEditor.h" -#include "Kismet2/KismetEditorUtilities.h" - -#include "Asset/AssetTypeAction_SlotInfo.h" -#include "Asset/AssetTypeAction_SlotData.h" -#include "Asset/AssetTypeAction_SavePreset.h" - -#include "Customizations/SavePresetDetails.h" +#include "Asset/AssetTypeAction_SaveSlot.h" +#include "Asset/AssetTypeAction_SaveSlotData.h" #include "Customizations/SEClassFilterCustomization.h" #include "Customizations/SEClassFilterGraphPanelPinFactory.h" -#include "Customizations/SEActorClassFilterCustomization.h" -#include "Customizations/SEComponentClassFilterCustomization.h" +#include "Customizations/SaveSlotDetails.h" +#include "Kismet2/KismetEditorUtilities.h" + #define LOCTEXT_NAMESPACE "SaveExtensionEditor" @@ -20,11 +16,11 @@ void FSaveExtensionEditor::StartupModule() { IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - AssetCategory = AssetTools.RegisterAdvancedAssetCategory(FName("SaveExtension"), LOCTEXT("Category", "Save Extension")); + AssetCategory = AssetTools.RegisterAdvancedAssetCategory( + FName("SaveExtension"), LOCTEXT("Category", "Save Extension")); - AssetTools.RegisterAssetTypeActions(MakeShared()); - AssetTools.RegisterAssetTypeActions(MakeShared()); - AssetTools.RegisterAssetTypeActions(MakeShared()); + AssetTools.RegisterAssetTypeActions(MakeShared()); + AssetTools.RegisterAssetTypeActions(MakeShared()); RegisterPropertyTypeCustomizations(); @@ -45,35 +41,42 @@ void FSaveExtensionEditor::ShutdownModule() void FSaveExtensionEditor::RegisterPropertyTypeCustomizations() { - RegisterCustomClassLayout("SavePreset", FOnGetDetailCustomizationInstance::CreateStatic(&FSavePresetDetails::MakeInstance)); + RegisterCustomClassLayout( + "SaveSlot", FOnGetDetailCustomizationInstance::CreateStatic(&FSaveSlotDetails::MakeInstance)); - RegisterCustomPropertyTypeLayout("SEClassFilter", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSEClassFilterCustomization::MakeInstance)); - RegisterCustomPropertyTypeLayout("SEActorClassFilter", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSEActorClassFilterCustomization::MakeInstance)); - RegisterCustomPropertyTypeLayout("SEComponentClassFilter", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSEComponentClassFilterCustomization::MakeInstance)); - //RegisterCustomPropertyTypeLayout("SavePreset", FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSavePresetCustomization::MakeInstance)); + RegisterCustomPropertyTypeLayout("SEClassFilter", + FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSEClassFilterCustomization::MakeInstance)); + RegisterCustomPropertyTypeLayout("SEActorClassFilter", + FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSEClassFilterCustomization::MakeInstance)); + RegisterCustomPropertyTypeLayout("SEComponentClassFilter", + FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FSEClassFilterCustomization::MakeInstance)); RegisterCustomPinFactory(); } -void FSaveExtensionEditor::RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate) +void FSaveExtensionEditor::RegisterCustomClassLayout( + FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate) { check(ClassName != NAME_None); static FName PropertyEditor("PropertyEditor"); - FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked(PropertyEditor); + FPropertyEditorModule& PropertyModule = + FModuleManager::GetModuleChecked(PropertyEditor); PropertyModule.RegisterCustomClassLayout(ClassName, DetailLayoutDelegate); } -void FSaveExtensionEditor::RegisterCustomPropertyTypeLayout(FName PropertyTypeName, FOnGetPropertyTypeCustomizationInstance PropertyTypeLayoutDelegate) +void FSaveExtensionEditor::RegisterCustomPropertyTypeLayout( + FName PropertyTypeName, FOnGetPropertyTypeCustomizationInstance PropertyTypeLayoutDelegate) { check(PropertyTypeName != NAME_None); static FName PropertyEditor("PropertyEditor"); - FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked(PropertyEditor); + FPropertyEditorModule& PropertyModule = + FModuleManager::GetModuleChecked(PropertyEditor); PropertyModule.RegisterCustomPropertyTypeLayout(PropertyTypeName, PropertyTypeLayoutDelegate); } -template +template void FSaveExtensionEditor::RegisterCustomPinFactory() { TSharedPtr PinFactory = MakeShareable(new T()); diff --git a/Source/Editor/Private/SaveExtensionEditor.h b/Source/Editor/Private/SaveExtensionEditor.h index 50cdcd1..d74c899 100644 --- a/Source/Editor/Private/SaveExtensionEditor.h +++ b/Source/Editor/Private/SaveExtensionEditor.h @@ -1,45 +1,51 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include -#include -#include +#include "ISaveExtensionEditor.h" +#include "SaveActorEditorTabSummoner.h" + #include #include #include #include +#include +#include +#include -#include "SaveActorEditorTabSummoner.h" -#include "ISaveExtensionEditor.h" -/** Shared class type that ensures safe binding to RegisterBlueprintEditorTab through an SP binding without interfering with module ownership semantics */ -class FSaveActorEditorTabBinding - : public TSharedFromThis +/** Shared class type that ensures safe binding to RegisterBlueprintEditorTab through an SP binding without + * interfering with module ownership semantics */ +class FSaveActorEditorTabBinding : public TSharedFromThis { public: - FSaveActorEditorTabBinding() { - FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked("Kismet"); - BlueprintEditorTabSpawnerHandle = BlueprintEditorModule.OnRegisterTabsForEditor().AddRaw(this, &FSaveActorEditorTabBinding::RegisterBlueprintEditorTab); - BlueprintEditorLayoutExtensionHandle = BlueprintEditorModule.OnRegisterLayoutExtensions().AddRaw(this, &FSaveActorEditorTabBinding::RegisterBlueprintEditorLayout); + FBlueprintEditorModule& BlueprintEditorModule = + FModuleManager::LoadModuleChecked("Kismet"); + BlueprintEditorTabSpawnerHandle = BlueprintEditorModule.OnRegisterTabsForEditor().AddRaw( + this, &FSaveActorEditorTabBinding::RegisterBlueprintEditorTab); + BlueprintEditorLayoutExtensionHandle = BlueprintEditorModule.OnRegisterLayoutExtensions().AddRaw( + this, &FSaveActorEditorTabBinding::RegisterBlueprintEditorLayout); } void RegisterBlueprintEditorLayout(FLayoutExtender& Extender) { - Extender.ExtendLayout(FBlueprintEditorTabs::MyBlueprintID, ELayoutExtensionPosition::After, FTabManager::FTab(FSaveActorEditorSummoner::TabName, ETabState::OpenedTab)); + Extender.ExtendLayout(FBlueprintEditorTabs::MyBlueprintID, ELayoutExtensionPosition::After, + FTabManager::FTab(FSaveActorEditorSummoner::TabName, ETabState::OpenedTab)); } - void RegisterBlueprintEditorTab(FWorkflowAllowedTabSet& TabFactories, FName InModeName, TSharedPtr BlueprintEditor) + void RegisterBlueprintEditorTab( + FWorkflowAllowedTabSet& TabFactories, FName InModeName, TSharedPtr BlueprintEditor) { TabFactories.RegisterFactory(MakeShared(BlueprintEditor)); } ~FSaveActorEditorTabBinding() { - FBlueprintEditorModule* BlueprintEditorModule = FModuleManager::GetModulePtr("Kismet"); + FBlueprintEditorModule* BlueprintEditorModule = + FModuleManager::GetModulePtr("Kismet"); if (BlueprintEditorModule) { BlueprintEditorModule->OnRegisterTabsForEditor().Remove(BlueprintEditorTabSpawnerHandle); @@ -48,7 +54,6 @@ class FSaveActorEditorTabBinding } private: - /** Delegate binding handle for FBlueprintEditorModule::OnRegisterTabsForEditor */ FDelegateHandle BlueprintEditorTabSpawnerHandle, BlueprintEditorLayoutExtensionHandle; }; @@ -56,39 +61,40 @@ class FSaveActorEditorTabBinding class FSaveExtensionEditor : public ISaveExtensionEditor { public: - virtual void StartupModule() override; virtual void ShutdownModule() override; private: - void RegisterPropertyTypeCustomizations(); /** - * Registers a custom class - * - * @param ClassName The class name to register for property customization - * @param DetailLayoutDelegate The delegate to call to get the custom detail layout instance - */ + * Registers a custom class + * + * @param ClassName The class name to register for property customization + * @param DetailLayoutDelegate The delegate to call to get the custom detail layout instance + */ void RegisterCustomClassLayout(FName ClassName, FOnGetDetailCustomizationInstance DetailLayoutDelegate); /** - * Registers a custom struct - * - * @param StructName The name of the struct to register for property customization - * @param StructLayoutDelegate The delegate to call to get the custom detail layout instance - */ - void RegisterCustomPropertyTypeLayout(FName PropertyTypeName, FOnGetPropertyTypeCustomizationInstance PropertyTypeLayoutDelegate); - - template + * Registers a custom struct + * + * @param StructName The name of the struct to register for property customization + * @param StructLayoutDelegate The delegate to call to get the custom detail layout instance + */ + void RegisterCustomPropertyTypeLayout( + FName PropertyTypeName, FOnGetPropertyTypeCustomizationInstance PropertyTypeLayoutDelegate); + + template void RegisterCustomPinFactory(); - //Simplify Registering generated default events -#define RegisterDefaultEventChecked(Class, FuncName) \ - (FKismetEditorUtilities::RegisterAutoGeneratedDefaultEvent(this, Class::StaticClass(), GET_FUNCTION_NAME_CHECKED(Class, FuncName))) + // Simplify Registering generated default events +#define RegisterDefaultEventChecked(Class, FuncName) \ + (FKismetEditorUtilities::RegisterAutoGeneratedDefaultEvent( \ + this, Class::StaticClass(), GET_FUNCTION_NAME_CHECKED(Class, FuncName))) -#define RegisterDefaultEvent(Class, FuncName) \ - (FKismetEditorUtilities::RegisterAutoGeneratedDefaultEvent(this, Class::StaticClass(), FName(TEXT(#FuncName)))) +#define RegisterDefaultEvent(Class, FuncName) \ + (FKismetEditorUtilities::RegisterAutoGeneratedDefaultEvent( \ + this, Class::StaticClass(), FName(TEXT(#FuncName)))) TSharedPtr BlueprintEditorTabBinding; @@ -96,5 +102,3 @@ class FSaveExtensionEditor : public ISaveExtensionEditor /** All created pin factories. Cached here so that we can unregister them during shutdown. */ TArray> CreatedPinFactories; }; - - diff --git a/Source/Editor/Public/ISaveExtensionEditor.h b/Source/Editor/Public/ISaveExtensionEditor.h index d4b37eb..4c38e9b 100644 --- a/Source/Editor/Public/ISaveExtensionEditor.h +++ b/Source/Editor/Public/ISaveExtensionEditor.h @@ -1,23 +1,24 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "AssetTypeCategories.h" #include "Modules/ModuleManager.h" -#include "AssetTypeCategories.h" class ISaveExtensionEditor : public IModuleInterface { public: - EAssetTypeCategories::Type AssetCategory; - static inline ISaveExtensionEditor &Get() { + static inline ISaveExtensionEditor& Get() + { return FModuleManager::LoadModuleChecked("SaveExtensionEditor"); } - static inline bool IsAvailable() { + static inline bool IsAvailable() + { return FModuleManager::Get().IsModuleLoaded("SaveExtensionEditor"); } }; diff --git a/Source/Editor/SaveExtensionEditor.Build.cs b/Source/Editor/SaveExtensionEditor.Build.cs index 9cd7e34..f8354f8 100644 --- a/Source/Editor/SaveExtensionEditor.Build.cs +++ b/Source/Editor/SaveExtensionEditor.Build.cs @@ -5,12 +5,14 @@ namespace UnrealBuildTool.Rules { - public class SaveExtensionEditor : ModuleRules { - public SaveExtensionEditor(ReadOnlyTargetRules Target) : base(Target) { + public class SaveExtensionEditor : ModuleRules + { + public SaveExtensionEditor(ReadOnlyTargetRules Target) : base(Target) + { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bEnforceIWYU = true; + IWYUSupport = IWYUSupport.Full; - PublicDependencyModuleNames.AddRange( new string[] + PublicDependencyModuleNames.AddRange(new string[] { "Core", "Engine", @@ -19,7 +21,7 @@ public SaveExtensionEditor(ReadOnlyTargetRules Target) : base(Target) { "SaveExtension" }); - PrivateDependencyModuleNames.AddRange( new string[] + PrivateDependencyModuleNames.AddRange(new string[] { "AssetTools", "Projects", @@ -30,7 +32,8 @@ public SaveExtensionEditor(ReadOnlyTargetRules Target) : base(Target) { "EditorStyle", "ClassViewer", "BlueprintGraph", - "GraphEditor" + "GraphEditor", + "PropertyEditor" }); } } diff --git a/Source/SaveExtension/Private/LatentActions/DeleteSlotsAction.cpp b/Source/SaveExtension/Private/LatentActions/DeleteSlotsAction.cpp deleted file mode 100644 index 0b984f3..0000000 --- a/Source/SaveExtension/Private/LatentActions/DeleteSlotsAction.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "LatentActions/DeleteSlotsAction.h" - -#include "SaveManager.h" -#include "SlotInfo.h" - - -FDeleteSlotsAction::FDeleteSlotsAction(USaveManager* Manager, EDeleteSlotsResult& OutResult, const FLatentActionInfo& LatentInfo) - : Result(OutResult) - , bFinished(false) - , ExecutionFunction(LatentInfo.ExecutionFunction) - , OutputLink(LatentInfo.Linkage) - , CallbackTarget(LatentInfo.CallbackTarget) -{ - Manager->DeleteAllSlots(FOnSlotsDeleted::CreateLambda([this]() { - bFinished = true; - })); -} - -void FDeleteSlotsAction::UpdateOperation(FLatentResponse& Response) -{ - Response.FinishAndTriggerIf(bFinished, ExecutionFunction, OutputLink, CallbackTarget); -} diff --git a/Source/SaveExtension/Private/LatentActions/LoadGameAction.cpp b/Source/SaveExtension/Private/LatentActions/LoadGameAction.cpp deleted file mode 100644 index 4ecd87d..0000000 --- a/Source/SaveExtension/Private/LatentActions/LoadGameAction.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "LatentActions/LoadGameAction.h" -#include "SaveManager.h" -#include "Serialization/SlotDataTask_Loader.h" -#include "SlotInfo.h" - - -FLoadGameAction::FLoadGameAction(USaveManager* Manager, FName SlotName, ELoadGameResult& OutResult, const FLatentActionInfo& LatentInfo) - : Result(OutResult) - , ExecutionFunction(LatentInfo.ExecutionFunction) - , OutputLink(LatentInfo.Linkage) - , CallbackTarget(LatentInfo.CallbackTarget) -{ - const bool bStarted = Manager->LoadSlot(SlotName, FOnGameLoaded::CreateRaw(this, &FLoadGameAction::OnLoadFinished)); - if (!bStarted) - { - Result = ELoadGameResult::Failed; - } -} - -void FLoadGameAction::UpdateOperation(FLatentResponse& Response) -{ - Response.FinishAndTriggerIf(Result != ELoadGameResult::Loading, ExecutionFunction, OutputLink, CallbackTarget); -} - -void FLoadGameAction::OnLoadFinished(USlotInfo* SavedSlot) -{ - Result = SavedSlot ? ELoadGameResult::Continue : ELoadGameResult::Failed; -} diff --git a/Source/SaveExtension/Private/LatentActions/LoadInfosAction.cpp b/Source/SaveExtension/Private/LatentActions/LoadInfosAction.cpp deleted file mode 100644 index 645d5d6..0000000 --- a/Source/SaveExtension/Private/LatentActions/LoadInfosAction.cpp +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "LatentActions/LoadInfosAction.h" -#include "SaveManager.h" -#include "SlotInfo.h" - - -FLoadInfosAction::FLoadInfosAction(USaveManager* Manager, const bool bSortByRecent, TArray& InSlotInfos, ELoadInfoResult& OutResult, const FLatentActionInfo& LatentInfo) - : Result(OutResult) - , SlotInfos(InSlotInfos) - , bFinished(false) - , ExecutionFunction(LatentInfo.ExecutionFunction) - , OutputLink(LatentInfo.Linkage) - , CallbackTarget(LatentInfo.CallbackTarget) -{ - Manager->LoadAllSlotInfos(bSortByRecent, FOnSlotInfosLoaded::CreateLambda([this](const TArray& Results) { - SlotInfos = Results; - bFinished = true; - })); -} - -void FLoadInfosAction::UpdateOperation(FLatentResponse& Response) -{ - Response.FinishAndTriggerIf(bFinished, ExecutionFunction, OutputLink, CallbackTarget); - -} diff --git a/Source/SaveExtension/Private/LatentActions/SaveGameAction.cpp b/Source/SaveExtension/Private/LatentActions/SaveGameAction.cpp deleted file mode 100644 index 0220c60..0000000 --- a/Source/SaveExtension/Private/LatentActions/SaveGameAction.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "LatentActions/SaveGameAction.h" -#include "SaveManager.h" -#include "SlotInfo.h" - - -FSaveGameAction::FSaveGameAction(USaveManager* Manager, FName SlotName, bool bOverrideIfNeeded, bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& OutResult, const FLatentActionInfo& LatentInfo) - : Result(OutResult) - , ExecutionFunction(LatentInfo.ExecutionFunction) - , OutputLink(LatentInfo.Linkage) - , CallbackTarget(LatentInfo.CallbackTarget) -{ - const bool bStarted = Manager->SaveSlot(SlotName, bOverrideIfNeeded, bScreenshot, Size, FOnGameSaved::CreateRaw(this, &FSaveGameAction::OnSaveFinished)); - - if (!bStarted) - { - Result = ESaveGameResult::Failed; - } -} - -void FSaveGameAction::UpdateOperation(FLatentResponse& Response) -{ - Response.FinishAndTriggerIf(Result != ESaveGameResult::Saving, ExecutionFunction, OutputLink, CallbackTarget); -} - -void FSaveGameAction::OnSaveFinished(USlotInfo* SavedSlot) -{ - Result = SavedSlot ? ESaveGameResult::Continue : ESaveGameResult::Failed; -} diff --git a/Source/SaveExtension/Private/LevelFilter.cpp b/Source/SaveExtension/Private/LevelFilter.cpp index c665d00..53c5f85 100644 --- a/Source/SaveExtension/Private/LevelFilter.cpp +++ b/Source/SaveExtension/Private/LevelFilter.cpp @@ -1,12 +1,80 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "LevelFilter.h" +#include "SaveSlot.h" + ///////////////////////////////////////////////////// // USaveDataTask -const FName FSELevelFilter::TagNoTransform { "!SaveTransform" }; -const FName FSELevelFilter::TagNoPhysics { "!SavePhysics" }; -const FName FSELevelFilter::TagNoTags { "!SaveTags" }; -const FName FSELevelFilter::TagTransform { "SaveTransform" }; +const FName FSELevelFilter::TagNoTransform{"!SaveTransform"}; +const FName FSELevelFilter::TagNoPhysics{"!SavePhysics"}; +const FName FSELevelFilter::TagNoTags{"!SaveTags"}; +const FName FSELevelFilter::TagTransform{"SaveTransform"}; + + +void FSELevelFilter::BakeAllowedClasses() const +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSELevelFilter::BakeAllowedClasses); + ActorFilter.BakeAllowedClasses(); + ComponentFilter.BakeAllowedClasses(); +} + +bool FSELevelFilter::Stores(const AActor* Actor) const +{ + return ActorFilter.IsAllowed(Actor->GetClass()); +} + +bool FSELevelFilter::StoresAnyComponents() const +{ + return ComponentFilter.IsAnyAllowed(); +} +bool FSELevelFilter::Stores(const UActorComponent* Component) const +{ + return ComponentFilter.IsAllowed(Component->GetClass()); +} + +bool FSELevelFilter::StoresTransform(const UActorComponent* Component) +{ + return Component->GetClass()->IsChildOf() && HasTag(Component, TagTransform); +} + +bool FSELevelFilter::StoresTags(const UActorComponent* Component) +{ + return !HasTag(Component, TagNoTags); +} + +bool FSELevelFilter::IsSaveTag(const FName& Tag) +{ + return Tag == TagNoTransform || Tag == TagNoPhysics || Tag == TagNoTags; +} + +bool FSELevelFilter::StoresTransform(const AActor* Actor) +{ + return Actor->IsRootComponentMovable() && !HasTag(Actor, TagNoTransform); +} +bool FSELevelFilter::StoresPhysics(const AActor* Actor) +{ + return !HasTag(Actor, TagNoPhysics); +} +bool FSELevelFilter::StoresTags(const AActor* Actor) +{ + return !HasTag(Actor, TagNoTags); +} +bool FSELevelFilter::IsProcedural(const AActor* Actor) +{ + return Actor->HasAnyFlags(RF_WasLoaded | RF_LoadCompleted); +} + +bool FSELevelFilter::HasTag(const AActor* Actor, const FName Tag) +{ + check(Actor); + return Actor->ActorHasTag(Tag); +} + +bool FSELevelFilter::HasTag(const UActorComponent* Component, const FName Tag) +{ + check(Component); + return Component->ComponentHasTag(Tag); +} diff --git a/Source/SaveExtension/Private/LevelStreamingNotifier.cpp b/Source/SaveExtension/Private/LevelStreamingNotifier.cpp index bd8d261..9f14565 100644 --- a/Source/SaveExtension/Private/LevelStreamingNotifier.cpp +++ b/Source/SaveExtension/Private/LevelStreamingNotifier.cpp @@ -1,3 +1,3 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "LevelStreamingNotifier.h" diff --git a/Source/SaveExtension/Private/LifetimeComponent.cpp b/Source/SaveExtension/Private/LifetimeComponent.cpp index f4b0394..d2e235a 100644 --- a/Source/SaveExtension/Private/LifetimeComponent.cpp +++ b/Source/SaveExtension/Private/LifetimeComponent.cpp @@ -1,12 +1,12 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "LifetimeComponent.h" -#include "Serialization/MTTask.h" +#include "Multithreading/MTTask.h" -ULifetimeComponent::ULifetimeComponent() - : Super() -{} + + +ULifetimeComponent::ULifetimeComponent() : Super() {} void ULifetimeComponent::BeginPlay() { @@ -25,7 +25,8 @@ void ULifetimeComponent::BeginPlay() } else { - UE_LOG(LogSaveExtension, Error, TEXT("LifetimeComponent couldnt find a SaveManager. It will do nothing.")) + UE_LOG(LogSaveExtension, Error, + TEXT("LifetimeComponent couldnt find a SaveManager. It will do nothing.")) } } @@ -48,7 +49,7 @@ void ULifetimeComponent::EndPlay(EEndPlayReason::Type Reason) void ULifetimeComponent::OnSaveBegan(const FSELevelFilter& Filter) { - if (Filter.ShouldSave(GetOwner())) + if (Filter.Stores(GetOwner())) { Saved.Broadcast(); } @@ -56,7 +57,7 @@ void ULifetimeComponent::OnSaveBegan(const FSELevelFilter& Filter) void ULifetimeComponent::OnLoadFinished(const FSELevelFilter& Filter, bool bError) { - if (Filter.ShouldSave(GetOwner())) + if (Filter.Stores(GetOwner())) { Resume.Broadcast(); } diff --git a/Source/SaveExtension/Private/Misc/ClassFilter.cpp b/Source/SaveExtension/Private/Misc/ClassFilter.cpp index ce01946..618a64a 100644 --- a/Source/SaveExtension/Private/Misc/ClassFilter.cpp +++ b/Source/SaveExtension/Private/Misc/ClassFilter.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Misc/ClassFilter.h" @@ -6,10 +6,7 @@ #include -FSEClassFilter::FSEClassFilter(UClass* BaseClass) - : BaseClass{ BaseClass } - , IgnoredClasses {} -{} +FSEClassFilter::FSEClassFilter(UClass* BaseClass) : BaseClass{BaseClass}, IgnoredClasses{} {} void FSEClassFilter::Merge(const FSEClassFilter& Other) { @@ -32,19 +29,19 @@ void FSEClassFilter::BakeAllowedClasses() const TRACE_CPUPROFILER_EVENT_SCOPE(FSEClassFilter::BakeAllowedClasses); BakedAllowedClasses.Empty(); - if(AllowedClasses.Num() <= 0) + if (AllowedClasses.Num() <= 0) { return; } TArray ChildrenOfAllowedClasses; { - TRACE_CPUPROFILER_EVENT_SCOPE(First Pass: Potential classes); - for(auto& AllowedClass : AllowedClasses) + TRACE_CPUPROFILER_EVENT_SCOPE(First Pass : Potential classes); + for (auto& AllowedClass : AllowedClasses) { UClass* const AllowedClassPtr = AllowedClass.Get(); - if(!AllowedClassPtr) + if (!AllowedClassPtr) { continue; } @@ -54,7 +51,7 @@ void FSEClassFilter::BakeAllowedClasses() const } } { - TRACE_CPUPROFILER_EVENT_SCOPE(Second Pass: Bake Classes); + TRACE_CPUPROFILER_EVENT_SCOPE(Second Pass : Bake Classes); for (UClass* Class : ChildrenOfAllowedClasses) { // Iterate parent classes of a class @@ -129,5 +126,5 @@ bool FSEClassFilter::operator==(const FSEClassFilter& Other) const { // Do all classes match? return AllowedClasses.Difference(Other.AllowedClasses).Num() <= 0 && - IgnoredClasses.Difference(Other.IgnoredClasses).Num() <= 0; + IgnoredClasses.Difference(Other.IgnoredClasses).Num() <= 0; } diff --git a/Source/SaveExtension/Private/Misc/SlotHelpers.cpp b/Source/SaveExtension/Private/Misc/SlotHelpers.cpp index 64c7c6a..4b55b98 100644 --- a/Source/SaveExtension/Private/Misc/SlotHelpers.cpp +++ b/Source/SaveExtension/Private/Misc/SlotHelpers.cpp @@ -1,14 +1,16 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "Misc/SlotHelpers.h" -#include -#include #include +#include void FSlotHelpers::FindSlotFileNames(TArray& FoundSlots) { - FFindSlotVisitor Visitor{ FoundSlots }; - FPlatformFileManager::Get().GetPlatformFile().IterateDirectory(*FFileAdapter::GetSaveFolder(), Visitor); + FFindSlotVisitor Visitor{FoundSlots}; + FPlatformFileManager::Get().GetPlatformFile().IterateDirectory( + *FSaveFileHelpers::GetSaveFolder(), Visitor); } bool FSlotHelpers::FFindSlotVisitor::Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) @@ -30,4 +32,3 @@ bool FSlotHelpers::FFindSlotVisitor::Visit(const TCHAR* FilenameOrDirectory, boo } return true; } - diff --git a/Source/SaveExtension/Private/Multithreading/DeleteSlotsTask.cpp b/Source/SaveExtension/Private/Multithreading/DeleteSlotsTask.cpp index 5e36387..41b2ade 100644 --- a/Source/SaveExtension/Private/Multithreading/DeleteSlotsTask.cpp +++ b/Source/SaveExtension/Private/Multithreading/DeleteSlotsTask.cpp @@ -1,21 +1,19 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Multithreading/DeleteSlotsTask.h" -#include - -#include "FileAdapter.h" -#include "SavePreset.h" -#include "SaveManager.h" -#include "Misc/SlotHelpers.h" #include "HAL/FileManager.h" +#include "Misc/SlotHelpers.h" +#include "SaveFileHelpers.h" +#include "SaveManager.h" + +#include -FDeleteSlotsTask::FDeleteSlotsTask(const USaveManager* InManager, FName SlotName) - : Manager(InManager) +FDeleteSlotsTask::FDeleteSlotsTask(const USaveManager* InManager, FName SlotName) : Manager(InManager) { check(Manager); - if(!SlotName.IsNone()) + if (!SlotName.IsNone()) { SpecificSlotName = SlotName.ToString(); } @@ -26,8 +24,8 @@ void FDeleteSlotsTask::DoWork() if (!SpecificSlotName.IsEmpty()) { // Delete a single slot by id - const FString ScreenshotPath = FFileAdapter::GetThumbnailPath(SpecificSlotName); - bool bIsDeleteSlotSuccess = FFileAdapter::DeleteFile(SpecificSlotName); + const FString ScreenshotPath = FSaveFileHelpers::GetThumbnailPath(SpecificSlotName); + bool bIsDeleteSlotSuccess = FSaveFileHelpers::DeleteFile(SpecificSlotName); bool bIsDeleteScreenshotSuccess = IFileManager::Get().Delete(*ScreenshotPath, true); bSuccess = bIsDeleteSlotSuccess || bIsDeleteScreenshotSuccess; } @@ -38,7 +36,7 @@ void FDeleteSlotsTask::DoWork() for (const FString& File : FoundSlots) { - FFileAdapter::DeleteFile(File); + FSaveFileHelpers::DeleteFile(File); } bSuccess = true; } diff --git a/Source/SaveExtension/Private/Multithreading/LoadFileTask.cpp b/Source/SaveExtension/Private/Multithreading/LoadFileTask.cpp index b8d947e..f9a4347 100644 --- a/Source/SaveExtension/Private/Multithreading/LoadFileTask.cpp +++ b/Source/SaveExtension/Private/Multithreading/LoadFileTask.cpp @@ -1,9 +1,48 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Multithreading/LoadFileTask.h" -#include "SavePreset.h" - ///////////////////////////////////////////////////// -// FSaveFileTask +// FLoadFileTask + +FLoadFileTask::FLoadFileTask(USaveManager* Manager, USaveSlot* InLastSlot, FStringView SlotName) + : Manager(Manager), SlotName(SlotName), LastSlot(InLastSlot) +{ + if (LastSlot.IsValid()) // If an slot was provided, that could be reused, mark it async + { + LastSlot->SetInternalFlags(EInternalObjectFlags::Async); + LastSlotData = LastSlot->GetData(); + if (LastSlotData.IsValid()) + { + LastSlotData->SetInternalFlags(EInternalObjectFlags::Async); + } + } +} + +FLoadFileTask::~FLoadFileTask() +{ + if (Slot.IsValid()) + { + Slot->ClearInternalFlags(EInternalObjectFlags::Async); + if (USaveSlotData* SlotData = Slot->GetData()) + { + SlotData->ClearInternalFlags(EInternalObjectFlags::Async); + } + } + if (LastSlot.IsValid()) + { + LastSlot->ClearInternalFlags(EInternalObjectFlags::Async); + } + if (LastSlotData.IsValid()) + { + LastSlotData->ClearInternalFlags(EInternalObjectFlags::Async); + } +} + +void FLoadFileTask::DoWork() +{ + USaveSlot* NewSlot = LastSlot.Get(); + FSaveFileHelpers::LoadFile(SlotName, NewSlot, true, Manager.Get()); + Slot = NewSlot; +} diff --git a/Source/SaveExtension/Private/Multithreading/LoadSlotInfosTask.cpp b/Source/SaveExtension/Private/Multithreading/LoadSlotsTask.cpp similarity index 59% rename from Source/SaveExtension/Private/Multithreading/LoadSlotInfosTask.cpp rename to Source/SaveExtension/Private/Multithreading/LoadSlotsTask.cpp index 79c95d1..0b4802b 100644 --- a/Source/SaveExtension/Private/Multithreading/LoadSlotInfosTask.cpp +++ b/Source/SaveExtension/Private/Multithreading/LoadSlotsTask.cpp @@ -1,16 +1,15 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. -#include "Multithreading/LoadSlotInfosTask.h" +#include "Multithreading/LoadSlotsTask.h" -#include - -#include "FileAdapter.h" -#include "SavePreset.h" -#include "SaveManager.h" #include "Misc/SlotHelpers.h" +#include "SaveFileHelpers.h" +#include "SaveManager.h" + +#include -void FLoadSlotInfosTask::DoWork() +void FLoadSlotsTask::DoWork() { if (!Manager) { @@ -19,7 +18,7 @@ void FLoadSlotInfosTask::DoWork() TArray FileNames; const bool bLoadingSingleInfo = !SlotName.IsNone(); - if(bLoadingSingleInfo) + if (bLoadingSingleInfo) { FileNames.Add(SlotName.ToString()); } @@ -33,8 +32,8 @@ void FLoadSlotInfosTask::DoWork() for (const FString& FileName : FileNames) { // Load all files - FScopedFileReader Reader(FFileAdapter::GetSlotPath(FileName)); - if(Reader.IsValid()) + FScopedFileReader Reader(FSaveFileHelpers::GetSlotPath(FileName)); + if (Reader.IsValid()) { auto& File = LoadedFiles.AddDefaulted_GetRef(); File.Read(Reader, true); @@ -45,20 +44,22 @@ void FLoadSlotInfosTask::DoWork() LoadedSlots.Reserve(LoadedFiles.Num()); for (const auto& File : LoadedFiles) { - LoadedSlots.Add(File.CreateAndDeserializeInfo(Manager)); + LoadedSlots.Add(Cast( + FSaveFileHelpers::DeserializeObject(nullptr, File.InfoClassName, Manager, File.InfoBytes) + )); } if (!bLoadingSingleInfo && bSortByRecent) { - LoadedSlots.Sort([](const USlotInfo& A, const USlotInfo& B) { - return A.SaveDate > B.SaveDate; + LoadedSlots.Sort([](const USaveSlot& A, const USaveSlot& B) { + return A.Stats.SaveDate > B.Stats.SaveDate; }); } } -void FLoadSlotInfosTask::AfterFinish() +void FLoadSlotsTask::AfterFinish() { - for(auto& Slot : LoadedSlots) + for (auto& Slot : LoadedSlots) { Slot->ClearInternalFlags(EInternalObjectFlags::Async); } diff --git a/Source/SaveExtension/Private/Serialization/MTTask_SerializeActors.cpp b/Source/SaveExtension/Private/Multithreading/MTTask_SerializeActors.cpp similarity index 72% rename from Source/SaveExtension/Private/Serialization/MTTask_SerializeActors.cpp rename to Source/SaveExtension/Private/Multithreading/MTTask_SerializeActors.cpp index e2e8836..8fc945a 100644 --- a/Source/SaveExtension/Private/Serialization/MTTask_SerializeActors.cpp +++ b/Source/SaveExtension/Private/Multithreading/MTTask_SerializeActors.cpp @@ -1,15 +1,15 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. -#include "Serialization/MTTask_SerializeActors.h" -#include -#include +#include "Multithreading/MTTask_SerializeActors.h" #include "SaveManager.h" -#include "SlotInfo.h" -#include "SlotData.h" -#include "SavePreset.h" +#include "SaveSlot.h" +#include "SaveSlotData.h" #include "Serialization/SEArchive.h" +#include +#include + ///////////////////////////////////////////////////// // FMTTask_SerializeActors @@ -21,15 +21,21 @@ void FMTTask_SerializeActors::DoWork() SerializeGameInstance(); } + TArray ActorsToSerialize; for (int32 I = 0; I < Num; ++I) { const AActor* const Actor = (*LevelActors)[StartIndex + I]; - if (Actor && Filter.ShouldSave(Actor)) + if (Actor && Filter->Stores(Actor)) { - FActorRecord& Record = ActorRecords.AddDefaulted_GetRef(); - SerializeActor(Actor, Record); + ActorsToSerialize.Add(Actor); } } + + for (const AActor* Actor : ActorsToSerialize) + { + FActorRecord& Record = ActorRecords.AddDefaulted_GetRef(); + SerializeActor(Actor, Record); + } } void FMTTask_SerializeActors::SerializeGameInstance() @@ -37,9 +43,9 @@ void FMTTask_SerializeActors::SerializeGameInstance() TRACE_CPUPROFILER_EVENT_SCOPE(FMTTask_SerializeActors::SerializeGameInstance); if (UGameInstance* GameInstance = World->GetGameInstance()) { - FObjectRecord Record{ GameInstance }; + FObjectRecord Record{GameInstance}; - //Serialize into Record Data + // Serialize into Record Data FMemoryWriter MemoryWriter(Record.Data, true); FSEArchive Archive(MemoryWriter, false); GameInstance->Serialize(Archive); @@ -52,13 +58,13 @@ bool FMTTask_SerializeActors::SerializeActor(const AActor* Actor, FActorRecord& { TRACE_CPUPROFILER_EVENT_SCOPE(FMTTask_SerializeActors::SerializeActor); - //Clean the record - Record = { Actor }; + // Clean the record + Record = {Actor}; Record.bHiddenInGame = Actor->IsHidden(); - Record.bIsProcedural = Filter.IsProcedural(Actor); + Record.bIsProcedural = Filter->IsProcedural(Actor); - if (Filter.StoresTags(Actor)) + if (Filter->StoresTags(Actor)) { Record.Tags = Actor->Tags; } @@ -67,18 +73,18 @@ bool FMTTask_SerializeActors::SerializeActor(const AActor* Actor, FActorRecord& // Only save save-tags for (const auto& Tag : Actor->Tags) { - if (Filter.IsSaveTag(Tag)) + if (Filter->IsSaveTag(Tag)) { Record.Tags.Add(Tag); } } } - if (Filter.StoresTransform(Actor)) + if (Filter->StoresTransform(Actor)) { Record.Transform = Actor->GetTransform(); - if (Filter.StoresPhysics(Actor)) + if (Filter->StoresPhysics(Actor)) { USceneComponent* const Root = Actor->GetRootComponent(); if (Root && Root->Mobility == EComponentMobility::Movable) @@ -96,10 +102,7 @@ bool FMTTask_SerializeActors::SerializeActor(const AActor* Actor, FActorRecord& } } - if (Filter.bStoreComponents) - { - SerializeActorComponents(Actor, Record, 1); - } + SerializeActorComponents(Actor, Record, 1); TRACE_CPUPROFILER_EVENT_SCOPE(Serialize); FMemoryWriter MemoryWriter(Record.Data, true); @@ -109,21 +112,27 @@ bool FMTTask_SerializeActors::SerializeActor(const AActor* Actor, FActorRecord& return true; } -void FMTTask_SerializeActors::SerializeActorComponents(const AActor* Actor, FActorRecord& ActorRecord, int8 Indent /*= 0*/) const +void FMTTask_SerializeActors::SerializeActorComponents( + const AActor* Actor, FActorRecord& ActorRecord, int8 Indent /*= 0*/) const { TRACE_CPUPROFILER_EVENT_SCOPE(FMTTask_SerializeActors::SerializeActorComponents); + if (!Filter->StoresAnyComponents()) + { + return; + } + const TSet& Components = Actor->GetComponents(); for (auto* Component : Components) { - TRACE_CPUPROFILER_EVENT_SCOPE(FMTTask_SerializeActors::SerializeActorComponents|Component); - if (Filter.ShouldSave(Component)) + TRACE_CPUPROFILER_EVENT_SCOPE(FMTTask_SerializeActors::SerializeActorComponents | Component); + if (IsValid(Component) && Filter->Stores(Component)) { FComponentRecord ComponentRecord; ComponentRecord.Name = Component->GetFName(); ComponentRecord.Class = Component->GetClass(); - if (Filter.StoresTransform(Component)) + if (Filter->StoresTransform(Component)) { const USceneComponent* Scene = CastChecked(Component); if (Scene->Mobility == EComponentMobility::Movable) @@ -132,7 +141,7 @@ void FMTTask_SerializeActors::SerializeActorComponents(const AActor* Actor, FAct } } - if (Filter.StoresTags(Component)) + if (Filter->StoresTags(Component)) { ComponentRecord.Tags = Component->ComponentTags; } diff --git a/Source/SaveExtension/Private/Multithreading/SaveFileTask.cpp b/Source/SaveExtension/Private/Multithreading/SaveFileTask.cpp deleted file mode 100644 index d9a3695..0000000 --- a/Source/SaveExtension/Private/Multithreading/SaveFileTask.cpp +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Multithreading/SaveFileTask.h" - -#include "SavePreset.h" - - -///////////////////////////////////////////////////// -// FSaveFileTask diff --git a/Source/SaveExtension/Private/Multithreading/ScopedTaskManager.cpp b/Source/SaveExtension/Private/Multithreading/ScopedTaskManager.cpp deleted file mode 100644 index a1b5d43..0000000 --- a/Source/SaveExtension/Private/Multithreading/ScopedTaskManager.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Multithreading/ScopedTaskManager.h" diff --git a/Source/SaveExtension/Private/SaveExtension.cpp b/Source/SaveExtension/Private/SaveExtension.cpp index 385ce26..7786a64 100644 --- a/Source/SaveExtension/Private/SaveExtension.cpp +++ b/Source/SaveExtension/Private/SaveExtension.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SaveExtension.h" diff --git a/Source/SaveExtension/Private/SaveExtension.h b/Source/SaveExtension/Private/SaveExtension.h index faac276..6b88367 100644 --- a/Source/SaveExtension/Private/SaveExtension.h +++ b/Source/SaveExtension/Private/SaveExtension.h @@ -1,22 +1,25 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "ISaveExtension.h" #if WITH_EDITORONLY_DATA - #include "ISettingsModule.h" - #include "ISettingsSection.h" - #include "ISettingsContainer.h" +# include "ISettingsContainer.h" +# include "ISettingsModule.h" +# include "ISettingsSection.h" + #endif class FSaveExtension : public ISaveExtension { public: - virtual void StartupModule() override {} virtual void ShutdownModule() override {} - virtual bool SupportsDynamicReloading() override { return true; } + virtual bool SupportsDynamicReloading() override + { + return true; + } }; diff --git a/Source/SaveExtension/Private/SaveExtensionInterface.cpp b/Source/SaveExtension/Private/SaveExtensionInterface.cpp index a1c7425..c562b67 100644 --- a/Source/SaveExtension/Private/SaveExtensionInterface.cpp +++ b/Source/SaveExtension/Private/SaveExtensionInterface.cpp @@ -1,7 +1,7 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SaveExtensionInterface.h" -USaveExtensionInterface::USaveExtensionInterface(const FObjectInitializer &ObjectInitializer) +USaveExtensionInterface::USaveExtensionInterface(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) {} diff --git a/Source/SaveExtension/Private/SaveExtensionLibrary.cpp b/Source/SaveExtension/Private/SaveExtensionLibrary.cpp index d07c6f2..b4cd797 100644 --- a/Source/SaveExtension/Private/SaveExtensionLibrary.cpp +++ b/Source/SaveExtension/Private/SaveExtensionLibrary.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SaveExtensionLibrary.h" @@ -6,13 +6,14 @@ #include "HighResScreenshot.h" #include "IImageWrapper.h" #include "IImageWrapperModule.h" -#include "Kismet/KismetSystemLibrary.h" #include "Kismet/GameplayStatics.h" - +#include "Kismet/KismetSystemLibrary.h" #include "SaveManager.h" -/*FSaveTimerHandle USaveExtensionLibrary::SetSaveTimerDelegate(FTimerDynamicDelegate Delegate, float Time, bool bLooping) + +/*FSaveTimerHandle USaveExtensionLibrary::SetSaveTimerDelegate(FTimerDynamicDelegate Delegate, float Time, +bool bLooping) { FTimerHandle Handle { UKismetSystemLibrary::K2_SetTimerDelegate(Delegate, Time, bLooping) }; return FSaveTimerHandle(Handle, Delegate, Time, bLooping); diff --git a/Source/SaveExtension/Private/SaveExtensionLibrary.h b/Source/SaveExtension/Private/SaveExtensionLibrary.h index 2ea550e..bc1eb9b 100644 --- a/Source/SaveExtension/Private/SaveExtensionLibrary.h +++ b/Source/SaveExtension/Private/SaveExtensionLibrary.h @@ -1,11 +1,11 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "Runtime/Engine/Classes/Kismet/BlueprintFunctionLibrary.h" +#include "SaveSlotData.h" -#include "SlotData.h" -//#include "SaveTimerHandle.h" +// #include "SaveTimerHandle.h" #include "SaveExtensionLibrary.generated.h" @@ -16,7 +16,7 @@ class SAVEEXTENSION_API USaveExtensionLibrary : public UBlueprintFunctionLibrary GENERATED_BODY() public: - - //UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Saveable Timer by Event"), Category = "Utilities|Time") - //static FSaveTimerHandle SetSaveTimerDelegate(FTimerDynamicDelegate Delegate, float Time, bool bLooping); + // UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Saveable Timer by Event"), Category = + // "Utilities|Time") static FSaveTimerHandle SetSaveTimerDelegate(FTimerDynamicDelegate Delegate, float + // Time, bool bLooping); }; diff --git a/Source/SaveExtension/Private/FileAdapter.cpp b/Source/SaveExtension/Private/SaveFileHelpers.cpp similarity index 66% rename from Source/SaveExtension/Private/FileAdapter.cpp rename to Source/SaveExtension/Private/SaveFileHelpers.cpp index e4290b9..df0ae3e 100644 --- a/Source/SaveExtension/Private/FileAdapter.cpp +++ b/Source/SaveExtension/Private/SaveFileHelpers.cpp @@ -1,22 +1,22 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. -#include "FileAdapter.h" +#include "SaveFileHelpers.h" -#include -#include +#include "Multithreading/SaveFileTask.h" +#include "Serialization/SEArchive.h" +#include "SaveSlot.h" +#include "SaveSlotData.h" + +#include +#include +#include #include #include -#include -#include -#include - -#include "SavePreset.h" -#include "SlotInfo.h" -#include "SlotData.h" -#include "Multithreading/SaveFileTask.h" +#include +#include -static const int SE_SAVEGAME_FILE_TYPE_TAG = 0x0001; // "sAvG" +static const int SE_SAVEGAME_FILE_TYPE_TAG = 0x0001; // "sAvG" struct FSaveGameFileVersion { @@ -83,7 +83,7 @@ void FSaveFile::Read(FScopedFileReader& Reader, bool bSkipData) Empty(); FArchive& Ar = Reader.GetArchive(); - { // Header information + { // Header information Ar << FileTypeTag; if (FileTypeTag != SE_SAVEGAME_FILE_TYPE_TAG) { @@ -104,7 +104,8 @@ void FSaveFile::Read(FScopedFileReader& Reader, bool bSkipData) if (SaveGameFileVersion >= FSaveGameFileVersion::AddedCustomVersions) { Ar << CustomVersionFormat; - CustomVersions.Serialize(Ar, static_cast(CustomVersionFormat)); + CustomVersions.Serialize( + Ar, static_cast(CustomVersionFormat)); Ar.SetCustomVersions(CustomVersions); } } @@ -113,13 +114,13 @@ void FSaveFile::Read(FScopedFileReader& Reader, bool bSkipData) Ar << InfoBytes; Ar << DataClassName; - if(bSkipData || DataClassName.IsEmpty()) + if (bSkipData || DataClassName.IsEmpty()) { return; } Ar << bIsDataCompressed; - if(bIsDataCompressed) + if (bIsDataCompressed) { TArray CompressedDataBytes; Ar << CompressedDataBytes; @@ -149,26 +150,27 @@ void FSaveFile::Write(FScopedFileWriter& Writer, bool bCompressData) bIsDataCompressed = bCompressData; FArchive& Ar = Writer.GetArchive(); - { // Header information + { // Header information Ar << FileTypeTag; Ar << SaveGameFileVersion; Ar << PackageFileUEVersion; Ar << SavedEngineVersion; Ar << CustomVersionFormat; - CustomVersions.Serialize(Ar, static_cast(CustomVersionFormat)); + CustomVersions.Serialize( + Ar, static_cast(CustomVersionFormat)); } Ar << InfoClassName; Ar << InfoBytes; Ar << DataClassName; - if(!DataClassName.IsEmpty()) + if (!DataClassName.IsEmpty()) { Ar << bIsDataCompressed; - if(bIsDataCompressed) + if (bIsDataCompressed) { TArray CompressedDataBytes; - { // Compression + { // Compression TRACE_CPUPROFILER_EVENT_SCOPE(Compression); // Compress Object data FArchiveSaveCompressedProxy Compressor(CompressedDataBytes, NAME_Zlib); @@ -185,18 +187,18 @@ void FSaveFile::Write(FScopedFileWriter& Writer, bool bCompressData) Ar.Close(); } -void FSaveFile::SerializeInfo(USlotInfo* SlotInfo) +void FSaveFile::SerializeInfo(USaveSlot* Slot) { TRACE_CPUPROFILER_EVENT_SCOPE(FSaveFile::SerializeInfo); - check(SlotInfo); + check(Slot); InfoBytes.Reset(); - InfoClassName = SlotInfo->GetClass()->GetPathName(); + InfoClassName = Slot->GetClass()->GetPathName(); FMemoryWriter BytesWriter(InfoBytes); FObjectAndNameAsStringProxyArchive Ar(BytesWriter, false); - SlotInfo->Serialize(Ar); + Slot->Serialize(Ar); } -void FSaveFile::SerializeData(USlotData* SlotData) +void FSaveFile::SerializeData(USaveSlotData* SlotData) { TRACE_CPUPROFILER_EVENT_SCOPE(FSaveFile::SerializeData); check(SlotData); @@ -208,127 +210,118 @@ void FSaveFile::SerializeData(USlotData* SlotData) SlotData->Serialize(Ar); } -USlotInfo* FSaveFile::CreateAndDeserializeInfo(const UObject* Outer) const +bool FSaveFileHelpers::SaveFile(FStringView SlotName, USaveSlot* Slot, const bool bUseCompression) { - TRACE_CPUPROFILER_EVENT_SCOPE(FSaveFile::CreateAndDeserializeInfo); - UObject* Object = nullptr; - FFileAdapter::DeserializeObject(Object, InfoClassName, Outer, InfoBytes); - return Cast(Object); -} - -USlotData* FSaveFile::CreateAndDeserializeData(const UObject* Outer) const -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FSaveFile::CreateAndDeserializeData); - UObject* Object = nullptr; - FFileAdapter::DeserializeObject(Object, DataClassName, Outer, DataBytes); - return Cast(Object); -} - -bool FFileAdapter::SaveFile(FStringView SlotName, USlotInfo* Info, USlotData* Data, const bool bUseCompression) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(FFileAdapter::SaveFile); + TRACE_CPUPROFILER_EVENT_SCOPE(FSaveFileHelpers::SaveFile); if (SlotName.IsEmpty()) { return false; } - if (!ensureMsgf(Info, TEXT("Info object must be valid")) || - !ensureMsgf(Data, TEXT("Data object must be valid"))) + if (!ensureMsgf(Slot, TEXT("Slot object must be valid")) || + !ensureMsgf(Slot->GetData(), TEXT("Slot Data object must be valid"))) { return false; } FScopedFileWriter FileWriter(GetSlotPath(SlotName)); - if(FileWriter.IsValid()) + if (FileWriter.IsValid()) { FSaveFile File{}; - File.SerializeInfo(Info); - File.SerializeData(Data); + File.SerializeInfo(Slot); + File.SerializeData(Slot->GetData()); File.Write(FileWriter, bUseCompression); return !FileWriter.IsError(); } return false; } -bool FFileAdapter::LoadFile(FStringView SlotName, USlotInfo*& Info, USlotData*& Data, bool bLoadData, const UObject* Outer) +bool FSaveFileHelpers::LoadFile(FStringView SlotName, USaveSlot*& Slot, bool bLoadData, const UObject* Outer) { - TRACE_CPUPROFILER_EVENT_SCOPE(FFileAdapter::LoadFile); + TRACE_CPUPROFILER_EVENT_SCOPE(FSaveFileHelpers::LoadFile); FScopedFileReader Reader(GetSlotPath(SlotName)); - if(Reader.IsValid()) + if (Reader.IsValid()) { FSaveFile File{}; File.Read(Reader, !bLoadData); - Info = File.CreateAndDeserializeInfo(Outer); - Data = File.CreateAndDeserializeData(Outer); + + { + TRACE_CPUPROFILER_EVENT_SCOPE(DeserializeInfo) + Slot = Cast(DeserializeObject(Slot, File.InfoClassName, Outer, File.InfoBytes)); + } + if (bLoadData) + { + TRACE_CPUPROFILER_EVENT_SCOPE(DeserializeData) + Slot->AssignData(Cast( + DeserializeObject(Slot->GetData(), File.DataClassName, Slot, File.DataBytes)) + ); + } return true; } return false; } -bool FFileAdapter::DeleteFile(FStringView SlotName) +bool FSaveFileHelpers::DeleteFile(FStringView SlotName) { return IFileManager::Get().Delete(*GetSlotPath(SlotName), true, false, true); } -bool FFileAdapter::DoesFileExist(FStringView SlotName) +bool FSaveFileHelpers::FileExists(FStringView SlotName) { return IFileManager::Get().FileSize(*GetSlotPath(SlotName)) >= 0; } -const FString& FFileAdapter::GetSaveFolder() +const FString& FSaveFileHelpers::GetSaveFolder() { static const FString Folder = FString::Printf(TEXT("%sSaveGames/"), *FPaths::ProjectSavedDir()); return Folder; } -FString FFileAdapter::GetSlotPath(FStringView SlotName) +FString FSaveFileHelpers::GetSlotPath(FStringView SlotName) { return GetSaveFolder() / FString::Printf(TEXT("%s.sav"), SlotName.GetData()); } -FString FFileAdapter::GetThumbnailPath(FStringView SlotName) +FString FSaveFileHelpers::GetThumbnailPath(FStringView SlotName) { return GetSaveFolder() / FString::Printf(TEXT("%s.png"), SlotName.GetData()); } -void FFileAdapter::DeserializeObject(UObject*& Object, FStringView ClassName, const UObject* Outer, const TArray& Bytes) +UObject* FSaveFileHelpers::DeserializeObject(UObject* Hint, FStringView ClassName, const UObject* Outer, const TArray& Bytes) { + UObject* Object = Hint; + if (ClassName.IsEmpty() || Bytes.Num() <= 0) { - return; + return Object; } - UClass* ObjectClass = FindObject(ANY_PACKAGE, ClassName.GetData()); + UClass* ObjectClass = FindObject(nullptr, ClassName.GetData()); if (!ObjectClass) { ObjectClass = LoadObject(nullptr, ClassName.GetData()); } if (!ObjectClass) { - return; + return Object; } - if(!Object) + // Can only reuse object if class matches + if (!Object || Object->GetClass() != ObjectClass) { - if(!Outer) + if (!Outer) { Outer = GetTransientPackage(); } Object = NewObject(const_cast(Outer), ObjectClass); } - // Can only reuse object if class matches - else if(Object->GetClass() != ObjectClass) - { - return; - } - if(Object) - { - FMemoryReader Reader{ Bytes }; - FObjectAndNameAsStringProxyArchive Ar(Reader, true); - Object->Serialize(Ar); - } + check(Object); + FMemoryReader Reader{Bytes}; + FSEArchive Ar(Reader, true); + Object->Serialize(Ar); + return Object; } diff --git a/Source/SaveExtension/Private/SaveManager.cpp b/Source/SaveExtension/Private/SaveManager.cpp index a177840..e5160e0 100644 --- a/Source/SaveExtension/Private/SaveManager.cpp +++ b/Source/SaveExtension/Private/SaveManager.cpp @@ -1,28 +1,181 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SaveManager.h" -#include "FileAdapter.h" -#include "LatentActions/LoadInfosAction.h" #include "Multithreading/DeleteSlotsTask.h" -#include "Multithreading/LoadSlotInfosTask.h" +#include "Multithreading/LoadSlotsTask.h" +#include "SaveFileHelpers.h" #include "SaveSettings.h" -#include "Serialization/SlotDataTask_LevelLoader.h" -#include "Serialization/SlotDataTask_LevelSaver.h" -#include "Serialization/SlotDataTask_Loader.h" -#include "Serialization/SlotDataTask_Saver.h" +#include "Serialization/SEDataTask_LoadLevel.h" +#include "Serialization/SEDataTask_SaveLevel.h" +#include "Serialization/SEDataTask_Load.h" +#include "Serialization/SEDataTask_Save.h" #include +#include #include #include #include #include #include #include +#include #include #include +// BEGIN Async Actions + +class FSELoadSlotDataAction : public FPendingLatentAction +{ +public: + ESEContinueOrFail& Result; + FName ExecutionFunction; + int32 OutputLink; + FWeakObjectPtr CallbackTarget; + + FSELoadSlotDataAction(USaveManager* Manager, FName SlotName, ESEContinueOrFail& OutResult, + const FLatentActionInfo& LatentInfo) + : Result(OutResult) + , ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + { + const bool bStarted = Manager->LoadSlot( + SlotName, FOnGameLoaded::CreateRaw(this, &FSELoadSlotDataAction::OnLoadFinished)); + Result = bStarted ? ESEContinueOrFail::InProgress : ESEContinueOrFail::Failed; + } + void UpdateOperation(FLatentResponse& Response) override + { + Response.FinishAndTriggerIf( + Result != ESEContinueOrFail::InProgress, ExecutionFunction, OutputLink, CallbackTarget); + } + void OnLoadFinished(USaveSlot* SavedSlot) + { + Result = SavedSlot ? ESEContinueOrFail::Continue : ESEContinueOrFail::Failed; + } +#if WITH_EDITOR + // Returns a human readable description of the latent operation's current state + FString GetDescription() const override + { + return TEXT("Loading Game..."); + } +#endif +}; + + +class FDeleteSlotsAction : public FPendingLatentAction +{ +public: + ESEContinue& Result; + FName ExecutionFunction; + int32 OutputLink; + FWeakObjectPtr CallbackTarget; + + FDeleteSlotsAction(USaveManager* Manager, ESEContinue& OutResult, const FLatentActionInfo& LatentInfo) + : Result(OutResult) + , ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + { + Result = ESEContinue::InProgress; + Manager->DeleteAllSlots(FOnSlotsDeleted::CreateLambda([this]() { + Result = ESEContinue::Continue; + })); + } + void UpdateOperation(FLatentResponse& Response) override + { + Response.FinishAndTriggerIf( + Result != ESEContinue::InProgress, ExecutionFunction, OutputLink, CallbackTarget); + } +#if WITH_EDITOR + FString GetDescription() const override + { + return TEXT("Deleting all slots..."); + } +#endif +}; + + +class FSELoadInfosAction : public FPendingLatentAction +{ +public: + TArray& Slots; + ESEContinue& Result; + FName ExecutionFunction; + int32 OutputLink; + FWeakObjectPtr CallbackTarget; + + FSELoadInfosAction(USaveManager* Manager, const bool bSortByRecent, TArray& OutSlots, + ESEContinue& OutResult, const FLatentActionInfo& LatentInfo) + : Slots(OutSlots) + , Result(OutResult) + , ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + { + Result = ESEContinue::InProgress; + Manager->FindAllSlots( + bSortByRecent, FOnSlotsLoaded::CreateLambda([this](const TArray& Results) { + Slots = Results; + Result = ESEContinue::Continue; + })); + } + virtual void UpdateOperation(FLatentResponse& Response) override + { + Response.FinishAndTriggerIf( + Result != ESEContinue::InProgress, ExecutionFunction, OutputLink, CallbackTarget); + } +#if WITH_EDITOR + virtual FString GetDescription() const override + { + return TEXT("Loading all slots..."); + } +#endif +}; + + +class FSaveGameAction : public FPendingLatentAction +{ +public: + ESEContinueOrFail& Result; + FName ExecutionFunction; + int32 OutputLink; + FWeakObjectPtr CallbackTarget; + + FSaveGameAction(USaveManager* Manager, FName SlotName, bool bOverrideIfNeeded, bool bScreenshot, + const FScreenshotSize Size, ESEContinueOrFail& OutResult, const FLatentActionInfo& LatentInfo) + : Result(OutResult) + , ExecutionFunction(LatentInfo.ExecutionFunction) + , OutputLink(LatentInfo.Linkage) + , CallbackTarget(LatentInfo.CallbackTarget) + { + const bool bStarted = Manager->SaveSlot(SlotName, bOverrideIfNeeded, bScreenshot, Size, + FOnGameSaved::CreateRaw(this, &FSaveGameAction::OnSaveFinished)); + Result = bStarted ? ESEContinueOrFail::InProgress : ESEContinueOrFail::Failed; + } + + virtual void UpdateOperation(FLatentResponse& Response) override + { + Response.FinishAndTriggerIf( + Result != ESEContinueOrFail::InProgress, ExecutionFunction, OutputLink, CallbackTarget); + } + void OnSaveFinished(USaveSlot* SavedSlot) + { + Result = SavedSlot ? ESEContinueOrFail::Continue : ESEContinueOrFail::Failed; + } +#if WITH_EDITOR + // Returns a human readable description of the latent operation's current state + virtual FString GetDescription() const override + { + return TEXT("Saving Game..."); + } +#endif +}; + +// END Async Actions + + USaveManager::USaveManager() : Super(), MTTasks{} {} void USaveManager::Initialize(FSubsystemCollectionBase& Collection) @@ -34,15 +187,12 @@ void USaveManager::Initialize(FSubsystemCollectionBase& Collection) FCoreUObjectDelegates::PreLoadMap.AddUObject(this, &USaveManager::OnMapLoadStarted); FCoreUObjectDelegates::PostLoadMapWithWorld.AddUObject(this, &USaveManager::OnMapLoadFinished); - ActivePreset = GetDefault()->CreatePreset(this); - - // AutoLoad - if (GetPreset() && GetPreset()->bAutoLoad) + AssureActiveSlot(); + if (ActiveSlot && ActiveSlot->bLoadOnStart) { ReloadCurrentSlot(); } - TryInstantiateInfo(); UpdateLevelStreamings(); } @@ -52,7 +202,7 @@ void USaveManager::Deinitialize() MTTasks.CancelAll(); - if (GetPreset()->bSaveOnExit) + if (GetActiveSlot()->bSaveOnClose) SaveCurrentSlot(); FCoreUObjectDelegates::PreLoadMap.RemoveAll(this); @@ -60,32 +210,31 @@ void USaveManager::Deinitialize() FGameDelegates::Get().GetEndPlayMapDelegate().RemoveAll(this); } -bool USaveManager::SaveSlot( - FName SlotName, bool bOverrideIfNeeded, bool bScreenshot, const FScreenshotSize Size, FOnGameSaved OnSaved) +bool USaveManager::SaveSlot(FName SlotName, bool bOverrideIfNeeded, bool bScreenshot, + const FScreenshotSize Size, FOnGameSaved OnSaved) { if (!CanLoadOrSave()) return false; - const USavePreset* Preset = GetPreset(); if (SlotName.IsNone()) { - SELog(Preset, "Can't use an empty slot name to save.", true); + SELog(ActiveSlot, "Can't use an empty slot name to save.", true); return false; } // Saving - SELog(Preset, "Saving to Slot " + SlotName.ToString()); + SELog(ActiveSlot, "Saving to Slot " + SlotName.ToString()); UWorld* World = GetWorld(); check(World); // Launch task, always fail if it didn't finish or wasn't scheduled - auto* Task = CreateTask() - ->Setup(SlotName, bOverrideIfNeeded, bScreenshot, Size.Width, Size.Height) - ->Bind(OnSaved) - ->Start(); + auto& Task = CreateTask() + .Setup(SlotName, bOverrideIfNeeded, bScreenshot, Size.Width, Size.Height) + .Bind(OnSaved) + .Start(); - return Task->IsSucceeded() || Task->IsScheduled(); + return Task.IsSucceeded() || Task.IsScheduled(); } bool USaveManager::LoadSlot(FName SlotName, FOnGameLoaded OnLoaded) @@ -95,14 +244,10 @@ bool USaveManager::LoadSlot(FName SlotName, FOnGameLoaded OnLoaded) return false; } - TryInstantiateInfo(); + AssureActiveSlot(); - auto* Task = CreateTask() - ->Setup(SlotName) - ->Bind(OnLoaded) - ->Start(); - - return Task->IsSucceeded() || Task->IsScheduled(); + auto& Task = CreateTask().Setup(SlotName).Bind(OnLoaded).Start(); + return Task.IsSucceeded() || Task.IsScheduled(); } bool USaveManager::DeleteSlot(FName SlotName) @@ -122,18 +267,21 @@ bool USaveManager::DeleteSlot(FName SlotName) return bSuccess; } -void USaveManager::LoadAllSlotInfos(bool bSortByRecent, FOnSlotInfosLoaded Delegate) +void USaveManager::FindAllSlots(bool bSortByRecent, FOnSlotsLoaded Delegate) { - MTTasks.CreateTask(this, bSortByRecent, MoveTemp(Delegate)) + MTTasks.CreateTask(this, bSortByRecent, MoveTemp(Delegate)) .OnFinished([](auto& Task) { Task->AfterFinish(); }) .StartBackgroundTask(); } -void USaveManager::LoadAllSlotInfosSync(bool bSortByRecent, FOnSlotInfosLoaded Delegate) +void USaveManager::FindAllSlotsSync(bool bSortByRecent, TArray& Slots) { - MTTasks.CreateTask(this, bSortByRecent, MoveTemp(Delegate)) + auto Delegate = FOnSlotsLoaded::CreateLambda([&Slots](const TArray& FoundSlots) { + Slots = FoundSlots; + }); + MTTasks.CreateTask(this, bSortByRecent, Delegate) .OnFinished([](auto& Task) { Task->AfterFinish(); }) @@ -150,60 +298,56 @@ void USaveManager::DeleteAllSlots(FOnSlotsDeleted Delegate) .StartBackgroundTask(); } -void USaveManager::BPSaveSlot(FName SlotName, bool bScreenshot, const FScreenshotSize Size, - ESaveGameResult& Result, struct FLatentActionInfo LatentInfo, bool bOverrideIfNeeded /*= true*/) +void USaveManager::BPSaveSlotByName(FName SlotName, bool bScreenshot, const FScreenshotSize Size, + ESEContinueOrFail& Result, struct FLatentActionInfo LatentInfo, bool bOverrideIfNeeded /*= true*/) { if (UWorld* World = GetWorld()) { - Result = ESaveGameResult::Saving; - FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); if (LatentActionManager.FindExistingAction( LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr) { LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, - new FSaveGameAction(this, SlotName, bOverrideIfNeeded, bScreenshot, Size, Result, LatentInfo)); + new FSaveGameAction( + this, SlotName, bOverrideIfNeeded, bScreenshot, Size, Result, LatentInfo)); } return; } - Result = ESaveGameResult::Failed; + Result = ESEContinueOrFail::Failed; } -void USaveManager::BPLoadSlot( - FName SlotName, ELoadGameResult& Result, struct FLatentActionInfo LatentInfo) +void USaveManager::BPLoadSlotByName(FName SlotName, ESEContinueOrFail& Result, struct FLatentActionInfo LatentInfo) { if (UWorld* World = GetWorld()) { - Result = ELoadGameResult::Loading; - FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); - if (LatentActionManager.FindExistingAction( + if (LatentActionManager.FindExistingAction( LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr) { LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, - new FLoadGameAction(this, SlotName, Result, LatentInfo)); + new FSELoadSlotDataAction(this, SlotName, Result, LatentInfo)); } return; } - Result = ELoadGameResult::Failed; + Result = ESEContinueOrFail::Failed; } -void USaveManager::BPLoadAllSlotInfos(const bool bSortByRecent, TArray& SaveInfos, - ELoadInfoResult& Result, struct FLatentActionInfo LatentInfo) +void USaveManager::BPFindAllSlots(const bool bSortByRecent, TArray& SaveInfos, + ESEContinue& Result, struct FLatentActionInfo LatentInfo) { if (UWorld* World = GetWorld()) { FLatentActionManager& LatentActionManager = World->GetLatentActionManager(); - if (LatentActionManager.FindExistingAction( + if (LatentActionManager.FindExistingAction( LatentInfo.CallbackTarget, LatentInfo.UUID) == nullptr) { LatentActionManager.AddNewAction(LatentInfo.CallbackTarget, LatentInfo.UUID, - new FLoadInfosAction(this, bSortByRecent, SaveInfos, Result, LatentInfo)); + new FSELoadInfosAction(this, bSortByRecent, SaveInfos, Result, LatentInfo)); } } } -void USaveManager::BPDeleteAllSlots(EDeleteSlotsResult& Result, struct FLatentActionInfo LatentInfo) +void USaveManager::BPDeleteAllSlots(ESEContinue& Result, struct FLatentActionInfo LatentInfo) { if (UWorld* World = GetWorld()) { @@ -219,40 +363,12 @@ void USaveManager::BPDeleteAllSlots(EDeleteSlotsResult& Result, struct FLatentAc bool USaveManager::IsSlotSaved(FName SlotName) const { - return FFileAdapter::DoesFileExist(SlotName.ToString()); -} - -USavePreset* USaveManager::SetActivePreset(TSubclassOf PresetClass) -{ - // We can only change a preset if we have no tasks running - if (HasTasks() || !PresetClass.Get()) - { - return nullptr; - } - - // If We have a preset and its already of the same class, dont do anything - if (ActivePreset && ActivePreset->GetClass() == PresetClass) - { - return nullptr; - } - - ActivePreset = NewObject(this, PresetClass); - return ActivePreset; -} - -const USavePreset* USaveManager::GetPreset() const -{ - if (IsValid(ActivePreset)) - { - return ActivePreset; - } - return GetDefault(); + return FSaveFileHelpers::FileExists(SlotName.ToString()); } bool USaveManager::CanLoadOrSave() { const AGameModeBase* GameMode = UGameplayStatics::GetGameMode(this); - if (GameMode && !GameMode->HasAuthority()) { return false; @@ -261,29 +377,26 @@ bool USaveManager::CanLoadOrSave() return IsValid(GetWorld()); } -void USaveManager::TryInstantiateInfo(bool bForced) +void USaveManager::AssureActiveSlot(TSubclassOf ActiveSlotClass, bool bForced) { if (IsInSlot() && !bForced) return; - const USavePreset* Preset = GetPreset(); - - UClass* InfoClass = Preset->SlotInfoClass.Get(); - if (!InfoClass) - InfoClass = USlotInfo::StaticClass(); - - UClass* DataClass = Preset->SlotDataClass.Get(); - if (!DataClass) - DataClass = USlotData::StaticClass(); - - CurrentInfo = NewObject(GetTransientPackage(), InfoClass); - CurrentData = NewObject(GetTransientPackage(), DataClass); + if (!ActiveSlotClass) + { + ActiveSlotClass = GetDefault()->ActiveSlot.Get(); + if (!ActiveSlotClass) + { + ActiveSlotClass = USaveSlot::StaticClass(); + } + } + ActiveSlot = NewObject(this, ActiveSlotClass); } void USaveManager::UpdateLevelStreamings() { UWorld* World = GetWorld(); - if(!World) + if (!World) { return; } @@ -308,47 +421,37 @@ void USaveManager::SerializeStreamingLevel(ULevelStreaming* LevelStreaming) { if (!LevelStreaming->GetLoadedLevel()->bIsBeingRemoved) { - CreateTask()->Setup(LevelStreaming)->Start(); + CreateTask().Setup(LevelStreaming).Start(); } } void USaveManager::DeserializeStreamingLevel(ULevelStreaming* LevelStreaming) { - CreateTask()->Setup(LevelStreaming)->Start(); + CreateTask().Setup(LevelStreaming).Start(); } -USlotInfo* USaveManager::LoadInfo(FName SlotName) +USaveSlot* USaveManager::LoadInfo(FName SlotName) { if (SlotName.IsNone()) { - SELog(GetPreset(), "Invalid Slot. Cant go under 0 or exceed MaxSlots", true); + SELog(ActiveSlot, "Invalid Slot. Cant go under 0 or exceed MaxSlots", true); return nullptr; } - auto& Task = MTTasks.CreateTask(this, SlotName) - .OnFinished([](auto& Task) - { - Task->AfterFinish(); - }); + auto& Task = MTTasks.CreateTask(this, SlotName).OnFinished([](auto& Task) { + Task->AfterFinish(); + }); Task.StartSynchronousTask(); check(Task.IsDone()); const auto& Infos = Task->GetLoadedSlots(); - return Infos.Num() > 0? Infos[0] : nullptr; + return Infos.Num() > 0 ? Infos[0] : nullptr; } -USlotDataTask* USaveManager::CreateTask(TSubclassOf TaskType) +void USaveManager::FinishTask(FSEDataTask* Task) { - USlotDataTask* Task = NewObject(this, TaskType.Get()); - Task->Prepare(CurrentData, *GetPreset()); - Tasks.Add(Task); - return Task; -} - -void USaveManager::FinishTask(USlotDataTask* Task) -{ - Tasks.Remove(Task); + Tasks.RemoveAll([Task](auto& TaskPtr) { return TaskPtr.Get() == Task; }); // Start next task if (Tasks.Num() > 0) @@ -357,28 +460,16 @@ void USaveManager::FinishTask(USlotDataTask* Task) } } -FName USaveManager::GetSlotNameFromId(const int32 SlotId) const -{ - if (const auto* Preset = GetPreset()) - { - FName Name; - Preset->BPGetSlotNameFromId(SlotId, Name); - return Name; - } - return FName{ FString::FromInt(SlotId) }; -} - bool USaveManager::IsLoading() const { - return HasTasks() && - (Tasks[0]->IsA() || Tasks[0]->IsA()); + return HasTasks() && Tasks[0]->Type == ESETaskType::Load; } void USaveManager::Tick(float DeltaTime) { if (Tasks.Num()) { - USlotDataTask* Task = Tasks[0]; + FSEDataTask* Task = Tasks[0].Get(); check(Task); if (Task->IsRunning()) { @@ -399,12 +490,12 @@ void USaveManager::UnsubscribeFromEvents(const TScriptInterfacetemplate Implements()); // C++ event @@ -413,15 +504,15 @@ void USaveManager::OnSaveBegan(const FSELevelFilter& Filter) Interface->OnSaveBegan(Filter); } ISaveExtensionInterface::Execute_ReceiveOnSaveBegan(Object, Filter); - }); + });*/ } -void USaveManager::OnSaveFinished(const FSELevelFilter& Filter, const bool bError) +void USaveManager::OnSaveFinished(const bool bError) { TRACE_CPUPROFILER_EVENT_SCOPE(USaveManager::OnSaveFinished); - IterateSubscribedInterfaces([&Filter, bError](auto* Object) - { + // TODO: Needs reworking + /*IterateSubscribedInterfaces([&Filter, bError](auto* Object) { check(Object->template Implements()); // C++ event @@ -430,20 +521,19 @@ void USaveManager::OnSaveFinished(const FSELevelFilter& Filter, const bool bErro Interface->OnSaveFinished(Filter, bError); } ISaveExtensionInterface::Execute_ReceiveOnSaveFinished(Object, Filter, bError); - }); + });*/ if (!bError) { - OnGameSaved.Broadcast(CurrentInfo); + OnGameSaved.Broadcast(ActiveSlot); } } -void USaveManager::OnLoadBegan(const FSELevelFilter& Filter) +void USaveManager::OnLoadBegan() { TRACE_CPUPROFILER_EVENT_SCOPE(USaveManager::OnLoadBegan); - IterateSubscribedInterfaces([&Filter](auto* Object) - { + /*IterateSubscribedInterfaces([&Filter](auto* Object) { check(Object->template Implements()); // C++ event @@ -452,15 +542,14 @@ void USaveManager::OnLoadBegan(const FSELevelFilter& Filter) Interface->OnLoadBegan(Filter); } ISaveExtensionInterface::Execute_ReceiveOnLoadBegan(Object, Filter); - }); + });*/ } -void USaveManager::OnLoadFinished(const FSELevelFilter& Filter, const bool bError) +void USaveManager::OnLoadFinished(const bool bError) { TRACE_CPUPROFILER_EVENT_SCOPE(USaveManager::OnLoadFinished); - IterateSubscribedInterfaces([&Filter, bError](auto* Object) - { + /*IterateSubscribedInterfaces([&Filter, bError](auto* Object) { check(Object->template Implements()); // C++ event @@ -469,24 +558,24 @@ void USaveManager::OnLoadFinished(const FSELevelFilter& Filter, const bool bErro Interface->OnLoadFinished(Filter, bError); } ISaveExtensionInterface::Execute_ReceiveOnLoadFinished(Object, Filter, bError); - }); + });*/ if (!bError) { - OnGameLoaded.Broadcast(CurrentInfo); + OnGameLoaded.Broadcast(ActiveSlot); } } void USaveManager::OnMapLoadStarted(const FString& MapName) { - SELog(GetPreset(), "Loading Map '" + MapName + "'", FColor::Purple); + SELog(ActiveSlot, "Loading Map '" + MapName + "'", FColor::Purple); } void USaveManager::OnMapLoadFinished(UWorld* LoadedWorld) { - if(auto* ActiveLoader = Cast(Tasks.Num() ? Tasks[0] : nullptr)) + if (IsLoading()) { - ActiveLoader->OnMapLoaded(); + static_cast(Tasks[0].Get())->OnMapLoaded(); } UpdateLevelStreamings(); diff --git a/Source/SaveExtension/Private/SavePreset.cpp b/Source/SaveExtension/Private/SavePreset.cpp deleted file mode 100644 index a8d9665..0000000 --- a/Source/SaveExtension/Private/SavePreset.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "SavePreset.h" - -#include "LevelFilter.h" -#include "SlotData.h" -#include "SlotInfo.h" - - -USavePreset::USavePreset() - : Super() - , SlotInfoClass(USlotInfo::StaticClass()) - , SlotDataClass(USlotData::StaticClass()) -{} - -void USavePreset::BPGetSlotNameFromId_Implementation(int32 Id, FName& Name) const -{ - // Call C++ inheritance chain by default - return GetSlotNameFromId(Id, Name); -} - -void USavePreset::GetSlotNameFromId(int32 Id, FName& Name) const -{ - if (IsValidId(Id)) - { - Name = FName{ FString::FromInt(Id) }; - } -} - -FSELevelFilter USavePreset::ToFilter() const -{ - FSELevelFilter Filter{}; - Filter.FromPreset(*this); - return Filter; -} diff --git a/Source/SaveExtension/Private/SaveSlot.cpp b/Source/SaveExtension/Private/SaveSlot.cpp new file mode 100644 index 0000000..40f5d1d --- /dev/null +++ b/Source/SaveExtension/Private/SaveSlot.cpp @@ -0,0 +1,188 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "SaveSlot.h" + +#include "SaveFileHelpers.h" + +#include +#include +#include +#include +#include +#include +#include + + +void USaveSlot::PostInitProperties() +{ + Super::PostInitProperties(); + Data = NewObject(this, DataClass, TEXT("SlotData")); +} + +bool USaveSlot::OnSetIndex(int32 Index) +{ + FileName = FName{FString::FromInt(Index)}; + return true; +} + +int32 USaveSlot::OnGetIndex() const +{ + return FCString::Atoi(*FileName.ToString()); +} + +UTexture2D* USaveSlot::GetThumbnail() const +{ + if (ThumbnailPath.IsEmpty()) + { + return nullptr; + } + + if (CachedThumbnail) + { + return CachedThumbnail; + } + + // Load thumbnail as Texture2D + UTexture2D* Texture{nullptr}; + TArray RawFileData; + if (GEngine && FFileHelper::LoadFileToArray(RawFileData, *ThumbnailPath)) + { + IImageWrapperModule& ImageWrapperModule = + FModuleManager::LoadModuleChecked(FName("ImageWrapper")); + TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); + if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num())) + { + TArray64 UncompressedBGRA; + if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) + { + Texture = UTexture2D::CreateTransient( + ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8); + void* TextureData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); + FMemory::Memcpy(TextureData, UncompressedBGRA.GetData(), UncompressedBGRA.Num()); + Texture->GetPlatformData()->Mips[0].BulkData.Unlock(); + Texture->UpdateResource(); + } + } + } + const_cast(this)->CachedThumbnail = Texture; + return Texture; +} + +bool USaveSlot::CaptureThumbnail(const int32 Width /*= 640*/, const int32 Height /*= 360*/) +{ + if (!GEngine || !GEngine->GameViewport || FileName.IsNone()) + { + return false; + } + + if (auto* Viewport = GEngine->GameViewport->Viewport) + { + _SetThumbnailPath(FSaveFileHelpers::GetThumbnailPath(FileName.ToString())); + + // TODO: Removal of a thumbnail should be standarized in a function + IFileManager& FM = IFileManager::Get(); + if (ThumbnailPath.Len() > 0 && FM.FileExists(*ThumbnailPath)) + { + FM.Delete(*ThumbnailPath, false, true, true); + } + + FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig(); + HighResScreenshotConfig.SetHDRCapture(false); + // Set Screenshot path + HighResScreenshotConfig.FilenameOverride = ThumbnailPath; + // Set Screenshot Resolution + GScreenshotResolutionX = Width; + GScreenshotResolutionY = Height; + Viewport->TakeHighResScreenShot(); + return true; + } + return false; +} + + +int32 USaveSlot::GetMaxIndexes() const +{ + return MaxSlots <= 0 ? 16384 : MaxSlots; +} + +bool USaveSlot::IsValidIndex(int32 Index) const +{ + return Index >= 0 && Index < GetMaxIndexes(); +} + +void USaveSlot::_SetThumbnailPath(const FString& Path) +{ + if (ThumbnailPath != Path) + { + ThumbnailPath = Path; + CachedThumbnail = nullptr; + } +} + +bool USaveSlot::SetIndex_Implementation(int32 Index) +{ + return OnSetIndex(Index); +} + +int32 USaveSlot::GetIndex_Implementation() const +{ + return OnGetIndex(); +} + +bool USaveSlot::IsMTSerializationLoad() const +{ + return MultithreadedSerialization == ESEAsyncMode::LoadAsync || + MultithreadedSerialization == ESEAsyncMode::SaveAndLoadAsync; +} +bool USaveSlot::IsMTSerializationSave() const +{ + return MultithreadedSerialization == ESEAsyncMode::SaveAsync || + MultithreadedSerialization == ESEAsyncMode::SaveAndLoadAsync; +} + +ESEAsyncMode USaveSlot::GetFrameSplitSerialization() const +{ + return FrameSplittedSerialization; +} +float USaveSlot::GetMaxFrameMs() const +{ + return MaxFrameMs; +} + +bool USaveSlot::IsFrameSplitLoad() const +{ + return !IsMTSerializationLoad() && (FrameSplittedSerialization == ESEAsyncMode::LoadAsync || + FrameSplittedSerialization == ESEAsyncMode::SaveAndLoadAsync); +} +bool USaveSlot::IsFrameSplitSave() const +{ + return !IsMTSerializationSave() && (FrameSplittedSerialization == ESEAsyncMode::SaveAsync || + FrameSplittedSerialization == ESEAsyncMode::SaveAndLoadAsync); +} + +bool USaveSlot::IsMTFilesLoad() const +{ + return MultithreadedFiles == ESEAsyncMode::LoadAsync || + MultithreadedFiles == ESEAsyncMode::SaveAndLoadAsync; +} +bool USaveSlot::IsMTFilesSave() const +{ + return MultithreadedFiles == ESEAsyncMode::SaveAsync || + MultithreadedFiles == ESEAsyncMode::SaveAndLoadAsync; +} + +bool USaveSlot::IsLoadingOrSaving() const +{ + return HasAnyInternalFlags(EInternalObjectFlags::Async); +} + +void USaveSlot::GetLevelFilter_Implementation(bool bIsLoading, FSELevelFilter& OutFilter) const +{ + OnGetLevelFilter(bIsLoading, OutFilter); +} + +void USaveSlot::OnGetLevelFilter(bool bIsLoading, FSELevelFilter& OutFilter) const +{ + OutFilter.ActorFilter = ActorFilter; + OutFilter.ComponentFilter = ComponentFilter; +} diff --git a/Source/SaveExtension/Private/SaveSlotData.cpp b/Source/SaveExtension/Private/SaveSlotData.cpp new file mode 100644 index 0000000..0a54930 --- /dev/null +++ b/Source/SaveExtension/Private/SaveSlotData.cpp @@ -0,0 +1,30 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "SaveSlotData.h" + +#include + + +///////////////////////////////////////////////////// +// USaveSlotData + +void USaveSlotData::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + Ar << TimeSeconds; + Ar << GameInstance; + RootLevel.Serialize(Ar); + Ar << SubLevels; +} + +void USaveSlotData::CleanRecords(bool bKeepSublevels) +{ + // Clean Up serialization data + GameInstance = {}; + + RootLevel.CleanRecords(); + if (!bKeepSublevels) + { + SubLevels.Empty(); + } +} diff --git a/Source/SaveExtension/Private/Serialization/LevelRecords.cpp b/Source/SaveExtension/Private/Serialization/LevelRecords.cpp index 99c7066..eb9ac3a 100644 --- a/Source/SaveExtension/Private/Serialization/LevelRecords.cpp +++ b/Source/SaveExtension/Private/Serialization/LevelRecords.cpp @@ -1,26 +1,20 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Serialization/LevelRecords.h" -#include "SlotData.h" + +#include "SaveSlotData.h" ///////////////////////////////////////////////////// // LevelRecords -const FName FPersistentLevelRecord::PersistentName{ "Persistent" }; +const FName FPersistentLevelRecord::PersistentName{"Persistent"}; bool FLevelRecord::Serialize(FArchive& Ar) { Super::Serialize(Ar); - Ar << bOverrideGeneralFilter; - if (bOverrideGeneralFilter) - { - static UScriptStruct* const LevelFilterType{ FSELevelFilter::StaticStruct() }; - LevelFilterType->SerializeItem(Ar, &Filter, nullptr); - } - Ar << LevelScript; Ar << Actors; diff --git a/Source/SaveExtension/Private/Serialization/MTTask.cpp b/Source/SaveExtension/Private/Serialization/MTTask.cpp deleted file mode 100644 index cb4369d..0000000 --- a/Source/SaveExtension/Private/Serialization/MTTask.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Serialization/MTTask.h" diff --git a/Source/SaveExtension/Private/Serialization/Records.cpp b/Source/SaveExtension/Private/Serialization/Records.cpp index 1faf389..797d56e 100644 --- a/Source/SaveExtension/Private/Serialization/Records.cpp +++ b/Source/SaveExtension/Private/Serialization/Records.cpp @@ -1,7 +1,8 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Serialization/Records.h" -#include "SlotData.h" + +#include "SaveSlotData.h" ///////////////////////////////////////////////////// @@ -66,7 +67,7 @@ bool FActorRecord::Serialize(FArchive& Ar) // Reduce memory footprint to 1 bool if not moving bool bIsMoving = Ar.IsSaving() && (!LinearVelocity.IsNearlyZero() || !AngularVelocity.IsNearlyZero()); Ar << bIsMoving; - if(bIsMoving) + if (bIsMoving) { Ar << LinearVelocity; Ar << AngularVelocity; diff --git a/Source/SaveExtension/Private/Serialization/SEArchive.cpp b/Source/SaveExtension/Private/Serialization/SEArchive.cpp index 4255783..71eb8dc 100644 --- a/Source/SaveExtension/Private/Serialization/SEArchive.cpp +++ b/Source/SaveExtension/Private/Serialization/SEArchive.cpp @@ -1,9 +1,11 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Serialization/SEArchive.h" + #include + ///////////////////////////////////////////////////// // FSEArchive @@ -45,7 +47,7 @@ FArchive& FSEArchive::operator<<(UObject*& Obj) if (Obj) { // Serialize the fully qualified object name - FString SavedString{ Obj->GetPathName() }; + FString SavedString{Obj->GetPathName()}; InnerArchive << SavedString; /*bool bIsLocallyOwned = IsObjectOwned(Obj); @@ -57,7 +59,7 @@ FArchive& FSEArchive::operator<<(UObject*& Obj) } else { - FString SavedString{ "" }; + FString SavedString{""}; InnerArchive << SavedString; /*bool bIsLocallyOwned = false; diff --git a/Source/SaveExtension/Private/Serialization/SEDataTask.cpp b/Source/SaveExtension/Private/Serialization/SEDataTask.cpp new file mode 100644 index 0000000..fe47f1a --- /dev/null +++ b/Source/SaveExtension/Private/Serialization/SEDataTask.cpp @@ -0,0 +1,52 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "Serialization/SEDataTask.h" + +#include "SaveManager.h" + + +///////////////////////////////////////////////////// +// USaveDataTask + +FSEDataTask& FSEDataTask::Start() +{ + // If not running and first task is this + if (!bRunning && Manager->Tasks.Num() > 0 && Manager->Tasks[0].Get() == this) + { + bRunning = true; + OnStart(); + } + return *this; +} + +void FSEDataTask::Finish(bool bSuccess) +{ + if (bRunning) + { + OnFinish(bSuccess); + Manager->FinishTask(this); + bFinished = true; + bSucceeded = bSuccess; + } +} + +bool FSEDataTask::IsScheduled() const +{ + return Manager->Tasks.ContainsByPredicate([this](auto& Task) { + return Task.Get() == this; + }); +} + +FLevelRecord* FSEDataTask::FindLevelRecord(const ULevelStreaming* Level) const +{ + if (Level) + { + return SlotData->SubLevels.FindByKey(Level); + } + return &SlotData->RootLevel; +} + +UWorld* FSEDataTask::GetWorld() const +{ + return Manager->GetWorld(); +} diff --git a/Source/SaveExtension/Private/Serialization/SEDataTask_Load.cpp b/Source/SaveExtension/Private/Serialization/SEDataTask_Load.cpp new file mode 100644 index 0000000..7ff3ec2 --- /dev/null +++ b/Source/SaveExtension/Private/Serialization/SEDataTask_Load.cpp @@ -0,0 +1,597 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "Serialization/SEDataTask_Load.h" + +#include "Misc/SlotHelpers.h" +#include "SaveManager.h" +#include "Serialization/SEArchive.h" + +#include +#include +#include +#include +#include + + +///////////////////////////////////////////////////// +// USaveDataTask_Loader + +FSEDataTask_Load::~FSEDataTask_Load() +{ + if (LoadDataTask) + { + LoadDataTask->EnsureCompletion(false); + delete LoadDataTask; + } +} + +void FSEDataTask_Load::OnStart() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::OnStart); + + Slot = Manager->LoadInfo(SlotName); + SELog(Slot, "Loading from Slot " + SlotName.ToString()); + if (!Slot) + { + SELog(Slot, "Slot Info not found! Can't load.", FColor::White, true, 1); + Finish(false); + return; + } + + // We load data while the map opens or GC runs + StartLoadingData(); + + const UWorld* World = GetWorld(); + + // Cross-Level loading + // TODO: Handle empty Map as empty world + FName CurrentMapName{FSlotHelpers::GetWorldName(World)}; + if (CurrentMapName != Slot->Map) + { + LoadState = ELoadDataTaskState::LoadingMap; + FString MapToOpen = Slot->Map.ToString(); + if (!GEngine->MakeSureMapNameIsValid(MapToOpen)) + { + UE_LOG(LogSaveExtension, Warning, + TEXT("Slot '%s' was saved in map '%s' but it did not exist while loading. Corrupted save " + "file?"), + *Slot->FileName.ToString(), *MapToOpen); + Finish(false); + return; + } + + UGameplayStatics::OpenLevel(Manager, FName{MapToOpen}); + + SELog(Slot, + "Slot '" + SlotName.ToString() + "' is recorded on another Map. Loading before charging slot.", + FColor::White, false, 1); + return; + } + else if (IsDataLoaded()) + { + StartDeserialization(); + } + else + { + LoadState = ELoadDataTaskState::WaitingForData; + } +} + +void FSEDataTask_Load::Tick(float DeltaTime) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::Tick); + switch (LoadState) + { + case ELoadDataTaskState::Deserializing: + if (CurrentLevel.IsValid()) + { + DeserializeASyncLoop(); + } + break; + + case ELoadDataTaskState::WaitingForData: + if (IsDataLoaded()) + { + StartDeserialization(); + } + } +} + +void FSEDataTask_Load::OnFinish(bool bSuccess) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::OnFinish); + if (bSuccess) + { + SELog(Slot, "Finished Loading", FColor::Green); + } + + // Execute delegates + Delegate.ExecuteIfBound((bSuccess) ? Slot : nullptr); + + Manager->OnLoadFinished(!bSuccess); +} + +void FSEDataTask_Load::OnMapLoaded() +{ + if (LoadState != ELoadDataTaskState::LoadingMap) + { + return; + } + + const UWorld* World = GetWorld(); + if (!World) + { + UE_LOG(LogSaveExtension, Warning, TEXT("Failed loading map from saved slot.")); + Finish(false); + } + const FName NewMapName{FSlotHelpers::GetWorldName(World)}; + if (NewMapName == Slot->Map) + { + if (IsDataLoaded()) + { + StartDeserialization(); + } + else + { + LoadState = ELoadDataTaskState::WaitingForData; + } + } +} + +void FSEDataTask_Load::StartDeserialization() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::StartDeserialization); + check(Slot); + + LoadState = ELoadDataTaskState::Deserializing; + + SlotData = GetLoadedData(); + if (!SlotData) + { + // Failed to load data + Finish(false); + return; + } + + Slot->Stats.LoadDate = FDateTime::Now(); + + // Apply current Info if succeeded + Manager->AssignActiveSlot(Slot); + + Manager->OnLoadBegan(); + + BeforeDeserialize(); + + if (Slot->IsFrameSplitLoad()) + DeserializeASync(); + else + DeserializeSync(); +} + +void FSEDataTask_Load::StartLoadingData() +{ + LoadDataTask = new FAsyncTask(Manager, Slot, SlotName.ToString()); + + if (Slot->IsMTFilesLoad()) + LoadDataTask->StartBackgroundTask(); + else + LoadDataTask->StartSynchronousTask(); +} + +USaveSlotData* FSEDataTask_Load::GetLoadedData() const +{ + if (IsDataLoaded()) + { + return LoadDataTask->GetTask().GetData(); + } + return nullptr; +} + +void FSEDataTask_Load::BeforeDeserialize() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::BeforeDeserialize); + UWorld* World = GetWorld(); + + // Set current game time to the saved value + World->TimeSeconds = SlotData->TimeSeconds; + + if (Slot->bStoreGameInstance) + { + DeserializeGameInstance(); + } +} + +void FSEDataTask_Load::DeserializeSync() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::DeserializeSync); + + const UWorld* World = GetWorld(); + check(World); + + SELog(Slot, "World '" + World->GetName() + "'", FColor::Green, false, 1); + + PrepareAllLevels(); + + // Deserialize world + { + DeserializeLevelSync(World->GetCurrentLevel()); + + const TArray& Levels = World->GetStreamingLevels(); + for (const ULevelStreaming* Level : Levels) + { + if (Level->IsLevelLoaded()) + { + DeserializeLevelSync(Level->GetLoadedLevel(), Level); + } + } + } + + FinishedDeserializing(); +} + +void FSEDataTask_Load::DeserializeLevelSync( + const ULevel* Level, const ULevelStreaming* StreamingLevel) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::DeserializeLevelSync); + + if (!IsValid(Level)) + { + return; + } + + const FName LevelName = + StreamingLevel ? StreamingLevel->GetWorldAssetPackageFName() : FPersistentLevelRecord::PersistentName; + SELog(Slot, "Level '" + LevelName.ToString() + "'", FColor::Green, false, 1); + + const FLevelRecord& LevelRecord = *FindLevelRecord(StreamingLevel); + for(const auto& RecordToActor : LevelRecord.RecordsToActors) + { + const FActorRecord* Record = RecordToActor.Key; + AActor* Actor = RecordToActor.Value.Get(); + check(Record && Actor); + DeserializeActor(Actor, *Record, LevelRecord); + } +} + +void FSEDataTask_Load::DeserializeASync() +{ + // Deserialize world + { + SELog(Slot, "World '" + GetWorld()->GetName() + "'", FColor::Green, false, 1); + + PrepareAllLevels(); + DeserializeLevelASync(GetWorld()->GetCurrentLevel()); + } +} + +void FSEDataTask_Load::DeserializeLevelASync(ULevel* Level, ULevelStreaming* StreamingLevel) +{ + check(IsValid(Level)); + + const FName LevelName = + StreamingLevel ? StreamingLevel->GetWorldAssetPackageFName() : FPersistentLevelRecord::PersistentName; + SELog(Slot, "Level '" + LevelName.ToString() + "'", FColor::Green, false, 1); + + FLevelRecord* LevelRecord = FindLevelRecord(StreamingLevel); + if (!LevelRecord) + { + Finish(false); + return; + } + + const float StartMS = GetTimeMilliseconds(); + + CurrentLevel = Level; + CurrentSLevel = StreamingLevel; + CurrentActorIndex = 0; + + DeserializeASyncLoop(StartMS); +} + +void FSEDataTask_Load::DeserializeASyncLoop(float StartMS) +{ + if (StartMS <= 0) + { + StartMS = GetTimeMilliseconds(); + } + + FLevelRecord& LevelRecord = *FindLevelRecord(CurrentSLevel.Get()); + + // Continue Iterating actors every tick + for (; CurrentActorIndex < LevelRecord.RecordsToActors.Num(); ++CurrentActorIndex) + { + auto& RecordToActor = LevelRecord.RecordsToActors[CurrentActorIndex]; + + const FActorRecord* Record = RecordToActor.Key; + AActor* Actor = RecordToActor.Value.Get(); + check(Record); + if (!Actor) + { + continue; + } + DeserializeActor(Actor, *Record, LevelRecord); + + const float CurrentMS = GetTimeMilliseconds(); + if (CurrentMS - StartMS >= MaxFrameMs) + { + // If x milliseconds passed, stop and continue on next frame + return; + } + } + + ULevelStreaming* CurrentLevelStreaming = CurrentSLevel.Get(); + FindNextAsyncLevel(CurrentLevelStreaming); + if (CurrentLevelStreaming) + { + // Iteration has ended. Deserialize next level + CurrentLevel = CurrentLevelStreaming->GetLoadedLevel(); + if (CurrentLevel.IsValid()) + { + DeserializeLevelASync(CurrentLevel.Get(), CurrentLevelStreaming); + return; + } + } + + // All levels deserialized + FinishedDeserializing(); +} + +void FSEDataTask_Load::PrepareLevel(const ULevel* Level, FLevelRecord& LevelRecord) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::PrepareLevel); + + Slot->GetLevelFilter(true, LevelRecord.Filter); + LevelRecord.Filter.BakeAllowedClasses(); + + // Records not contained in Scene Actors => Actors to be Respawned + // Scene Actors not contained in loaded records => Actors to be Destroyed + // The rest => Just deserialize + + TArray ActorRecordsToSpawn; + ActorRecordsToSpawn.Reserve(LevelRecord.Actors.Num()); + for (FActorRecord& Record : LevelRecord.Actors) + { + ActorRecordsToSpawn.Add(&Record); + } + + TArray ActorsToDestroy{}; + { // Filter actors by whether they should be destroyed or spawned - O(M*Log(N)) + for (AActor* const Actor : Level->Actors) + { + if (UNLIKELY(!Actor)) + { + continue; + } + + const int32 Index = ActorRecordsToSpawn.IndexOfByPredicate([Actor](auto* Record) { + return *Record == Actor; + }); + if (Index != INDEX_NONE) // Actor found, therefore doesn't need to be spawned + { + if (LevelRecord.Filter.Stores(Actor)) + { + FActorRecord* Record = ActorRecordsToSpawn[Index]; + LevelRecord.RecordsToActors.Add({Record, Actor}); + } + ActorRecordsToSpawn.RemoveAtSwap(Index, 1, false); + } + else if (LevelRecord.Filter.Stores(Actor)) + { + ActorsToDestroy.Add(Actor); + } + // TODO: Consider unmatching class actors to be respawned + } + + } + + // The serializable actors that were not found will be destroyed + for (AActor* Actor : ActorsToDestroy) + { + Actor->Destroy(); + } + + // Spawn Actors that don't exist but were saved + ActorRecordsToSpawn.Shrink(); + RespawnActors(ActorRecordsToSpawn, Level, LevelRecord); +} + +void FSEDataTask_Load::FinishedDeserializing() +{ + // Clean serialization data + SlotData->CleanRecords(true); + Slot->AssignData(SlotData); + Finish(true); +} + +void FSEDataTask_Load::PrepareAllLevels() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::PrepareAllLevels); + + const UWorld* World = GetWorld(); + check(World); + + // Prepare root level + PrepareLevel(World->GetCurrentLevel(), SlotData->RootLevel); + + // Prepare other loaded sub-levels + const TArray& Levels = World->GetStreamingLevels(); + for (const ULevelStreaming* Level : Levels) + { + if (Level->IsLevelLoaded()) + { + FLevelRecord* LevelRecord = FindLevelRecord(Level); + if (LevelRecord) + { + PrepareLevel(Level->GetLoadedLevel(), *LevelRecord); + } + } + } +} + +void FSEDataTask_Load::RespawnActors(const TArray& Records, const ULevel* Level, FLevelRecord& LevelRecord) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::RespawnActors); + + FActorSpawnParameters SpawnInfo{}; + SpawnInfo.OverrideLevel = const_cast(Level); + SpawnInfo.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + + UWorld* const World = GetWorld(); + + // Respawn all procedural actors + for (auto* Record : Records) + { + SpawnInfo.Name = Record->Name; + auto* NewActor = World->SpawnActor(Record->Class, &Record->Transform, SpawnInfo); + + // We update the name on the record in case it changed + Record->Name = NewActor->GetFName(); + LevelRecord.RecordsToActors.Add({Record, NewActor}); + } +} + +void FSEDataTask_Load::DeserializeGameInstance() +{ + bool bSuccess = true; + auto* GameInstance = GetWorld()->GetGameInstance(); + const FObjectRecord& Record = SlotData->GameInstance; + + if (!IsValid(GameInstance) || GameInstance->GetClass() != Record.Class) + bSuccess = false; + + if (bSuccess) + { + // Serialize from Record Data + FMemoryReader MemoryReader(Record.Data, true); + FSEArchive Archive(MemoryReader, false); + GameInstance->Serialize(Archive); + } + + SELog(Slot, "Game Instance '" + Record.Name.ToString() + "'", FColor::Green, !bSuccess, 1); +} + +bool FSEDataTask_Load::DeserializeActor(AActor* Actor, const FActorRecord& ActorRecord, const FLevelRecord& LevelRecord) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Load::DeserializeActor); + + if (Actor->GetClass() != ActorRecord.Class) + { + SELog(Slot, "Actor '" + ActorRecord.Name.ToString() + "' already exists but class doesn't match", FColor::Green, true, 1); + return false; + } + + // Always load saved tags + Actor->Tags = ActorRecord.Tags; + + const bool bSavesPhysics = FSELevelFilter::StoresPhysics(Actor); + if (FSELevelFilter::StoresTransform(Actor)) + { + Actor->SetActorTransform(ActorRecord.Transform); + + if (FSELevelFilter::StoresPhysics(Actor)) + { + USceneComponent* Root = Actor->GetRootComponent(); + if (auto* Primitive = Cast(Root)) + { + Primitive->SetPhysicsLinearVelocity(ActorRecord.LinearVelocity); + Primitive->SetPhysicsAngularVelocityInRadians(ActorRecord.AngularVelocity); + } + else + { + Root->ComponentVelocity = ActorRecord.LinearVelocity; + } + } + } + + Actor->SetActorHiddenInGame(ActorRecord.bHiddenInGame); + + DeserializeActorComponents(Actor, ActorRecord, LevelRecord, 2); + + { + // Serialize from Record Data + FMemoryReader MemoryReader(ActorRecord.Data, true); + FSEArchive Archive(MemoryReader, false); + Actor->Serialize(Archive); + } + + return true; +} + +void FSEDataTask_Load::DeserializeActorComponents(AActor* Actor, const FActorRecord& ActorRecord, const FLevelRecord& LevelRecord, int8 Indent) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(UFSEDataTask_Load::DeserializeActorComponents); + + if (!LevelRecord.Filter.StoresAnyComponents()) + { + return; + } + + for (auto* Component : Actor->GetComponents()) + { + if (!IsValid(Component) || !LevelRecord.Filter.Stores(Component)) + { + continue; + } + + // Find the record + const FComponentRecord* Record = ActorRecord.ComponentRecords.FindByKey(Component); + if (!Record) + { + SELog(Slot, "Component '" + Component->GetFName().ToString() + "' - Record not found", + FColor::Red, false, Indent + 1); + continue; + } + + if (FSELevelFilter::StoresTransform(Component)) + { + USceneComponent* Scene = CastChecked(Component); + if (Scene->Mobility == EComponentMobility::Movable) + { + Scene->SetRelativeTransform(Record->Transform); + } + } + + if (FSELevelFilter::StoresTags(Component)) + { + Component->ComponentTags = Record->Tags; + } + + if (!Component->GetClass()->IsChildOf()) + { + FMemoryReader MemoryReader(Record->Data, true); + FSEArchive Archive(MemoryReader, false); + Component->Serialize(Archive); + } + } +} + +void FSEDataTask_Load::FindNextAsyncLevel(ULevelStreaming*& OutLevelStreaming) const +{ + OutLevelStreaming = nullptr; + + const UWorld* World = GetWorld(); + const TArray& Levels = World->GetStreamingLevels(); + if (CurrentLevel.IsValid() && Levels.Num() > 0) + { + if (!CurrentSLevel.IsValid()) + { + // Current is persistent, get first streaming level + OutLevelStreaming = Levels[0]; + return; + } + else + { + int32 CurrentIndex = Levels.IndexOfByKey(CurrentSLevel); + if (CurrentIndex != INDEX_NONE && Levels.Num() > CurrentIndex + 1) + { + OutLevelStreaming = Levels[CurrentIndex + 1]; + } + } + } + + // If this level is unloaded, find next + if (OutLevelStreaming && !OutLevelStreaming->IsLevelLoaded()) + { + FindNextAsyncLevel(OutLevelStreaming); + } +} diff --git a/Source/SaveExtension/Private/Serialization/SEDataTask_LoadLevel.cpp b/Source/SaveExtension/Private/Serialization/SEDataTask_LoadLevel.cpp new file mode 100644 index 0000000..cfcd469 --- /dev/null +++ b/Source/SaveExtension/Private/Serialization/SEDataTask_LoadLevel.cpp @@ -0,0 +1,72 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "Serialization/SEDataTask_LoadLevel.h" + + +///////////////////////////////////////////////////// +// USaveDataTask_LevelLoader + +void FSEDataTask_LoadLevel::OnStart() +{ + if (!SlotData || !StreamingLevel || !StreamingLevel->IsLevelLoaded()) + { + Finish(false); + return; + } + + FLevelRecord* LevelRecord = FindLevelRecord(StreamingLevel); + if (!LevelRecord) + { + Finish(false); + return; + } + + PrepareLevel(StreamingLevel->GetLoadedLevel(), *LevelRecord); + + if (Slot->IsFrameSplitLoad()) + { + DeserializeLevelASync(StreamingLevel->GetLoadedLevel(), StreamingLevel); + } + else + { + DeserializeLevelSync(StreamingLevel->GetLoadedLevel(), StreamingLevel); + FinishedDeserializing(); + return; + } +} + +void FSEDataTask_LoadLevel::DeserializeASyncLoop(float StartMS /*= 0.0f*/) +{ + + if (StartMS <= 0) + { + StartMS = GetTimeMilliseconds(); + } + + FLevelRecord& LevelRecord = *FindLevelRecord(CurrentSLevel.Get()); + + // Continue Iterating actors every tick + for (; CurrentActorIndex < LevelRecord.RecordsToActors.Num(); ++CurrentActorIndex) + { + auto& RecordToActor = LevelRecord.RecordsToActors[CurrentActorIndex]; + + const FActorRecord* Record = RecordToActor.Key; + AActor* Actor = RecordToActor.Value.Get(); + check(Record); + if (!Actor) + { + continue; + } + DeserializeActor(Actor, *Record, LevelRecord); + + const float CurrentMS = GetTimeMilliseconds(); + if (CurrentMS - StartMS >= MaxFrameMs) + { + // If x milliseconds passed, stop and continue on next frame + return; + } + } + + // All levels deserialized + FinishedDeserializing(); +} diff --git a/Source/SaveExtension/Private/Serialization/SEDataTask_Save.cpp b/Source/SaveExtension/Private/Serialization/SEDataTask_Save.cpp new file mode 100644 index 0000000..a4b9cc5 --- /dev/null +++ b/Source/SaveExtension/Private/Serialization/SEDataTask_Save.cpp @@ -0,0 +1,298 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "Serialization/SEDataTask_Save.h" + +#include "Misc/SlotHelpers.h" +#include "SaveFileHelpers.h" +#include "SaveManager.h" +#include "SaveSlot.h" +#include "SaveSlotData.h" + +#include +#include + + +///////////////////////////////////////////////////// +// FSEDataTask_Save + +FSEDataTask_Save::~FSEDataTask_Save() +{ + if (SaveTask) + { + SaveTask->EnsureCompletion(false); + delete SaveTask; + } +} + +void FSEDataTask_Save::OnStart() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Save::OnStart); + Manager->AssureActiveSlot(); + + bool bSave = true; + const FString SlotNameStr = SlotName.ToString(); + // Overriding + { + const bool bFileExists = FSaveFileHelpers::FileExists(SlotNameStr); + if (bOverride) + { + // Delete previous save + if (bFileExists) + { + FSaveFileHelpers::DeleteFile(SlotNameStr); + } + } + else + { + // Only save if previous files don't exist + // We don't want to serialize since it won't be saved anyway + bSave = !bFileExists; + } + } + + if (bSave) + { + const UWorld* World = GetWorld(); + + Manager->OnSaveBegan(); + + Slot = Manager->GetActiveSlot(); + SlotData = Slot->GetData(); + check(SlotData->GetClass() == Slot->DataClass); + SlotData->CleanRecords(true); + + check(Slot && SlotData); + + const bool bSlotExisted = Slot->FileName == SlotName; + Slot->FileName = SlotName; + + if (bSaveThumbnail) + { + Slot->CaptureThumbnail(Width, Height); + } + + // Time stats + { + FSaveSlotStats& Stats = Slot->Stats; + Stats.SaveDate = FDateTime::Now(); + + // If this info has been loaded ever + const bool bWasLoaded = Stats.LoadDate.GetTicks() > 0; + if (bWasLoaded) + { + // Now - Loaded + const FTimespan SessionTime = Stats.SaveDate - Stats.LoadDate; + Stats.PlayedTime += SessionTime; + Stats.SlotPlayedTime = bSlotExisted ? (Stats.SlotPlayedTime + SessionTime) : SessionTime; + } + else + { + // Slot is new, played time is world seconds + Stats.PlayedTime = FTimespan::FromSeconds(World->TimeSeconds); + Stats.SlotPlayedTime = Stats.PlayedTime; + } + + // Save current game seconds + SlotData->TimeSeconds = World->TimeSeconds; + } + + // Save Level info + Slot->Map = FName{FSlotHelpers::GetWorldName(World)}; + + SerializeWorld(); + SaveFile(); + return; + } + Finish(false); +} + +void FSEDataTask_Save::Tick(float DeltaTime) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Save::Tick); + FSEDataTask::Tick(DeltaTime); + + if (SaveTask && SaveTask->IsDone()) + { + if (bSaveThumbnail) + { + if (Slot && Slot->GetThumbnail()) + { + Finish(true); + } + } + else + { + Finish(true); + } + } +} + +void FSEDataTask_Save::OnFinish(bool bSuccess) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Save::OnFinish); + if (bSuccess) + { + // Clean serialization data + SlotData->CleanRecords(true); + + SELog(Slot, "Finished Saving", FColor::Green); + } + + // Execute delegates + Delegate.ExecuteIfBound((Manager && bSuccess) ? Manager->GetActiveSlot() : nullptr); + + Manager->OnSaveFinished(!bSuccess); +} + +void FSEDataTask_Save::SerializeWorld() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Save::SerializeWorld); + + // Must have Authority + const UWorld* World = GetWorld(); + if (!World->GetAuthGameMode()) + { + return; + } + + SELog(Slot, "World '" + World->GetName() + "'", FColor::Green, false, 1); + + const TArray& Levels = World->GetStreamingLevels(); + PrepareAllLevels(Levels); + + // Threads available + 1 (Synchronous Thread) + const int32 NumberOfThreads = FMath::Max(1, FPlatformMisc::NumberOfWorkerThreadsToSpawn() + 1); + const int32 TasksPerLevel = FMath::Max(1, FMath::RoundToInt(float(NumberOfThreads) / (Levels.Num() + 1))); + Tasks.Reserve(NumberOfThreads); + + SerializeLevelSync(World->GetCurrentLevel(), TasksPerLevel); + for (const ULevelStreaming* Level : Levels) + { + if (Level->IsLevelLoaded()) + { + SerializeLevelSync(Level->GetLoadedLevel(), TasksPerLevel, Level); + } + } + + RunScheduledTasks(); +} + +void FSEDataTask_Save::PrepareAllLevels(const TArray& Levels) +{ + // Prepare root level + PrepareLevel(GetWorld()->GetCurrentLevel(), SlotData->RootLevel); + + // Create the sub-level records if non existent + for (const ULevelStreaming* Level : Levels) + { + if (Level->IsLevelLoaded()) + { + FLevelRecord& LevelRecord = SlotData->SubLevels.Add_GetRef({*Level}); + PrepareLevel(Level->GetLoadedLevel(), LevelRecord); + } + } +} + +void FSEDataTask_Save::PrepareLevel(const ULevel* Level, FLevelRecord& LevelRecord) +{ + Slot->GetLevelFilter(true, LevelRecord.Filter); + LevelRecord.Filter.BakeAllowedClasses(); +} + +void FSEDataTask_Save::SerializeLevelSync( + const ULevel* Level, int32 AssignedTasks, const ULevelStreaming* StreamingLevel) +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Save::SerializeLevelSync); + check(IsValid(Level)); + + if (!Slot->IsMTSerializationSave()) + { + AssignedTasks = 1; + } + + const FName LevelName = + StreamingLevel ? StreamingLevel->GetWorldAssetPackageFName() : FPersistentLevelRecord::PersistentName; + SELog(Slot, "Level '" + LevelName.ToString() + "'", FColor::Green, false, 1); + + // Find level record. By default, main level + FLevelRecord* LevelRecord = &SlotData->RootLevel; + if (StreamingLevel) + { + LevelRecord = FindLevelRecord(StreamingLevel); + } + check(LevelRecord); + + // Empty level record before serializing it + LevelRecord->CleanRecords(); + + const int32 MinObjectsPerTask = 40; + const int32 ActorCount = Level->Actors.Num(); + const int32 NumBalancedPerTask = FMath::CeilToInt((float) ActorCount / AssignedTasks); + const int32 NumPerTask = FMath::Max(NumBalancedPerTask, MinObjectsPerTask); + + // Split all actors between multi-threaded tasks + int32 Index = 0; + while (Index < ActorCount) + { + const int32 NumRemaining = ActorCount - Index; + const int32 NumToSerialize = FMath::Min(NumRemaining, NumPerTask); + + // First task saves the GameInstance + bool bStoreGameInstance = Index <= 0 && Slot->bStoreGameInstance; + // Add new Task + Tasks.Emplace(FMTTask_SerializeActors{GetWorld(), SlotData, &Level->Actors, Index, NumToSerialize, + bStoreGameInstance, LevelRecord, &LevelRecord->Filter}); + + Index += NumToSerialize; + } +} + +void FSEDataTask_Save::RunScheduledTasks() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Save::RunScheduledTasks); + // Start all serialization tasks + if (Tasks.Num() > 0) + { + for (int32 I = 1; I < Tasks.Num(); ++I) + { + if (Slot->IsMTSerializationSave()) + Tasks[I].StartBackgroundTask(); + else + Tasks[I].StartSynchronousTask(); + } + // First task stores + Tasks[0].StartSynchronousTask(); + } + // Wait until all tasks have finished + for (auto& AsyncTask : Tasks) + { + AsyncTask.EnsureCompletion(); + } + // All tasks finished, sync data + for (auto& AsyncTask : Tasks) + { + AsyncTask.GetTask().DumpData(); + } + Tasks.Empty(); +} + +void FSEDataTask_Save::SaveFile() +{ + TRACE_CPUPROFILER_EVENT_SCOPE(FSEDataTask_Save::SaveFile); + SaveTask = + new FAsyncTask(Manager->GetActiveSlot(), SlotName.ToString(), Slot->bUseCompression); + + if (Slot->IsMTFilesSave()) + { + SaveTask->StartBackgroundTask(); + } + else + { + SaveTask->StartSynchronousTask(); + + if (!bSaveThumbnail) + { + Finish(true); + } + } +} diff --git a/Source/SaveExtension/Private/Serialization/SlotDataTask_LevelSaver.cpp b/Source/SaveExtension/Private/Serialization/SEDataTask_SaveLevel.cpp similarity index 72% rename from Source/SaveExtension/Private/Serialization/SlotDataTask_LevelSaver.cpp rename to Source/SaveExtension/Private/Serialization/SEDataTask_SaveLevel.cpp index 72eae1c..a45e9c5 100644 --- a/Source/SaveExtension/Private/Serialization/SlotDataTask_LevelSaver.cpp +++ b/Source/SaveExtension/Private/Serialization/SEDataTask_SaveLevel.cpp @@ -1,12 +1,12 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. -#include "Serialization/SlotDataTask_LevelSaver.h" +#include "Serialization/SEDataTask_SaveLevel.h" ///////////////////////////////////////////////////// // FSaveDataTask_LevelSaver -void USlotDataTask_LevelSaver::OnStart() +void FSEDataTask_SaveLevel::OnStart() { if (SlotData && StreamingLevel && StreamingLevel->IsLevelLoaded()) { @@ -17,7 +17,7 @@ void USlotDataTask_LevelSaver::OnStart() return; } - GetLevelFilter(*LevelRecord).BakeAllowedClasses(); + PrepareLevel(StreamingLevel->GetLoadedLevel(), *LevelRecord); const int32 NumberOfThreads = FMath::Max(1, FPlatformMisc::NumberOfWorkerThreadsToSpawn()); SerializeLevelSync(StreamingLevel->GetLoadedLevel(), NumberOfThreads, StreamingLevel); diff --git a/Source/SaveExtension/Private/Serialization/SlotDataTask.cpp b/Source/SaveExtension/Private/Serialization/SlotDataTask.cpp deleted file mode 100644 index a5cd2de..0000000 --- a/Source/SaveExtension/Private/Serialization/SlotDataTask.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Serialization/SlotDataTask.h" - -#include "SaveManager.h" -#include "SavePreset.h" - - -///////////////////////////////////////////////////// -// USaveDataTask - -USlotDataTask* USlotDataTask::Start() -{ - const USaveManager* Manager = GetManager(); - - // If not running and first task is this - if (!bRunning && Manager->Tasks.Num() > 0 && Manager->Tasks[0] == this) - { - bRunning = true; - OnStart(); - } - return this; -} - -void USlotDataTask::Finish(bool bSuccess) -{ - if (bRunning) - { - OnFinish(bSuccess); - MarkAsGarbage(); - GetManager()->FinishTask(this); - bFinished = true; - bSucceeded = bSuccess; - } -} - -bool USlotDataTask::IsScheduled() const -{ - return GetManager()->Tasks.Contains(this); -} - -USaveManager* USlotDataTask::GetManager() const -{ - return Cast(GetOuter()); -} - -void USlotDataTask::BakeAllFilters() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask::BakeAllFilters); - SlotData->GeneralLevelFilter.BakeAllowedClasses(); - - if(SlotData->MainLevel.bOverrideGeneralFilter) - { - SlotData->MainLevel.Filter.BakeAllowedClasses(); - } - - for(const auto& Level : SlotData->SubLevels) - { - if(Level.bOverrideGeneralFilter) - { - Level.Filter.BakeAllowedClasses(); - } - } -} - -const FSELevelFilter& USlotDataTask::GetGeneralFilter() const -{ - check(SlotData); - return SlotData->GeneralLevelFilter; -} - -const FSELevelFilter& USlotDataTask::GetLevelFilter(const FLevelRecord& Level) const -{ - if(Level.bOverrideGeneralFilter) - { - return Level.Filter; - } - return GetGeneralFilter(); -} - -FLevelRecord* USlotDataTask::FindLevelRecord(const ULevelStreaming* Level) const -{ - if (!Level) - return &SlotData->MainLevel; - else // Find the Sub-Level - return SlotData->SubLevels.FindByKey(Level); -} - -UWorld* USlotDataTask::GetWorld() const -{ - return GetOuter()->GetWorld(); -} diff --git a/Source/SaveExtension/Private/Serialization/SlotDataTask_LevelLoader.cpp b/Source/SaveExtension/Private/Serialization/SlotDataTask_LevelLoader.cpp deleted file mode 100644 index 362e2de..0000000 --- a/Source/SaveExtension/Private/Serialization/SlotDataTask_LevelLoader.cpp +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Serialization/SlotDataTask_LevelLoader.h" - - -///////////////////////////////////////////////////// -// USaveDataTask_LevelLoader - -void USlotDataTask_LevelLoader::OnStart() -{ - if (SlotData && StreamingLevel && StreamingLevel->IsLevelLoaded()) - { - FLevelRecord* LevelRecord = FindLevelRecord(StreamingLevel); - if (!LevelRecord) - { - Finish(false); - return; - } - - GetLevelFilter(*LevelRecord).BakeAllowedClasses(); - - if (Preset->IsFrameSplitLoad()) - { - DeserializeLevelASync(StreamingLevel->GetLoadedLevel(), StreamingLevel); - } - else - { - DeserializeLevelSync(StreamingLevel->GetLoadedLevel(), StreamingLevel); - FinishedDeserializing(); - } - return; - } - Finish(false); -} - -void USlotDataTask_LevelLoader::DeserializeASyncLoop(float StartMS /*= 0.0f*/) -{ - FLevelRecord& LevelRecord = *FindLevelRecord(CurrentSLevel.Get()); - - if (StartMS <= 0) - { - StartMS = GetTimeMilliseconds(); - } - - const auto& Filter = GetLevelFilter(LevelRecord); - - // Continue Iterating actors every tick - for (; CurrentActorIndex < CurrentLevelActors.Num(); ++CurrentActorIndex) - { - AActor* Actor{ CurrentLevelActors[CurrentActorIndex].Get() }; - if (Actor) - { - DeserializeLevel_Actor(Actor, LevelRecord, Filter); - - const float CurrentMS = GetTimeMilliseconds(); - // If x milliseconds passed, continue on next frame - if (CurrentMS - StartMS >= MaxFrameMs) - return; - } - } - - // All levels deserialized - FinishedDeserializing(); -} diff --git a/Source/SaveExtension/Private/Serialization/SlotDataTask_Loader.cpp b/Source/SaveExtension/Private/Serialization/SlotDataTask_Loader.cpp deleted file mode 100644 index 720a229..0000000 --- a/Source/SaveExtension/Private/Serialization/SlotDataTask_Loader.cpp +++ /dev/null @@ -1,624 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Serialization/SlotDataTask_Loader.h" - -#include -#include -#include -#include -#include - -#include "Misc/SlotHelpers.h" -#include "SavePreset.h" -#include "SaveManager.h" -#include "Serialization/SEArchive.h" - - -///////////////////////////////////////////////////// -// Helpers - -namespace Loader -{ - static int32 RemoveSingleRecordPtrSwap(TArray& Records, AActor* Actor, bool bAllowShrinking = true) - { - if(!Actor) - { - return 0; - } - - const int32 I = Records.IndexOfByPredicate([Records, Actor](auto* Record) - { - return *Record == Actor; - }); - if (I != INDEX_NONE) - { - Records.RemoveAtSwap(I, 1, bAllowShrinking); - return 1; - } - return 0; - } -} - - -///////////////////////////////////////////////////// -// USaveDataTask_Loader - -void USlotDataTask_Loader::OnStart() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::OnStart); - USaveManager* Manager = GetManager(); - - SELog(Preset, "Loading from Slot " + SlotName.ToString()); - - NewSlotInfo = Manager->LoadInfo(SlotName); - if (!NewSlotInfo) - { - SELog(Preset, "Slot Info not found! Can't load.", FColor::White, true, 1); - Finish(false); - return; - } - - // We load data while the map opens or GC runs - StartLoadingData(); - - const UWorld* World = GetWorld(); - - // Cross-Level loading - // TODO: Handle empty Map as empty world - FName CurrentMapName { FSlotHelpers::GetWorldName(World) }; - if (CurrentMapName != NewSlotInfo->Map) - { - LoadState = ELoadDataTaskState::LoadingMap; - FString MapToOpen = NewSlotInfo->Map.ToString(); - if (!GEngine->MakeSureMapNameIsValid(MapToOpen)) - { - UE_LOG(LogSaveExtension, Warning, TEXT("Slot '%s' was saved in map '%s' but it did not exist while loading. Corrupted save file?"), *NewSlotInfo->FileName.ToString(), *MapToOpen); - Finish(false); - return; - } - - UGameplayStatics::OpenLevel(this, FName{ MapToOpen }); - - SELog(Preset, "Slot '" + SlotName.ToString() + "' is recorded on another Map. Loading before charging slot.", FColor::White, false, 1); - return; - } - else if (IsDataLoaded()) - { - StartDeserialization(); - } - else - { - LoadState = ELoadDataTaskState::WaitingForData; - } -} - -void USlotDataTask_Loader::Tick(float DeltaTime) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::Tick); - switch(LoadState) - { - case ELoadDataTaskState::Deserializing: - if (CurrentLevel.IsValid()) - { - DeserializeASyncLoop(); - } - break; - - case ELoadDataTaskState::WaitingForData: - if (IsDataLoaded()) - { - StartDeserialization(); - } - } -} - -void USlotDataTask_Loader::OnFinish(bool bSuccess) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::OnFinish); - if (bSuccess) - { - SELog(Preset, "Finished Loading", FColor::Green); - } - - // Execute delegates - Delegate.ExecuteIfBound((bSuccess) ? NewSlotInfo : nullptr); - - GetManager()->OnLoadFinished( - SlotData? GetGeneralFilter() : FSELevelFilter{}, - !bSuccess - ); -} - -void USlotDataTask_Loader::BeginDestroy() -{ - if (LoadDataTask) - { - LoadDataTask->EnsureCompletion(false); - delete LoadDataTask; - } - - Super::BeginDestroy(); -} - -void USlotDataTask_Loader::OnMapLoaded() -{ - if(LoadState != ELoadDataTaskState::LoadingMap) - { - return; - } - - const UWorld* World = GetWorld(); - if(!World) - { - UE_LOG(LogSaveExtension, Warning, TEXT("Failed loading map from saved slot.")); - Finish(false); - } - const FName NewMapName { FSlotHelpers::GetWorldName(World) }; - if (NewMapName == NewSlotInfo->Map) - { - if(IsDataLoaded()) - { - StartDeserialization(); - } - else - { - LoadState = ELoadDataTaskState::WaitingForData; - } - } -} - -void USlotDataTask_Loader::StartDeserialization() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::StartDeserialization); - check(NewSlotInfo); - - LoadState = ELoadDataTaskState::Deserializing; - - SlotData = GetLoadedData(); - if (!SlotData) - { - // Failed to load data - Finish(false); - return; - } - - NewSlotInfo->LoadDate = FDateTime::Now(); - - GetManager()->OnLoadBegan(GetGeneralFilter()); - //Apply current Info if succeeded - GetManager()->__SetCurrentInfo(NewSlotInfo); - - BakeAllFilters(); - - BeforeDeserialize(); - - if (Preset->IsFrameSplitLoad()) - DeserializeASync(); - else - DeserializeSync(); -} - -void USlotDataTask_Loader::StartLoadingData() -{ - LoadDataTask = new FAsyncTask(GetManager(), SlotName.ToString()); - - if (Preset->IsMTFilesLoad()) - LoadDataTask->StartBackgroundTask(); - else - LoadDataTask->StartSynchronousTask(); -} - -USlotData* USlotDataTask_Loader::GetLoadedData() const -{ - if (IsDataLoaded()) - { - return LoadDataTask->GetTask().GetData(); - } - return nullptr; -} - -void USlotDataTask_Loader::BeforeDeserialize() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::BeforeDeserialize); - UWorld* World = GetWorld(); - - // Set current game time to the saved value - World->TimeSeconds = SlotData->TimeSeconds; - - if (SlotData->bStoreGameInstance) - { - DeserializeGameInstance(); - } -} - -void USlotDataTask_Loader::DeserializeSync() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::DeserializeSync); - - const UWorld* World = GetWorld(); - check(World); - - SELog(Preset, "World '" + World->GetName() + "'", FColor::Green, false, 1); - - PrepareAllLevels(); - - // Deserialize world - { - DeserializeLevelSync(World->GetCurrentLevel()); - - const TArray& Levels = World->GetStreamingLevels(); - for (const ULevelStreaming* Level : Levels) - { - if (Level->IsLevelLoaded()) - { - DeserializeLevelSync(Level->GetLoadedLevel(), Level); - } - } - } - - FinishedDeserializing(); -} - -void USlotDataTask_Loader::DeserializeLevelSync(const ULevel* Level, const ULevelStreaming* StreamingLevel) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::DeserializeLevelSync); - - if (!IsValid(Level)) - return; - - const FName LevelName = StreamingLevel ? StreamingLevel->GetWorldAssetPackageFName() : FPersistentLevelRecord::PersistentName; - SELog(Preset, "Level '" + LevelName.ToString() + "'", FColor::Green, false, 1); - - if (FLevelRecord* LevelRecord = FindLevelRecord(StreamingLevel)) - { - const auto& Filter = GetLevelFilter(*LevelRecord); - - for (auto ActorItr = Level->Actors.CreateConstIterator(); ActorItr; ++ActorItr) - { - auto* Actor = *ActorItr; - if (IsValid(Actor) && Filter.ShouldSave(Actor)) - { - DeserializeLevel_Actor(Actor, *LevelRecord, Filter); - } - } - } -} - -void USlotDataTask_Loader::DeserializeASync() -{ - // Deserialize world - { - SELog(Preset, "World '" + GetWorld()->GetName() + "'", FColor::Green, false, 1); - - PrepareAllLevels(); - DeserializeLevelASync(GetWorld()->GetCurrentLevel()); - } -} - -void USlotDataTask_Loader::DeserializeLevelASync(ULevel* Level, ULevelStreaming* StreamingLevel) -{ - check(IsValid(Level)); - - const FName LevelName = StreamingLevel ? StreamingLevel->GetWorldAssetPackageFName() : FPersistentLevelRecord::PersistentName; - SELog(Preset, "Level '" + LevelName.ToString() + "'", FColor::Green, false, 1); - - FLevelRecord* LevelRecord = FindLevelRecord(StreamingLevel); - if (!LevelRecord) { - Finish(false); - return; - } - - const float StartMS = GetTimeMilliseconds(); - - CurrentLevel = Level; - CurrentSLevel = StreamingLevel; - CurrentActorIndex = 0; - - // Copy actors array. New actors won't be considered for deserialization - CurrentLevelActors.Empty(Level->Actors.Num()); - for (auto* Actor : Level->Actors) - { - if(IsValid(Actor)) - { - CurrentLevelActors.Add(Actor); - } - } - - DeserializeASyncLoop(StartMS); -} - -void USlotDataTask_Loader::DeserializeASyncLoop(float StartMS) -{ - FLevelRecord * LevelRecord = FindLevelRecord(CurrentSLevel.Get()); - if (!LevelRecord) - { - return; - } - - const auto& Filter = GetLevelFilter(*LevelRecord); - - if(StartMS <= 0) - { - StartMS = GetTimeMilliseconds(); - } - - // Continue Iterating actors every tick - for (; CurrentActorIndex < CurrentLevelActors.Num(); ++CurrentActorIndex) - { - AActor* const Actor{ CurrentLevelActors[CurrentActorIndex].Get() }; - if (IsValid(Actor) && Filter.ShouldSave(Actor)) - { - DeserializeLevel_Actor(Actor, *LevelRecord, Filter); - - const float CurrentMS = GetTimeMilliseconds(); - // If x milliseconds passed, stop and continue on next frame - if (CurrentMS - StartMS >= MaxFrameMs) - { - return; - } - } - } - - ULevelStreaming* CurrentLevelStreaming = CurrentSLevel.Get(); - FindNextAsyncLevel(CurrentLevelStreaming); - if (CurrentLevelStreaming) - { - // Iteration has ended. Deserialize next level - CurrentLevel = CurrentLevelStreaming->GetLoadedLevel(); - if (CurrentLevel.IsValid()) - { - DeserializeLevelASync(CurrentLevel.Get(), CurrentLevelStreaming); - return; - } - } - - // All levels deserialized - FinishedDeserializing(); -} - -void USlotDataTask_Loader::PrepareLevel(const ULevel* Level, FLevelRecord& LevelRecord) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::PrepareLevel); - - const auto& Filter = GetLevelFilter(LevelRecord); - - - // Records not contained in Scene Actors => Actors to be Respawned - // Scene Actors not contained in loaded records => Actors to be Destroyed - // The rest => Just deserialize - - TArray ActorsToSpawn; - ActorsToSpawn.Reserve(LevelRecord.Actors.Num()); - for(FActorRecord& Record : LevelRecord.Actors) - { - ActorsToSpawn.Add(&Record); - } - - TArray ActorsToDestroy{}; - { - // O(M*Log(N)) - for (AActor* const Actor : Level->Actors) - { - // Remove records which actors do exist - const bool bFoundActorRecord = Loader::RemoveSingleRecordPtrSwap(ActorsToSpawn, Actor, false) > 0; - - if (Actor && Filter.ShouldSave(Actor)) - { - if (!bFoundActorRecord) // Don't destroy level actors - { - // If the actor wasn't found, mark it for destruction - Actor->Destroy(); - } - } - } - ActorsToSpawn.Shrink(); - } - - // Create Actors that doesn't exist now but were saved - RespawnActors(ActorsToSpawn, Level); -} - -void USlotDataTask_Loader::FinishedDeserializing() -{ - // Clean serialization data - SlotData->CleanRecords(true); - GetManager()->__SetCurrentData(SlotData); - - Finish(true); -} - -void USlotDataTask_Loader::PrepareAllLevels() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::PrepareAllLevels); - - const UWorld* World = GetWorld(); - check(World); - - // Prepare Main level - PrepareLevel(World->GetCurrentLevel(), SlotData->MainLevel); - - // Prepare other loaded sub-levels - const TArray& Levels = World->GetStreamingLevels(); - for (const ULevelStreaming* Level : Levels) - { - if (Level->IsLevelLoaded()) - { - FLevelRecord* LevelRecord = FindLevelRecord(Level); - if (LevelRecord) - { - PrepareLevel(Level->GetLoadedLevel(), *LevelRecord); - } - } - } -} - -void USlotDataTask_Loader::RespawnActors(const TArray& Records, const ULevel* Level) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::RespawnActors); - - FActorSpawnParameters SpawnInfo{}; - SpawnInfo.OverrideLevel = const_cast(Level); - SpawnInfo.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; - - UWorld* const World = GetWorld(); - - // Respawn all procedural actors - for (auto* Record : Records) - { - SpawnInfo.Name = Record->Name; - auto* NewActor = World->SpawnActor(Record->Class, &Record->Transform, SpawnInfo); - - // We update the name on the record in case it changed - Record->Name = NewActor->GetFName(); - } -} - -void USlotDataTask_Loader::DeserializeLevel_Actor(AActor* const Actor, const FLevelRecord& LevelRecord, const FSELevelFilter& Filter) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::DeserializeLevel_Actor); - - // Find the record - const FActorRecord* const Record = LevelRecord.Actors.FindByKey(Actor); - if (Record && Record->IsValid() && Record->Class == Actor->GetClass()) - { - DeserializeActor(Actor, *Record, Filter); - } -} - -void USlotDataTask_Loader::DeserializeGameInstance() -{ - bool bSuccess = true; - auto* GameInstance = GetWorld()->GetGameInstance(); - const FObjectRecord& Record = SlotData->GameInstance; - - if (!IsValid(GameInstance) || GameInstance->GetClass() != Record.Class) - bSuccess = false; - - if (bSuccess) - { - //Serialize from Record Data - FMemoryReader MemoryReader(Record.Data, true); - FSEArchive Archive(MemoryReader, false); - GameInstance->Serialize(Archive); - } - - SELog(Preset, "Game Instance '" + Record.Name.ToString() + "'", FColor::Green, !bSuccess, 1); -} - -bool USlotDataTask_Loader::DeserializeActor(AActor* Actor, const FActorRecord& Record, const FSELevelFilter& Filter) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Loader::DeserializeActor); - - // Always load saved tags - Actor->Tags = Record.Tags; - - const bool bSavesPhysics = FSELevelFilter::StoresPhysics(Actor); - if (FSELevelFilter::StoresTransform(Actor)) - { - Actor->SetActorTransform(Record.Transform); - - if (FSELevelFilter::StoresPhysics(Actor)) - { - USceneComponent* Root = Actor->GetRootComponent(); - if (auto* Primitive = Cast(Root)) - { - Primitive->SetPhysicsLinearVelocity(Record.LinearVelocity); - Primitive->SetPhysicsAngularVelocityInRadians(Record.AngularVelocity); - } - else - { - Root->ComponentVelocity = Record.LinearVelocity; - } - } - } - - Actor->SetActorHiddenInGame(Record.bHiddenInGame); - - DeserializeActorComponents(Actor, Record, Filter, 2); - - { - //Serialize from Record Data - FMemoryReader MemoryReader(Record.Data, true); - FSEArchive Archive(MemoryReader, false); - Actor->Serialize(Archive); - } - - return true; -} - -void USlotDataTask_Loader::DeserializeActorComponents(AActor* Actor, const FActorRecord& ActorRecord, const FSELevelFilter& Filter, int8 Indent) -{ - if (Filter.bStoreComponents) - { - TRACE_CPUPROFILER_EVENT_SCOPE(UUSlotDataTask_Loader::DeserializeActorComponents); - - const TSet& Components = Actor->GetComponents(); - for (auto* Component : Components) - { - if (!Filter.ShouldSave(Component)) - { - continue; - } - - // Find the record - const FComponentRecord* Record = ActorRecord.ComponentRecords.FindByKey(Component); - if (!Record) - { - SELog(Preset, "Component '" + Component->GetFName().ToString() + "' - Record not found", FColor::Red, false, Indent + 1); - continue; - } - - if (FSELevelFilter::StoresTransform(Component)) - { - USceneComponent* Scene = CastChecked(Component); - if (Scene->Mobility == EComponentMobility::Movable) - { - Scene->SetRelativeTransform(Record->Transform); - } - } - - if (FSELevelFilter::StoresTags(Component)) - { - Component->ComponentTags = Record->Tags; - } - - if (!Component->GetClass()->IsChildOf()) - { - FMemoryReader MemoryReader(Record->Data, true); - FSEArchive Archive(MemoryReader, false); - Component->Serialize(Archive); - } - } - } -} - -void USlotDataTask_Loader::FindNextAsyncLevel(ULevelStreaming*& OutLevelStreaming) const -{ - OutLevelStreaming = nullptr; - - const UWorld* World = GetWorld(); - const TArray& Levels = World->GetStreamingLevels(); - if (CurrentLevel.IsValid() && Levels.Num() > 0) - { - if (!CurrentSLevel.IsValid()) - { - //Current is persistent, get first streaming level - OutLevelStreaming = Levels[0]; - return; - } - else - { - int32 CurrentIndex = Levels.IndexOfByKey(CurrentSLevel); - if (CurrentIndex != INDEX_NONE && Levels.Num() > CurrentIndex + 1) - { - OutLevelStreaming = Levels[CurrentIndex + 1]; - } - } - } - - // If this level is unloaded, find next - if (OutLevelStreaming && !OutLevelStreaming->IsLevelLoaded()) - { - FindNextAsyncLevel(OutLevelStreaming); - } -} diff --git a/Source/SaveExtension/Private/Serialization/SlotDataTask_Saver.cpp b/Source/SaveExtension/Private/Serialization/SlotDataTask_Saver.cpp deleted file mode 100644 index c67418b..0000000 --- a/Source/SaveExtension/Private/Serialization/SlotDataTask_Saver.cpp +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "Serialization/SlotDataTask_Saver.h" - -#include -#include - -#include "Misc/SlotHelpers.h" -#include "SaveManager.h" -#include "SlotInfo.h" -#include "SlotData.h" -#include "SavePreset.h" -#include "FileAdapter.h" - - -///////////////////////////////////////////////////// -// USaveDataTask_Saver - -void USlotDataTask_Saver::OnStart() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Saver::OnStart); - USaveManager* Manager = GetManager(); - Manager->TryInstantiateInfo(); - - bool bSave = true; - const FString SlotNameStr = SlotName.ToString(); - //Overriding - { - const bool bFileExists = FFileAdapter::DoesFileExist(SlotNameStr); - if (bOverride) - { - // Delete previous save - if (bFileExists) - { - FFileAdapter::DeleteFile(SlotNameStr); - } - } - else - { - //Only save if previous files don't exist - //We don't want to serialize since it won't be saved anyway - bSave = !bFileExists; - } - } - - if (bSave) - { - const UWorld* World = GetWorld(); - - GetManager()->OnSaveBegan(GetGeneralFilter()); - - SlotInfo = Manager->GetCurrentInfo(); - SlotData = Manager->GetCurrentData(); - SlotData->CleanRecords(true); - - check(SlotInfo && SlotData); - - const bool bSlotWasDifferent = SlotInfo->FileName != SlotName; - SlotInfo->FileName = SlotName; - - if (bSaveThumbnail) - { - SlotInfo->CaptureThumbnail(Width, Height); - } - - // Time stats - { - SlotInfo->SaveDate = FDateTime::Now(); - - // If this info has been loaded ever - const bool bWasLoaded = SlotInfo->LoadDate.GetTicks() > 0; - if (bWasLoaded) - { - // Now - Loaded - const FTimespan SessionTime = SlotInfo->SaveDate - SlotInfo->LoadDate; - - SlotInfo->PlayedTime += SessionTime; - - if (!bSlotWasDifferent) - SlotInfo->SlotPlayedTime += SessionTime; - else - SlotInfo->SlotPlayedTime = SessionTime; - } - else - { - // Slot is new, played time is world seconds - SlotInfo->PlayedTime = FTimespan::FromSeconds(World->TimeSeconds); - SlotInfo->SlotPlayedTime = SlotInfo->PlayedTime; - } - - // Save current game seconds - SlotData->TimeSeconds = World->TimeSeconds; - } - - //Save Level info in both files - SlotInfo->Map = FName{ FSlotHelpers::GetWorldName(World) }; - SlotData->Map = SlotData->Map; - - SlotData->bStoreGameInstance = Preset->bStoreGameInstance; - SlotData->GeneralLevelFilter = Preset->ToFilter(); - - SerializeWorld(); - SaveFile(); - return; - } - Finish(false); -} - -void USlotDataTask_Saver::Tick(float DeltaTime) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Saver::Tick); - Super::Tick(DeltaTime); - - if (SaveTask && SaveTask->IsDone()) - { - if (bSaveThumbnail) - { - if (SlotInfo && SlotInfo->GetThumbnail()) - { - Finish(true); - } - } - else - { - Finish(true); - } - } -} - -void USlotDataTask_Saver::OnFinish(bool bSuccess) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Saver::OnFinish); - if (bSuccess) - { - // Clean serialization data - SlotData->CleanRecords(true); - - SELog(Preset, "Finished Saving", FColor::Green); - } - - // Execute delegates - USaveManager* Manager = GetManager(); - check(Manager); - Delegate.ExecuteIfBound((Manager && bSuccess)? Manager->GetCurrentInfo() : nullptr); - Manager->OnSaveFinished( - SlotData? GetGeneralFilter() : FSELevelFilter{}, - !bSuccess - ); -} - -void USlotDataTask_Saver::BeginDestroy() -{ - if (SaveTask) - { - SaveTask->EnsureCompletion(false); - delete SaveTask; - } - - Super::BeginDestroy(); -} - -void USlotDataTask_Saver::SerializeWorld() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Saver::SerializeWorld); - - // Must have Authority - if (!GetWorld()->GetAuthGameMode()) - { - return; - } - - const UWorld* World = GetWorld(); - SELog(Preset, "World '" + World->GetName() + "'", FColor::Green, false, 1); - - const TArray& Levels = World->GetStreamingLevels(); - PrepareAllLevels(Levels); - - // Threads available + 1 (Synchronous Thread) - const int32 NumberOfThreads = FMath::Max(1, FPlatformMisc::NumberOfWorkerThreadsToSpawn() + 1); - const int32 TasksPerLevel = FMath::Max(1, FMath::RoundToInt(float(NumberOfThreads) / (Levels.Num() + 1))); - Tasks.Reserve(NumberOfThreads); - - SerializeLevelSync(World->GetCurrentLevel(), TasksPerLevel); - for (const ULevelStreaming* Level : Levels) - { - if (Level->IsLevelLoaded()) - { - SerializeLevelSync(Level->GetLoadedLevel(), TasksPerLevel, Level); - } - } - - RunScheduledTasks(); -} - -void USlotDataTask_Saver::PrepareAllLevels(const TArray& Levels) -{ - BakeAllFilters(); - - // Create the sub-level records if non existent - for (const ULevelStreaming* Level : Levels) - { - if (Level->IsLevelLoaded()) - { - SlotData->SubLevels.AddUnique({ *Level }); - } - } -} - -void USlotDataTask_Saver::SerializeLevelSync(const ULevel* Level, int32 AssignedTasks, const ULevelStreaming* StreamingLevel) -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Saver::SerializeLevelSync); - check(IsValid(Level)); - - if (!Preset->IsMTSerializationSave()) - { - AssignedTasks = 1; - } - - const FName LevelName = StreamingLevel ? StreamingLevel->GetWorldAssetPackageFName() : FPersistentLevelRecord::PersistentName; - SELog(Preset, "Level '" + LevelName.ToString() + "'", FColor::Green, false, 1); - - // Find level record. By default, main level - FLevelRecord* LevelRecord = &SlotData->MainLevel; - if (StreamingLevel) - { - LevelRecord = FindLevelRecord(StreamingLevel); - } - check(LevelRecord); - - // Empty level record before serializing it - LevelRecord->CleanRecords(); - - auto& Filter = GetLevelFilter(*LevelRecord); - - const int32 MinObjectsPerTask = 40; - const int32 ActorCount = Level->Actors.Num(); - const int32 NumBalancedPerTask = FMath::CeilToInt((float) ActorCount / AssignedTasks); - const int32 NumPerTask = FMath::Max(NumBalancedPerTask, MinObjectsPerTask); - - // Split all actors between multi-threaded tasks - int32 Index = 0; - while (Index < ActorCount) - { - const int32 NumRemaining = ActorCount - Index; - const int32 NumToSerialize = FMath::Min(NumRemaining, NumPerTask); - - // First task saves the GameInstance - bool bStoreGameInstance = Index <= 0 && SlotData->bStoreGameInstance; - // Add new Task - Tasks.Emplace(FMTTask_SerializeActors - { - GetWorld(), SlotData, &Level->Actors, Index, NumToSerialize, - bStoreGameInstance, LevelRecord, Filter - }); - - Index += NumToSerialize; - } -} - -void USlotDataTask_Saver::RunScheduledTasks() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Saver::RunScheduledTasks); - // Start all serialization tasks - if (Tasks.Num() > 0) - { - for (int32 I = 1; I < Tasks.Num(); ++I) - { - if (Preset->IsMTSerializationSave()) - Tasks[I].StartBackgroundTask(); - else - Tasks[I].StartSynchronousTask(); - } - // First task stores - Tasks[0].StartSynchronousTask(); - } - // Wait until all tasks have finished - for (auto& AsyncTask : Tasks) - { - AsyncTask.EnsureCompletion(); - } - // All tasks finished, sync data - for (auto& AsyncTask : Tasks) - { - AsyncTask.GetTask().DumpData(); - } - Tasks.Empty(); -} - -void USlotDataTask_Saver::SaveFile() -{ - TRACE_CPUPROFILER_EVENT_SCOPE(USlotDataTask_Saver::SaveFile); - USaveManager* Manager = GetManager(); - - SaveTask = new FAsyncTask( - Manager->GetCurrentInfo(), Manager->GetCurrentData(), - SlotName.ToString(), Preset->bUseCompression); - - if (Preset->IsMTFilesSave()) - { - SaveTask->StartBackgroundTask(); - } - else - { - SaveTask->StartSynchronousTask(); - - if (!bSaveThumbnail) - { - Finish(true); - } - } -} diff --git a/Source/SaveExtension/Private/SlotData.cpp b/Source/SaveExtension/Private/SlotData.cpp deleted file mode 100644 index 5454cef..0000000 --- a/Source/SaveExtension/Private/SlotData.cpp +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "SlotData.h" -#include - -#include "SavePreset.h" - - -/** - * A macro for tidying up accessing of private members, through the above code - * - * @param InClass The class being accessed (not a string, just the class, i.e. FStackTracker) - * @param InObj Pointer to an instance of the specified class - * @param MemberName Name of the member being accessed (again, not a string) - * @return The value of the member - */ -#define GET_PRIVATE(InClass, InObj, MemberName) (*InObj).*GetPrivate(InClass##MemberName##Accessor()) - -///////////////////////////////////////////////////// -// USlotData - -void USlotData::Serialize(FArchive& Ar) -{ - Super::Serialize(Ar); - - Ar << bStoreGameInstance; - Ar << GameInstance; - - static UScriptStruct* const LevelFilterType{ FSELevelFilter::StaticStruct() }; - LevelFilterType->SerializeItem(Ar, &GeneralLevelFilter, nullptr); - MainLevel.Serialize(Ar); - Ar << SubLevels; -} - -void USlotData::CleanRecords(bool bKeepSublevels) -{ - //Clean Up serialization data - GameInstance = {}; - - MainLevel.CleanRecords(); - if (!bKeepSublevels) - { - SubLevels.Empty(); - } -} diff --git a/Source/SaveExtension/Private/SlotInfo.cpp b/Source/SaveExtension/Private/SlotInfo.cpp deleted file mode 100644 index a15794c..0000000 --- a/Source/SaveExtension/Private/SlotInfo.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#include "SlotInfo.h" - -#include -#include -#include -#include -#include -#include -#include - - -UTexture2D* USlotInfo::GetThumbnail() const -{ - if (ThumbnailPath.IsEmpty()) - { - return nullptr; - } - - if (CachedThumbnail) - { - return CachedThumbnail; - } - - // Load thumbnail as Texture2D - UTexture2D* Texture{ nullptr }; - TArray RawFileData; - if (GEngine && FFileHelper::LoadFileToArray(RawFileData, *ThumbnailPath)) - { - IImageWrapperModule & ImageWrapperModule = FModuleManager::LoadModuleChecked < IImageWrapperModule >(FName("ImageWrapper")); - TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); - if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num())) - { - TArray64 UncompressedBGRA; - if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedBGRA)) - { - Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8); - void* TextureData = Texture->GetPlatformData()->Mips[0].BulkData.Lock(LOCK_READ_WRITE); - FMemory::Memcpy(TextureData, UncompressedBGRA.GetData(), UncompressedBGRA.Num()); - Texture->GetPlatformData()->Mips[0].BulkData.Unlock(); - Texture->UpdateResource(); - } - } - } - const_cast(this)->CachedThumbnail = Texture; - return Texture; -} - -bool USlotInfo::CaptureThumbnail(const int32 Width /*= 640*/, const int32 Height /*= 360*/) -{ - if (!GEngine || !GEngine->GameViewport || FileName.IsNone()) - { - return false; - } - - if (auto* Viewport = GEngine->GameViewport->Viewport) - { - _SetThumbnailPath(FFileAdapter::GetThumbnailPath(FileName.ToString())); - - // TODO: Removal of a thumbnail should be standarized in a function - IFileManager& FM = IFileManager::Get(); - if (ThumbnailPath.Len() > 0 && FM.FileExists(*ThumbnailPath)) - { - FM.Delete(*ThumbnailPath, false, true, true); - } - - FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig(); - HighResScreenshotConfig.SetHDRCapture(false); - //Set Screenshot path - HighResScreenshotConfig.FilenameOverride = ThumbnailPath; - //Set Screenshot Resolution - GScreenshotResolutionX = Width; - GScreenshotResolutionY = Height; - Viewport->TakeHighResScreenShot(); - return true; - } - return false; -} - -void USlotInfo::_SetThumbnailPath(const FString& Path) -{ - if (ThumbnailPath != Path) - { - ThumbnailPath = Path; - CachedThumbnail = nullptr; - } -} - diff --git a/Source/SaveExtension/Public/Delegates.h b/Source/SaveExtension/Public/Delegates.h index 5e1ddaa..fbb9b04 100644 --- a/Source/SaveExtension/Public/Delegates.h +++ b/Source/SaveExtension/Public/Delegates.h @@ -1,16 +1,16 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include "SlotInfo.h" +#include "SaveSlot.h" /** Called when game has been saved - * @param SlotInfo the saved slot. Null if save failed + * @param SaveSlot the saved slot. Null if save failed */ -DECLARE_DELEGATE_OneParam(FOnGameSaved, USlotInfo*); +DECLARE_DELEGATE_OneParam(FOnGameSaved, USaveSlot*); /** Called when game has been loaded - * @param SlotInfo the loaded slot. Null if load failed + * @param SaveSlot the loaded slot. Null if load failed */ -DECLARE_DELEGATE_OneParam(FOnGameLoaded, USlotInfo*); +DECLARE_DELEGATE_OneParam(FOnGameLoaded, USaveSlot*); diff --git a/Source/SaveExtension/Public/ISaveExtension.h b/Source/SaveExtension/Public/ISaveExtension.h index 2cfd2ae..f8b644c 100644 --- a/Source/SaveExtension/Public/ISaveExtension.h +++ b/Source/SaveExtension/Public/ISaveExtension.h @@ -1,38 +1,42 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include "Modules/ModuleManager.h" #include "Engine/Engine.h" - -#include "SavePreset.h" +#include "Modules/ModuleManager.h" +#include "SaveSlot.h" DECLARE_LOG_CATEGORY_EXTERN(LogSaveExtension, All, All); -class ISaveExtension : public IModuleInterface { +class ISaveExtension : public IModuleInterface +{ public: - static inline ISaveExtension& Get() { + static inline ISaveExtension& Get() + { return FModuleManager::LoadModuleChecked("SaveExtension"); } - static inline bool IsAvailable() { + static inline bool IsAvailable() + { return FModuleManager::Get().IsModuleLoaded("SaveExtension"); } - static void Log(const USavePreset* Preset, const FString Message, bool bError) + static void Log(const USaveSlot* Slot, const FString& Message, bool bError) { - Log(Preset, Message, FColor::White, bError, 2.f); + Log(Slot, Message, FColor::White, bError, 2.f); } - static void Log(const USavePreset* Preset, const FString Message, FColor Color = FColor::White, bool bError = false, const float Duration = 2.f) + static void Log(const USaveSlot* Slot, const FString& Message, FColor Color = FColor::White, + bool bError = false, const float Duration = 2.f) { - if (Preset->bDebug) + if (Slot->bDebug) { - if (bError) { + if (bError) + { Color = FColor::Red; } - const FString ComposedMessage { FString::Printf(TEXT("SE: %s"), *Message) }; + const FString ComposedMessage{FString::Printf(TEXT("SE: %s"), *Message)}; if (bError) { @@ -43,7 +47,7 @@ class ISaveExtension : public IModuleInterface { UE_LOG(LogSaveExtension, Log, TEXT("%s"), *ComposedMessage); } - if (Preset->bDebugInScreen && GEngine) + if (Slot->bDebugInScreen && GEngine) { GEngine->AddOnScreenDebugMessage(-1, Duration, Color, ComposedMessage); } @@ -51,11 +55,15 @@ class ISaveExtension : public IModuleInterface { } }; -//Only log in Editor +// Only log in Editor #if WITH_EDITORONLY_DATA -template -void SELog(Args&& ...args) { ISaveExtension::Log(Forward(args)...); } +template +void SELog(Args&&... args) +{ + ISaveExtension::Log(Forward(args)...); +} #else -template -void SELog(Args&& ...args) {} // Optimized away by compiler +template +void SELog(Args&&... args) +{} // Optimized away by compiler #endif diff --git a/Source/SaveExtension/Public/LatentActions/DeleteSlotsAction.h b/Source/SaveExtension/Public/LatentActions/DeleteSlotsAction.h deleted file mode 100644 index fea0a52..0000000 --- a/Source/SaveExtension/Public/LatentActions/DeleteSlotsAction.h +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include -#include -#include - - -class USaveManager; -class USlotInfo; - -/** - * Enum used to indicate quote execution results - */ -UENUM() -enum class EDeleteSlotsResult : uint8 -{ - Completed -}; - -/** FLoadInfosction */ -class FDeleteSlotsAction : public FPendingLatentAction -{ -public: - EDeleteSlotsResult& Result; - - bool bFinished; - - FName ExecutionFunction; - int32 OutputLink; - FWeakObjectPtr CallbackTarget; - - - /** - * @param SlotId will load that Saved Game if Id > 0, otherwise it will load all infos - */ - FDeleteSlotsAction(USaveManager* Manager, EDeleteSlotsResult& OutResult, const FLatentActionInfo& LatentInfo); - - virtual void UpdateOperation(FLatentResponse& Response) override; - -#if WITH_EDITOR - // Returns a human readable description of the latent operation's current state - virtual FString GetDescription() const override - { - return TEXT("Deleting all slots..."); - } -#endif -}; diff --git a/Source/SaveExtension/Public/LatentActions/LoadGameAction.h b/Source/SaveExtension/Public/LatentActions/LoadGameAction.h deleted file mode 100644 index 444d721..0000000 --- a/Source/SaveExtension/Public/LatentActions/LoadGameAction.h +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include -#include -#include - - -class USaveManager; -class USlotInfo; - -/** - * Enum used to indicate quote execution results - */ -UENUM() -enum class ELoadGameResult : uint8 -{ - Loading UMETA(Hidden), - Continue, - Failed -}; - -/** FLoadGameAction */ -class FLoadGameAction : public FPendingLatentAction -{ -public: - ELoadGameResult& Result; - - FName ExecutionFunction; - int32 OutputLink; - FWeakObjectPtr CallbackTarget; - - - FLoadGameAction(USaveManager* Manager, FName SlotName, ELoadGameResult& Result, const FLatentActionInfo& LatentInfo); - - virtual void UpdateOperation(FLatentResponse& Response) override; - - void OnLoadFinished(USlotInfo* SavedSlot); - -#if WITH_EDITOR - // Returns a human readable description of the latent operation's current state - virtual FString GetDescription() const override - { - return TEXT("Loading Game..."); - } -#endif -}; diff --git a/Source/SaveExtension/Public/LatentActions/LoadInfosAction.h b/Source/SaveExtension/Public/LatentActions/LoadInfosAction.h deleted file mode 100644 index 34df5e8..0000000 --- a/Source/SaveExtension/Public/LatentActions/LoadInfosAction.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include -#include -#include - -#include "Multithreading/LoadSlotInfosTask.h" - - -class USaveManager; -class USlotInfo; - -/** - * Enum used to indicate quote execution results - */ -UENUM() -enum class ELoadInfoResult : uint8 -{ - Completed -}; - -/** FLoadInfosction */ -class FLoadInfosAction : public FPendingLatentAction -{ - -public: - ELoadInfoResult& Result; - - TArray& SlotInfos; - bool bFinished; - - FName ExecutionFunction; - int32 OutputLink; - FWeakObjectPtr CallbackTarget; - - - /** - * @param SlotId will load that Saved Game if Id > 0, otherwise it will load all infos - */ - FLoadInfosAction(USaveManager* Manager, const bool bSortByRecent, TArray& SaveInfos, ELoadInfoResult& OutResult, const FLatentActionInfo& LatentInfo); - - virtual void UpdateOperation(FLatentResponse& Response) override; - -#if WITH_EDITOR - virtual FString GetDescription() const override - { - return TEXT("Loading all infos..."); - } -#endif -}; diff --git a/Source/SaveExtension/Public/LatentActions/SaveGameAction.h b/Source/SaveExtension/Public/LatentActions/SaveGameAction.h deleted file mode 100644 index 7c669c7..0000000 --- a/Source/SaveExtension/Public/LatentActions/SaveGameAction.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include -#include -#include - - -class USaveManager; -class USlotInfo; -struct FScreenshotSize; - -/** - * Enum used to indicate quote execution results - */ -UENUM() -enum class ESaveGameResult : uint8 -{ - Saving UMETA(Hidden), - Continue, - Failed -}; - -/** FSaveGameAction */ -class FSaveGameAction : public FPendingLatentAction -{ - -public: - ESaveGameResult& Result; - - FName ExecutionFunction; - int32 OutputLink; - FWeakObjectPtr CallbackTarget; - - - FSaveGameAction(USaveManager* Manager, FName SlotName, bool bOverrideIfNeeded, bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& OutResult, const FLatentActionInfo& LatentInfo); - - virtual void UpdateOperation(FLatentResponse& Response) override; - - void OnSaveFinished(USlotInfo* SavedSlot); - -#if WITH_EDITOR - // Returns a human readable description of the latent operation's current state - virtual FString GetDescription() const override - { - return TEXT("Saving Game..."); - } -#endif -}; diff --git a/Source/SaveExtension/Public/LevelFilter.h b/Source/SaveExtension/Public/LevelFilter.h index 6bf7d72..8748961 100644 --- a/Source/SaveExtension/Public/LevelFilter.h +++ b/Source/SaveExtension/Public/LevelFilter.h @@ -1,11 +1,14 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include "SavePreset.h" +#include "Misc/ClassFilter.h" + #include "LevelFilter.generated.h" + class USaveManager; +class USaveSlot; /** @@ -23,95 +26,31 @@ struct FSELevelFilter static const FName TagTransform; public: - UPROPERTY(SaveGame) FSEActorClassFilter ActorFilter; UPROPERTY(SaveGame) - FSEActorClassFilter LoadActorFilter; + FSEComponentClassFilter ComponentFilter; - UPROPERTY(SaveGame) - bool bStoreComponents = false; - UPROPERTY(SaveGame) - FSEComponentClassFilter ComponentFilter; + FSELevelFilter() = default; - UPROPERTY(SaveGame) - FSEComponentClassFilter LoadComponentFilter; - - - FSELevelFilter() {} - - void FromPreset(const USavePreset& Preset) - { - ActorFilter = Preset.GetActorFilter(true); - LoadActorFilter = Preset.GetActorFilter(false); - bStoreComponents = Preset.bStoreComponents; - ComponentFilter = Preset.GetComponentFilter(true); - LoadComponentFilter = Preset.GetComponentFilter(false); - } - - void BakeAllowedClasses() const - { - TRACE_CPUPROFILER_EVENT_SCOPE(FSELevelFilter::BakeAllowedClasses); - ActorFilter.BakeAllowedClasses(); - ComponentFilter.BakeAllowedClasses(); - LoadActorFilter.BakeAllowedClasses(); - LoadComponentFilter.BakeAllowedClasses(); - } - - bool ShouldSave(const AActor* Actor) const - { - return ActorFilter.IsClassAllowed(Actor->GetClass()); - } - - bool ShouldLoad(const AActor* Actor) const - { - return LoadActorFilter.IsClassAllowed(Actor->GetClass()); - } - - bool ShouldSave(const UActorComponent* Component) const - { - return IsValid(Component) - && ComponentFilter.IsClassAllowed(Component->GetClass()); - } - - bool ShouldLoad(const UActorComponent* Component) const - { - return IsValid(Component) - && LoadComponentFilter.IsClassAllowed(Component->GetClass()); - } - - static bool StoresTransform(const UActorComponent* Component) - { - return Component->GetClass()->IsChildOf() - && HasTag(Component, TagTransform); - } - - static bool StoresTags(const UActorComponent* Component) - { - return !HasTag(Component, TagNoTags); - } - - static bool IsSaveTag(const FName& Tag) - { - return Tag == TagNoTransform || Tag == TagNoPhysics || Tag == TagNoTags; - } - - static FORCEINLINE bool StoresTransform(const AActor* Actor) { return Actor->IsRootComponentMovable() && !HasTag(Actor, TagNoTransform); } - static FORCEINLINE bool StoresPhysics(const AActor* Actor) { return !HasTag(Actor, TagNoPhysics); } - static FORCEINLINE bool StoresTags(const AActor* Actor) { return !HasTag(Actor, TagNoTags); } - static FORCEINLINE bool IsProcedural(const AActor* Actor) { return Actor->HasAnyFlags(RF_WasLoaded | RF_LoadCompleted); } - - static FORCEINLINE bool HasTag(const AActor* Actor, const FName Tag) - { - check(Actor); - return Actor->ActorHasTag(Tag); - } - - static FORCEINLINE bool HasTag(const UActorComponent * Component, const FName Tag) - { - check(Component); - return Component->ComponentHasTag(Tag); - } + void BakeAllowedClasses() const; + + bool Stores(const AActor* Actor) const; + bool StoresAnyComponents() const; + bool Stores(const UActorComponent* Component) const; + + static bool StoresTransform(const UActorComponent* Component); + static bool StoresTags(const UActorComponent* Component); + + static bool IsSaveTag(const FName& Tag); + + static bool StoresTransform(const AActor* Actor); + static bool StoresPhysics(const AActor* Actor); + static bool StoresTags(const AActor* Actor); + static bool IsProcedural(const AActor* Actor); + + static bool HasTag(const AActor* Actor, const FName Tag); + static bool HasTag(const UActorComponent* Component, const FName Tag); }; diff --git a/Source/SaveExtension/Public/LevelStreamingNotifier.h b/Source/SaveExtension/Public/LevelStreamingNotifier.h index f7ffcf5..aee208c 100644 --- a/Source/SaveExtension/Public/LevelStreamingNotifier.h +++ b/Source/SaveExtension/Public/LevelStreamingNotifier.h @@ -1,16 +1,18 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include #include + #include "LevelStreamingNotifier.generated.h" -DECLARE_DELEGATE_OneParam(FLevelNotifierLoaded, ULevelStreaming* ); -DECLARE_DELEGATE_OneParam(FLevelNotifierUnloaded, ULevelStreaming* ); -DECLARE_DELEGATE_OneParam(FLevelNotifierShown, ULevelStreaming* ); -DECLARE_DELEGATE_OneParam(FLevelNotifierHidden, ULevelStreaming* ); + +DECLARE_DELEGATE_OneParam(FLevelNotifierLoaded, ULevelStreaming*); +DECLARE_DELEGATE_OneParam(FLevelNotifierUnloaded, ULevelStreaming*); +DECLARE_DELEGATE_OneParam(FLevelNotifierShown, ULevelStreaming*); +DECLARE_DELEGATE_OneParam(FLevelNotifierHidden, ULevelStreaming*); /** ULevelStreamingNotifier is an adapter that expands UE4's native @@ -22,7 +24,6 @@ class SAVEEXTENSION_API ULevelStreamingNotifier : public UObject GENERATED_BODY() public: - void SetLevelStreaming(ULevelStreaming* InLevelStreaming) { UnBind(); @@ -38,13 +39,24 @@ class SAVEEXTENSION_API ULevelStreamingNotifier : public UObject } } - FLevelNotifierLoaded& OnLevelLoaded() { return LoadedDelegate; } - FLevelNotifierUnloaded& OnLevelUnloaded() { return UnloadedDelegate; } - FLevelNotifierShown& OnLevelShown() { return ShownDelegate; } - FLevelNotifierHidden& OnLevelHidden() { return HiddenDelegate; } + FLevelNotifierLoaded& OnLevelLoaded() + { + return LoadedDelegate; + } + FLevelNotifierUnloaded& OnLevelUnloaded() + { + return UnloadedDelegate; + } + FLevelNotifierShown& OnLevelShown() + { + return ShownDelegate; + } + FLevelNotifierHidden& OnLevelHidden() + { + return HiddenDelegate; + } private: - void UnBind() { if (LevelStreaming.IsValid()) @@ -68,29 +80,33 @@ class SAVEEXTENSION_API ULevelStreamingNotifier : public UObject // Associated Level Streaming TWeakObjectPtr LevelStreaming; - FLevelNotifierLoaded LoadedDelegate; + FLevelNotifierLoaded LoadedDelegate; FLevelNotifierUnloaded UnloadedDelegate; - FLevelNotifierShown ShownDelegate; - FLevelNotifierHidden HiddenDelegate; + FLevelNotifierShown ShownDelegate; + FLevelNotifierHidden HiddenDelegate; UFUNCTION() - void OnLoaded() { + void OnLoaded() + { LoadedDelegate.ExecuteIfBound(LevelStreaming.Get()); } UFUNCTION() - void OnUnloaded() { + void OnUnloaded() + { UnloadedDelegate.ExecuteIfBound(LevelStreaming.Get()); } UFUNCTION() - void OnShown() { + void OnShown() + { ShownDelegate.ExecuteIfBound(LevelStreaming.Get()); } UFUNCTION() - void OnHidden() { + void OnHidden() + { HiddenDelegate.ExecuteIfBound(LevelStreaming.Get()); } }; diff --git a/Source/SaveExtension/Public/LifetimeComponent.h b/Source/SaveExtension/Public/LifetimeComponent.h index 7fe7a00..0475315 100644 --- a/Source/SaveExtension/Public/LifetimeComponent.h +++ b/Source/SaveExtension/Public/LifetimeComponent.h @@ -1,16 +1,17 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include -#include - #include "SaveExtensionInterface.h" #include "SaveManager.h" +#include +#include + #include "LifetimeComponent.generated.h" + DECLARE_DYNAMIC_MULTICAST_DELEGATE(FLifetimeStartSignature); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FLifetimeSavedSignature); @@ -20,7 +21,6 @@ DECLARE_DYNAMIC_MULTICAST_DELEGATE(FLifetimeResumeSignature); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FLifetimeFinishSignature); - /** * Controls the complete saving and loading process */ @@ -33,7 +33,6 @@ class SAVEEXTENSION_API ULifetimeComponent : public UActorComponent, public ISav /* METHODS */ /************************************************************************/ public: - ULifetimeComponent(); virtual void BeginPlay() override; @@ -57,7 +56,6 @@ class SAVEEXTENSION_API ULifetimeComponent : public UActorComponent, public ISav /* EVENTS */ /***********************************************************************/ protected: - // Called once when this actor gets created for the first time. Similar to BeginPlay UPROPERTY(BlueprintAssignable, Category = SaveExtension) FLifetimeStartSignature Start; @@ -76,9 +74,20 @@ class SAVEEXTENSION_API ULifetimeComponent : public UActorComponent, public ISav public: - - FLifetimeStartSignature& OnStart() { return Start; } - FLifetimeSavedSignature& OnSaved() { return Saved; } - FLifetimeResumeSignature& OnResume() { return Resume; } - FLifetimeFinishSignature& OnFinish() { return Finish; } + FLifetimeStartSignature& OnStart() + { + return Start; + } + FLifetimeSavedSignature& OnSaved() + { + return Saved; + } + FLifetimeResumeSignature& OnResume() + { + return Resume; + } + FLifetimeFinishSignature& OnFinish() + { + return Finish; + } }; diff --git a/Source/SaveExtension/Public/Misc/ClassFilter.h b/Source/SaveExtension/Public/Misc/ClassFilter.h index b31896b..7db8940 100644 --- a/Source/SaveExtension/Public/Misc/ClassFilter.h +++ b/Source/SaveExtension/Public/Misc/ClassFilter.h @@ -1,26 +1,27 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "CoreMinimal.h" -#include + #include +#include + #include "ClassFilter.generated.h" + USTRUCT(BlueprintType) struct SAVEEXTENSION_API FSEClassFilter { GENERATED_BODY() private: - // Used from editor side to limit displayed classes UPROPERTY() UClass* BaseClass; public: - /** This classes are allowed (and their children) */ UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization") TSet> AllowedClasses; @@ -30,13 +31,11 @@ struct SAVEEXTENSION_API FSEClassFilter TSet> IgnoredClasses; protected: - UPROPERTY(Transient) mutable TSet BakedAllowedClasses; public: - FSEClassFilter() : FSEClassFilter(UObject::StaticClass()) {} FSEClassFilter(UClass* const BaseClass); @@ -46,13 +45,21 @@ struct SAVEEXTENSION_API FSEClassFilter /** Bakes a set of allowed classes based on the current settings */ void BakeAllowedClasses() const; - FORCEINLINE bool IsClassAllowed(UClass* const Class) const + bool IsAllowed(UClass* Class) const { // Check is a single O(1) pointer hash comparison return BakedAllowedClasses.Contains(Class); } - FORCEINLINE UClass* GetBaseClass() const { return BaseClass; } + bool IsAnyAllowed() const + { + return BakedAllowedClasses.Num() > 0; + } + + UClass* GetBaseClass() const + { + return BaseClass; + } FString ToString(); void FromString(FString String); @@ -62,48 +69,20 @@ struct SAVEEXTENSION_API FSEClassFilter USTRUCT(BlueprintType) -struct FSEActorClassFilter +struct FSEActorClassFilter : public FSEClassFilter { GENERATED_BODY() - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Filter) - FSEClassFilter ClassFilter; - - - FSEActorClassFilter() - : ClassFilter(AActor::StaticClass()) - {} - FSEActorClassFilter(TSubclassOf actorClass) : ClassFilter(actorClass) {} - - /** Bakes a set of allowed classes based on the current settings */ - void BakeAllowedClasses() const { ClassFilter.BakeAllowedClasses(); } - - FORCEINLINE bool IsClassAllowed(UClass* const Class) const - { - return ClassFilter.IsClassAllowed(Class); - } + FSEActorClassFilter() : Super(AActor::StaticClass()) {} + FSEActorClassFilter(TSubclassOf ActorClass) : Super(ActorClass) {} }; USTRUCT(BlueprintType) -struct FSEComponentClassFilter +struct FSEComponentClassFilter : public FSEClassFilter { GENERATED_BODY() - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Filter) - FSEClassFilter ClassFilter; - - - FSEComponentClassFilter() - : ClassFilter(UActorComponent::StaticClass()) - {} - FSEComponentClassFilter(TSubclassOf compClass) : ClassFilter(compClass) {} - - /** Bakes a set of allowed classes based on the current settings */ - void BakeAllowedClasses() const { ClassFilter.BakeAllowedClasses(); } - - FORCEINLINE bool IsClassAllowed(UClass* const Class) const - { - return ClassFilter.IsClassAllowed(Class); - } + FSEComponentClassFilter() : Super(UActorComponent::StaticClass()) {} + FSEComponentClassFilter(TSubclassOf CompClass) : Super(CompClass) {} }; diff --git a/Source/SaveExtension/Public/Misc/SlotHelpers.h b/Source/SaveExtension/Public/Misc/SlotHelpers.h index 31440d8..8acee7b 100644 --- a/Source/SaveExtension/Public/Misc/SlotHelpers.h +++ b/Source/SaveExtension/Public/Misc/SlotHelpers.h @@ -1,12 +1,12 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "SaveFileHelpers.h" + #include #include -#include "FileAdapter.h" - struct FSlotHelpers { @@ -18,9 +18,7 @@ struct FSlotHelpers public: TArray& FoundSlots; - FFindSlotVisitor(TArray& FoundSlots) - : FoundSlots(FoundSlots) - {} + FFindSlotVisitor(TArray& FoundSlots) : FoundSlots(FoundSlots) {} virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override; }; diff --git a/Source/SaveExtension/Public/Misc/TypeTraits.h b/Source/SaveExtension/Public/Misc/TypeTraits.h index e6dc8f1..e466846 100644 --- a/Source/SaveExtension/Public/Misc/TypeTraits.h +++ b/Source/SaveExtension/Public/Misc/TypeTraits.h @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once @@ -6,23 +6,27 @@ template -constexpr bool VariadicContainsType() { +constexpr bool VariadicContainsType() +{ return false; }; template -constexpr bool VariadicContainsType() { +constexpr bool VariadicContainsType() +{ return TIsSame::Value || VariadicContainsType(); }; template -constexpr uint32 GetVariadicTypeIndex() { - return Index+1; +constexpr uint32 GetVariadicTypeIndex() +{ + return Index + 1; }; template -constexpr uint32 GetVariadicTypeIndex() { +constexpr uint32 GetVariadicTypeIndex() +{ if (TIsSame::Value) return Index; else diff --git a/Source/SaveExtension/Public/Multithreading/Delegates.h b/Source/SaveExtension/Public/Multithreading/Delegates.h index 97576dd..7870bca 100644 --- a/Source/SaveExtension/Public/Multithreading/Delegates.h +++ b/Source/SaveExtension/Public/Multithreading/Delegates.h @@ -1,11 +1,11 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include -DECLARE_DELEGATE_OneParam(FOnSlotInfosLoaded, const TArray&); +DECLARE_DELEGATE_OneParam(FOnSlotsLoaded, const TArray&); // @param Amount of slots removed DECLARE_DELEGATE(FOnSlotsDeleted); diff --git a/Source/SaveExtension/Public/Multithreading/DeleteSlotsTask.h b/Source/SaveExtension/Public/Multithreading/DeleteSlotsTask.h index 10e20bd..b3ee532 100644 --- a/Source/SaveExtension/Public/Multithreading/DeleteSlotsTask.h +++ b/Source/SaveExtension/Public/Multithreading/DeleteSlotsTask.h @@ -1,13 +1,14 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include "FileAdapter.h" #include "Multithreading/Delegates.h" -#include "SlotInfo.h" +#include "SaveFileHelpers.h" +#include "SaveSlot.h" -#include #include +#include + class USaveManager; @@ -16,14 +17,13 @@ class USaveManager; * FDeleteSlotsTask * Async task to remove an specific or all slots */ -class FDeleteSlotsTask : public FNonAbandonableTask { +class FDeleteSlotsTask : public FNonAbandonableTask +{ protected: - const USaveManager* const Manager = nullptr; FString SpecificSlotName; public: - bool bSuccess = false; /** All infos Constructor */ @@ -37,6 +37,5 @@ class FDeleteSlotsTask : public FNonAbandonableTask { } private: - - USlotInfo* LoadInfoFromFile(const FString Name) const; + USaveSlot* LoadInfoFromFile(const FString Name) const; }; diff --git a/Source/SaveExtension/Public/Multithreading/LoadFileTask.h b/Source/SaveExtension/Public/Multithreading/LoadFileTask.h index f2f5ef6..24c928a 100644 --- a/Source/SaveExtension/Public/Multithreading/LoadFileTask.h +++ b/Source/SaveExtension/Public/Multithreading/LoadFileTask.h @@ -1,9 +1,11 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "SaveFileHelpers.h" +#include "SaveManager.h" + #include -#include "FileAdapter.h" ///////////////////////////////////////////////////// @@ -12,55 +14,33 @@ class FLoadFileTask : public FNonAbandonableTask { protected: - TWeakObjectPtr Manager; const FString SlotName; - TWeakObjectPtr SlotInfo; - TWeakObjectPtr SlotData; + TWeakObjectPtr LastSlot; + TWeakObjectPtr LastSlotData; + TWeakObjectPtr Slot; -public: - explicit FLoadFileTask(USaveManager* Manager, FStringView SlotName) - : Manager(Manager) - , SlotName(SlotName) - {} - ~FLoadFileTask() - { - if(SlotInfo.IsValid()) - { - SlotInfo->ClearInternalFlags(EInternalObjectFlags::Async); - } - if(SlotData.IsValid()) - { - SlotData->ClearInternalFlags(EInternalObjectFlags::Async); - } - } +public: + explicit FLoadFileTask(USaveManager* Manager, USaveSlot* LastSlot, FStringView SlotName); + ~FLoadFileTask(); - void DoWork() - { - FScopedFileReader FileReader(FFileAdapter::GetSlotPath(SlotName)); - if(FileReader.IsValid()) - { - FSaveFile File; - File.Read(FileReader, false); - SlotInfo = File.CreateAndDeserializeInfo(Manager.Get()); - SlotData = File.CreateAndDeserializeData(Manager.Get()); - } - } + void DoWork(); - USlotInfo* GetInfo() + /** Game thread */ + USaveSlot* GetInfo() const { - return SlotInfo.Get(); + return Slot.Get(); } - USlotData* GetData() + USaveSlotData* GetData() const { - return SlotData.Get(); + return Slot.IsValid()? Slot->GetData() : nullptr; } - FORCEINLINE TStatId GetStatId() const + TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FLoadFileTask, STATGROUP_ThreadPoolAsyncTasks); } diff --git a/Source/SaveExtension/Public/Multithreading/LoadSlotInfosTask.h b/Source/SaveExtension/Public/Multithreading/LoadSlotsTask.h similarity index 50% rename from Source/SaveExtension/Public/Multithreading/LoadSlotInfosTask.h rename to Source/SaveExtension/Public/Multithreading/LoadSlotsTask.h index 9082a13..4a709c9 100644 --- a/Source/SaveExtension/Public/Multithreading/LoadSlotInfosTask.h +++ b/Source/SaveExtension/Public/Multithreading/LoadSlotsTask.h @@ -1,62 +1,58 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include -#include "FileAdapter.h" #include "Multithreading/Delegates.h" +#include "SaveFileHelpers.h" +#include "SaveSlot.h" + +#include -#include "SlotInfo.h" class USaveManager; /** - * FLoadSlotInfosTask + * FLoadSlotsTask * Async task to load one or many slot infos */ -class FLoadSlotInfosTask : public FNonAbandonableTask +class FLoadSlotsTask : public FNonAbandonableTask { protected: - const USaveManager* Manager; const bool bSortByRecent = false; // If not empty, only this specific slot will be loaded const FName SlotName; - TArray LoadedSlots; + TArray LoadedSlots; - FOnSlotInfosLoaded Delegate; + FOnSlotsLoaded Delegate; public: - /** All infos Constructor */ - explicit FLoadSlotInfosTask(const USaveManager* Manager, bool bInSortByRecent, const FOnSlotInfosLoaded& Delegate) + explicit FLoadSlotsTask(const USaveManager* Manager, bool bInSortByRecent, const FOnSlotsLoaded& Delegate) : Manager(Manager) , bSortByRecent(bInSortByRecent) , Delegate(Delegate) {} /** One info Constructor */ - explicit FLoadSlotInfosTask(USaveManager* Manager, FName SlotName) - : Manager(Manager) - , SlotName(SlotName) - {} + explicit FLoadSlotsTask(USaveManager* Manager, FName SlotName) : Manager(Manager), SlotName(SlotName) {} void DoWork(); /** Called after the task has finished */ void AfterFinish(); - const TArray& GetLoadedSlots() const + const TArray& GetLoadedSlots() const { return LoadedSlots; } FORCEINLINE TStatId GetStatId() const { - RETURN_QUICK_DECLARE_CYCLE_STAT(FLoadAllSlotInfosTask, STATGROUP_ThreadPoolAsyncTasks); + RETURN_QUICK_DECLARE_CYCLE_STAT(FLoadAllSlotsTask, STATGROUP_ThreadPoolAsyncTasks); } }; diff --git a/Source/SaveExtension/Public/Multithreading/MTTask.h b/Source/SaveExtension/Public/Multithreading/MTTask.h new file mode 100644 index 0000000..f480bfa --- /dev/null +++ b/Source/SaveExtension/Public/Multithreading/MTTask.h @@ -0,0 +1,29 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "ISaveExtension.h" +#include "LevelFilter.h" + +#include +#include +#include +#include + + +///////////////////////////////////////////////////// +// FSlotDataActorsTask +// Async task to serialize actors from a level. +class FMTTask : public FNonAbandonableTask +{ +public: + /** Used only if Sync */ + UWorld* const World = nullptr; + USaveSlotData* SlotData = nullptr; + + + FMTTask(const bool bIsloading, UWorld* World, USaveSlotData* SlotData) + : World(World) + , SlotData(SlotData) + {} +}; diff --git a/Source/SaveExtension/Public/Serialization/MTTask_SerializeActors.h b/Source/SaveExtension/Public/Multithreading/MTTask_SerializeActors.h similarity index 59% rename from Source/SaveExtension/Public/Serialization/MTTask_SerializeActors.h rename to Source/SaveExtension/Public/Multithreading/MTTask_SerializeActors.h index 702c6d3..14568df 100644 --- a/Source/SaveExtension/Public/Serialization/MTTask_SerializeActors.h +++ b/Source/SaveExtension/Public/Multithreading/MTTask_SerializeActors.h @@ -1,27 +1,25 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include "MTTask_SerializeActors.h" +#include "Multithreading/MTTask.h" +#include "Multithreading/MTTask_SerializeActors.h" +#include "Serialization/LevelRecords.h" +#include "Serialization/Records.h" -#include +#include #include +#include #include -#include -#include "SavePreset.h" - -#include "MTTask.h" -#include "Serialization/Records.h" -#include "Serialization/LevelRecords.h" +class USaveSlotData; -class USlotData; /** Called when game has been saved - * @param SlotInfo the saved slot. Null if save failed + * @param Slot the saved slot. Null if save failed */ -DECLARE_DELEGATE_OneParam(FOnGameSaved, USlotInfo*); +DECLARE_DELEGATE_OneParam(FOnGameSaved, USaveSlot*); ///////////////////////////////////////////////////// @@ -29,30 +27,30 @@ DECLARE_DELEGATE_OneParam(FOnGameSaved, USlotInfo*); // Async task to serialize actors from a level. class FMTTask_SerializeActors : public FMTTask { - const TArray* const LevelActors; - const int32 StartIndex; - const int32 Num; + const TArray* LevelActors; + const int32 StartIndex = 0; + const int32 Num = 0; const bool bStoreGameInstance = false; - /** USE ONLY FOR DUMPING DATA */ FLevelRecord* LevelRecord = nullptr; + const FSELevelFilter* Filter = nullptr; + FActorRecord LevelScriptRecord; TArray ActorRecords; public: - FMTTask_SerializeActors(const UWorld* World, USlotData* SlotData, - const TArray* const InLevelActors, const int32 InStartIndex, const int32 InNum, bool bStoreGameInstance, - FLevelRecord* InLevelRecord, const FSELevelFilter& Filter) - : FMTTask(false, World, SlotData, Filter) - , LevelActors(InLevelActors) - , StartIndex(InStartIndex) - , Num(InNum) + FMTTask_SerializeActors(UWorld* World, USaveSlotData* SlotData, + const TArray* LevelActors, const int32 StartIndex, const int32 Num, + bool bStoreGameInstance, FLevelRecord* LevelRecord, const FSELevelFilter* Filter) + : FMTTask(false, World, SlotData) + , LevelActors(LevelActors) + , StartIndex(StartIndex) + , Num(Num) , bStoreGameInstance(bStoreGameInstance) - , LevelRecord(InLevelRecord) - , LevelScriptRecord{} - , ActorRecords{} + , LevelRecord(LevelRecord) + , Filter(Filter) { // No apparent performance benefit // ActorRecords.Reserve(Num); @@ -61,7 +59,8 @@ class FMTTask_SerializeActors : public FMTTask void DoWork(); /** Called after task has completed to recover resulting information */ - void DumpData() { + void DumpData() + { if (LevelScriptRecord.IsValid()) LevelRecord->LevelScript = LevelScriptRecord; @@ -75,12 +74,12 @@ class FMTTask_SerializeActors : public FMTTask } private: - void SerializeGameInstance(); /** Serializes an actor into this Actor Record */ bool SerializeActor(const AActor* Actor, FActorRecord& Record) const; /** Serializes the components of an actor into a provided Actor Record */ - inline void SerializeActorComponents(const AActor* Actor, FActorRecord& ActorRecord, int8 indent = 0) const; + inline void SerializeActorComponents( + const AActor* Actor, FActorRecord& ActorRecord, int8 indent = 0) const; }; diff --git a/Source/SaveExtension/Public/Multithreading/SaveFileTask.h b/Source/SaveExtension/Public/Multithreading/SaveFileTask.h index 2162649..8887cae 100644 --- a/Source/SaveExtension/Public/Multithreading/SaveFileTask.h +++ b/Source/SaveExtension/Public/Multithreading/SaveFileTask.h @@ -1,34 +1,32 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "SaveFileHelpers.h" + #include -#include "FileAdapter.h" ///////////////////////////////////////////////////// // FSaveFileTask // Async task to save a File -class FSaveFileTask : public FNonAbandonableTask { +class FSaveFileTask : public FNonAbandonableTask +{ protected: - - USlotInfo* Info; - USlotData* Data; + USaveSlot* Info; const FString SlotName; const bool bUseCompression; public: - - FSaveFileTask(USlotInfo* Info, USlotData* Data, const FString& InSlotName, const bool bInUseCompression) : - Info(Info), - Data(Data), - SlotName(InSlotName), - bUseCompression(bInUseCompression) + FSaveFileTask(USaveSlot* Info, const FString& InSlotName, const bool bInUseCompression) + : Info(Info) + , SlotName(InSlotName) + , bUseCompression(bInUseCompression) {} void DoWork() { - FFileAdapter::SaveFile(SlotName, Info, Data, bUseCompression); + FSaveFileHelpers::SaveFile(SlotName, Info, bUseCompression); } FORCEINLINE TStatId GetStatId() const diff --git a/Source/SaveExtension/Public/Multithreading/ScopedTaskManager.h b/Source/SaveExtension/Public/Multithreading/ScopedTaskManager.h index 0c11117..6a47800 100644 --- a/Source/SaveExtension/Public/Multithreading/ScopedTaskManager.h +++ b/Source/SaveExtension/Public/Multithreading/ScopedTaskManager.h @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once @@ -11,9 +11,7 @@ class ITaskHolder public: virtual bool Tick() = 0; virtual void Cancel(bool bFinishSynchronously) = 0; - virtual ~ITaskHolder() - { - } + virtual ~ITaskHolder() {} }; template @@ -27,17 +25,13 @@ class FTaskHolder : public FAsyncTask, public ITaskHolder bool bNotified = false; FFinishedEvent _OnFinished; - FTaskHolder() : ITaskHolder(), Super() - { - } - virtual ~FTaskHolder() - { - } + FTaskHolder() : ITaskHolder(), Super() {} + virtual ~FTaskHolder() {} template - FTaskHolder(ArgTypes&&... CtrArgs) : Super(Forward(CtrArgs)...), ITaskHolder() - { - } + FTaskHolder(ArgTypes&&... CtrArgs) : Super(Forward(CtrArgs)...) + , ITaskHolder() + {} auto& OnFinished(TFunction&)> Delegate) { @@ -74,7 +68,6 @@ class FTaskHolder : public FAsyncTask, public ITaskHolder } private: - void TryNotifyFinish() { if (!bNotified) @@ -91,9 +84,7 @@ class FScopedTaskList TArray> Tasks; public: - FScopedTaskList() - { - } + FScopedTaskList() {} template inline FTaskHolder& CreateTask(ArgTypes&&... CtrArgs) @@ -107,7 +98,9 @@ class FScopedTaskList void Tick() { // Tick all running tasks and remove the ones that finished - Tasks.RemoveAllSwap([](auto& Task) { return Task->Tick(); }); + Tasks.RemoveAllSwap([](auto& Task) { + return Task->Tick(); + }); } void CancelAll() diff --git a/Source/SaveExtension/Public/SaveExtensionInterface.h b/Source/SaveExtension/Public/SaveExtensionInterface.h index 32cec10..2554d8f 100644 --- a/Source/SaveExtension/Public/SaveExtensionInterface.h +++ b/Source/SaveExtension/Public/SaveExtensionInterface.h @@ -1,13 +1,15 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "LevelFilter.h" + #include -#include "LevelFilter.h" #include "SaveExtensionInterface.generated.h" + UINTERFACE(Category = SaveExtension, BlueprintType) class SAVEEXTENSION_API USaveExtensionInterface : public UInterface { @@ -16,11 +18,9 @@ class SAVEEXTENSION_API USaveExtensionInterface : public UInterface class SAVEEXTENSION_API ISaveExtensionInterface { - GENERATED_BODY() public: - /** BP API **/ // Event called when Save process starts diff --git a/Source/SaveExtension/Public/FileAdapter.h b/Source/SaveExtension/Public/SaveFileHelpers.h similarity index 60% rename from Source/SaveExtension/Public/FileAdapter.h rename to Source/SaveExtension/Public/SaveFileHelpers.h index 238cb5c..53fff6d 100644 --- a/Source/SaveExtension/Public/FileAdapter.h +++ b/Source/SaveExtension/Public/SaveFileHelpers.h @@ -1,22 +1,21 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include +#include "ISaveExtension.h" + #include +#include #include #include -#include +#include #include #include -#include - -#include "ISaveExtension.h" +#include -class USavePreset; -class USlotInfo; -class USlotData; +class USaveSlot; +class USaveSlotData; class FMemoryReader; class FMemoryWriter; @@ -33,9 +32,18 @@ struct FScopedFileWriter delete Writer; } - FArchive& GetArchive() { return *Writer; } - bool IsValid() const { return Writer != nullptr; } - bool IsError() const { return Writer && (Writer->IsError() || Writer->IsCriticalError()); } + FArchive& GetArchive() + { + return *Writer; + } + bool IsValid() const + { + return Writer != nullptr; + } + bool IsError() const + { + return Writer && (Writer->IsError() || Writer->IsCriticalError()); + } }; @@ -52,8 +60,14 @@ struct FScopedFileReader delete Reader; } - FArchive& GetArchive() { return *Reader; } - bool IsValid() const { return Reader != nullptr; } + FArchive& GetArchive() + { + return *Reader; + } + bool IsValid() const + { + return Reader != nullptr; + } }; @@ -62,7 +76,7 @@ struct FSaveFile { int32 FileTypeTag = 0; int32 SaveGameFileVersion = 0; - FPackageFileVersion PackageFileUEVersion {}; + FPackageFileVersion PackageFileUEVersion{}; FEngineVersion SavedEngineVersion; int32 CustomVersionFormat = int32(ECustomVersionSerializationFormat::Unknown); FCustomVersionContainer CustomVersions; @@ -83,29 +97,27 @@ struct FSaveFile void Read(FScopedFileReader& Reader, bool bSkipData); void Write(FScopedFileWriter& Writer, bool bCompressData); - void SerializeInfo(USlotInfo* SlotInfo); - void SerializeData(USlotData* SlotData); - USlotInfo* CreateAndDeserializeInfo(const UObject* Outer) const; - USlotData* CreateAndDeserializeData(const UObject* Outer) const; + void SerializeInfo(USaveSlot* Slot); + void SerializeData(USaveSlotData* SlotData); }; /** Based on GameplayStatics to add multi-threading */ -class SAVEEXTENSION_API FFileAdapter +class SAVEEXTENSION_API FSaveFileHelpers { public: - - static bool SaveFile(FStringView SlotName, USlotInfo* Info, USlotData* Data, const bool bUseCompression); + static bool SaveFile(FStringView SlotName, USaveSlot* Slot, const bool bUseCompression); // Not safe for Multi-threading - static bool LoadFile(FStringView SlotName, USlotInfo*& Info, USlotData*& Data, bool bLoadData, const UObject* Outer); + static bool LoadFile(FStringView SlotName, USaveSlot*& Slot, bool bLoadData, const UObject* Outer); static bool DeleteFile(FStringView SlotName); - static bool DoesFileExist(FStringView SlotName); + static bool FileExists(FStringView SlotName); static const FString& GetSaveFolder(); static FString GetSlotPath(FStringView SlotName); static FString GetThumbnailPath(FStringView SlotName); - static void DeserializeObject(UObject*& Object, FStringView ClassName, const UObject* Outer, const TArray& Bytes); + static UObject* DeserializeObject( + UObject* Hint, FStringView ClassName, const UObject* Outer, const TArray& Bytes); }; diff --git a/Source/SaveExtension/Public/SaveManager.h b/Source/SaveExtension/Public/SaveManager.h index 5af97e8..672eaa9 100644 --- a/Source/SaveExtension/Public/SaveManager.h +++ b/Source/SaveExtension/Public/SaveManager.h @@ -1,19 +1,15 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "Delegates.h" -#include "LatentActions/DeleteSlotsAction.h" -#include "LatentActions/LoadGameAction.h" -#include "LatentActions/SaveGameAction.h" #include "LevelStreamingNotifier.h" -#include "Multithreading/ScopedTaskManager.h" #include "Multithreading/Delegates.h" +#include "Multithreading/ScopedTaskManager.h" #include "SaveExtensionInterface.h" -#include "SavePreset.h" -#include "Serialization/SlotDataTask.h" -#include "SlotData.h" -#include "SlotInfo.h" +#include "SaveSlot.h" +#include "SaveSlotData.h" +#include "Serialization/SEDataTask.h" #include #include @@ -26,11 +22,28 @@ #include "SaveManager.generated.h" -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGameSavedMC, USlotInfo*, SlotInfo); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGameLoadedMC, USlotInfo*, SlotInfo); +struct FLatentActionInfo; -struct FLatentActionInfo; +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGameSavedMC, USaveSlot*, Slot); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnGameLoadedMC, USaveSlot*, Slot); + + +UENUM() +enum class ESEContinue : uint8 +{ + InProgress UMETA(Hidden), + Continue +}; + +UENUM() +enum class ESEContinueOrFail : uint8 +{ + InProgress UMETA(Hidden), + Continue, + Failed +}; + USTRUCT(BlueprintType) struct FScreenshotSize @@ -56,30 +69,21 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi { GENERATED_BODY() - friend USlotDataTask; + friend FSEDataTask; /************************************************************************/ /* PROPERTIES */ /************************************************************************/ public: - // Loaded from settings. Can be changed at runtime - UPROPERTY(Transient, BlueprintReadWrite, Category=SaveManager) + UPROPERTY(Transient, BlueprintReadWrite, Category = SaveManager) bool bTickWithGameWorld = false; private: - UPROPERTY(Transient) - USavePreset* ActivePreset; - - /** Currently loaded SaveInfo. SaveInfo stores basic information about a saved game. Played time, levels, - * progress, etc. */ - UPROPERTY() - USlotInfo* CurrentInfo; - - /** Currently loaded SaveData. SaveData stores all serialized info about the world. */ + // Active SaveSlot used for current saves (load on start, periodic save, etc) UPROPERTY() - USlotData* CurrentData; + TObjectPtr ActiveSlot; /** The game instance to which this save manager is owned. */ TWeakObjectPtr OwningGameInstance; @@ -92,8 +96,7 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi UPROPERTY(Transient) TArray> SubscribedInterfaces; - UPROPERTY(Transient) - TArray Tasks; + TArray> Tasks; /************************************************************************/ @@ -120,40 +123,34 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi bool SaveSlot(FName SlotName, bool bOverrideIfNeeded = true, bool bScreenshot = false, const FScreenshotSize Size = {}, FOnGameSaved OnSaved = {}); - /** Save the Game info an SlotInfo */ - bool SaveSlot(const USlotInfo* SlotInfo, bool bOverrideIfNeeded = true, bool bScreenshot = false, - const FScreenshotSize Size = {}, FOnGameSaved OnSaved = {}); - - /** Save the Game into an specified slot id */ - bool SaveSlot(int32 SlotId, bool bOverrideIfNeeded = true, bool bScreenshot = false, + /** Save the Game info an Slot */ + bool SaveSlot(const USaveSlot* Slot, bool bOverrideIfNeeded = true, bool bScreenshot = false, const FScreenshotSize Size = {}, FOnGameSaved OnSaved = {}); /** Save the currently loaded Slot */ - bool SaveCurrentSlot(bool bScreenshot = false, const FScreenshotSize Size = {}, FOnGameSaved OnSaved = {}); + bool SaveCurrentSlot( + bool bScreenshot = false, const FScreenshotSize Size = {}, FOnGameSaved OnSaved = {}); /** Load game from a file name */ bool LoadSlot(FName SlotName, FOnGameLoaded OnLoaded = {}); - /** Load game from a slot Id */ - bool LoadSlot(int32 SlotId, FOnGameLoaded OnLoaded = {}); - - /** Load game from a SlotInfo */ - bool LoadSlot(const USlotInfo* SlotInfo, FOnGameLoaded OnLoaded = {}); + /** Load game from a Slot */ + bool LoadSlot(const USaveSlot* Slot, FOnGameLoaded OnLoaded = {}); /** Reload the currently loaded slot if any */ bool ReloadCurrentSlot(FOnGameLoaded OnLoaded = {}) { - return LoadSlot(CurrentInfo, MoveTemp(OnLoaded)); + return LoadSlot(ActiveSlot, MoveTemp(OnLoaded)); } /** - * Find all saved games and return their SlotInfos + * Find all saved games and return their Slots * @param bSortByRecent Should slots be ordered by save date? * @param SaveInfos All saved games found on disk */ - void LoadAllSlotInfos(bool bSortByRecent, FOnSlotInfosLoaded Delegate); - void LoadAllSlotInfosSync(bool bSortByRecent, FOnSlotInfosLoaded Delegate); + void FindAllSlots(bool bSortByRecent, FOnSlotsLoaded Delegate); + void FindAllSlotsSync(bool bSortByRecent, TArray& Slots); /** Delete a saved game on an specified slot name * Performance: Interacts with disk, can be slow @@ -170,135 +167,92 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi // with the normal C++ API /** Save the Game into an specified Slot */ - UFUNCTION(Category = "SaveExtension|Saving", BlueprintCallable, meta = (AdvancedDisplay = "bScreenshot, Size", - DisplayName = "Save Slot", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPSaveSlot(FName SlotName, bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& Result, FLatentActionInfo LatentInfo, bool bOverrideIfNeeded = true); - - /** Save the Game into an specified Slot */ - UFUNCTION(Category = "SaveExtension|Saving", BlueprintCallable, meta = (AdvancedDisplay = "bScreenshot, Size", - DisplayName = "Save Slot by Id", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPSaveSlotById(int32 SlotId, bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& Result, FLatentActionInfo LatentInfo, bool bOverrideIfNeeded = true); + UFUNCTION(Category = "SaveExtension|Saving", BlueprintCallable, + meta = (AdvancedDisplay = "bScreenshot, Size", DisplayName = "Save Slot by Name", Latent, + LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) + void BPSaveSlotByName(FName SlotName, bool bScreenshot, + UPARAM(meta = (EditCondition = bScreenshot)) const FScreenshotSize Size, ESEContinueOrFail& Result, + FLatentActionInfo LatentInfo, bool bOverrideIfNeeded = true); /** Save the Game to a Slot */ - UFUNCTION(Category = "SaveExtension|Saving", BlueprintCallable, meta = (AdvancedDisplay = "bScreenshot, Size", - DisplayName = "Save Slot by Info", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPSaveSlotByInfo(const USlotInfo* SlotInfo, bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& Result, FLatentActionInfo LatentInfo, bool bOverrideIfNeeded = true); + UFUNCTION(Category = "SaveExtension|Saving", BlueprintCallable, + meta = (AdvancedDisplay = "bScreenshot, Size", DisplayName = "Save Slot", Latent, + LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) + void BPSaveSlot(const USaveSlot* Slot, bool bScreenshot, + UPARAM(meta = (EditCondition = bScreenshot)) const FScreenshotSize Size, ESEContinueOrFail& Result, + FLatentActionInfo LatentInfo, bool bOverrideIfNeeded = true); /** Save the currently loaded Slot */ UFUNCTION(BlueprintCallable, Category = "SaveExtension|Saving", meta = (AdvancedDisplay = "bScreenshot, Size", DisplayName = "Save Current Slot", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPSaveCurrentSlot(bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& Result, FLatentActionInfo LatentInfo) + void BPSaveCurrentSlot(bool bScreenshot, + UPARAM(meta = (EditCondition = bScreenshot)) const FScreenshotSize Size, ESEContinueOrFail& Result, + FLatentActionInfo LatentInfo) { - BPSaveSlotByInfo(CurrentInfo, bScreenshot, Size, Result, MoveTemp(LatentInfo), true); + BPSaveSlot(ActiveSlot, bScreenshot, Size, Result, MoveTemp(LatentInfo), true); } /** Load game from a slot name */ UFUNCTION(BlueprintCallable, Category = "SaveExtension|Loading", - meta = (DisplayName = "Load Slot", Latent, LatentInfo = "LatentInfo", - ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPLoadSlot(FName SlotName, ELoadGameResult& Result, FLatentActionInfo LatentInfo); + meta = (DisplayName = "Load Slot by Name", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", + UnsafeDuringActorConstruction)) + void BPLoadSlotByName(FName SlotName, ESEContinueOrFail& Result, FLatentActionInfo LatentInfo); - /** Load game from a slot Id */ + /** Load game from a Slot */ UFUNCTION(BlueprintCallable, Category = "SaveExtension|Loading", - meta = (DisplayName = "Load Slot by Id", Latent, LatentInfo = "LatentInfo", + meta = (DisplayName = "Load Slot", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPLoadSlotById(int32 SlotId, ELoadGameResult& Result, FLatentActionInfo LatentInfo); - - /** Load game from a SlotInfo */ - UFUNCTION(BlueprintCallable, Category = "SaveExtension|Loading", - meta = (DisplayName = "Load Slot by Info", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", - UnsafeDuringActorConstruction)) - void BPLoadSlotByInfo(const USlotInfo* SlotInfo, ELoadGameResult& Result, FLatentActionInfo LatentInfo); + void BPLoadSlot(const USaveSlot* Slot, ESEContinueOrFail& Result, FLatentActionInfo LatentInfo); /** Reload the currently loaded slot if any */ UFUNCTION(BlueprintCallable, Category = "SaveExtension|Loading", meta = (DisplayName = "Reload Current Slot", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPReloadCurrentSlot(ELoadGameResult& Result, FLatentActionInfo LatentInfo) + void BPReloadCurrentSlot(ESEContinueOrFail& Result, FLatentActionInfo LatentInfo) { - BPLoadSlotByInfo(CurrentInfo, Result, MoveTemp(LatentInfo)); + BPLoadSlot(ActiveSlot, Result, MoveTemp(LatentInfo)); } /** - * Find all saved games and return their SlotInfos + * Find all saved games and return their Slots * @param bSortByRecent Should slots be ordered by save date? * @param SaveInfos All saved games found on disk */ UFUNCTION(BlueprintCallable, Category = "SaveExtension", meta = (Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", - DisplayName = "Load All Slot Infos")) - void BPLoadAllSlotInfos(const bool bSortByRecent, TArray& SaveInfos, ELoadInfoResult& Result, + DisplayName = "Find All Slots")) + void BPFindAllSlots(const bool bSortByRecent, TArray& Slots, ESEContinue& Result, struct FLatentActionInfo LatentInfo); - /** Delete a saved game on an specified slot Id - * Performance: Interacts with disk, can be slow - */ - UFUNCTION(BlueprintCallable, Category = "SaveExtension") - FORCEINLINE bool DeleteSlotById(int32 SlotId) - { - if (!IsValidSlot(SlotId)) - { - return false; - } - return DeleteSlot(GetSlotNameFromId(SlotId)); - } - /** Delete all saved slots from disk, loaded or not */ UFUNCTION(BlueprintCallable, Category = "SaveExtension", meta = (Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", DisplayName = "Delete All Slots")) - void BPDeleteAllSlots(EDeleteSlotsResult& Result, FLatentActionInfo LatentInfo); - - UFUNCTION(BlueprintPure, Category = "SaveExtension") - USavePreset* BPGetPreset() const - { - return ActivePreset; - } + void BPDeleteAllSlots(ESEContinue& Result, FLatentActionInfo LatentInfo); /** BLUEPRINTS & C++ API */ public: - /** Delete a saved game on an specified slot * Performance: Interacts with disk, can be slow */ UFUNCTION(BlueprintCallable, Category = "SaveExtension") - bool DeleteSlot(USlotInfo* Slot) - { - return Slot? DeleteSlot(Slot->FileName) : false; - } - - /** Get the currently loaded SlotInfo. If game was never loaded returns a new SlotInfo */ - UFUNCTION(BlueprintPure, Category = "SaveExtension") - FORCEINLINE USlotInfo* GetCurrentInfo() + bool DeleteSlot(USaveSlot* Slot) { - TryInstantiateInfo(); - return CurrentInfo; + return Slot ? DeleteSlot(Slot->FileName) : false; } - /** Get the currently loaded SlotData. If game was never loaded returns an empty SlotData */ + /** Get the currently loaded Slot. If game was never loaded returns a new Slot */ UFUNCTION(BlueprintPure, Category = "SaveExtension") - FORCEINLINE USlotData* GetCurrentData() - { - TryInstantiateInfo(); - return CurrentData; - } - - /** - * Load and return an SlotInfo by Id if it exists - * Performance: Interacts with disk, could be slow if called frequently - * @param SlotId Id of the SlotInfo to be loaded - * @return the SlotInfo associated with an Id - */ - UFUNCTION(BlueprintCallable, Category = "SaveExtension|Slots") - FORCEINLINE USlotInfo* GetSlotInfoById(int32 SlotId) + FORCEINLINE USaveSlot* GetActiveSlot() { - return LoadInfo(SlotId); + AssureActiveSlot(); + return ActiveSlot; } UFUNCTION(BlueprintCallable, Category = "SaveExtension|Slots") - FORCEINLINE USlotInfo* GetSlotInfo(FName SlotName) + FORCEINLINE USaveSlot* GetSlot(FName SlotName) { return LoadInfo(SlotName); } @@ -309,14 +263,6 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi UFUNCTION(BlueprintPure, Category = "SaveExtension|Slots") bool IsSlotSaved(FName SlotName) const; - /** Check if an slot exists on disk - * @return true if the slot exists - */ - UFUNCTION(BlueprintPure, Category = "SaveExtension|Slots") - bool IsSlotSavedById(int32 SlotId) const - { - return IsValidSlot(SlotId)? IsSlotSaved(GetSlotNameFromId(SlotId)) : false; - } /** Check if currently playing in a saved slot * @return true if currently playing in a saved slot @@ -324,40 +270,17 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi UFUNCTION(BlueprintPure, Category = "SaveExtension|Slots") FORCEINLINE bool IsInSlot() const { - return CurrentInfo && CurrentData; + return ActiveSlot != nullptr; } - /** - * Set the preset to be used for saving and loading - * @return true if the preset was set successfully - */ - UFUNCTION(BlueprintCallable, Category = "SaveExtension") - USavePreset* SetActivePreset(TSubclassOf PresetClass); - - const USavePreset* GetPreset() const; - - void TryInstantiateInfo(bool bForced = false); - - UFUNCTION(BlueprintPure, Category = "SaveExtension") - FName GetSlotNameFromId(const int32 SlotId) const; - - bool IsValidSlot(const int32 Slot) const; + void AssureActiveSlot(TSubclassOf ActiveSlotClass = {}, bool bForced = false); - void __SetCurrentInfo(USlotInfo* NewInfo) + void AssignActiveSlot(USaveSlot* NewInfo) { - CurrentInfo = NewInfo; + ActiveSlot = NewInfo; } - void __SetCurrentData(USlotData* NewData) - { - CurrentData = NewData; - } - - USlotInfo* LoadInfo(FName SlotName); - USlotInfo* LoadInfo(uint32 SlotId) - { - return IsValidSlot(SlotId)? LoadInfo(GetSlotNameFromId(SlotId)) : nullptr; - } + USaveSlot* LoadInfo(FName FileName); protected: bool CanLoadOrSave(); @@ -374,15 +297,15 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi void OnLevelLoaded(ULevelStreaming* StreamingLevel) {} - USlotDataTask* CreateTask(TSubclassOf TaskType); - - template - TaskType* CreateTask() + template + TaskType& CreateTask() { - return Cast(CreateTask(TaskType::StaticClass())); + return static_cast( + *Tasks.Add_GetRef(MakeUnique(this, ActiveSlot)) + ); } - void FinishTask(USlotDataTask* Task); + void FinishTask(FSEDataTask* Task); public: bool HasTasks() const @@ -431,10 +354,10 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi UFUNCTION(Category = SaveExtension, BlueprintCallable) void UnsubscribeFromEvents(const TScriptInterface& Interface); - void OnSaveBegan(const FSELevelFilter& Filter); - void OnSaveFinished(const FSELevelFilter& Filter, const bool bError); - void OnLoadBegan(const FSELevelFilter& Filter); - void OnLoadFinished(const FSELevelFilter& Filter, const bool bError); + void OnSaveBegan(); + void OnSaveFinished(const bool bError); + void OnLoadBegan(); + void OnLoadFinished(const bool bError); private: void OnMapLoadStarted(const FString& MapName); @@ -449,115 +372,55 @@ class SAVEEXTENSION_API USaveManager : public UGameInstanceSubsystem, public FTi public: /** Get the global save manager */ static USaveManager* Get(const UObject* ContextObject); - - - /***********************************************************************/ - /* DEPRECATED */ - /***********************************************************************/ - - UFUNCTION(Category = "SaveExtension|Saving", BlueprintCallable, meta = (DeprecatedFunction, DeprecationMessage="Use 'Save Slot by Id' instead.", - AdvancedDisplay = "bScreenshot, Size", DisplayName = "Save Slot to Id", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPSaveSlotToId(int32 SlotId, bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& Result, FLatentActionInfo LatentInfo, bool bOverrideIfNeeded = true) - { - BPSaveSlotById(SlotId, bScreenshot, Size, Result, LatentInfo, bOverrideIfNeeded); - } - - UFUNCTION(BlueprintCallable, Category = "SaveExtension|Loading", meta = (DeprecatedFunction, DeprecationMessage="Use 'Load Slot by Id' instead.", - DisplayName = "Load Slot from Id", Latent, LatentInfo = "LatentInfo", ExpandEnumAsExecs = "Result", UnsafeDuringActorConstruction)) - void BPLoadSlotFromId(int32 SlotId, ELoadGameResult& Result, FLatentActionInfo LatentInfo) - { - BPLoadSlotById(SlotId, Result, LatentInfo); - } }; -inline bool USaveManager::SaveSlot(int32 SlotId, bool bOverrideIfNeeded, bool bScreenshot, - const FScreenshotSize Size, FOnGameSaved OnSaved) -{ - if (!IsValidSlot(SlotId)) - { - SELog(GetPreset(), "Invalid Slot. Cant go under 0 or exceed MaxSlots.", true); - return false; - } - return SaveSlot(GetSlotNameFromId(SlotId), bOverrideIfNeeded, bScreenshot, Size, OnSaved); -} - -inline bool USaveManager::SaveSlot(const USlotInfo* SlotInfo, bool bOverrideIfNeeded, bool bScreenshot, +inline bool USaveManager::SaveSlot(const USaveSlot* Slot, bool bOverrideIfNeeded, bool bScreenshot, const FScreenshotSize Size, FOnGameSaved OnSaved) { - if (!SlotInfo) + if (!Slot) { return false; } - return SaveSlot(SlotInfo->FileName, bOverrideIfNeeded, bScreenshot, Size, OnSaved); + return SaveSlot(Slot->FileName, bOverrideIfNeeded, bScreenshot, Size, OnSaved); } -inline void USaveManager::BPSaveSlotById(int32 SlotId, bool bScreenshot, const FScreenshotSize Size, ESaveGameResult& Result, FLatentActionInfo LatentInfo, bool bOverrideIfNeeded) +inline void USaveManager::BPSaveSlot(const USaveSlot* Slot, bool bScreenshot, + const FScreenshotSize Size, ESEContinueOrFail& Result, struct FLatentActionInfo LatentInfo, + bool bOverrideIfNeeded) { - if (!IsValidSlot(SlotId)) + if (!Slot) { - SELog(GetPreset(), "Invalid Slot. Cant go under 0 or exceed MaxSlots.", true); - Result = ESaveGameResult::Failed; + Result = ESEContinueOrFail::Failed; return; } - BPSaveSlot(GetSlotNameFromId(SlotId), bScreenshot, Size, Result, MoveTemp(LatentInfo), bOverrideIfNeeded); -} - -inline void USaveManager::BPSaveSlotByInfo(const USlotInfo* SlotInfo, bool bScreenshot, const FScreenshotSize Size, - ESaveGameResult& Result, struct FLatentActionInfo LatentInfo, bool bOverrideIfNeeded) -{ - if (!SlotInfo) - { - Result = ESaveGameResult::Failed; - return; - } - BPSaveSlot(SlotInfo->FileName, bScreenshot, Size, Result, MoveTemp(LatentInfo), bOverrideIfNeeded); + BPSaveSlotByName(Slot->FileName, bScreenshot, Size, Result, MoveTemp(LatentInfo), bOverrideIfNeeded); } /** Save the currently loaded Slot */ inline bool USaveManager::SaveCurrentSlot(bool bScreenshot, const FScreenshotSize Size, FOnGameSaved OnSaved) { - return SaveSlot(CurrentInfo, true, bScreenshot, Size, OnSaved); + return SaveSlot(ActiveSlot, true, bScreenshot, Size, OnSaved); } -inline bool USaveManager::LoadSlot(int32 SlotId, FOnGameLoaded OnLoaded) +inline bool USaveManager::LoadSlot(const USaveSlot* Slot, FOnGameLoaded OnLoaded) { - if (!IsValidSlot(SlotId)) + if (!Slot) { - SELog(GetPreset(), "Invalid Slot. Can't go under 0 or exceed MaxSlots.", true); return false; } - return LoadSlot(GetSlotNameFromId(SlotId), OnLoaded); + return LoadSlot(Slot->FileName, OnLoaded); } -inline bool USaveManager::LoadSlot(const USlotInfo* SlotInfo, FOnGameLoaded OnLoaded) +inline void USaveManager::BPLoadSlot( + const USaveSlot* Slot, ESEContinueOrFail& Result, FLatentActionInfo LatentInfo) { - if (!SlotInfo) + if (!Slot) { - return false; - } - return LoadSlot(SlotInfo->FileName, OnLoaded); -} - -inline void USaveManager::BPLoadSlotById( - int32 SlotId, ELoadGameResult& Result, struct FLatentActionInfo LatentInfo) -{ - BPLoadSlot(GetSlotNameFromId(SlotId), Result, MoveTemp(LatentInfo)); -} - -inline void USaveManager::BPLoadSlotByInfo(const USlotInfo* SlotInfo, ELoadGameResult& Result, FLatentActionInfo LatentInfo) -{ - if (!SlotInfo) - { - Result = ELoadGameResult::Failed; + Result = ESEContinueOrFail::Failed; return; } - BPLoadSlot(SlotInfo->FileName, Result, MoveTemp(LatentInfo)); -} - -inline bool USaveManager::IsValidSlot(const int32 Slot) const -{ - return GetPreset()->IsValidId(Slot); + BPLoadSlotByName(Slot->FileName, Result, MoveTemp(LatentInfo)); } inline void USaveManager::IterateSubscribedInterfaces(TFunction&& Callback) @@ -588,7 +451,7 @@ inline bool USaveManager::IsTickable() const inline UWorld* USaveManager::GetTickableGameObjectWorld() const { - return bTickWithGameWorld? GetWorld() : nullptr; + return bTickWithGameWorld ? GetWorld() : nullptr; } inline TStatId USaveManager::GetStatId() const diff --git a/Source/SaveExtension/Public/SavePreset.h b/Source/SaveExtension/Public/SavePreset.h deleted file mode 100644 index 16713bb..0000000 --- a/Source/SaveExtension/Public/SavePreset.h +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Engine/DataAsset.h" -#include "UObject/NoExportTypes.h" - -#include "Misc/ClassFilter.h" -#include "SavePreset.generated.h" - - -/** -* Specifies the behavior while saving or loading -*/ -UENUM() -enum class ESaveASyncMode : uint8 { - OnlySync, - LoadAsync, - SaveAsync, - SaveAndLoadAsync -}; - -class USlotInfo; -class USlotData; - - -/** - * What to save, how to save it, when, every x minutes, what info file, what data file, save by level streaming? - */ -UCLASS(ClassGroup = SaveExtension, Blueprintable) -class SAVEEXTENSION_API USavePreset : public UObject -{ - GENERATED_BODY() - -public: - - /** - * Specify the SaveInfo object to be used with this preset - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) - TSubclassOf SlotInfoClass; - - /** - * Specify the SaveData object to be used with this preset - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) - TSubclassOf SlotDataClass; - - /** Maximum amount of saved slots at the same time */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Gameplay, meta = (ClampMin = "0")) - int32 MaxSlots = 0; - - /** If checked, will attempt to Save Game to first Slot found, timed event. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) - bool bAutoSave = true; - - /** Interval in seconds for auto saving */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay, meta = (EditCondition = "bAutoSave", UIMin = "15", UIMax = "3600")) - int32 AutoSaveInterval = 120.f; - - /** If checked, will attempt to Save Game to first Slot found, timed event. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay, meta = (EditCondition = "bAutoSave")) - bool bSaveOnExit = false; - - /** If checked, will attempt to Load Game from last Slot found, when game starts */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay) - bool bAutoLoad = true; - - /** - * If checked, will print messages to Log, and Viewport if DebugInScreen is enabled. - * Ignored in package or Shipping mode. - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay, AdvancedDisplay) - bool bDebug = false; - - /** - * If checked and Debug is enabled, will print messages to Viewport. - * Ignored in package or Shipping mode. - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Gameplay, AdvancedDisplay, meta = (EditCondition = "bDebug")) - bool bDebugInScreen = true; - - - /** If true save files will be compressed - * Performance: Can add from 10ms to 20ms to loading and saving (estimate) but reduce file sizes making them up to 30x smaller - */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Serialization) - bool bUseCompression = true; - - /** If true will store the game instance */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Serialization) - bool bStoreGameInstance = true; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization|Actors") - FSEActorClassFilter ActorFilter; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization|Actors", meta = (PinHiddenByDefault, InlineEditConditionToggle)) - bool bUseLoadActorFilter = false; - - /** If enabled, this filter will be used while loading instead of "ActorFilter" */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization|Actors", meta = (EditCondition="bUseLoadActorFilter")) - FSEActorClassFilter LoadActorFilter; - - /** If true will store ActorComponents depending on the filters */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization|Components") - bool bStoreComponents = true; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization|Components") - FSEComponentClassFilter ComponentFilter; - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization|Components", meta = (PinHiddenByDefault, InlineEditConditionToggle)) - bool bUseLoadComponentFilter = false; - - /** If enabled, this filter will be used while loading instead of "ComponentFilter" */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Serialization|Components", meta = (EditCondition = "bUseLoadComponentFilter")) - FSEComponentClassFilter LoadComponentFilter; - -public: - - /** Serialization will be multi-threaded between all available cores. */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Asynchronous") - ESaveASyncMode MultithreadedSerialization = ESaveASyncMode::SaveAsync; - - /** Split serialization between multiple frames. Ignored if MultithreadedSerialization is used - * Currently only implemented on Loading - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Asynchronous") - ESaveASyncMode FrameSplittedSerialization = ESaveASyncMode::OnlySync; - - - /** Max milliseconds to use every frame in an asynchronous operation. - * If running at 60Fps (16.6ms), loading or saving asynchronously will add MaxFrameMS: - * 16.6ms + 5MS = 21.6ms -> 46Fps - * This means gameplay will not be stopped nor have frame drops while saving or loading. Works best for non multi-threaded platforms - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Asynchronous", meta = (UIMin="3", UIMax="10")) - float MaxFrameMs = 5.f; - - /** Files will be loaded or saved on a secondary thread while game continues */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Asynchronous") - ESaveASyncMode MultithreadedFiles = ESaveASyncMode::SaveAndLoadAsync; - - -protected: - - /** If true, will Save and Load levels when they are shown or hidden. - * This includes level streaming and world composition. - */ - UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Level Streaming") - bool bSaveAndLoadSublevels = true; - - -public: - - USavePreset(); - - UFUNCTION(BlueprintNativeEvent, Category = "Slots", meta = (DisplayName="Get Slot Name from Id")) - void BPGetSlotNameFromId(int32 Id, FName& Name) const; - -protected: - - virtual void GetSlotNameFromId(int32 Id, FName& Name) const; - - - /** HELPERS */ -public: - - int32 GetMaxIds() const; - - bool IsValidId(int32 Id) const; - - UFUNCTION(BlueprintPure, Category = SavePreset) - FSEActorClassFilter& GetActorFilter(bool bIsLoading) - { - return (bIsLoading && bUseLoadActorFilter)? LoadActorFilter : ActorFilter; - } - - const FSEActorClassFilter& GetActorFilter(bool bIsLoading) const - { - return (bIsLoading && bUseLoadActorFilter)? LoadActorFilter : ActorFilter; - } - - UFUNCTION(BlueprintPure, Category = SavePreset) - FSEComponentClassFilter& GetComponentFilter(bool bIsLoading) - { - return (bIsLoading && bUseLoadComponentFilter) ? LoadComponentFilter : ComponentFilter; - } - - const FSEComponentClassFilter& GetComponentFilter(bool bIsLoading) const - { - return (bIsLoading && bUseLoadActorFilter) ? LoadComponentFilter : ComponentFilter; - } - - bool IsMTSerializationLoad() const - { - return MultithreadedSerialization == ESaveASyncMode::LoadAsync || MultithreadedSerialization == ESaveASyncMode::SaveAndLoadAsync; - } - bool IsMTSerializationSave() const - { - return MultithreadedSerialization == ESaveASyncMode::SaveAsync || MultithreadedSerialization == ESaveASyncMode::SaveAndLoadAsync; - } - - ESaveASyncMode GetFrameSplitSerialization() const { return FrameSplittedSerialization; } - float GetMaxFrameMs() const { return MaxFrameMs; } - - bool IsFrameSplitLoad() const - { - return !IsMTSerializationLoad() && (FrameSplittedSerialization == ESaveASyncMode::LoadAsync || FrameSplittedSerialization == ESaveASyncMode::SaveAndLoadAsync); - } - bool IsFrameSplitSave() const - { - return !IsMTSerializationSave() && (FrameSplittedSerialization == ESaveASyncMode::SaveAsync || FrameSplittedSerialization == ESaveASyncMode::SaveAndLoadAsync); - } - - bool IsMTFilesLoad() const - { - return MultithreadedFiles == ESaveASyncMode::LoadAsync || MultithreadedFiles == ESaveASyncMode::SaveAndLoadAsync; - } - bool IsMTFilesSave() const - { - return MultithreadedFiles == ESaveASyncMode::SaveAsync || MultithreadedFiles == ESaveASyncMode::SaveAndLoadAsync; - } - - struct FSELevelFilter ToFilter() const; -}; - - -inline int32 USavePreset::GetMaxIds() const -{ - return MaxSlots <= 0 ? 16384 : MaxSlots; -} - -inline bool USavePreset::IsValidId(int32 Id) const -{ - const int32 MaxIds = GetMaxIds(); - return Id >= 0 && (MaxIds <= 0 || Id < MaxIds); -} diff --git a/Source/SaveExtension/Public/SaveSettings.h b/Source/SaveExtension/Public/SaveSettings.h index 33ad07d..214d652 100644 --- a/Source/SaveExtension/Public/SaveSettings.h +++ b/Source/SaveExtension/Public/SaveSettings.h @@ -1,8 +1,8 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include "SavePreset.h" +#include "SaveSlot.h" #include #include @@ -10,32 +10,20 @@ #include "SaveSettings.generated.h" -UCLASS(ClassGroup = SaveExtension, defaultconfig, config = Game, meta=(DisplayName="Save Extension")) +UCLASS(ClassGroup = SaveExtension, defaultconfig, config = Game, meta = (DisplayName = "Save Extension")) class SAVEEXTENSION_API USaveSettings : public UDeveloperSettings { - GENERATED_BODY() - -protected: - - UPROPERTY(EditAnywhere, Category = "Save Extension", Config, meta = (DisplayName = "Preset")) - TSubclassOf Preset; + GENERATED_BODY() public: - - // If true SaveManager will tick with the world. If game is paused, saving process may be interrupted. - UPROPERTY(EditAnywhere, Category = "Save Extension", Config) - bool bTickWithGameWorld = false; - - - USavePreset* CreatePreset(UObject* Outer) const; + // Active Slot classes are initialized as active slots. + // Active saves represent an always valid save game in memory that can be filled periodically or + // saved. + UPROPERTY(EditAnywhere, Category = "Save Extension", Config) + TSubclassOf ActiveSlot; + + // If true SaveManager will tick with the world, otherwise with the engine. If true, the saving process + // may be interrupted if the game is paused. + UPROPERTY(EditAnywhere, Category = "Save Extension", Config) + bool bTickWithGameWorld = false; }; - - -inline USavePreset* USaveSettings::CreatePreset(UObject* Outer) const -{ - if(UClass* PresetClass = Preset.Get()) - { - return NewObject(Outer, PresetClass); - } - return NewObject(Outer); -} diff --git a/Source/SaveExtension/Public/SaveSlot.h b/Source/SaveExtension/Public/SaveSlot.h new file mode 100644 index 0000000..1ec8d33 --- /dev/null +++ b/Source/SaveExtension/Public/SaveSlot.h @@ -0,0 +1,252 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "SaveSlotData.h" + +#include +#include +#include + +#include "SaveSlot.generated.h" + +struct FSELevelFilter; + + +/** + * Specifies the behavior while saving or loading + */ +UENUM() +enum class ESEAsyncMode : uint8 +{ + SaveAndLoadSync, + LoadAsync, + SaveAsync, + SaveAndLoadAsync +}; + + +USTRUCT(Blueprintable) +struct FSaveSlotStats +{ + GENERATED_BODY() + + /** Played time since this saved game was started. Not related to slots, slots can change */ + UPROPERTY(BlueprintReadOnly, Category = Slot) + FTimespan PlayedTime = FTimespan::Zero(); + + /** Played time since this saved game was created */ + UPROPERTY(BlueprintReadOnly, Category = Slot) + FTimespan SlotPlayedTime = FTimespan::Zero(); + + /** Last date at which this slot was saved. */ + UPROPERTY(BlueprintReadOnly, Category = Slot) + FDateTime SaveDate = FDateTime::Now(); + + /** Date at which this slot was loaded. */ + UPROPERTY(BlueprintReadOnly, Transient, Category = Slot) + FDateTime LoadDate; +}; + + +/** + * USaveSlot stores information that needs to be accessible WITHOUT loading the game. + * Works like a common SaveGame object + * E.g: Dates, played time, progress, level + */ +UCLASS(ClassGroup = SaveExtension, hideCategories = ("Activation", "Actor Tick", "Actor", "Input", + "Rendering", "Replication", "Socket", "Thumbnail")) +class SAVEEXTENSION_API USaveSlot : public USaveGame +{ + GENERATED_BODY() + +public: + /** Begin Settings */ + + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings") + TSubclassOf DataClass = USaveSlotData::StaticClass(); + + /** Maximum amount of saved slots that use this class. 0 is infinite (~16000) */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings", meta = (ClampMin = "0")) + int32 MaxSlots = 0; + + /** If checked, will attempt to Save Game to first Slot found, timed event. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Settings") + bool bPeriodicSave = false; + + /** Interval in seconds for auto saving */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Settings", + meta = (EditCondition = "bPeriodicSave", UIMin = "15", UIMax = "3600")) + int32 PeriodicSaveInterval = 120.f; + + /** If checked, will attempt to Save Game to current Slot found, timed event. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Settings") + bool bSaveOnClose = false; + + /** If checked, will attempt to Load Game from last Slot found, when game starts */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Settings") + bool bLoadOnStart = false; + + /** If true save files will be compressed + * Performance: Can add from 10ms to 20ms to loading and saving (estimate) but reduce file sizes making + * them up to 30x smaller + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Settings|Serialization") + bool bUseCompression = true; + + /** If true will store the game instance */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = Serialization) + bool bStoreGameInstance = true; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Settings|Serialization") + FSEActorClassFilter ActorFilter; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Settings|Serialization") + FSEComponentClassFilter ComponentFilter; + + /** If true, will Save and Load levels when they are shown or hidden. + * This includes level streaming and world composition. + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings|Level Streaming") + bool bSaveAndLoadSublevels = true; + + /** Serialization will be multi-threaded between all available cores. */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings|Async") + ESEAsyncMode MultithreadedSerialization = ESEAsyncMode::SaveAndLoadSync; + + /** Split serialization between multiple frames. Ignored if MultithreadedSerialization is used + * Currently only implemented on Loading + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings|Async") + ESEAsyncMode FrameSplittedSerialization = ESEAsyncMode::SaveAndLoadSync; + + /** Max milliseconds to use every frame in an asynchronous operation. + * If running at 60Fps (16.6ms), loading or saving asynchronously will add MaxFrameMS: + * 16.6ms + 5MS = 21.6ms -> 46Fps + * This means gameplay will not be stopped nor have frame drops while saving or loading. Works best for + * non multi-threaded platforms + */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings|Async", meta = (UIMin = "3", UIMax = "10")) + float MaxFrameMs = 5.f; + + /** Files will be loaded or saved on a secondary thread while game continues */ + UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Settings|Async") + ESEAsyncMode MultithreadedFiles = ESEAsyncMode::SaveAndLoadAsync; + + /** + * If checked, will print messages to Log, and Viewport if DebugInScreen is enabled. + * Ignored in package or Shipping mode. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings|Debug", AdvancedDisplay) + bool bDebug = false; + + /** + * If checked and Debug is enabled, will print messages to Viewport. + * Ignored in package or Shipping mode. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Settings|Debug", AdvancedDisplay, + meta = (EditCondition = "bDebug")) + bool bDebugInScreen = true; + + /** End Settings */ + + +public: + /** Slot where this SaveInfo and its saveData are saved */ + UPROPERTY(SaveGame, BlueprintReadWrite, Category = Slot) + FName FileName = TEXT("Default"); + + UPROPERTY(SaveGame, BlueprintReadWrite, Category = Slot) + FText DisplayName; + + /** Root Level where this Slot was saved */ + UPROPERTY(SaveGame, BlueprintReadOnly, Category = Slot) + FName Map; + + UPROPERTY(SaveGame, BlueprintReadWrite, Category = Slot) + FSaveSlotStats Stats; + +protected: + UPROPERTY(SaveGame) + FString ThumbnailPath; + + /** Thumbnail gets cached here the first time it is requested */ + UPROPERTY(Transient) + TObjectPtr CachedThumbnail; + + UPROPERTY(Transient, BlueprintReadOnly, Category = Slot) + TObjectPtr Data; + + +public: + void PostInitProperties() override; + + /** Returns this slot's thumbnail if any */ + UFUNCTION(BlueprintCallable, Category = Slot) + UTexture2D* GetThumbnail() const; + + /** Captures a thumbnail for the current slot */ + bool CaptureThumbnail(const int32 Width = 640, const int32 Height = 360); + + + USaveSlotData* GetData() const + { + return Data; + } + + // Internal use only recommended + void AssignData(USaveSlotData* NewData) + { + Data = NewData; + } + + UFUNCTION(BlueprintPure, Category = Slot) + int32 GetMaxIndexes() const; + + UFUNCTION(BlueprintPure, Category = Slot) + bool IsValidIndex(int32 Index) const; + + /** Internal Usage. Will be called when an screenshot is captured */ + void _SetThumbnailPath(const FString& Path); + + /** Internal Usage. Will be called to remove previous thumbnail */ + FString _GetThumbnailPath() + { + return ThumbnailPath; + } + +public: + UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = Slot) + bool SetIndex(int32 Index); + + UFUNCTION(BlueprintPure, BlueprintNativeEvent, Category = Slot) + int32 GetIndex() const; + +protected: + virtual bool OnSetIndex(int32 Index); + virtual int32 OnGetIndex() const; + +public: + bool IsMTSerializationLoad() const; + bool IsMTSerializationSave() const; + + ESEAsyncMode GetFrameSplitSerialization() const; + float GetMaxFrameMs() const; + + bool IsFrameSplitLoad() const; + bool IsFrameSplitSave() const; + + bool IsMTFilesLoad() const; + bool IsMTFilesSave() const; + + UFUNCTION(BlueprintPure, Category = SaveSlot) + bool IsLoadingOrSaving() const; + + // Called for every level before being saved or loaded + UFUNCTION(BlueprintNativeEvent, Category = Slot) + void GetLevelFilter(bool bIsLoading, FSELevelFilter& OutFilter) const; + +private: + // Called for every level before being saved or loaded + virtual void OnGetLevelFilter(bool bIsLoading, FSELevelFilter& OutFilter) const; +}; diff --git a/Source/SaveExtension/Public/SlotData.h b/Source/SaveExtension/Public/SaveSlotData.h similarity index 55% rename from Source/SaveExtension/Public/SlotData.h rename to Source/SaveExtension/Public/SaveSlotData.h index 1cf8f1d..e53e9b6 100644 --- a/Source/SaveExtension/Public/SlotData.h +++ b/Source/SaveExtension/Public/SaveSlotData.h @@ -1,54 +1,42 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once -#include "ISaveExtension.h" +#include "Serialization/LevelRecords.h" +#include "Serialization/Records.h" - #include -#include +#include #include +#include #include #include -#include "Serialization/Records.h" -#include "Serialization/LevelRecords.h" - -#include "SlotData.generated.h" +#include "SaveSlotData.generated.h" /** - * USaveData stores all information that can be accessible only while the game is loaded. + * USaveSlotData stores all information that can be accessible only while the game is loaded. * Works like a common SaveGame object * E.g: Items, Quests, Enemies, World Actors, AI, Physics */ -UCLASS(ClassGroup = SaveExtension, hideCategories = ("Activation", "Actor Tick", "Actor", "Input", "Rendering", "Replication", "Socket", "Thumbnail")) -class SAVEEXTENSION_API USlotData : public USaveGame +UCLASS(ClassGroup = SaveExtension, hideCategories = ("Activation", "Actor Tick", "Actor", "Input", + "Rendering", "Replication", "Socket", "Thumbnail")) +class SAVEEXTENSION_API USaveSlotData : public USaveGame { GENERATED_BODY() public: - - USlotData() : Super() {} - - - /** Full Name of the Map where this SlotData was saved */ - UPROPERTY(Category = SaveData, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) - FName Map; - /** Game world time since game started in seconds */ - UPROPERTY(Category = SaveData, BlueprintReadOnly) + UPROPERTY(SaveGame, Category = SaveData, BlueprintReadOnly) float TimeSeconds; /** Records * All serialized information to be saved or loaded * Serialized manually for performance */ - bool bStoreGameInstance = false; FObjectRecord GameInstance; - - FSELevelFilter GeneralLevelFilter; - FPersistentLevelRecord MainLevel; + FPersistentLevelRecord RootLevel; TArray SubLevels; diff --git a/Source/SaveExtension/Public/Serialization/LevelRecords.h b/Source/SaveExtension/Public/Serialization/LevelRecords.h index 4dbcf9a..232adae 100644 --- a/Source/SaveExtension/Public/Serialization/LevelRecords.h +++ b/Source/SaveExtension/Public/Serialization/LevelRecords.h @@ -1,14 +1,15 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once +#include "LevelFilter.h" +#include "Records.h" + #include -#include #include +#include -#include "Records.h" -#include "LevelFilter.h" #include "LevelRecords.generated.h" @@ -18,22 +19,26 @@ struct FLevelRecord : public FBaseRecord { GENERATED_BODY() - bool bOverrideGeneralFilter = false; - // Filter is used if bOverrideGeneralFilter is true - FSELevelFilter Filter; - /** Record of the Level Script Actor */ FActorRecord LevelScript; /** Records of the World Actors */ TArray Actors; + /** Not-serialized. Assigned before loading and saving by the SaveSlot */ + FSELevelFilter Filter; + + /** Not-serialized. During saving or loading points to the live actor */ + TArray>> RecordsToActors; FLevelRecord() : Super() {} virtual bool Serialize(FArchive& Ar) override; - bool IsValid() const { return !Name.IsNone(); } + bool IsValid() const + { + return !Name.IsNone(); + } void CleanRecords(); }; diff --git a/Source/SaveExtension/Public/Serialization/MTTask.h b/Source/SaveExtension/Public/Serialization/MTTask.h deleted file mode 100644 index d29eeae..0000000 --- a/Source/SaveExtension/Public/Serialization/MTTask.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "ISaveExtension.h" - -#include -#include -#include -#include - -#include "SavePreset.h" -#include "LevelFilter.h" - - -///////////////////////////////////////////////////// -// FSlotDataActorsTask -// Async task to serialize actors from a level. -class FMTTask : public FNonAbandonableTask { -protected: - - /** Used only if Sync */ - const UWorld* const World; - USlotData* SlotData; - - // Locally cached settings - const FSELevelFilter& Filter; - - - FMTTask(const bool bIsloading, const UWorld* InWorld, USlotData* InSlotData, const FSELevelFilter& Filter) - : World(InWorld) - , SlotData(InSlotData) - , Filter(Filter) - {} -}; diff --git a/Source/SaveExtension/Public/Serialization/Records.h b/Source/SaveExtension/Public/Serialization/Records.h index c41c40b..8c3704f 100644 --- a/Source/SaveExtension/Public/Serialization/Records.h +++ b/Source/SaveExtension/Public/Serialization/Records.h @@ -1,15 +1,16 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include -#include #include +#include #include "Records.generated.h" -class USlotData; + +class USaveSlotData; USTRUCT() @@ -31,11 +32,19 @@ struct FBaseRecord virtual ~FBaseRecord() {} }; -template<> +template <> struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 -{ enum { WithSerializer = true }; }; +{ + enum + { + WithSerializer = true + }; +}; -FORCEINLINE bool operator==(const FBaseRecord& A, const FBaseRecord& B) { return A.Name == B.Name; } +inline bool operator==(const FBaseRecord& A, const FBaseRecord& B) +{ + return A.Name == B.Name; +} /** Represents a serialized Object */ @@ -58,10 +67,10 @@ struct FObjectRecord : public FBaseRecord bool IsValid() const { - return !Name.IsNone() && Class && Data.Num() > 0; + return !Name.IsNone() && Class; } - FORCEINLINE bool operator== (const UObject* Other) const + bool operator==(const UObject* Other) const { return Other && Name == Other->GetFName() && Class == Other->GetClass(); } diff --git a/Source/SaveExtension/Public/Serialization/SEDataTask.h b/Source/SaveExtension/Public/Serialization/SEDataTask.h new file mode 100644 index 0000000..23d70ee --- /dev/null +++ b/Source/SaveExtension/Public/Serialization/SEDataTask.h @@ -0,0 +1,116 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "ISaveExtension.h" +#include "LevelFilter.h" +#include "SaveSlotData.h" + +#include +#include +#include + + +class USaveManager; + +enum class ESETaskType : uint8 +{ + None, + Load, + Save +}; + +/** + * Base class for managing the data of SaveSlot file + */ +struct FSEDataTask +{ + ESETaskType Type = ESETaskType::None; +private: + bool bRunning = false; + bool bFinished = false; + bool bSucceeded = false; + +protected: + + TObjectPtr Manager; + TObjectPtr SlotData; + float MaxFrameMs = 0.f; + + +public: + FSEDataTask(USaveManager* Manager, USaveSlot* Slot, ESETaskType Type) + : Type(Type) + , Manager(Manager) + , SlotData(Slot->GetData()) + , MaxFrameMs(Slot->GetMaxFrameMs()) + {} + virtual ~FSEDataTask() = default; + + FSEDataTask& Start(); + + virtual void Tick(float DeltaTime) {} + + void Finish(bool bSuccess); + + bool IsRunning() const + { + return bRunning; + } + bool IsFinished() const + { + return bFinished; + } + bool IsSucceeded() const + { + return IsFinished() && bSucceeded; + } + bool IsFailed() const + { + return IsFinished() && !bSucceeded; + } + bool IsScheduled() const; + + virtual void OnTick(float DeltaTime) {} + +protected: + virtual void OnStart() {} + + virtual void OnFinish(bool bSuccess) {} + + void BakeAllFilters(); + + FLevelRecord* FindLevelRecord(const ULevelStreaming* Level) const; + + float GetTimeMilliseconds() const + { + return FPlatformTime::ToMilliseconds(FPlatformTime::Cycles()); + } + + UWorld* GetWorld() const; +}; + + +///////////////////////////////////////////////////// +// FSlotDataActorsTask +// Async task to serialize actors from a level. +class FSlotDataActorsTask : public FNonAbandonableTask +{ +protected: + const bool bIsSync; + /** USE ONLY IF SYNC */ + const UWorld* const World; + /** USE ONLY IF SYNC */ + USaveSlotData* SlotData; + + const FSELevelFilter& Filter; + + + FSlotDataActorsTask( + const bool bInIsSync, const UWorld* InWorld, USaveSlotData* InSlotData, const FSELevelFilter& Filter) + : bIsSync(bInIsSync) + , World(InWorld) + , SlotData(InSlotData) + , Filter(Filter) + {} +}; diff --git a/Source/SaveExtension/Public/Serialization/SlotDataTask_Loader.h b/Source/SaveExtension/Public/Serialization/SEDataTask_Load.h similarity index 66% rename from Source/SaveExtension/Public/Serialization/SlotDataTask_Loader.h rename to Source/SaveExtension/Public/Serialization/SEDataTask_Load.h index 7e5e911..d7c7c6f 100644 --- a/Source/SaveExtension/Public/Serialization/SlotDataTask_Loader.h +++ b/Source/SaveExtension/Public/Serialization/SEDataTask_Load.h @@ -1,23 +1,20 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "Delegates.h" #include "ISaveExtension.h" #include "Multithreading/LoadFileTask.h" -#include "SavePreset.h" -#include "SlotInfo.h" -#include "SlotData.h" -#include "SlotDataTask.h" +#include "SaveSlot.h" +#include "SaveSlotData.h" +#include "SEDataTask.h" #include +#include #include #include -#include #include -#include "SlotDataTask_Loader.generated.h" - enum class ELoadDataTaskState : uint8 { @@ -32,22 +29,18 @@ enum class ELoadDataTaskState : uint8 }; /** -* Manages the loading process of a SaveData file -*/ -UCLASS() -class USlotDataTask_Loader : public USlotDataTask + * Manages the loading process of a SaveData file + */ +struct FSEDataTask_Load : public FSEDataTask { - GENERATED_BODY() - +protected: FName SlotName; - UPROPERTY() - USlotInfo* NewSlotInfo; + TObjectPtr Slot; FOnGameLoaded Delegate; protected: - // Async variables TWeakObjectPtr CurrentLevel; TWeakObjectPtr CurrentSLevel; @@ -63,39 +56,45 @@ class USlotDataTask_Loader : public USlotDataTask public: + FSEDataTask_Load(USaveManager* Manager, USaveSlot* Slot) + : FSEDataTask(Manager, Slot, ESETaskType::Load) + {} + ~FSEDataTask_Load(); - USlotDataTask_Loader() : Super() {} - - auto Setup(FName InSlotName) + auto& Setup(FName InSlotName) { SlotName = InSlotName; - return this; + return *this; } - auto Bind(const FOnGameLoaded& OnLoaded) { Delegate = OnLoaded; return this; } + auto& Bind(const FOnGameLoaded& OnLoaded) + { + Delegate = OnLoaded; + return *this; + } void OnMapLoaded(); private: - virtual void OnStart() override; virtual void Tick(float DeltaTime) override; virtual void OnFinish(bool bSuccess) override; - virtual void BeginDestroy() override; void StartDeserialization(); /** Spawns Actors hat were saved but which actors are not in the world. */ - void RespawnActors(const TArray& Records, const ULevel* Level); + void RespawnActors(const TArray& Records, const ULevel* Level, FLevelRecord& LevelRecord); protected: - //~ Begin Files void StartLoadingData(); - USlotData* GetLoadedData() const; - FORCEINLINE const bool IsDataLoaded() const { return LoadDataTask && LoadDataTask->IsDone(); }; + USaveSlotData* GetLoadedData() const; + const bool IsDataLoaded() const + { + return LoadDataTask && LoadDataTask->IsDone(); + }; //~ End Files @@ -114,21 +113,17 @@ class USlotDataTask_Loader : public USlotDataTask void PrepareAllLevels(); void PrepareLevel(const ULevel* Level, FLevelRecord& LevelRecord); - /** Deserializes all Level actors. */ - inline void DeserializeLevel_Actor(AActor* const Actor, const FLevelRecord& LevelRecord, const FSELevelFilter& Filter); - void FindNextAsyncLevel(ULevelStreaming*& OutLevelStreaming) const; -private: - /** Deserializes Game Instance Object and its Properties. - Requires 'SaveGameMode' flag to be used. */ + * Requires 'SaveGameInstance' flag to be used. + */ void DeserializeGameInstance(); /** Serializes an actor into this Actor Record */ - bool DeserializeActor(AActor* Actor, const FActorRecord& Record, const FSELevelFilter& Filter); + bool DeserializeActor(AActor* Actor, const FActorRecord& ActorRecord, const FLevelRecord& LevelRecord); /** Deserializes the components of an actor from a provided Record */ - void DeserializeActorComponents(AActor* Actor, const FActorRecord& ActorRecord, const FSELevelFilter& Filter, int8 indent = 0); + void DeserializeActorComponents(AActor* Actor, const FActorRecord& ActorRecord, const FLevelRecord& LevelRecord, int8 indent = 0); /** END Deserialization */ }; \ No newline at end of file diff --git a/Source/SaveExtension/Public/Serialization/SEDataTask_LoadLevel.h b/Source/SaveExtension/Public/Serialization/SEDataTask_LoadLevel.h new file mode 100644 index 0000000..f2a5665 --- /dev/null +++ b/Source/SaveExtension/Public/Serialization/SEDataTask_LoadLevel.h @@ -0,0 +1,32 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "ISaveExtension.h" +#include "SEDataTask_Load.h" + + +/** + * Manages the serializing process of a single level + */ +struct FSEDataTask_LoadLevel : public FSEDataTask_Load +{ + TObjectPtr StreamingLevel; + + +public: + FSEDataTask_LoadLevel(USaveManager* Manager, USaveSlot* Slot) + : FSEDataTask_Load(Manager, Slot) + {} + + auto& Setup(ULevelStreaming* InStreamingLevel) + { + StreamingLevel = InStreamingLevel; + return *this; + } + +private: + virtual void OnStart() override; + + virtual void DeserializeASyncLoop(float StartMS = 0.0f) override; +}; diff --git a/Source/SaveExtension/Public/Serialization/SlotDataTask_Saver.h b/Source/SaveExtension/Public/Serialization/SEDataTask_Save.h similarity index 57% rename from Source/SaveExtension/Public/Serialization/SlotDataTask_Saver.h rename to Source/SaveExtension/Public/Serialization/SEDataTask_Save.h index 46fb9b0..32939cd 100644 --- a/Source/SaveExtension/Public/Serialization/SlotDataTask_Saver.h +++ b/Source/SaveExtension/Public/Serialization/SEDataTask_Save.h @@ -1,68 +1,59 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include "Delegates.h" #include "ISaveExtension.h" -#include "MTTask_SerializeActors.h" +#include "Multithreading/MTTask_SerializeActors.h" #include "Multithreading/SaveFileTask.h" -#include "SavePreset.h" -#include "SlotData.h" -#include "SlotDataTask.h" +#include "SaveSlotData.h" +#include "SEDataTask.h" +#include +#include #include +#include #include #include -#include #include -#include -#include - -#include "SlotDataTask_Saver.generated.h" /** -* Manages the saving process of a SaveData file -*/ -UCLASS() -class USlotDataTask_Saver : public USlotDataTask + * Manages the saving process of a SaveData file + */ +struct FSEDataTask_Save : public FSEDataTask { - GENERATED_BODY() - - bool bOverride; - bool bSaveThumbnail; + bool bOverride = false; + bool bSaveThumbnail = false; FName SlotName; - int32 Width; - int32 Height; + int32 Width = 0; + int32 Height = 0; FOnGameSaved Delegate; protected: - - UPROPERTY() - USlotInfo* SlotInfo; + TObjectPtr Slot; /** Start Async variables */ TWeakObjectPtr CurrentLevel; TWeakObjectPtr CurrentSLevel; - int32 CurrentActorIndex; TArray> CurrentLevelActors; /** End Async variables */ /** Begin AsyncTasks */ TArray> Tasks; - FAsyncTask* SaveTask; + FAsyncTask* SaveTask = nullptr; /** End AsyncTasks */ public: - - USlotDataTask_Saver() - : USlotDataTask() - , SaveTask(nullptr) + FSEDataTask_Save(USaveManager* Manager, USaveSlot* Slot) + : FSEDataTask(Manager, Slot, ESETaskType::Save) {} + ~FSEDataTask_Save(); - auto* Setup(FName InSlotName, bool bInOverride, bool bInSaveThumbnail, const int32 InWidth, const int32 InHeight) + auto& Setup( + FName InSlotName, bool bInOverride, bool bInSaveThumbnail, const int32 InWidth, const int32 InHeight) { SlotName = InSlotName; bOverride = bInOverride; @@ -70,33 +61,36 @@ class USlotDataTask_Saver : public USlotDataTask Width = InWidth; Height = InHeight; - return this; + return *this; } - auto* Bind(const FOnGameSaved& OnSaved) { Delegate = OnSaved; return this; } + auto& Bind(const FOnGameSaved& OnSaved) + { + Delegate = OnSaved; + return *this; + } // Where all magic happens virtual void OnStart() override; virtual void Tick(float DeltaTime) override; virtual void OnFinish(bool bSuccess) override; - virtual void BeginDestroy() override; protected: - /** BEGIN Serialization */ /** Serializes all world actors. */ void SerializeWorld(); void PrepareAllLevels(const TArray& Levels); + void PrepareLevel(const ULevel* Level, FLevelRecord& LevelRecord); - void SerializeLevelSync(const ULevel* Level, int32 AssignedThreads, const ULevelStreaming* StreamingLevel = nullptr); + void SerializeLevelSync( + const ULevel* Level, int32 AssignedThreads, const ULevelStreaming* StreamingLevel = nullptr); /** END Serialization */ void RunScheduledTasks(); private: - /** BEGIN FileSaving */ void SaveFile(); /** End FileSaving */ diff --git a/Source/SaveExtension/Public/Serialization/SEDataTask_SaveLevel.h b/Source/SaveExtension/Public/Serialization/SEDataTask_SaveLevel.h new file mode 100644 index 0000000..ede2300 --- /dev/null +++ b/Source/SaveExtension/Public/Serialization/SEDataTask_SaveLevel.h @@ -0,0 +1,34 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#pragma once + +#include "ISaveExtension.h" +#include "SEDataTask_Save.h" + + +/** + * Manages the serializing process of a single level + */ +struct FSEDataTask_SaveLevel : public FSEDataTask_Save +{ + TObjectPtr StreamingLevel; + + +public: + FSEDataTask_SaveLevel(USaveManager* Manager, USaveSlot* Slot) + : FSEDataTask_Save(Manager, Slot) + {} + + auto& Setup(ULevelStreaming* InStreamingLevel) + { + StreamingLevel = InStreamingLevel; + return *this; + } + +private: + virtual void OnStart() override; + virtual void OnFinish(bool bSuccess) override + { + SELog(Slot, "Finished Serializing level", FColor::Green); + } +}; diff --git a/Source/SaveExtension/Public/Serialization/SlotDataTask.h b/Source/SaveExtension/Public/Serialization/SlotDataTask.h deleted file mode 100644 index 387ab59..0000000 --- a/Source/SaveExtension/Public/Serialization/SlotDataTask.h +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "ISaveExtension.h" - -#include -#include -#include - -#include "SlotData.h" -#include "LevelFilter.h" - -#include "SlotDataTask.generated.h" - -class USaveManager; - - -/** -* Base class for managing a SaveData file -*/ -UCLASS() -class USlotDataTask : public UObject -{ - GENERATED_BODY() - -private: - - uint8 bRunning : 1; - uint8 bFinished : 1; - uint8 bSucceeded : 1; - -protected: - - UPROPERTY() - USlotData* SlotData; - - UPROPERTY() - const USavePreset* Preset; - - UPROPERTY() - float MaxFrameMs = 0.f; - -public: - - USlotDataTask() : Super(), bRunning(false), bFinished(false) {} - - void Prepare(USlotData* InSaveData, const USavePreset& InPreset) - { - SlotData = InSaveData; - Preset = &InPreset; - MaxFrameMs = Preset->GetMaxFrameMs(); - } - - USlotDataTask* Start(); - - virtual void Tick(float DeltaTime) {} - - void Finish(bool bSuccess); - - bool IsRunning() const { return bRunning; } - bool IsFinished() const { return bFinished; } - bool IsSucceeded() const { return IsFinished() && bSucceeded; } - bool IsFailed() const { return IsFinished() && !bSucceeded; } - bool IsScheduled() const; - - virtual void OnTick(float DeltaTime) {} - -protected: - - virtual void OnStart() {} - - virtual void OnFinish(bool bSuccess) {} - - USaveManager* GetManager() const; - - void BakeAllFilters(); - - const FSELevelFilter& GetGeneralFilter() const; - const FSELevelFilter& GetLevelFilter(const FLevelRecord& Level) const; - - FLevelRecord* FindLevelRecord(const ULevelStreaming* Level) const; - - //~ Begin UObject Interface - virtual UWorld* GetWorld() const override; - //~ End UObject Interface - - FORCEINLINE float GetTimeMilliseconds() const { - return FPlatformTime::ToMilliseconds(FPlatformTime::Cycles()); - } -}; - - -///////////////////////////////////////////////////// -// FSlotDataActorsTask -// Async task to serialize actors from a level. -class FSlotDataActorsTask : public FNonAbandonableTask { -protected: - - const bool bIsSync; - /** USE ONLY IF SYNC */ - const UWorld* const World; - /** USE ONLY IF SYNC */ - USlotData* SlotData; - - const FSELevelFilter& Filter; - - - FSlotDataActorsTask(const bool bInIsSync, const UWorld* InWorld, USlotData* InSlotData, const FSELevelFilter& Filter) : - bIsSync(bInIsSync), - World(InWorld), - SlotData(InSlotData), - Filter(Filter) - {} -}; diff --git a/Source/SaveExtension/Public/Serialization/SlotDataTask_LevelLoader.h b/Source/SaveExtension/Public/Serialization/SlotDataTask_LevelLoader.h deleted file mode 100644 index 17d0bd1..0000000 --- a/Source/SaveExtension/Public/Serialization/SlotDataTask_LevelLoader.h +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "ISaveExtension.h" - -#include "SavePreset.h" - -#include "SlotDataTask_Loader.h" -#include "SlotDataTask_LevelLoader.generated.h" - - -/** -* Manages the serializing process of a single level -*/ -UCLASS() -class USlotDataTask_LevelLoader : public USlotDataTask_Loader -{ - GENERATED_BODY() - - - UPROPERTY() - ULevelStreaming* StreamingLevel; - -public: - - auto Setup(ULevelStreaming* InStreamingLevel) - { - StreamingLevel = InStreamingLevel; - return this; - } - -private: - - virtual void OnStart() override; - - virtual void DeserializeASyncLoop(float StartMS = 0.0f) override; -}; diff --git a/Source/SaveExtension/Public/Serialization/SlotDataTask_LevelSaver.h b/Source/SaveExtension/Public/Serialization/SlotDataTask_LevelSaver.h deleted file mode 100644 index 63f1c88..0000000 --- a/Source/SaveExtension/Public/Serialization/SlotDataTask_LevelSaver.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include "ISaveExtension.h" - -#include "SavePreset.h" - -#include "SlotDataTask_Saver.h" -#include "SlotDataTask_LevelSaver.generated.h" - - -/** -* Manages the serializing process of a single level -*/ -UCLASS() -class USlotDataTask_LevelSaver : public USlotDataTask_Saver -{ - GENERATED_BODY() - - - UPROPERTY() - ULevelStreaming* StreamingLevel; - -public: - - auto Setup(ULevelStreaming* InStreamingLevel) - { - StreamingLevel = InStreamingLevel; - return this; - } - -private: - - virtual void OnStart() override; - virtual void OnFinish(bool bSuccess) override { - SELog(Preset, "Finished Serializing level", FColor::Green); - } -}; diff --git a/Source/SaveExtension/Public/SlotInfo.h b/Source/SaveExtension/Public/SlotInfo.h deleted file mode 100644 index 115b18b..0000000 --- a/Source/SaveExtension/Public/SlotInfo.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. - -#pragma once - -#include -#include -#include - -#include "SlotInfo.generated.h" - - -/** - * USaveInfo stores information that needs to be accessible WITHOUT loading the game. - * Works like a common SaveGame object - * E.g: Dates, played time, progress, level - */ -UCLASS(ClassGroup = SaveExtension, hideCategories = ("Activation", "Actor Tick", "Actor", "Input", "Rendering", "Replication", "Socket", "Thumbnail")) -class SAVEEXTENSION_API USlotInfo : public USaveGame -{ - GENERATED_BODY() - -public: - - USlotInfo() : Super() - , PlayedTime(FTimespan::Zero()) - , SlotPlayedTime(FTimespan::Zero()) - , SaveDate(FDateTime::Now()) - {} - - - /** Slot where this SaveInfo and its saveData are saved */ - UPROPERTY(BlueprintReadOnly, Category = SlotInfo) - FName FileName; - - UPROPERTY(BlueprintReadWrite, Category = SlotInfo) - FText DisplayName; - - /** Played time since this saved game was started. Not related to slots, slots can change */ - UPROPERTY(BlueprintReadOnly, Category = SlotInfo) - FTimespan PlayedTime; - - /** Played time since this saved game was created */ - UPROPERTY(BlueprintReadOnly, Category = SlotInfo) - FTimespan SlotPlayedTime; - - /** Last date this slot was saved. */ - UPROPERTY(BlueprintReadOnly, Category = SlotInfo) - FDateTime SaveDate; - - /** Date at which this slot was loaded. */ - UPROPERTY(BlueprintReadOnly, Transient, Category = SlotInfo) - FDateTime LoadDate; - - /** Root Level where this Slot was saved */ - UPROPERTY(BlueprintReadOnly, Category = SlotInfo) - FName Map; - -private: - - UPROPERTY() - FString ThumbnailPath; - - /** Thumbnail gets cached here the first time it is requested */ - UPROPERTY(Transient) - UTexture2D* CachedThumbnail; - - -public: - - /** Returns this slot's thumbnail if any */ - UFUNCTION(BlueprintCallable, Category = SlotInfo) - UTexture2D* GetThumbnail() const; - - /** Captures a thumbnail for the current slot */ - bool CaptureThumbnail(const int32 Width = 640, const int32 Height = 360); - - - /** Internal Usage. Will be called when an screenshot is captured */ - void _SetThumbnailPath(const FString& Path); - - /** Internal Usage. Will be called to remove previous thumbnail */ - FString _GetThumbnailPath() { return ThumbnailPath; } -}; diff --git a/Source/SaveExtension/SaveExtension.Build.cs b/Source/SaveExtension/SaveExtension.Build.cs index c09e149..891befc 100644 --- a/Source/SaveExtension/SaveExtension.Build.cs +++ b/Source/SaveExtension/SaveExtension.Build.cs @@ -3,17 +3,18 @@ using UnrealBuildTool; using System.IO; -namespace UnrealBuildTool.Rules { - -public class SaveExtension : ModuleRules +namespace UnrealBuildTool.Rules { - public SaveExtension(ReadOnlyTargetRules Target) : base(Target) - { - PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bEnforceIWYU = true; - PublicDependencyModuleNames.AddRange(new string[] + public class SaveExtension : ModuleRules + { + public SaveExtension(ReadOnlyTargetRules Target) : base(Target) { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + IWYUSupport = IWYUSupport.Full; + + PublicDependencyModuleNames.AddRange(new string[] + { "Core", "Engine", "Foliage", @@ -22,8 +23,8 @@ public SaveExtension(ReadOnlyTargetRules Target) : base(Target) "DeveloperSettings", "ImageWrapper", "NavigationSystem" - }); + }); + } } -} } diff --git a/Source/Test/Private/Files.spec.cpp b/Source/Test/Private/Files.spec.cpp index 6218601..97da352 100644 --- a/Source/Test/Private/Files.spec.cpp +++ b/Source/Test/Private/Files.spec.cpp @@ -1,9 +1,10 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "Automatron.h" #include "Helpers/TestActor.h" -#include "SaveManager.h" -#include "FileAdapter.h" + +#include +#include class FSaveSpec_Files : public Automatron::FTestSpec @@ -13,7 +14,6 @@ class FSaveSpec_Files : public Automatron::FTestSpec USaveManager* SaveManager = nullptr; ATestActor* TestActor = nullptr; - USavePreset* TestPreset = nullptr; // Helper for some test delegates bool bFinishTick = false; @@ -34,32 +34,31 @@ void FSaveSpec_Files::Define() SaveManager->bTickWithGameWorld = true; - // Set test preset - TestPreset = SaveManager->SetActivePreset(USavePreset::StaticClass()); - TestPreset->MultithreadedSerialization = ESaveASyncMode::OnlySync; + SaveManager->GetActiveSlot()->MultithreadedSerialization = ESEAsyncMode::SaveAndLoadSync; }); It("Can save files synchronously", [this]() { - TestPreset->MultithreadedFiles = ESaveASyncMode::OnlySync; + SaveManager->GetActiveSlot()->MultithreadedFiles = ESEAsyncMode::SaveAndLoadSync; TestTrue("Saved", SaveManager->SaveSlot(0)); - TestTrue("Info File exists in disk", FFileAdapter::DoesFileExist(TEXT("0"))); + TestTrue("Info File exists in disk", FSaveFileHelpers::FileExists(TEXT("0"))); }); It("Can save files asynchronously", [this]() { - TestPreset->MultithreadedFiles = ESaveASyncMode::SaveAsync; + SaveManager->GetActiveSlot()->MultithreadedFiles = ESEAsyncMode::SaveAsync; bFinishTick = false; - bool bSaving = SaveManager->SaveSlot(0, true, false, {}, FOnGameSaved::CreateLambda([this](auto* Info) { - // Notified that files have been saved asynchronously - TestTrue("Info File exists in disk", FFileAdapter::DoesFileExist(TEXT("0"))); - bFinishTick = true; - })); + bool bSaving = + SaveManager->SaveSlot(0, true, false, {}, FOnGameSaved::CreateLambda([this](auto* Info) { + // Notified that files have been saved asynchronously + TestTrue("Info File exists in disk", FSaveFileHelpers::FileExists(TEXT("0"))); + bFinishTick = true; + })); TestTrue("Started Saving", bSaving); // Files shouldn't exist yet - TestFalse("Info File exists in disk", FFileAdapter::DoesFileExist(TEXT("0"))); + TestFalse("Info File exists in disk", FSaveFileHelpers::FileExists(TEXT("0"))); TickWorldUntil(GetMainWorld(), true, [this](float) { return !bFinishTick; @@ -67,15 +66,14 @@ void FSaveSpec_Files::Define() }); It("Can load files synchronously", [this]() { - TestPreset->MultithreadedFiles = ESaveASyncMode::OnlySync; + SaveManager->GetActiveSlot()->MultithreadedFiles = ESEAsyncMode::SaveAndLoadSync; TestTrue("Saved", SaveManager->SaveSlot(0)); - USlotInfo* Info = nullptr; - USlotData* Data = nullptr; - TestTrue("File was loaded", FFileAdapter::LoadFile(TEXT("0"), Info, Data, true, SaveManager)); - TestNotNull("Info is valid", Info); - TestNotNull("Data is valid", Data); + USaveSlot* Slot = nullptr; + TestTrue("File was loaded", FSaveFileHelpers::LoadFile(TEXT("0"), Slot, true, SaveManager)); + TestNotNull("Info is valid", Slot); + TestNotNull("Data is valid", Slot->GetData()); }); AfterEach([this]() { diff --git a/Source/Test/Private/GameInstance.spec.cpp b/Source/Test/Private/GameInstanceSpec.cpp similarity index 74% rename from Source/Test/Private/GameInstance.spec.cpp rename to Source/Test/Private/GameInstanceSpec.cpp index 0b72cfd..522fc53 100644 --- a/Source/Test/Private/GameInstance.spec.cpp +++ b/Source/Test/Private/GameInstanceSpec.cpp @@ -1,7 +1,8 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "GameInstanceSpec.h" #include "Automatron.h" -#include "Helpers/TestGameInstance.h" #include "SaveManager.h" @@ -11,7 +12,6 @@ class FSaveSpec_GameInstance : public Automatron::FTestSpec EAutomationTestFlags::ApplicationContextMask | EAutomationTestFlags::ProductFilter); USaveManager* SaveManager = nullptr; - USavePreset* TestPreset = nullptr; // Helper for some test delegates bool bFinishTick = false; @@ -34,15 +34,10 @@ void FSaveSpec_GameInstance::Define() SaveManager->bTickWithGameWorld = true; - TestPreset = SaveManager->SetActivePreset(USavePreset::StaticClass()); - TestPreset->bStoreGameInstance = true; - - TestPreset->MultithreadedFiles = ESaveASyncMode::OnlySync; - TestPreset->MultithreadedSerialization = ESaveASyncMode::OnlySync; + SaveManager->AssureActiveSlot(UTestSaveSlot::StaticClass(), true); }); - It("GameInstance can be saved", [this]() - { + It("GameInstance can be saved", [this]() { auto* GI = GetMainWorld()->GetGameInstance(); GI->bMyBool = true; @@ -56,13 +51,11 @@ void FSaveSpec_GameInstance::Define() TestTrue("Saved variable loaded", GI->bMyBool); }); - AfterEach([this]() - { + AfterEach([this]() { if (SaveManager) { bFinishTick = false; - SaveManager->DeleteAllSlots(FOnSlotsDeleted::CreateLambda([this]() - { + SaveManager->DeleteAllSlots(FOnSlotsDeleted::CreateLambda([this]() { bFinishTick = true; })); diff --git a/Source/Test/Private/GameInstanceSpec.h b/Source/Test/Private/GameInstanceSpec.h new file mode 100644 index 0000000..afe795c --- /dev/null +++ b/Source/Test/Private/GameInstanceSpec.h @@ -0,0 +1,23 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. +#pragma once + +#include "Helpers/TestGameInstance.h" + +#include + +#include "GameInstanceSpec.generated.h" + + +UCLASS() +class UTestSaveSlot : public USaveSlot +{ + GENERATED_BODY() + + UTestSaveSlot() : Super() + { + bStoreGameInstance = true; + + MultithreadedFiles = ESEAsyncMode::SaveAndLoadSync; + MultithreadedSerialization = ESEAsyncMode::SaveAndLoadSync; + } +}; diff --git a/Source/Test/Private/Helpers/TestActor.h b/Source/Test/Private/Helpers/TestActor.h index 610d7f6..f380d13 100644 --- a/Source/Test/Private/Helpers/TestActor.h +++ b/Source/Test/Private/Helpers/TestActor.h @@ -1,61 +1,62 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include #include + #include "TestActor.generated.h" + USTRUCT() struct FTestSaveStruct { - GENERATED_BODY() + GENERATED_BODY() }; UCLASS() class ATestActor : public AActor { - GENERATED_BODY() + GENERATED_BODY() public: + UPROPERTY(SaveGame) + bool bMyBool = false; - UPROPERTY(SaveGame) - bool bMyBool = false; - - UPROPERTY(SaveGame) - float MyFloat = 0.f; + UPROPERTY(SaveGame) + float MyFloat = 0.f; - // INTEGERS + // INTEGERS - UPROPERTY(SaveGame) - uint8 MyU8 = 0.f; + UPROPERTY(SaveGame) + uint8 MyU8 = 0.f; - UPROPERTY(SaveGame) - uint16 MyU16 = 0; + UPROPERTY(SaveGame) + uint16 MyU16 = 0; - UPROPERTY(SaveGame) - uint32 MyU32 = 0; + UPROPERTY(SaveGame) + uint32 MyU32 = 0; - UPROPERTY(SaveGame) - uint64 MyU64 = 0; + UPROPERTY(SaveGame) + uint64 MyU64 = 0; - UPROPERTY(SaveGame) - int8 MyI8 = 0.f; + UPROPERTY(SaveGame) + int8 MyI8 = 0.f; - UPROPERTY(SaveGame) - int16 MyI16 = 0; + UPROPERTY(SaveGame) + int16 MyI16 = 0; - UPROPERTY(SaveGame) - int32 MyI32 = 0; + UPROPERTY(SaveGame) + int32 MyI32 = 0; - UPROPERTY(SaveGame) - int64 MyI64 = 0; + UPROPERTY(SaveGame) + int64 MyI64 = 0; - UPROPERTY(SaveGame) - FTestSaveStruct MyStruct; + UPROPERTY(SaveGame) + FTestSaveStruct MyStruct; }; diff --git a/Source/Test/Private/Helpers/TestGameInstance.h b/Source/Test/Private/Helpers/TestGameInstance.h index 7ede425..dfb6145 100644 --- a/Source/Test/Private/Helpers/TestGameInstance.h +++ b/Source/Test/Private/Helpers/TestGameInstance.h @@ -1,19 +1,20 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once #include #include + #include "TestGameInstance.generated.h" + UCLASS() class UTestGameInstance : public UGameInstance { - GENERATED_BODY() + GENERATED_BODY() public: - - UPROPERTY(SaveGame) - bool bMyBool = false; + UPROPERTY(SaveGame) + bool bMyBool = false; }; diff --git a/Source/Test/Private/SaveExtensionTest.cpp b/Source/Test/Private/SaveExtensionTest.cpp index 69ff67e..ce019a9 100644 --- a/Source/Test/Private/SaveExtensionTest.cpp +++ b/Source/Test/Private/SaveExtensionTest.cpp @@ -1,9 +1,11 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #include "SaveExtensionTest.h" + #include "Automatron.h" + IMPLEMENT_MODULE(FSaveExtensionTest, SaveExtensionTest); void FSaveExtensionTest::StartupModule() diff --git a/Source/Test/Private/Save.spec.cpp b/Source/Test/Private/SavingSpec.cpp similarity index 83% rename from Source/Test/Private/Save.spec.cpp rename to Source/Test/Private/SavingSpec.cpp index 3da63a9..f84a5f3 100644 --- a/Source/Test/Private/Save.spec.cpp +++ b/Source/Test/Private/SavingSpec.cpp @@ -1,7 +1,8 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. + +#include "SavingSpec.h" #include "Automatron.h" -#include "Helpers/TestActor.h" #include "SaveManager.h" @@ -12,7 +13,6 @@ class FSaveSpec_Preset : public Automatron::FTestSpec USaveManager* SaveManager = nullptr; ATestActor* TestActor = nullptr; - USavePreset* TestPreset = nullptr; // Helper for some test delegates bool bFinishTick = false; @@ -57,18 +57,12 @@ void FSaveSpec_Preset::Define() Describe("Serialization", [this]() { BeforeEach([this]() { - TestPreset = SaveManager->SetActivePreset(USavePreset::StaticClass()); - TestPreset->ActorFilter.ClassFilter.AllowedClasses.Add(ATestActor::StaticClass()); - - // We don't need Async files are tested independently - TestPreset->MultithreadedFiles = ESaveASyncMode::OnlySync; + SaveManager->AssureActiveSlot(UTestSaveSlot_SyncSaving::StaticClass(), true); TestActor = GetMainWorld()->SpawnActor(); }); It("Can save an actor synchronously", [this]() { - TestPreset->MultithreadedSerialization = ESaveASyncMode::OnlySync; - TestTrue("Saved", SaveManager->SaveSlot(0)); TestTrue("Loaded", SaveManager->LoadSlot(0)); }); @@ -78,12 +72,7 @@ void FSaveSpec_Preset::Define() }); Describe("Properties", [this]() { - BeforeEach([this]() { - TestPreset->MultithreadedSerialization = ESaveASyncMode::OnlySync; - }); - - It("bool", [this]() - { + It("bool", [this]() { TestActor->bMyBool = true; SaveManager->SaveSlot(0); TestTrue("bool didn't change after save", TestActor->bMyBool); @@ -95,8 +84,7 @@ void FSaveSpec_Preset::Define() TestTrue("bool was saved", TestActor->bMyBool); }); - It("uint8", [this]() - { + It("uint8", [this]() { TestActor->MyU8 = 34; SaveManager->SaveSlot(0); TestEqual("uint8 didn't change after save", TestActor->MyU8, 34); @@ -106,8 +94,7 @@ void FSaveSpec_Preset::Define() TestEqual("uint8 was saved", TestActor->MyU8, 34); }); - It("uint16", [this]() - { + It("uint16", [this]() { TestActor->MyU16 = 34; SaveManager->SaveSlot(0); TestEqual("uint16 didn't change after save", TestActor->MyU16, 34); @@ -117,8 +104,7 @@ void FSaveSpec_Preset::Define() TestEqual("uint16 was saved", TestActor->MyU16, 34); }); - It("uint32", [this]() - { + It("uint32", [this]() { TestActor->MyU32 = 34; SaveManager->SaveSlot(0); TestEqual("uint32 didn't change after save", TestActor->MyU32, 34); @@ -128,8 +114,7 @@ void FSaveSpec_Preset::Define() TestEqual("uint32 was saved", TestActor->MyU32, 34); }); - It("uint64", [this]() - { + It("uint64", [this]() { TestActor->MyU64 = 34; SaveManager->SaveSlot(0); TestEqual("uint16 didn't change after save", TestActor->MyU64, 34); @@ -139,8 +124,7 @@ void FSaveSpec_Preset::Define() TestEqual("uint16 was saved", TestActor->MyU64, 34); }); - It("int8", [this]() - { + It("int8", [this]() { TestActor->MyI8 = 34; SaveManager->SaveSlot(0); TestEqual("int8 didn't change after save", TestActor->MyI8, 34); @@ -150,8 +134,7 @@ void FSaveSpec_Preset::Define() TestEqual("int8 was saved", TestActor->MyI8, 34); }); - It("int16", [this]() - { + It("int16", [this]() { TestActor->MyI16 = 34; SaveManager->SaveSlot(0); TestEqual("int16 didn't change after save", TestActor->MyI16, 34); @@ -161,8 +144,7 @@ void FSaveSpec_Preset::Define() TestEqual("int16 was saved", TestActor->MyI16, 34); }); - It("int32", [this]() - { + It("int32", [this]() { TestActor->MyI32 = 34; SaveManager->SaveSlot(0); TestEqual("int32 didn't change after save", TestActor->MyI32, 34); @@ -172,8 +154,7 @@ void FSaveSpec_Preset::Define() TestEqual("int32 was saved", TestActor->MyI32, 34); }); - It("int64", [this]() - { + It("int64", [this]() { TestActor->MyI64 = 34; SaveManager->SaveSlot(0); TestEqual("int64 didn't change after save", TestActor->MyI64, 34); diff --git a/Source/Test/Private/SavingSpec.h b/Source/Test/Private/SavingSpec.h new file mode 100644 index 0000000..92b07b7 --- /dev/null +++ b/Source/Test/Private/SavingSpec.h @@ -0,0 +1,24 @@ +// Copyright 2015-2024 Piperift. All Rights Reserved. +#pragma once + +#include "Helpers/TestActor.h" + +#include + +#include "SavingSpec.generated.h" + + +UCLASS() +class UTestSaveSlot_SyncSaving : public USaveSlot +{ + GENERATED_BODY() + + UTestSaveSlot_SyncSaving() : Super() + { + bStoreGameInstance = true; + + MultithreadedFiles = ESEAsyncMode::SaveAndLoadSync; + MultithreadedSerialization = ESEAsyncMode::SaveAndLoadSync; + ActorFilter.AllowedClasses.Add(ATestActor::StaticClass()); + } +}; diff --git a/Source/Test/Public/SaveExtensionTest.h b/Source/Test/Public/SaveExtensionTest.h index e13d993..4c347c2 100644 --- a/Source/Test/Public/SaveExtensionTest.h +++ b/Source/Test/Public/SaveExtensionTest.h @@ -1,4 +1,4 @@ -// Copyright 2015-2020 Piperift. All Rights Reserved. +// Copyright 2015-2024 Piperift. All Rights Reserved. #pragma once diff --git a/Source/Test/SaveExtensionTest.Build.cs b/Source/Test/SaveExtensionTest.Build.cs index 5281610..5297223 100644 --- a/Source/Test/SaveExtensionTest.Build.cs +++ b/Source/Test/SaveExtensionTest.Build.cs @@ -5,11 +5,12 @@ namespace UnrealBuildTool.Rules { - public class SaveExtensionTest : ModuleRules { + public class SaveExtensionTest : ModuleRules + { public SaveExtensionTest(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - bEnforceIWYU = true; + IWYUSupport = IWYUSupport.Full; PublicDependencyModuleNames.AddRange(new string[] { @@ -20,10 +21,10 @@ public SaveExtensionTest(ReadOnlyTargetRules Target) : base(Target) "EngineSettings" }); - if (Target.bBuildEditor == true) - { - PrivateDependencyModuleNames.Add("UnrealEd"); - } - } + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + } } } \ No newline at end of file