From 7f5b1c190095f63dc68de3f5c11169dcfe7b87fb Mon Sep 17 00:00:00 2001 From: _Redstone_c_ <2824517378@qq.com> Date: Sat, 12 Dec 2020 16:43:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=9F=BA=E6=9C=AC=E7=9A=84?= =?UTF-8?q?=E8=BD=BD=E5=85=A5/=E4=BF=9D=E5=AD=98=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Source/AutoSave/Private/AutoSave.cpp | 16 + Source/AutoSave/Private/AutoSaveLog.cpp | 3 + Source/AutoSave/Private/AutoSaveSubsystem.cpp | 307 ++++++++++++++++++ .../Blueprint/AutoSaveBlueprintLibrary.cpp | 30 ++ Source/AutoSave/Private/SaveStruct.cpp | 1 + Source/AutoSave/Public/AutoSaveLog.h | 5 + Source/AutoSave/Public/AutoSaveSubsystem.h | 121 +++++++ .../Blueprint/AutoSaveBlueprintLibrary.h | 20 ++ Source/AutoSave/Public/SaveStruct.h | 10 + 9 files changed, 513 insertions(+) create mode 100644 Source/AutoSave/Private/AutoSaveLog.cpp create mode 100644 Source/AutoSave/Private/AutoSaveSubsystem.cpp create mode 100644 Source/AutoSave/Private/Blueprint/AutoSaveBlueprintLibrary.cpp create mode 100644 Source/AutoSave/Private/SaveStruct.cpp create mode 100644 Source/AutoSave/Public/AutoSaveLog.h create mode 100644 Source/AutoSave/Public/AutoSaveSubsystem.h create mode 100644 Source/AutoSave/Public/Blueprint/AutoSaveBlueprintLibrary.h create mode 100644 Source/AutoSave/Public/SaveStruct.h diff --git a/Source/AutoSave/Private/AutoSave.cpp b/Source/AutoSave/Private/AutoSave.cpp index 2c566d7..ccd69fd 100644 --- a/Source/AutoSave/Private/AutoSave.cpp +++ b/Source/AutoSave/Private/AutoSave.cpp @@ -1,18 +1,34 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "AutoSave.h" +#include "AutoSaveSubsystem.h" +#include "Developer/Settings/Public/ISettingsModule.h" #define LOCTEXT_NAMESPACE "FAutoSaveModule" void FAutoSaveModule::StartupModule() { // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + + if (ISettingsModule* SettingModule = FModuleManager::GetModulePtr("Settings")) + { + SettingModule->RegisterSettings("Project", "Plugins", "Auto Save", + LOCTEXT("RuntimeSettingsName", "Auto Save"), + LOCTEXT("RuntimeSettingsDescription", "Configure the Auto Save plugin"), + GetMutableDefault() + ); + } } void FAutoSaveModule::ShutdownModule() { // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, // we call this function before unloading the module. + + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "Auto Save"); + } } #undef LOCTEXT_NAMESPACE diff --git a/Source/AutoSave/Private/AutoSaveLog.cpp b/Source/AutoSave/Private/AutoSaveLog.cpp new file mode 100644 index 0000000..9c18a45 --- /dev/null +++ b/Source/AutoSave/Private/AutoSaveLog.cpp @@ -0,0 +1,3 @@ +#include "AutoSaveLog.h" + +DEFINE_LOG_CATEGORY(LogAutoSave); diff --git a/Source/AutoSave/Private/AutoSaveSubsystem.cpp b/Source/AutoSave/Private/AutoSaveSubsystem.cpp new file mode 100644 index 0000000..22f7508 --- /dev/null +++ b/Source/AutoSave/Private/AutoSaveSubsystem.cpp @@ -0,0 +1,307 @@ +#include "AutoSaveSubsystem.h" + +#include "AutoSaveLog.h" +#include "Engine/UserDefinedStruct.h" +#include "Serialization/MemoryReader.h" +#include "Serialization/MemoryWriter.h" + +UAutoSaveSubsystem::UAutoSaveSubsystem(const class FObjectInitializer & ObjectInitializer) +{ +} + +void UAutoSaveSubsystem::GetSaveStructInfosWithoutData(TArray& OutSaveStructInfos) const +{ + OutSaveStructInfos.SetNum(StructInfos.Num()); + + int32 Index = 0; + + for (const TPair>& Info : StructInfos) + { + OutSaveStructInfos[Index].Filename = Info.Value->Filename; + OutSaveStructInfos[Index].Struct = Info.Value->Struct; + OutSaveStructInfos[Index].State = Info.Value->State; + OutSaveStructInfos[Index].RefConut = Info.Value->RefConut; + OutSaveStructInfos[Index].LastRefConut = Info.Value->LastRefConut; + OutSaveStructInfos[Index].LastSaveTime = Info.Value->LastSaveTime; + + ++Index; + } +} + +FSaveStruct * UAutoSaveSubsystem::AddSaveStructRef(const FString& Filename, UScriptStruct * ScriptStruct) +{ + const bool bIsCppStruct = ScriptStruct->IsChildOf(FSaveStruct::StaticStruct()); + const bool bIsBlueprintStruct = ScriptStruct->GetClass() == UUserDefinedStruct::StaticClass(); + + if (!bIsCppStruct && !bIsBlueprintStruct) + return nullptr; + + if (StructInfos.Contains(Filename)) + { + FSaveStructInfo* StructInfo = StructInfos[Filename].Get(); + + // Increase the reference count of SaveStruct by one, and then decrease it accordingly in UAutoSaveSubsystem::RemoveSaveStructRef + StructInfo->RefConut++; + + return (FSaveStruct*)StructInfo->Data.GetData(); + } + + TUniquePtr NewStructInfo(new FSaveStructInfo()); + + if (FPaths::FileExists(Filename)) + { + NewStructInfo->Filename = Filename; + NewStructInfo->Struct = ScriptStruct; + NewStructInfo->Data.SetNumUninitialized(ScriptStruct->GetStructureSize()); + NewStructInfo->State = ESaveStructState::Preload; + NewStructInfo->RefConut = 1; + NewStructInfo->LastRefConut = 0; + NewStructInfo->LastSaveTime = FDateTime::Now(); + } + else + { + // Check if the target is writable + if (!FFileHelper::SaveStringToFile(TEXT(""), *Filename)) + return nullptr; + + NewStructInfo->Filename = Filename; + NewStructInfo->Struct = ScriptStruct; + NewStructInfo->Data.SetNumUninitialized(ScriptStruct->GetStructureSize()); + ScriptStruct->InitializeStruct(NewStructInfo->Data.GetData()); + NewStructInfo->State = ESaveStructState::Idle; + NewStructInfo->RefConut = 1; + NewStructInfo->LastRefConut = 0; + NewStructInfo->LastSaveTime = FDateTime::Now(); + } + + ScriptStructHooker.Add(Filename, ScriptStruct); + + StructInfos.Add(Filename, nullptr); + StructInfos[Filename].Reset(NewStructInfo.Release()); + + return (FSaveStruct*)StructInfos[Filename]->Data.GetData(); +} + +void UAutoSaveSubsystem::RemoveSaveStructRef(const FString& Filename) +{ + if (StructInfos.Contains(Filename)) + { + if (StructInfos[Filename]->RefConut > 0) + { + // Decrement the reference count of SaveStruct by one, and increase it accordingly in UAutoSaveSubsystem::AddSaveStructRef + StructInfos[Filename]->RefConut--; + } + else + { + UE_LOG(LogAutoSave, Warning, TEXT("Save Struct '%s' reference is negative, But was tried to remove the reference."), *Filename); + } + } + else + { + UE_LOG(LogAutoSave, Warning, TEXT("Save Struct '%s' is invalid, But was tried to remove the reference."), *Filename); + } +} + +UAutoSaveSubsystem::FStructLoadOrSaveTask::FStructLoadOrSaveTask(FSaveStructInfo * InStructInfoPtr) + : StructInfoPtr(InStructInfoPtr) +{ + StructInfoPtr->LastRefConut = StructInfoPtr->RefConut; + StructInfoPtr->LastSaveTime = FDateTime::Now(); + + switch (StructInfoPtr->State) + { + case ESaveStructState::Preload: + StructInfoPtr->State = ESaveStructState::Loading; + DataCopy.SetNumUninitialized(StructInfoPtr->Data.Num()); + break; + + case ESaveStructState::Idle: + StructInfoPtr->State = ESaveStructState::Saving; + DataCopy = StructInfoPtr->Data; + break; + + default: checkNoEntry() + } +} + +UAutoSaveSubsystem::FStructLoadOrSaveTask::~FStructLoadOrSaveTask() +{ + switch (StructInfoPtr->State) + { + case ESaveStructState::Loading: + StructInfoPtr->State = ESaveStructState::Idle; + StructInfoPtr->Data = DataCopy; + break; + + case ESaveStructState::Saving: + StructInfoPtr->State = ESaveStructState::Idle; + break; + + default: checkNoEntry() + } +} + +void UAutoSaveSubsystem::FStructLoadOrSaveTask::DoWork() +{ + switch (StructInfoPtr->State) + { + case ESaveStructState::Loading: + LoadWork(); + break; + + case ESaveStructState::Saving: + SaveWork(); + break; + + default: checkNoEntry() + } +} + +void UAutoSaveSubsystem::FStructLoadOrSaveTask::LoadWork() +{ + TArray DataBuffer; + + const bool bSuccessful = FFileHelper::LoadFileToArray(DataBuffer, *StructInfoPtr->Filename); + + check(bSuccessful); + + FMemoryReader MemoryReader(DataBuffer); + + UScriptStruct* Struct = StructInfoPtr->Struct; + + check(Struct); + + Struct->SerializeItem(MemoryReader, DataCopy.GetData(), nullptr); +} + +void UAutoSaveSubsystem::FStructLoadOrSaveTask::SaveWork() +{ + TArray DataBuffer; + FMemoryWriter MemoryWriter(DataBuffer); + + UScriptStruct* Struct = StructInfoPtr->Struct; + + check(Struct); + + Struct->SerializeItem(MemoryWriter, DataCopy.GetData(), nullptr); + + const bool bSuccessful = FFileHelper::SaveArrayToFile(DataBuffer, *StructInfoPtr->Filename); + + check(bSuccessful); +} + +void UAutoSaveSubsystem::HandleTaskStart() +{ + const FDateTime NowTime = FDateTime::Now(); + + TFunction FindPreHandleStruct = [&]() -> FSaveStructInfo* + { + FSaveStructInfo* PreHandleStruct = nullptr; + + for (const TPair>& Info : StructInfos) + { + check(Info.Value); + + if (Info.Value->State == ESaveStructState::Preload) + { + PreHandleStruct = Info.Value.Get(); + break; + } + + if (Info.Value->State != ESaveStructState::Idle) continue; + + if (Info.Value->RefConut == 0) + { + PreHandleStruct = Info.Value.Get(); + break; + } + + const bool bTimeout = PreHandleStruct + ? PreHandleStruct->LastSaveTime > Info.Value->LastSaveTime + : NowTime - Info.Value->LastSaveTime > SaveWaitTime; + + if (bTimeout) + { + PreHandleStruct = Info.Value.Get(); + } + } + + return PreHandleStruct; + }; + + if (MaxThreadNum <= 0) + { + FSaveStructInfo* PreHandleStruct = FindPreHandleStruct(); + + if (PreHandleStruct) + { + FAsyncTask Task(PreHandleStruct); + Task.StartSynchronousTask(); + } + } + + for (TUniquePtr>& Task : TaskThreads) + { + if (Task) continue; + + FSaveStructInfo* PreHandleStruct = FindPreHandleStruct(); + + if (!PreHandleStruct) break; + + Task.Reset(new FAsyncTask(PreHandleStruct)); + Task->StartBackgroundTask(); + } + +} + +void UAutoSaveSubsystem::HandleTaskDone() +{ + for (TUniquePtr>& Task : TaskThreads) + { + if (!Task) continue; + if (!Task->IsDone()) continue; + Task = nullptr; + } + + TArray StructToRemove; + + for (const TPair>& Info : StructInfos) + { + check(Info.Value); + + if (Info.Value->State != ESaveStructState::Idle) continue; + + if (Info.Value->RefConut <= 0 && Info.Value->LastRefConut <= 0) + { + StructToRemove.Add(Info.Value->Filename); + } + } + + for (const FString& Filename : StructToRemove) + { + StructInfos.Remove(Filename); + ScriptStructHooker.Remove(Filename); + } +} + +void UAutoSaveSubsystem::Initialize(FSubsystemCollectionBase & Collection) +{ + if (MaxThreadNum > 0) + TaskThreads.SetNum(MaxThreadNum); +} + +void UAutoSaveSubsystem::Deinitialize() +{ + for (TUniquePtr>& Task : TaskThreads) + { + if (!Task) continue; + Task->EnsureCompletion(); + Task = nullptr; + } +} + +void UAutoSaveSubsystem::Tick(float DeltaTime) +{ + HandleTaskDone(); + HandleTaskStart(); +} diff --git a/Source/AutoSave/Private/Blueprint/AutoSaveBlueprintLibrary.cpp b/Source/AutoSave/Private/Blueprint/AutoSaveBlueprintLibrary.cpp new file mode 100644 index 0000000..51b61ec --- /dev/null +++ b/Source/AutoSave/Private/Blueprint/AutoSaveBlueprintLibrary.cpp @@ -0,0 +1,30 @@ +#include "Blueprint/AutoSaveBlueprintLibrary.h" + +#include "AutoSaveSubsystem.h" +#include "Kismet/GameplayStatics.h" + +bool UAutoSaveBlueprintLibrary::AddSaveStructRef(UObject * WorldContextObject, const FString & Filename, UScriptStruct * ScriptStruct) +{ + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldContextObject); + + if (!GameInstance) return false; + + UAutoSaveSubsystem* AutoSaveSubsystem = GameInstance->GetSubsystem(); + + if (!AutoSaveSubsystem) return false; + + return AutoSaveSubsystem->AddSaveStructRef(Filename, ScriptStruct) != nullptr; +} + +void UAutoSaveBlueprintLibrary::RemoveSaveStructRef(UObject * WorldContextObject, const FString & Filename) +{ + UGameInstance* GameInstance = UGameplayStatics::GetGameInstance(WorldContextObject); + + if (!GameInstance) return; + + UAutoSaveSubsystem* AutoSaveSubsystem = GameInstance->GetSubsystem(); + + if (!AutoSaveSubsystem) return; + + AutoSaveSubsystem->RemoveSaveStructRef(Filename); +} diff --git a/Source/AutoSave/Private/SaveStruct.cpp b/Source/AutoSave/Private/SaveStruct.cpp new file mode 100644 index 0000000..ab13c45 --- /dev/null +++ b/Source/AutoSave/Private/SaveStruct.cpp @@ -0,0 +1 @@ +#include "SaveStruct.h" diff --git a/Source/AutoSave/Public/AutoSaveLog.h b/Source/AutoSave/Public/AutoSaveLog.h new file mode 100644 index 0000000..e23a125 --- /dev/null +++ b/Source/AutoSave/Public/AutoSaveLog.h @@ -0,0 +1,5 @@ +#pragma once + +#include "Logging/LogMacros.h" + +AUTOSAVE_API DECLARE_LOG_CATEGORY_EXTERN(LogAutoSave, Log, All); diff --git a/Source/AutoSave/Public/AutoSaveSubsystem.h b/Source/AutoSave/Public/AutoSaveSubsystem.h new file mode 100644 index 0000000..159ba92 --- /dev/null +++ b/Source/AutoSave/Public/AutoSaveSubsystem.h @@ -0,0 +1,121 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Subsystems/GameInstanceSubsystem.h" +#include "AutoSaveSubsystem.generated.h" + +USTRUCT(BlueprintType) +struct AUTOSAVE_API FSaveStruct { GENERATED_BODY() }; + +UENUM(BlueprintType, Category = "AutoSave") +enum class ESaveStructState : uint8 +{ + Preload, + Loading, + Idle, + Saving, +}; + +USTRUCT(BlueprintType) +struct FSaveStructInfo +{ + GENERATED_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "AutoSave") + FString Filename; + + UPROPERTY(BlueprintReadWrite, Category = "AutoSave") + UScriptStruct* Struct; + + UPROPERTY(BlueprintReadWrite, Category = "AutoSave") + ESaveStructState State; + + UPROPERTY(BlueprintReadWrite, Category = "AutoSave") + int32 RefConut; + + UPROPERTY(BlueprintReadWrite, Category = "AutoSave") + int32 LastRefConut; + + UPROPERTY(BlueprintReadWrite, Category = "AutoSave") + FDateTime LastSaveTime; + + UPROPERTY() + TArray Data; + // FSaveStruct* Data; + +}; + +UCLASS(Config = Engine, DefaultConfig) +class AUTOSAVE_API UAutoSaveSubsystem : public UGameInstanceSubsystem, public FTickableGameObject +{ + GENERATED_BODY() + + friend struct FSaveStructPtr; + +public: + + UAutoSaveSubsystem(const class FObjectInitializer& ObjectInitializer); + + UPROPERTY(Config, EditAnywhere, Category = "AutoSave") + int32 MaxThreadNum = 4; + + UPROPERTY(Config, EditAnywhere, Category = "AutoSave") + FTimespan SaveWaitTime = FTimespan(ETimespan::MaxTicks); + + UFUNCTION(BlueprintCallable, Category = "AutoSave") + void GetSaveStructInfosWithoutData(TArray& OutSaveStructInfos) const; + + FSaveStruct* AddSaveStructRef(const FString& Filename, UScriptStruct* ScriptStruct); + + void RemoveSaveStructRef(const FString& Filename); + +private: + + UPROPERTY() + TMap ScriptStructHooker; + + TMap> StructInfos; + + class FStructLoadOrSaveTask : public FNonAbandonableTask + { + friend class FAsyncTask; + + TArray DataCopy; + + FSaveStructInfo* StructInfoPtr; + + FStructLoadOrSaveTask(FSaveStructInfo* InStructInfoPtr); + + ~FStructLoadOrSaveTask(); + + void DoWork(); + + void LoadWork(); + + void SaveWork(); + + FORCEINLINE TStatId GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FStructLoadOrSaveTask, STATGROUP_ThreadPoolAsyncTasks); } + + }; + + TArray>> TaskThreads; + + void HandleTaskStart(); + + void HandleTaskDone(); + +private: + + //~ Begin USubsystem Interface + virtual bool ShouldCreateSubsystem(UObject* Outer) const override { return true; } + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; + //~ End USubsystem Interface + + //~ Begin FTickableGameObject Interface + virtual void Tick(float DeltaTime) override; + virtual bool IsTickable() const override { return !IsTemplate(); } + virtual TStatId GetStatId() const override { return GetStatID(); } + //~ End FTickableGameObject Interface + +}; diff --git a/Source/AutoSave/Public/Blueprint/AutoSaveBlueprintLibrary.h b/Source/AutoSave/Public/Blueprint/AutoSaveBlueprintLibrary.h new file mode 100644 index 0000000..e66aec3 --- /dev/null +++ b/Source/AutoSave/Public/Blueprint/AutoSaveBlueprintLibrary.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "AutoSaveBlueprintLibrary.generated.h" + +UCLASS() +class AUTOSAVE_API UAutoSaveBlueprintLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "AutoSave", meta = (WorldContext = "WorldContextObject")) + static bool AddSaveStructRef(UObject* WorldContextObject, const FString& Filename, UScriptStruct* ScriptStruct); + + UFUNCTION(BlueprintCallable, Category = "AutoSave", meta = (WorldContext = "WorldContextObject")) + static void RemoveSaveStructRef(UObject* WorldContextObject, const FString& Filename); + +}; diff --git a/Source/AutoSave/Public/SaveStruct.h b/Source/AutoSave/Public/SaveStruct.h new file mode 100644 index 0000000..80f2937 --- /dev/null +++ b/Source/AutoSave/Public/SaveStruct.h @@ -0,0 +1,10 @@ +#pragma once + +#include "CoreMinimal.h" +#include "SaveStruct.generated.h" + +USTRUCT(BlueprintType) +struct AUTOSAVE_API FSaveStructPtr +{ + GENERATED_BODY() +};