实现基本的载入/保存功能

This commit is contained in:
_Redstone_c_ 2020-12-12 16:43:35 +08:00
parent c98fe4b6c8
commit 7f5b1c1900
9 changed files with 513 additions and 0 deletions

View File

@ -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<ISettingsModule>("Settings"))
{
SettingModule->RegisterSettings("Project", "Plugins", "Auto Save",
LOCTEXT("RuntimeSettingsName", "Auto Save"),
LOCTEXT("RuntimeSettingsDescription", "Configure the Auto Save plugin"),
GetMutableDefault<UAutoSaveSubsystem>()
);
}
}
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<ISettingsModule>("Settings"))
{
SettingsModule->UnregisterSettings("Project", "Plugins", "Auto Save");
}
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,3 @@
#include "AutoSaveLog.h"
DEFINE_LOG_CATEGORY(LogAutoSave);

View File

@ -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<FSaveStructInfo>& OutSaveStructInfos) const
{
OutSaveStructInfos.SetNum(StructInfos.Num());
int32 Index = 0;
for (const TPair<FString, TUniquePtr<FSaveStructInfo>>& 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<FSaveStructInfo> 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<uint8> 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<uint8> 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<FSaveStructInfo*(void)> FindPreHandleStruct = [&]() -> FSaveStructInfo*
{
FSaveStructInfo* PreHandleStruct = nullptr;
for (const TPair<FString, TUniquePtr<FSaveStructInfo>>& 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<FStructLoadOrSaveTask> Task(PreHandleStruct);
Task.StartSynchronousTask();
}
}
for (TUniquePtr<FAsyncTask<FStructLoadOrSaveTask>>& Task : TaskThreads)
{
if (Task) continue;
FSaveStructInfo* PreHandleStruct = FindPreHandleStruct();
if (!PreHandleStruct) break;
Task.Reset(new FAsyncTask<FStructLoadOrSaveTask>(PreHandleStruct));
Task->StartBackgroundTask();
}
}
void UAutoSaveSubsystem::HandleTaskDone()
{
for (TUniquePtr<FAsyncTask<FStructLoadOrSaveTask>>& Task : TaskThreads)
{
if (!Task) continue;
if (!Task->IsDone()) continue;
Task = nullptr;
}
TArray<FString> StructToRemove;
for (const TPair<FString, TUniquePtr<FSaveStructInfo>>& 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<FAsyncTask<FStructLoadOrSaveTask>>& Task : TaskThreads)
{
if (!Task) continue;
Task->EnsureCompletion();
Task = nullptr;
}
}
void UAutoSaveSubsystem::Tick(float DeltaTime)
{
HandleTaskDone();
HandleTaskStart();
}

View File

@ -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<UAutoSaveSubsystem>();
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<UAutoSaveSubsystem>();
if (!AutoSaveSubsystem) return;
AutoSaveSubsystem->RemoveSaveStructRef(Filename);
}

View File

@ -0,0 +1 @@
#include "SaveStruct.h"

View File

@ -0,0 +1,5 @@
#pragma once
#include "Logging/LogMacros.h"
AUTOSAVE_API DECLARE_LOG_CATEGORY_EXTERN(LogAutoSave, Log, All);

View File

@ -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<uint8> 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<FSaveStructInfo>& OutSaveStructInfos) const;
FSaveStruct* AddSaveStructRef(const FString& Filename, UScriptStruct* ScriptStruct);
void RemoveSaveStructRef(const FString& Filename);
private:
UPROPERTY()
TMap<FString, UScriptStruct*> ScriptStructHooker;
TMap<FString, TUniquePtr<FSaveStructInfo>> StructInfos;
class FStructLoadOrSaveTask : public FNonAbandonableTask
{
friend class FAsyncTask<FStructLoadOrSaveTask>;
TArray<uint8> 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<TUniquePtr<FAsyncTask<FStructLoadOrSaveTask>>> 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
};

View File

@ -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);
};

View File

@ -0,0 +1,10 @@
#pragma once
#include "CoreMinimal.h"
#include "SaveStruct.generated.h"
USTRUCT(BlueprintType)
struct AUTOSAVE_API FSaveStructPtr
{
GENERATED_BODY()
};