CelticCraft/Plugins/VoxelFree/Source/Voxel/Public/VoxelData/VoxelData.h

398 lines
13 KiB
C
Raw Normal View History

2023-07-03 16:17:13 +00:00
// Copyright 2020 Phyronnaz
#pragma once
#include "CoreMinimal.h"
#include "VoxelMinimal.h"
#include "VoxelIntBox.h"
#include "VoxelValue.h"
#include "VoxelMaterial.h"
#include "VoxelSharedMutex.h"
#include "VoxelData/IVoxelData.h"
#include "HAL/ConsoleManager.h"
class AVoxelWorld;
class FVoxelData;
class FVoxelDataLockInfo;
class FVoxelDataOctreeBase;
class FVoxelDataOctreeLeaf;
class FVoxelDataOctreeParent;
class FVoxelGeneratorInstance;
class FVoxelTransformableGeneratorInstance;
struct FVoxelDataItem;
struct FVoxelAssetItem;
struct FVoxelObjectArchiveEntry;
struct FVoxelDisableEditsBoxItem;
struct FVoxelPlaceableItemLoadInfo;
struct FVoxelUncompressedWorldSaveImpl;
template<typename T>
struct TVoxelRange;
template<typename T>
class TVoxelQueryZone;
template<typename T>
struct TVoxelChunkDiff;
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Num Voxel Asset Items"), STAT_NumVoxelAssetItems, STATGROUP_VoxelCounters, VOXEL_API);
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Num Voxel Disable Edits Items"), STAT_NumVoxelDisableEditsItems, STATGROUP_VoxelCounters, VOXEL_API);
DECLARE_DWORD_ACCUMULATOR_STAT_EXTERN(TEXT("Num Voxel Data Items"), STAT_NumVoxelDataItems, STATGROUP_VoxelCounters, VOXEL_API);
extern VOXEL_API TAutoConsoleVariable<int32> CVarMaxPlaceableItemsPerOctree;
extern VOXEL_API TAutoConsoleVariable<int32> CVarStoreSpecialValueForGeneratorValuesInSaves;
// Turns off some expensive compression settings that aren't needed if you just want to save, recreate world, load
// TODO REMOVE AND MAKE Save/Load param
struct FVoxelScopedFastSaveLoad
{
// No need to diff against generator as it's very slow
const int32 DiffGenerator = CVarStoreSpecialValueForGeneratorValuesInSaves.GetValueOnGameThread();
FVoxelScopedFastSaveLoad()
{
CVarStoreSpecialValueForGeneratorValuesInSaves->Set(0);
}
~FVoxelScopedFastSaveLoad()
{
CVarStoreSpecialValueForGeneratorValuesInSaves->Set(DiffGenerator);
}
};
template<typename T>
class TVoxelDataItemWrapper
{
public:
T Item;
private:
mutable int32 Index = -1;
TVoxelWeakPtr<FVoxelData> Data;
friend class FVoxelData;
};
struct VOXEL_API FVoxelDataSettings
{
const int32 Depth;
const FVoxelIntBox WorldBounds;
const TVoxelSharedRef<FVoxelGeneratorInstance> Generator;
const bool bEnableMultiplayer;
const bool bEnableUndoRedo;
FVoxelDataSettings(const AVoxelWorld* World, EVoxelPlayType PlayType);
FVoxelDataSettings(
int32 Depth,
const TVoxelSharedRef<FVoxelGeneratorInstance>& Generator,
bool bEnableMultiplayer,
bool bEnableUndoRedo);
FVoxelDataSettings(
const FVoxelIntBox& WorldBounds,
const TVoxelSharedRef<FVoxelGeneratorInstance>& Generator,
bool bEnableMultiplayer,
bool bEnableUndoRedo);
};
/**
* Class that handle voxel data
*/
class VOXEL_API FVoxelData : public IVoxelData, public TVoxelSharedFromThis<FVoxelData>
{
private:
explicit FVoxelData(const FVoxelDataSettings& Settings);
public:
static TVoxelSharedRef<FVoxelData> Create(const FVoxelDataSettings& Settings, int32 DataOctreeInitialSubdivisionDepth = 0);
// Clone without keeping the voxel data
TVoxelSharedRef<FVoxelData> Clone() const;
~FVoxelData();
private:
TUniquePtr<FVoxelDataOctreeParent> Octree;
// Is locked as read when a lock is done
// Lock as write to clear the octree, making sure no octrees are locked
mutable FVoxelSharedMutex MainLock;
public:
FORCEINLINE int32 Size() const
{
return DATA_CHUNK_SIZE << Depth;
}
FVoxelDataOctreeBase& GetOctree() const;
// NOTE: what if we query between WorldBounds.Max - 1 and WorldBounds.Max?
template<typename T>
FORCEINLINE bool IsInWorld(T X, T Y, T Z) const
{
return WorldBounds.ContainsTemplate(X, Y, Z);
}
template<typename T>
FORCEINLINE bool IsInWorld(const T& P) const
{
return WorldBounds.ContainsTemplate(P);
}
template<typename T>
FORCEINLINE void ClampToWorld(T& X, T& Y, T& Z) const
{
WorldBounds.Clamp(X, Y, Z);
ensureVoxelSlow(IsInWorld(X, Y, Z));
}
template<typename T>
FORCEINLINE T ClampToWorld(const T& P) const
{
return WorldBounds.Clamp(P);
}
public:
/**
* Lock the bounds
* @param LockType Read or write lock
* @param Bounds Bounds to lock
* @param Name The name of the task locking these bounds, for debug
*/
TUniquePtr<FVoxelDataLockInfo> Lock(EVoxelLockType LockType, const FVoxelIntBox& Bounds, FName Name) const;
/**
* Unlock previously locked bounds
*/
void Unlock(TUniquePtr<FVoxelDataLockInfo> LockInfo) const;
public:
// Must NOT be locked. Will delete the entire octree & recreate one
// Destroys all items
void ClearData();
// Will clear all the edited data. Keeps items
// Requires write lock
void ClearOctreeData(TArray<FVoxelIntBox>& OutBoundsToUpdate);
// Requires write lock
template<typename T>
void CacheBounds(const FVoxelIntBox& Bounds, bool bMultiThreaded);
// Requires write lock
template<typename T>
void ClearCacheInBounds(const FVoxelIntBox& Bounds);
// Requires write lock
template<typename T>
void CheckIsSingle(const FVoxelIntBox& Bounds);
// Get the data in zone. Requires read lock
template<typename T>
void Get(TVoxelQueryZone<T>& QueryZone, int32 LOD) const;
template<typename T>
TArray<T> Get(const FVoxelIntBox& Bounds) const;
// Will always use 8 threads
template<typename T>
TArray<T> ParallelGet(const FVoxelIntBox& Bounds, bool bForceSingleThread = false) const;
TArray<FVoxelValue> GetValues(const FVoxelIntBox& Bounds) const
{
return Get<FVoxelValue>(Bounds);
}
TArray<FVoxelMaterial> GetMaterials(const FVoxelIntBox& Bounds) const
{
return Get<FVoxelMaterial>(Bounds);
}
// Requires read lock
TVoxelRange<FVoxelValue> GetValueRange(const FVoxelIntBox& Bounds, int32 LOD) const;
bool IsEmpty(const FVoxelIntBox& Bounds, int32 LOD) const;
template<typename T>
T GetCustomOutput(T DefaultValue, FName Name, v_flt X, v_flt Y, v_flt Z, int32 LOD) const;
template<typename T, typename U>
FORCEINLINE T GetCustomOutput(T DefaultValue, FName Name, const U& P, int32 LOD) const
{
return GetCustomOutput<T>(DefaultValue, Name, P.X, P.Y, P.Z, LOD);
}
TVoxelRange<v_flt> GetCustomOutputRange(TVoxelRange<v_flt> DefaultValue, FName Name, const FVoxelIntBox& Bounds, int32 LOD) const;
public:
template<typename ...TArgs, typename F>
void Set(const FVoxelIntBox& Bounds, F Apply);
template<typename ...TArgs, typename F>
void ParallelSet(const FVoxelIntBox& Bounds, F Apply, bool bForceSingleThread = false);
public:
/**
* Getters/Setters
*/
// Set value or material at position depending on template argument (FVoxelValue or FVoxelMaterial)
template<typename T>
void Set(int32 X, int32 Y, int32 Z, const T& Value);
template<typename T>
FORCEINLINE void Set(const FIntVector& P, const T& Value)
{
Set<T>(P.X, P.Y, P.Z, Value);
}
template<typename T>
T Get(int32 X, int32 Y, int32 Z, int32 LOD) const;
template<typename T>
FORCEINLINE T Get(const FIntVector& P, int32 LOD) const
{
return Get<T>(P.X, P.Y, P.Z, LOD);
}
// Get the value at position. Requires read lock
FORCEINLINE FVoxelValue GetValue(int32 X, int32 Y, int32 Z, int32 LOD) const { return Get<FVoxelValue>(X, Y, Z, LOD); }
FORCEINLINE FVoxelValue GetValue(const FIntVector& P , int32 LOD) const { return Get<FVoxelValue>(P, LOD); }
// Get the material at position. Requires read lock
FORCEINLINE FVoxelMaterial GetMaterial(int32 X, int32 Y, int32 Z, int32 LOD) const { return Get<FVoxelMaterial>(X, Y, Z, LOD); }
FORCEINLINE FVoxelMaterial GetMaterial(const FIntVector& P , int32 LOD) const { return Get<FVoxelMaterial>(P, LOD); }
// Set value at position. Requires write lock
FORCEINLINE void SetValue(int32 X, int32 Y, int32 Z, FVoxelValue Value) { Set<FVoxelValue>(X, Y, Z, Value); }
FORCEINLINE void SetValue(const FIntVector& P , FVoxelValue Value) { Set<FVoxelValue>(P, Value); }
// Set material at position. Requires write lock
FORCEINLINE void SetMaterial(int32 X, int32 Y, int32 Z, FVoxelMaterial Material) { Set<FVoxelMaterial>(X, Y, Z, Material); }
FORCEINLINE void SetMaterial(const FIntVector& P , FVoxelMaterial Material) { Set<FVoxelMaterial>(P, Material); }
public:
/**
* Load/Save
*/
// Get a save of this world. No lock required
void GetSave(FVoxelUncompressedWorldSaveImpl& OutSave, TArray<FVoxelObjectArchiveEntry>& OutObjects);
/**
* Load this world from save. No lock required
* @param Save Save to load from
* @param LoadInfo Used to load placeable items. Can use {}
* @param OutBoundsToUpdate The modified bounds
* @return true if loaded successfully, false if the world is corrupted and must not be saved again
*/
bool LoadFromSave(const FVoxelUncompressedWorldSaveImpl& Save, const FVoxelPlaceableItemLoadInfo& LoadInfo, TArray<FVoxelIntBox>* OutBoundsToUpdate = nullptr);
public:
/**
* Undo/Redo
*/
// Undo one frame and add it to the redo stack. Current frame must be empty. No lock required
bool Undo(TArray<FVoxelIntBox>& OutBoundsToUpdate);
// Redo one frame and add it to the undo stack. Current frame must be empty. No lock required
bool Redo(TArray<FVoxelIntBox>& OutBoundsToUpdate);
// Clear all the frames. No lock required
void ClearFrames();
// Add the current frame to the undo stack. Clear the redo stack. No lock required. Bounds: must contain all the edits since last SaveFrame
void SaveFrame(const FVoxelIntBox& Bounds);
// Check that the current frame is empty (safe to call Undo/Redo). No lock required
bool IsCurrentFrameEmpty();
// Get the history position. No lock required
inline int32 GetHistoryPosition() const { return UndoRedo.HistoryPosition; }
// Get the max history position, ie HistoryPosition + redo frames. No lock required
inline int32 GetMaxHistoryPosition() const { return UndoRedo.MaxHistoryPosition; }
// Dirty state: can use that to track if the data is dirty
// MarkAsDirty is called on Undo, Redo, SaveFrame and ClearData
FORCEINLINE void MarkAsDirty() { bIsDirty = true; }
FORCEINLINE void ClearDirtyFlag() { bIsDirty = false; }
FORCEINLINE bool IsDirty() const { return bIsDirty; }
// Each save frame call gets assigned a unique ID, can be used to track the state of the world
// Will always be != 0
FORCEINLINE uint64 GetCurrentFrameUniqueId() const { return UndoRedo.CurrentFrameUniqueId; }
private:
struct FUndoRedo
{
int32 HistoryPosition = 0;
int32 MaxHistoryPosition = 0;
TArray<FVoxelIntBox> UndoFramesBounds;
TArray<FVoxelIntBox> RedoFramesBounds;
// Used to clear redo stacks on SaveFrame without iterating the entire octree
// Stack: added when undoing, poping when redoing
TArray<TArray<FVoxelDataOctreeLeaf*>> LeavesWithRedoStackStack;
// Each save frame is assigned a unique ID
uint64 FrameUniqueIdCounter = 2;
uint64 CurrentFrameUniqueId = 1;
TArray<uint64> UndoUniqueIds;
TArray<uint64> RedoUniqueIds;
};
FUndoRedo UndoRedo;
bool bIsDirty = false;
public:
/**
* Placeable items
*/
/** Add a FVoxelPlaceableItem to the world. Requires write lock on the item bounds
* @param Args Passed to the constructor of T
* @param bDoNotModifyExistingDataChunks Used when loading from a save
*/
template<typename T, bool bDoNotModifyExistingDataChunks = false, typename... TArgs>
TVoxelWeakPtr<const TVoxelDataItemWrapper<T>> AddItem(TArgs&&... Args);
// Requires write lock on item bounds
template<typename T>
bool RemoveItem(TVoxelWeakPtr<const TVoxelDataItemWrapper<T>>& Item, FString& OutError);
private:
template<typename T>
struct TItemData
{
FCriticalSection Section;
TArray<TVoxelSharedPtr<const TVoxelDataItemWrapper<T>>> Items;
};
TItemData<FVoxelAssetItem> AssetItemsData;
TItemData<FVoxelDisableEditsBoxItem> DisableEditsItemsData;
TItemData<FVoxelDataItem> DataItemsData;
// When adding a new item type also add it to ClearData, AddItem & RemoveItem, ApplyToAllItems, NumItems, NeedToSubdivide
template<typename T>
TItemData<T>& GetItemsData();
};
namespace FVoxelDataUtilities
{
// This will NOT add the item to the item holder
VOXEL_API void AddAssetItemToLeafData(
const FVoxelData& Data,
FVoxelDataOctreeLeaf& Leaf,
const FVoxelTransformableGeneratorInstance& Generator,
const FVoxelIntBox& Bounds,
const FTransform& LocalToWorld,
bool bModifyValues,
bool bModifyMaterials);
// Will update all the values that were the same as the old generator to the new generator values
template<typename T, typename TApplyOld, typename TApplyNew>
void MigrateLeafDataToNewGenerator(
const FVoxelData& Data,
FVoxelDataOctreeLeaf& Leaf,
const FVoxelIntBox& BoundsToMigrate,
TApplyOld ApplyOldGenerator,
TApplyNew ApplyNewGenerator);
// This will NOT add the item to the item holder, but will assume it has already been added
template<typename T, typename TItem>
void AddItemToLeafData(
const FVoxelData& Data,
FVoxelDataOctreeLeaf& Leaf,
const TItem& Item);
// This will NOT remove the item from the item holder, but will assume it has already been removed
template<typename T, typename TItem>
void RemoveItemFromLeafData(
const FVoxelData& Data,
FVoxelDataOctreeLeaf& Leaf,
const TItem& Item);
}