// 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 struct TVoxelRange; template class TVoxelQueryZone; template 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 CVarMaxPlaceableItemsPerOctree; extern VOXEL_API TAutoConsoleVariable 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 class TVoxelDataItemWrapper { public: T Item; private: mutable int32 Index = -1; TVoxelWeakPtr Data; friend class FVoxelData; }; struct VOXEL_API FVoxelDataSettings { const int32 Depth; const FVoxelIntBox WorldBounds; const TVoxelSharedRef Generator; const bool bEnableMultiplayer; const bool bEnableUndoRedo; FVoxelDataSettings(const AVoxelWorld* World, EVoxelPlayType PlayType); FVoxelDataSettings( int32 Depth, const TVoxelSharedRef& Generator, bool bEnableMultiplayer, bool bEnableUndoRedo); FVoxelDataSettings( const FVoxelIntBox& WorldBounds, const TVoxelSharedRef& Generator, bool bEnableMultiplayer, bool bEnableUndoRedo); }; /** * Class that handle voxel data */ class VOXEL_API FVoxelData : public IVoxelData, public TVoxelSharedFromThis { private: explicit FVoxelData(const FVoxelDataSettings& Settings); public: static TVoxelSharedRef Create(const FVoxelDataSettings& Settings, int32 DataOctreeInitialSubdivisionDepth = 0); // Clone without keeping the voxel data TVoxelSharedRef Clone() const; ~FVoxelData(); private: TUniquePtr 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 FORCEINLINE bool IsInWorld(T X, T Y, T Z) const { return WorldBounds.ContainsTemplate(X, Y, Z); } template FORCEINLINE bool IsInWorld(const T& P) const { return WorldBounds.ContainsTemplate(P); } template FORCEINLINE void ClampToWorld(T& X, T& Y, T& Z) const { WorldBounds.Clamp(X, Y, Z); ensureVoxelSlow(IsInWorld(X, Y, Z)); } template 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 Lock(EVoxelLockType LockType, const FVoxelIntBox& Bounds, FName Name) const; /** * Unlock previously locked bounds */ void Unlock(TUniquePtr 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& OutBoundsToUpdate); // Requires write lock template void CacheBounds(const FVoxelIntBox& Bounds, bool bMultiThreaded); // Requires write lock template void ClearCacheInBounds(const FVoxelIntBox& Bounds); // Requires write lock template void CheckIsSingle(const FVoxelIntBox& Bounds); // Get the data in zone. Requires read lock template void Get(TVoxelQueryZone& QueryZone, int32 LOD) const; template TArray Get(const FVoxelIntBox& Bounds) const; // Will always use 8 threads template TArray ParallelGet(const FVoxelIntBox& Bounds, bool bForceSingleThread = false) const; TArray GetValues(const FVoxelIntBox& Bounds) const { return Get(Bounds); } TArray GetMaterials(const FVoxelIntBox& Bounds) const { return Get(Bounds); } // Requires read lock TVoxelRange GetValueRange(const FVoxelIntBox& Bounds, int32 LOD) const; bool IsEmpty(const FVoxelIntBox& Bounds, int32 LOD) const; template T GetCustomOutput(T DefaultValue, FName Name, v_flt X, v_flt Y, v_flt Z, int32 LOD) const; template FORCEINLINE T GetCustomOutput(T DefaultValue, FName Name, const U& P, int32 LOD) const { return GetCustomOutput(DefaultValue, Name, P.X, P.Y, P.Z, LOD); } TVoxelRange GetCustomOutputRange(TVoxelRange DefaultValue, FName Name, const FVoxelIntBox& Bounds, int32 LOD) const; public: template void Set(const FVoxelIntBox& Bounds, F Apply); template 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 void Set(int32 X, int32 Y, int32 Z, const T& Value); template FORCEINLINE void Set(const FIntVector& P, const T& Value) { Set(P.X, P.Y, P.Z, Value); } template T Get(int32 X, int32 Y, int32 Z, int32 LOD) const; template FORCEINLINE T Get(const FIntVector& P, int32 LOD) const { return Get(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(X, Y, Z, LOD); } FORCEINLINE FVoxelValue GetValue(const FIntVector& P , int32 LOD) const { return Get(P, LOD); } // Get the material at position. Requires read lock FORCEINLINE FVoxelMaterial GetMaterial(int32 X, int32 Y, int32 Z, int32 LOD) const { return Get(X, Y, Z, LOD); } FORCEINLINE FVoxelMaterial GetMaterial(const FIntVector& P , int32 LOD) const { return Get(P, LOD); } // Set value at position. Requires write lock FORCEINLINE void SetValue(int32 X, int32 Y, int32 Z, FVoxelValue Value) { Set(X, Y, Z, Value); } FORCEINLINE void SetValue(const FIntVector& P , FVoxelValue Value) { Set(P, Value); } // Set material at position. Requires write lock FORCEINLINE void SetMaterial(int32 X, int32 Y, int32 Z, FVoxelMaterial Material) { Set(X, Y, Z, Material); } FORCEINLINE void SetMaterial(const FIntVector& P , FVoxelMaterial Material) { Set(P, Material); } public: /** * Load/Save */ // Get a save of this world. No lock required void GetSave(FVoxelUncompressedWorldSaveImpl& OutSave, TArray& 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* 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& OutBoundsToUpdate); // Redo one frame and add it to the undo stack. Current frame must be empty. No lock required bool Redo(TArray& 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 UndoFramesBounds; TArray RedoFramesBounds; // Used to clear redo stacks on SaveFrame without iterating the entire octree // Stack: added when undoing, poping when redoing TArray> LeavesWithRedoStackStack; // Each save frame is assigned a unique ID uint64 FrameUniqueIdCounter = 2; uint64 CurrentFrameUniqueId = 1; TArray UndoUniqueIds; TArray 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 TVoxelWeakPtr> AddItem(TArgs&&... Args); // Requires write lock on item bounds template bool RemoveItem(TVoxelWeakPtr>& Item, FString& OutError); private: template struct TItemData { FCriticalSection Section; TArray>> Items; }; TItemData AssetItemsData; TItemData DisableEditsItemsData; TItemData DataItemsData; // When adding a new item type also add it to ClearData, AddItem & RemoveItem, ApplyToAllItems, NumItems, NeedToSubdivide template TItemData& 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 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 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 void RemoveItemFromLeafData( const FVoxelData& Data, FVoxelDataOctreeLeaf& Leaf, const TItem& Item); }