471 lines
11 KiB
C++
471 lines
11 KiB
C++
#include "AutoSaveSubsystem.h"
|
|
|
|
#include "AutoSaveLog.h"
|
|
#include "Engine/UserDefinedStruct.h"
|
|
#include "Serialization/MemoryReader.h"
|
|
#include "Serialization/MemoryWriter.h"
|
|
|
|
UAutoSaveSubsystem::UAutoSaveSubsystem(const class FObjectInitializer & ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
FString UAutoSaveSubsystem::GetSaveStructDebugString() const
|
|
{
|
|
FString Result;
|
|
|
|
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
|
|
|
|
for (const TPair<FString, TUniquePtr<FSaveStructInfo>>& Info : StructInfos)
|
|
{
|
|
Result.Append(Info.Value->Filename);
|
|
|
|
Result.Append(TEXT(" - "));
|
|
|
|
Result.Append(Info.Value->Struct->GetName());
|
|
|
|
Result.Append(TEXT(" - "));
|
|
|
|
switch (Info.Value->State)
|
|
{
|
|
case ESaveStructState::Preload:
|
|
Result.Append(TEXT("Preload"));
|
|
break;
|
|
case ESaveStructState::Loading:
|
|
Result.Append(TEXT("Loading"));
|
|
break;
|
|
case ESaveStructState::Idle:
|
|
Result.Append(TEXT("Idle"));
|
|
break;
|
|
case ESaveStructState::Saving:
|
|
Result.Append(TEXT("Saving"));
|
|
break;
|
|
default: checkNoEntry();
|
|
}
|
|
|
|
Result.Append(TEXT(" - "));
|
|
|
|
Result.Append(FString::Printf(TEXT("%d"), Info.Value->RefConut));
|
|
|
|
Result.Append(TEXT(" - "));
|
|
|
|
Result.Append(FString::Printf(TEXT("%d"), Info.Value->LastRefConut));
|
|
|
|
Result.Append(TEXT(" - "));
|
|
|
|
Result.Append(FString::Printf(TEXT("%f"), (FDateTime::Now() - Info.Value->LastSaveTime).GetTotalSeconds()));
|
|
|
|
Result.Append(TEXT("\n"));
|
|
}
|
|
|
|
#endif
|
|
|
|
return Result;
|
|
}
|
|
|
|
int32 UAutoSaveSubsystem::GetIdleThreadNum() const
|
|
{
|
|
int32 Result = 0;
|
|
|
|
for (const TUniquePtr<FAsyncTask<FStructLoadOrSaveTask>>& Task : TaskThreads)
|
|
{
|
|
if (!Task) ++Result;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
FSaveStruct * UAutoSaveSubsystem::AddSaveStructRef(const FString& Filename, UScriptStruct * ScriptStruct)
|
|
{
|
|
if (StructInfos.Contains(Filename))
|
|
{
|
|
FSaveStructInfo* StructInfo = StructInfos[Filename].Get();
|
|
|
|
if (ScriptStruct && ScriptStruct != StructInfo->Struct)
|
|
{
|
|
UE_LOG(LogAutoSave, Warning, TEXT("The requested Save Struct '%s' type conflicts with the existing."), *Filename);
|
|
return nullptr;
|
|
}
|
|
|
|
// Increase the reference count of SaveStruct by one, and then decrease it accordingly in UAutoSaveSubsystem::RemoveSaveStructRef
|
|
StructInfo->RefConut++;
|
|
|
|
return (FSaveStruct*)StructInfo->Data.GetData();
|
|
}
|
|
|
|
if (!ScriptStruct) return nullptr;
|
|
|
|
const bool bIsCppStruct = ScriptStruct->IsChildOf(FSaveStruct::StaticStruct());
|
|
const bool bIsBlueprintStruct = ScriptStruct->GetClass() == UUserDefinedStruct::StaticClass();
|
|
|
|
if (!bIsCppStruct && !bIsBlueprintStruct)
|
|
return nullptr;
|
|
|
|
TUniquePtr<FSaveStructInfo> NewStructInfo(new FSaveStructInfo());
|
|
|
|
if (FPaths::FileExists(Filename))
|
|
{
|
|
NewStructInfo->Filename = Filename;
|
|
NewStructInfo->Struct = ScriptStruct;
|
|
NewStructInfo->State = ESaveStructState::Preload;
|
|
NewStructInfo->RefConut = 1;
|
|
NewStructInfo->LastRefConut = 0;
|
|
NewStructInfo->LastSaveTime = FDateTime::Now();
|
|
NewStructInfo->Data.SetNumUninitialized(ScriptStruct->GetStructureSize());
|
|
ScriptStruct->InitializeStruct(NewStructInfo->Data.GetData());
|
|
}
|
|
else
|
|
{
|
|
// Check if the target is writable
|
|
if (!FFileHelper::SaveStringToFile(TEXT(""), *Filename))
|
|
return nullptr;
|
|
|
|
NewStructInfo->Filename = Filename;
|
|
NewStructInfo->Struct = ScriptStruct;
|
|
NewStructInfo->State = ESaveStructState::Idle;
|
|
NewStructInfo->RefConut = 1;
|
|
NewStructInfo->LastRefConut = 0;
|
|
NewStructInfo->LastSaveTime = FDateTime::Now();
|
|
NewStructInfo->Data.SetNumUninitialized(ScriptStruct->GetStructureSize());
|
|
ScriptStruct->InitializeStruct(NewStructInfo->Data.GetData());
|
|
}
|
|
|
|
ScriptStructHooker.Add(Filename, ScriptStruct);
|
|
|
|
StructInfos.Add(Filename, nullptr);
|
|
StructInfos[Filename].Reset(NewStructInfo.Release());
|
|
|
|
return (FSaveStruct*)StructInfos[Filename]->Data.GetData();
|
|
}
|
|
|
|
FSaveStruct * UAutoSaveSubsystem::AddSaveStructRef(const FString & Filename, UScriptStruct * ScriptStruct, FSaveStructLoadDelegate LoadCallback)
|
|
{
|
|
FSaveStruct* Result = AddSaveStructRef(Filename, ScriptStruct);
|
|
|
|
if (!LoadCallback.IsBound()) return Result;
|
|
|
|
if (!Result) return nullptr;
|
|
|
|
if (!LoadDelegates.Contains(Filename))
|
|
{
|
|
LoadDelegates.Add(Filename);
|
|
}
|
|
|
|
LoadDelegates[Filename].Add(LoadCallback);
|
|
|
|
return Result;
|
|
}
|
|
|
|
FSaveStruct * UAutoSaveSubsystem::AddSaveStructRef(const FString & Filename, UScriptStruct * ScriptStruct, FSaveStructLoadDynamicDelegate LoadCallback)
|
|
{
|
|
FSaveStruct* Result = AddSaveStructRef(Filename, ScriptStruct);
|
|
|
|
if (!LoadCallback.IsBound()) return Result;
|
|
|
|
if (!Result) return nullptr;
|
|
|
|
if (!LoadDynamicDelegates.Contains(Filename))
|
|
{
|
|
LoadDynamicDelegates.Add(Filename);
|
|
}
|
|
|
|
LoadDynamicDelegates[Filename].Add(LoadCallback);
|
|
|
|
return Result;
|
|
}
|
|
|
|
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 = StructInfoPtr->Data;
|
|
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::HandleLoadDelegates()
|
|
{
|
|
// Delegates
|
|
{
|
|
TArray<FString> DelegatesToRemove;
|
|
|
|
for (const TPair<FString, FSaveStructLoadDelegates>& Delegates : LoadDelegates)
|
|
{
|
|
if (!StructInfos.Contains(Delegates.Key))
|
|
{
|
|
DelegatesToRemove.Add(Delegates.Key);
|
|
continue;
|
|
}
|
|
|
|
if (StructInfos[Delegates.Key]->State == ESaveStructState::Idle || StructInfos[Delegates.Key]->State == ESaveStructState::Saving)
|
|
{
|
|
Delegates.Value.Broadcast(Delegates.Key);
|
|
DelegatesToRemove.Add(Delegates.Key);
|
|
}
|
|
}
|
|
|
|
for (const FString& Filename : DelegatesToRemove)
|
|
{
|
|
LoadDelegates.Remove(Filename);
|
|
}
|
|
}
|
|
|
|
// DynamicDelegates
|
|
{
|
|
TArray<FString> DynamicDelegatesToRemove;
|
|
|
|
for (const TPair<FString, FSaveStructLoadDynamicDelegates>& Delegates : LoadDynamicDelegates)
|
|
{
|
|
if (!StructInfos.Contains(Delegates.Key))
|
|
{
|
|
DynamicDelegatesToRemove.Add(Delegates.Key);
|
|
continue;
|
|
}
|
|
|
|
if (StructInfos[Delegates.Key]->State == ESaveStructState::Idle || StructInfos[Delegates.Key]->State == ESaveStructState::Saving)
|
|
{
|
|
Delegates.Value.Broadcast(Delegates.Key);
|
|
DynamicDelegatesToRemove.Add(Delegates.Key);
|
|
}
|
|
}
|
|
|
|
for (const FString& Filename : DynamicDelegatesToRemove)
|
|
{
|
|
LoadDynamicDelegates.Remove(Filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAutoSaveSubsystem::Initialize(FSubsystemCollectionBase & Collection)
|
|
{
|
|
if (MaxThreadNum > 0)
|
|
TaskThreads.SetNum(MaxThreadNum);
|
|
}
|
|
|
|
void UAutoSaveSubsystem::Deinitialize()
|
|
{
|
|
// Make sure the tasks are completed
|
|
for (TUniquePtr<FAsyncTask<FStructLoadOrSaveTask>>& Task : TaskThreads)
|
|
{
|
|
if (!Task) continue;
|
|
Task->EnsureCompletion();
|
|
Task = nullptr;
|
|
}
|
|
|
|
// Make sure objects are saved
|
|
for (const TPair<FString, TUniquePtr<FSaveStructInfo>>& Info : StructInfos)
|
|
{
|
|
// Skip objects that are not loaded
|
|
if (Info.Value->State == ESaveStructState::Preload) continue;
|
|
|
|
check(Info.Value->State == ESaveStructState::Idle);
|
|
|
|
if (Info.Value->RefConut > 0)
|
|
{
|
|
UE_LOG(LogAutoSave, Warning, TEXT("The subsystem deinitialize, but '%s' still has references."), *Info.Value->Filename);
|
|
}
|
|
|
|
FAsyncTask<FStructLoadOrSaveTask> Task(Info.Value.Get());
|
|
Task.StartSynchronousTask();
|
|
}
|
|
}
|
|
|
|
void UAutoSaveSubsystem::Tick(float DeltaTime)
|
|
{
|
|
HandleTaskDone();
|
|
HandleTaskStart();
|
|
HandleLoadDelegates();
|
|
}
|