使用 AutoSave 插件管理文件

修复 AVoxelWorld::CenterAgent 为空时 RE
修复区块不能根据距离正常卸载的问题
This commit is contained in:
_Redstone_c_ 2020-12-13 11:20:17 +08:00
parent d1103c580b
commit 0663e3192e
8 changed files with 168 additions and 107 deletions

View File

@ -6,6 +6,24 @@
#include "ProceduralMeshComponent.h"
#include "KismetProceduralMeshLibrary.h"
bool FVoxelChunkData::Serialize(FArchive & Slot)
{
UScriptStruct* VoxelBlockStruct = FVoxelBlock::StaticStruct();
for (int32 X = 0; X < 16; ++X)
{
for (int32 Y = 0; Y < 16; ++Y)
{
for (int32 Z = 0; Z < 256; ++Z)
{
VoxelBlockStruct->SerializeItem(Slot, &Blocks[X][Y][Z], nullptr);
}
}
}
return true;
}
AVoxelChunk::AVoxelChunk(const class FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
@ -24,14 +42,35 @@ AVoxelChunk::AVoxelChunk(const class FObjectInitializer& ObjectInitializer)
void AVoxelChunk::BeginPlay()
{
Super::BeginPlay();
UGameInstance* GameInstance = GetGameInstance();
VoxelSubsystem = GameInstance->GetSubsystem<UVoxelSubsystem>();
AttachToActor(GetOwner(), FAttachmentTransformRules::KeepRelativeTransform);
FlushMaterials();
ArchiveFile = VoxelWorld->GetWorldSetting().ArchiveFolder / FString::Printf(TEXT("%08X%08X"), ChunkLocation.X, ChunkLocation.Y);
FSaveStructLoadDelegate OnLoaded;
OnLoaded.BindUFunction(this, TEXT("OnDataLoaded"));
Data = MakeShared<FSaveStructPtr<FVoxelChunkData>>(GetGameInstance()->GetSubsystem<UAutoSaveSubsystem>(), ArchiveFile, OnLoaded);
check(Data->IsValid());
}
void AVoxelChunk::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
Data = nullptr;
Super::EndPlay(EndPlayReason);
}
const FVoxelBlock & AVoxelChunk::GetBlockByRelativeLocation(const FIntVector & Location) const
{
checkCode(
if (!FMath::IsWithin(Location.X, 0, 16)
if (!Data->IsLoaded()
|| !FMath::IsWithin(Location.X, 0, 16)
|| !FMath::IsWithin(Location.Y, 0, 16)
|| !FMath::IsWithin(Location.Z, 0, 256))
{
@ -40,13 +79,14 @@ const FVoxelBlock & AVoxelChunk::GetBlockByRelativeLocation(const FIntVector & L
}
);
return Blocks[Location.X][Location.Y][Location.Z];
return (*Data)->Blocks[Location.X][Location.Y][Location.Z];
}
void AVoxelChunk::SetBlockByRelativeLocation(const FIntVector& Location, const FVoxelBlock& NewBlock)
{
checkCode(
if (!FMath::IsWithin(Location.X, 0, 16)
if (!Data->IsLoaded()
|| !FMath::IsWithin(Location.X, 0, 16)
|| !FMath::IsWithin(Location.Y, 0, 16)
|| !FMath::IsWithin(Location.Z, 0, 256))
{
@ -55,7 +95,7 @@ void AVoxelChunk::SetBlockByRelativeLocation(const FIntVector& Location, const F
}
);
Blocks[Location.X][Location.Y][Location.Z] = NewBlock;
(*Data)->Blocks[Location.X][Location.Y][Location.Z] = NewBlock;
FlushMeshFlags |= 1 << (Location.Z / 16);
@ -146,78 +186,10 @@ void AVoxelChunk::FlushMaterials()
}
}
void AVoxelChunk::Load()
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_VoxelChunk_Load);
if (VoxelWorld->GetWorldSetting().ArchiveFolder.IsEmpty())
{
ArchiveFile.Empty();
return;
}
ArchiveFile = VoxelWorld->GetWorldSetting().ArchiveFolder / FString::Printf(TEXT("%08X%08X"), ChunkLocation.X, ChunkLocation.Y) + ChunkFileExtension;
if (FPaths::FileExists(ArchiveFile))
{
TArray<uint8> ChunkDataBuffer;
if (FFileHelper::LoadFileToArray(ChunkDataBuffer, *ArchiveFile))
{
FMemoryReader MemoryReader(ChunkDataBuffer);
UScriptStruct* VoxelBlockStruct = FVoxelBlock::StaticStruct();
for (int32 X = 0; X < 16; ++X)
{
for (int32 Y = 0; Y < 16; ++Y)
{
for (int32 Z = 0; Z < 256; ++Z)
{
VoxelBlockStruct->SerializeItem(MemoryReader, &Blocks[X][Y][Z], nullptr);
}
}
}
UE_LOG(LogVoxel, Log, TEXT("Load Chunk %d, %d From File '%s'"), ChunkLocation.X, ChunkLocation.Y, *ArchiveFile);
}
}
}
void AVoxelChunk::Unload()
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_VoxelChunk_Unload);
if (ArchiveFile.IsEmpty()) return;
TArray<uint8> ChunkDataBuffer;
FMemoryWriter MemoryWriter(ChunkDataBuffer);
UScriptStruct* VoxelBlockStruct = FVoxelBlock::StaticStruct();
for (int32 X = 0; X < 16; ++X)
{
for (int32 Y = 0; Y < 16; ++Y)
{
for (int32 Z = 0; Z < 256; ++Z)
{
VoxelBlockStruct->SerializeItem(MemoryWriter, &Blocks[X][Y][Z], nullptr);
}
}
}
if (FFileHelper::SaveArrayToFile(ChunkDataBuffer, *ArchiveFile))
{
UE_LOG(LogVoxel, Log, TEXT("Save Chunk %d, %d To File '%s'"), ChunkLocation.X, ChunkLocation.Y, *ArchiveFile);
}
else
{
UE_LOG(LogVoxel, Error, TEXT("Failed To Save Chunk %d, %d To File '%s'"), ChunkLocation.X, ChunkLocation.Y, *ArchiveFile);
}
}
void AVoxelChunk::FlushMeshSection(int32 SectionIndex)
{
if (!Data->IsLoaded()) return;
static FVoxelMeshData CubeTopFaceBuffer = FVoxelMeshData::CubeTopFace;
static FVoxelMeshData CubeBottomFaceBuffer = FVoxelMeshData::CubeBottomFace;
static FVoxelMeshData CubeFrontFaceBuffer = FVoxelMeshData::CubeFrontFace;
@ -244,16 +216,16 @@ void AVoxelChunk::FlushMeshSection(int32 SectionIndex)
{
for (int32 Z = 0 + SectionIndex * 16; Z < 16 + SectionIndex * 16; ++Z)
{
const FVoxelBlockType& CurrentVoxelBlock = VoxelSubsystem->GetBlockType(Blocks[X][Y][Z].Type);
const FVoxelBlockType& CurrentVoxelBlock = VoxelSubsystem->GetBlockType((*Data)->Blocks[X][Y][Z].Type);
if (CurrentVoxelBlock.Shape != EVoxelBlockShape::Cube) continue;
const FVoxelBlockType& TopVoxelBlock = VoxelSubsystem->GetBlockType(Z + 1 < 256 ? Blocks[X][Y][Z + 1].Type : TEXT("Air"));
const FVoxelBlockType& BottomVoxelBlock = VoxelSubsystem->GetBlockType(Z - 1 >= 0 ? Blocks[X][Y][Z - 1].Type : TEXT("Air"));
const FVoxelBlockType& FrontVoxelBlock = VoxelSubsystem->GetBlockType(X + 1 < 16 ? Blocks[X + 1][Y][Z].Type : (FrontChunk ? FrontChunk->GetBlockByRelativeLocation(FIntVector(0, Y, Z)).Type : TEXT("Air")));
const FVoxelBlockType& BackVoxelBlock = VoxelSubsystem->GetBlockType(X - 1 >= 0 ? Blocks[X - 1][Y][Z].Type : (BackChunk ? BackChunk->GetBlockByRelativeLocation(FIntVector(15, Y, Z)).Type : TEXT("Air")));
const FVoxelBlockType& LeftVoxelBlock = VoxelSubsystem->GetBlockType(Y - 1 >= 0 ? Blocks[X][Y - 1][Z].Type : (LeftChunk ? LeftChunk->GetBlockByRelativeLocation(FIntVector(X, 15, Z)).Type : TEXT("Air")));
const FVoxelBlockType& RightVoxelBlock = VoxelSubsystem->GetBlockType(Y + 1 < 16 ? Blocks[X][Y + 1][Z].Type : (RightChunk ? RightChunk->GetBlockByRelativeLocation(FIntVector(X, 0, Z)).Type : TEXT("Air")));
const FVoxelBlockType& TopVoxelBlock = VoxelSubsystem->GetBlockType(Z + 1 < 256 ? (*Data)->Blocks[X][Y][Z + 1].Type : TEXT("Air"));
const FVoxelBlockType& BottomVoxelBlock = VoxelSubsystem->GetBlockType(Z - 1 >= 0 ? (*Data)->Blocks[X][Y][Z - 1].Type : TEXT("Air"));
const FVoxelBlockType& FrontVoxelBlock = VoxelSubsystem->GetBlockType(X + 1 < 16 ? (*Data)->Blocks[X + 1][Y][Z].Type : (FrontChunk ? FrontChunk->GetBlockByRelativeLocation(FIntVector(0, Y, Z)).Type : TEXT("Air")));
const FVoxelBlockType& BackVoxelBlock = VoxelSubsystem->GetBlockType(X - 1 >= 0 ? (*Data)->Blocks[X - 1][Y][Z].Type : (BackChunk ? BackChunk->GetBlockByRelativeLocation(FIntVector(15, Y, Z)).Type : TEXT("Air")));
const FVoxelBlockType& LeftVoxelBlock = VoxelSubsystem->GetBlockType(Y - 1 >= 0 ? (*Data)->Blocks[X][Y - 1][Z].Type : (LeftChunk ? LeftChunk->GetBlockByRelativeLocation(FIntVector(X, 15, Z)).Type : TEXT("Air")));
const FVoxelBlockType& RightVoxelBlock = VoxelSubsystem->GetBlockType(Y + 1 < 16 ? (*Data)->Blocks[X][Y + 1][Z].Type : (RightChunk ? RightChunk->GetBlockByRelativeLocation(FIntVector(X, 0, Z)).Type : TEXT("Air")));
if (TopVoxelBlock.Shape != EVoxelBlockShape::Cube)
{
@ -321,3 +293,8 @@ void AVoxelChunk::FlushMeshSection(int32 SectionIndex)
true
);
}
void AVoxelChunk::OnDataLoaded(const FString & Filename)
{
VoxelWorld->AddChunkToMeshFlushBuffer(ChunkLocation);
}

View File

@ -1,20 +1,46 @@
#include "VoxelHelper.h"
#include "VoxelWorld.h"
#include "Kismet/GameplayStatics.h"
AVoxelWorld * UVoxelHelper::CreateVoxelWorld(UObject* WorldContextObject, const FVoxelWorldSetting& WorldSetting)
{
if (!IsWorldSettingValid(WorldContextObject, WorldSetting)) return nullptr;
UWorld* World = WorldContextObject->GetWorld();
if (!World) return nullptr;
AVoxelWorld* VoxelWorld = World->SpawnActor<AVoxelWorld>();
AVoxelWorld* VoxelWorld = Cast<AVoxelWorld>(
UGameplayStatics::BeginDeferredActorSpawnFromClass(
World,
AVoxelWorld::StaticClass(),
FTransform(),
ESpawnActorCollisionHandlingMethod::AlwaysSpawn,
nullptr
)
);
VoxelWorld->WorldSetting = WorldSetting;
UGameplayStatics::FinishSpawningActor(VoxelWorld, FTransform());
return VoxelWorld;
}
bool UVoxelHelper::IsWorldSettingValid(UObject * WorldContextObject, const FVoxelWorldSetting & WorldSetting)
{
FString TestFilePath = WorldSetting.ArchiveFolder / TEXT("TestFile");
if (!FFileHelper::SaveStringToFile(TEXT(""), *TestFilePath))
return false;
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
PlatformFile.DeleteFile(*TestFilePath);
return true;
}
void UVoxelHelper::WorldToRelativeLocation(const FIntVector & InWorldLocation, FIntPoint & OutChunkLocation, FIntVector & OutRelativeLocation)
{
OutChunkLocation.X = InWorldLocation.X / 16;

View File

@ -6,6 +6,7 @@
#include "Block/VoxelBlock.h"
#include "Chunk/VoxelChunk.h"
#include "VoxelAgentInterface.h"
#include "Kismet/GameplayStatics.h"
const TArray<FIntPoint> ChunkLoadOrder =
{
@ -160,6 +161,10 @@ void AVoxelWorld::BeginPlay()
UGameInstance* GameInstance = GetGameInstance();
VoxelSubsystem = GameInstance->GetSubsystem<UVoxelSubsystem>();
Data = MakeShared<FSaveStructPtr<FVoxelWorldData>>(GetGameInstance()->GetSubsystem<UAutoSaveSubsystem>(), WorldSetting.ArchiveFolder / TEXT("VoxelWorld"));
check(Data->IsValid());
}
void AVoxelWorld::Tick(float DeltaSeconds)
@ -169,7 +174,6 @@ void AVoxelWorld::Tick(float DeltaSeconds)
ManageChunk();
FlushMeshs();
}
void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
@ -180,6 +184,8 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
for (const FIntPoint& Chunk : ChunksToUnload)
UnloadChunk(Chunk);
Data = nullptr;
Super::EndPlay(EndPlayReason);
}
@ -223,14 +229,22 @@ void AVoxelWorld::LoadChunk(const FIntPoint & ChunkLocation)
check(World);
AVoxelChunk* NewVoxelChunk = World->SpawnActor<AVoxelChunk>();
FTransform ChunkTransform(FVector(ChunkLocation.X * 1600.0f, ChunkLocation.Y * 1600.0f, 0.0f));
AVoxelChunk* NewVoxelChunk = Cast<AVoxelChunk>(
UGameplayStatics::BeginDeferredActorSpawnFromClass(
this,
AVoxelChunk::StaticClass(),
ChunkTransform,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn,
this
)
);
NewVoxelChunk->VoxelWorld = this;
NewVoxelChunk->ChunkLocation = ChunkLocation;
NewVoxelChunk->SetActorLocation(FVector(ChunkLocation.X * 1600.0f, ChunkLocation.Y * 1600.0f, 0.0f));
NewVoxelChunk->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform);
NewVoxelChunk->FlushMaterials();
NewVoxelChunk->Load();
AddChunkToMeshFlushBuffer(ChunkLocation);
UGameplayStatics::FinishSpawningActor(NewVoxelChunk, ChunkTransform);
Chunks.Add(ChunkLocation, NewVoxelChunk);
@ -241,7 +255,6 @@ void AVoxelWorld::UnloadChunk(const FIntPoint & ChunkLocation)
{
if (!Chunks.Contains(ChunkLocation)) return;
Chunks[ChunkLocation]->Unload();
Chunks[ChunkLocation]->Destroy();
Chunks.Remove(ChunkLocation);
@ -259,9 +272,16 @@ void AVoxelWorld::FlushMaterials()
void AVoxelWorld::ManageChunk()
{
FIntPoint RootChunk;
FIntVector RLocation;
UVoxelHelper::WorldToRelativeLocation(IVoxelAgentInterface::Execute_GetAgentLocation(CenterAgent.GetObject()), RootChunk, RLocation);
if (GetGameInstance()->GetSubsystem<UAutoSaveSubsystem>()->GetIdleThreadNum() <= 0) return;
FIntPoint RootChunk = FIntPoint(0, 0);
if (CenterAgent.GetObject())
{
FIntVector RLocation;
UVoxelHelper::WorldToRelativeLocation(IVoxelAgentInterface::Execute_GetAgentLocation(CenterAgent.GetObject()), RootChunk, RLocation);
}
// Load
for (const FIntPoint& Chunk : ChunkLoadOrder)
@ -286,7 +306,7 @@ void AVoxelWorld::ManageChunk()
const FIntPoint WorldLocation = Chunk.Key;
const FIntPoint RelativeLocation = WorldLocation - RootChunk;
const bool bInUnloadDistance = FMath::Abs(RelativeLocation.X) > ChunkUnloadDistance && FMath::Abs(RelativeLocation.Y) > ChunkUnloadDistance;
const bool bInUnloadDistance = FMath::Abs(RelativeLocation.X) > ChunkUnloadDistance || FMath::Abs(RelativeLocation.Y) > ChunkUnloadDistance;
if (bInUnloadDistance)
{

View File

@ -1,6 +1,7 @@
#pragma once
#include "CoreMinimal.h"
#include "SaveStruct.h"
#include "Block/VoxelBlock.h"
#include "GameFramework/Actor.h"
#include "VoxelChunk.generated.h"
@ -9,7 +10,24 @@ class AVoxelWorld;
class UVoxelSubsystem;
class UProceduralMeshComponent;
const FString ChunkFileExtension = TEXT(".voxelchunk");
USTRUCT()
struct FVoxelChunkData : public FSaveStruct
{
GENERATED_BODY()
FVoxelBlock Blocks[16][16][256];
bool Serialize(FArchive& Slot);
};
template<>
struct TStructOpsTypeTraits<FVoxelChunkData> : public TStructOpsTypeTraitsBase2<FVoxelChunkData>
{
enum
{
WithSerializer = true,
};
};
UCLASS(BlueprintType)
class VOXEL_API AVoxelChunk : public AActor
@ -24,6 +42,8 @@ public:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
UFUNCTION(BlueprintCallable, Category = "Voxel|Chunk")
AVoxelWorld* GetVoxelWorld() const { return VoxelWorld; }
@ -59,16 +79,15 @@ private:
uint16 FlushMeshFlags;
FVoxelBlock Blocks[16][16][256];
TSharedPtr<FSaveStructPtr<FVoxelChunkData>> Data;
FVoxelMeshData MeshSectionBuffer;
private:
void Load();
void Unload();
void FlushMeshSection(int32 SectionIndex);
UFUNCTION()
void OnDataLoaded(const FString& Filename);
};

View File

@ -17,6 +17,9 @@ public:
UFUNCTION(BlueprintCallable, Category = "Voxel|Helper", meta = (WorldContext = "WorldContextObject"))
static AVoxelWorld* CreateVoxelWorld(UObject* WorldContextObject, const FVoxelWorldSetting& WorldSetting);
UFUNCTION(BlueprintCallable, Category = "Voxel|Helper", meta = (WorldContext = "WorldContextObject"))
static bool IsWorldSettingValid(UObject* WorldContextObject, const FVoxelWorldSetting& WorldSetting);
UFUNCTION(BlueprintPure, Category = "Voxel|Helper")
static void WorldToRelativeLocation(const FIntVector& InWorldLocation, FIntPoint& OutChunkLocation, FIntVector& OutRelativeLocation);

View File

@ -1,6 +1,7 @@
#pragma once
#include "CoreMinimal.h"
#include "SaveStruct.h"
#include "GameFramework/Info.h"
#include "VoxelWorld.generated.h"
@ -22,6 +23,12 @@ struct VOXEL_API FVoxelWorldSetting
};
USTRUCT()
struct FVoxelWorldData : public FSaveStruct
{
GENERATED_BODY()
};
UCLASS(BlueprintType)
class VOXEL_API AVoxelWorld : public AInfo
{
@ -79,10 +86,14 @@ private:
UPROPERTY()
TSet<FIntPoint> MeshFlushBuffer;
TSharedPtr<FSaveStructPtr<FVoxelWorldData>> Data;
private:
void ManageChunk();
void FlushMeshs();
bool TestWorldSettingValid();
};

View File

@ -39,6 +39,7 @@ public class Voxel : ModuleRules
"Slate",
"SlateCore",
"ProceduralMeshComponent",
"AutoSave",
// ... add private dependencies that you statically link with here ...
}
);

View File

@ -21,10 +21,14 @@
"LoadingPhase": "Default"
}
],
"Plugins": [
{
"Name": "ProceduralMeshComponent",
"Enabled": true
}
]
"Plugins": [
{
"Name": "ProceduralMeshComponent",
"Enabled": true
},
{
"Name": "AutoSave",
"Enabled": true
}
]
}