godot_voxel/storage/voxel_data.h

305 lines
12 KiB
C++

#ifndef VOXEL_DATA_H
#define VOXEL_DATA_H
#include "../generators/voxel_generator.h"
#include "../streams/voxel_stream.h"
#include "modifiers.h"
#include "voxel_data_map.h"
namespace zylann::voxel {
class VoxelDataGrid;
// Generic storage containing everything needed to access voxel data.
// Contains edits, procedural sources and file stream so voxels not physically stored in memory can be obtained.
// This does not contain meshing or instancing information, only voxels.
// Individual calls should be thread-safe.
class VoxelData {
public:
VoxelData();
~VoxelData();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Configuration.
// Changing these settings while data is already loaded can be expensive, or cause data to be reset.
// If threaded tasks are still working on the data while this happens, they should be cancelled or ignored.
inline unsigned int get_block_size() const {
return _lods[0].map.get_block_size();
}
inline unsigned int get_block_size_po2() const {
return _lods[0].map.get_block_size_pow2();
}
inline Vector3i voxel_to_block(Vector3i pos) const {
return _lods[0].map.voxel_to_block(pos);
}
void set_lod_count(unsigned int p_lod_count);
// Clears voxel data. Keeps modifiers, generator and settings.
void reset_maps();
inline unsigned int get_lod_count() const {
RWLockRead rlock(_rw_lock);
return _lod_count;
}
void set_bounds(Box3i bounds);
inline Box3i get_bounds() const {
RWLockRead rlock(_rw_lock);
return _bounds_in_voxels;
}
void set_generator(Ref<VoxelGenerator> generator);
inline Ref<VoxelGenerator> get_generator() const {
RWLockRead rlock(_rw_lock);
return _generator;
}
void set_stream(Ref<VoxelStream> stream);
inline Ref<VoxelStream> get_stream() const {
RWLockRead rlock(_rw_lock);
return _stream;
}
inline VoxelModifierStack &get_modifiers() {
return _modifiers;
}
inline const VoxelModifierStack &get_modifiers() const {
return _modifiers;
}
void set_streaming_enabled(bool enabled);
inline bool is_streaming_enabled() const {
return _streaming_enabled;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Queries.
// When not specified, the used LOD index is 0.
VoxelSingleValue get_voxel(Vector3i pos, unsigned int channel_index, VoxelSingleValue defval) const;
bool try_set_voxel(uint64_t value, Vector3i pos, unsigned int channel_index);
float get_voxel_f(Vector3i pos, unsigned int channel_index) const;
bool try_set_voxel_f(real_t value, Vector3i pos, unsigned int channel_index);
void copy(Vector3i min_pos, VoxelBufferInternal &dst_buffer, unsigned int channels_mask) const;
void paste(Vector3i min_pos, const VoxelBufferInternal &src_buffer, unsigned int channels_mask, bool use_mask,
uint64_t mask_value, bool create_new_blocks);
bool is_area_loaded(const Box3i p_voxels_box) const;
// Executes a read+write operation on all voxels in the given area, on a specific channel.
// If the area intersects the boundaries of the volume, it will be clipped.
// If the area intersects blocks that aren't loaded, the operation will be cancelled.
// Returns the box of voxels which were effectively processed.
template <typename F>
Box3i write_box(const Box3i &p_voxel_box, unsigned int channel_index, F action) {
const Box3i voxel_box = p_voxel_box.clipped(get_bounds());
if (!is_area_loaded(voxel_box)) {
ZN_PRINT_VERBOSE("Area not editable");
return Box3i();
}
Ref<VoxelGenerator> generator = _generator;
VoxelDataLodMap::Lod &data_lod0 = _data->lods[0];
{
RWLockWrite wlock(data_lod0.map_lock);
data_lod0.map.write_box(
voxel_box, channel_index, action, [&generator](VoxelBufferInternal &voxels, Vector3i pos) {
if (generator.is_valid()) {
VoxelGenerator::VoxelQueryData q{ voxels, pos, 0 };
generator->generate_block(q);
}
});
}
return voxel_box;
}
// Executes a read+write operation on all voxels in the given area, on two specific channels.
// If the area intersects the boundaries of the volume, it will be clipped.
// If the area intersects blocks that aren't loaded, the operation will be cancelled.
// Returns the box of voxels which were effectively processed.
template <typename F>
Box3i write_box_2(const Box3i &p_voxel_box, unsigned int channel1_index, unsigned int channel2_index, F action) {
const Box3i voxel_box = p_voxel_box.clipped(get_bounds());
if (!is_area_loaded(voxel_box)) {
ZN_PRINT_VERBOSE("Area not editable");
return Box3i();
}
Ref<VoxelGenerator> generator = _generator;
VoxelDataLodMap::Lod &data_lod0 = _data->lods[0];
{
RWLockWrite wlock(data_lod0.map_lock);
data_lod0.map.write_box_2(voxel_box, channel1_index, channel2_index, action,
[&generator](VoxelBufferInternal &voxels, Vector3i pos) {
if (generator.is_valid()) {
VoxelGenerator::VoxelQueryData q{ voxels, pos, 0 };
generator->generate_block(q);
}
});
}
return voxel_box;
}
// Generates all non-present blocks in preparation for an edit.
// Every block intersecting with the box at every LOD will be checked.
// This function runs sequentially and should be thread-safe. May be used if blocks are immediately needed.
// It will block if other threads are accessing the same data.
// WARNING: this does not check if the area is editable.
void pre_generate_box(Box3i voxel_box);
// Clears voxel data from blocks that are pure results of generators and modifiers.
// WARNING: this does not check if the area is editable.
void clear_cached_blocks_in_voxel_area(Box3i p_voxel_box);
// Flags all blocks in the given area as modified at LOD0.
// Also marks them as requiring LOD updates (if lod count is 1 this has no effect).
// Optionally, returns a list of affected block positions which did not require LOD updates before.
void mark_area_modified(Box3i p_voxel_box, std::vector<Vector3i> *lod0_new_blocks_to_lod);
// Sets voxel data at a block position. Also sets wether this is edited data (otherwise it is cached generator
// results).
// If the block has different size than expected, returns false and doesn't set the data.
// If the block already exists, it will not be overwritten, but still returns true.
// Otherwise, returns true.
// TODO Might need to expose a parameter for the overwriting behavior.
bool try_set_block_buffer(
Vector3i block_position, unsigned int lod_index, std::shared_ptr<VoxelBufferInternal> buffer, bool edited);
// Sets empty voxel data at a block position. It means this block is known to have no edits and no cached generator
// data.
// If the block already exists, it is not overwritten.
// TODO Might need to expose a parameter for the overwriting behavior.
void set_empty_block_buffer(Vector3i block_position, unsigned int lod_index);
template <typename F>
void for_each_block(F op) {
for (unsigned int lod_index = 0; lod_index < _lod_count; ++lod_index) {
Lod &lod = _lods[lod_index];
RWLockRead rlock(lod.map_lock);
lod.map.for_each_block(op);
}
}
template <typename F>
void for_each_block_at_lod(F op, unsigned int lod_index) const {
const Lod &lod = _lods[lod_index];
RWLockRead rlock(lod.map_lock);
lod.map.for_each_block(op);
}
// Tests if a block exists at the specified block position and LOD index.
// This is mainly used for debugging so it isn't optimal, don't use this if you plan to query many blocks.
bool has_block(Vector3i bpos, unsigned int lod_index) const;
// Gets the total amount of allocated blocks. This includes blocks having no voxel data.
unsigned int get_block_count() const;
struct BlockLocation {
Vector3i position;
uint32_t lod_index;
};
// Updates the LODs of all blocks at given positions, and resets their flags telling that they need LOD updates.
// Optionally, returns a list of affected block positions.
void update_lods(Span<const Vector3i> modified_lod0_blocks, std::vector<BlockLocation> *out_updated_blocks);
// void action(VoxelDataBlock &block, Vector3i bpos)
template <typename F>
void unload_blocks(Box3i bbox, unsigned int lod_index, F action) {
Lod &lod = _lods[lod_index];
RWLockWrite wlock(lod.map_lock);
bbox.for_each_cell_zxy([&lod, &action](Vector3i bpos) {
lod.map.remove_block(bpos, [&action, bpos](VoxelDataBlock &block) { action(block, bpos); });
});
}
// Gets missing blocks out of the given block positions.
// WARNING: positions outside bounds will be considered missing too.
// TODO Don't consider positions outside bounds to be missing? This is only a byproduct of migrating old code.
// It doesnt check this because the code using this function already does it (a bit more efficiently, but still).
void get_missing_blocks(
Span<const Vector3i> block_positions, unsigned int lod_index, std::vector<Vector3i> &out_missing) const;
// Gets missing blocks out of the given area in block coordinates.
// If the area intersects the outside of the bounds, it will be clipped.
void get_missing_blocks(Box3i p_blocks_box, unsigned int lod_index, std::vector<Vector3i> &out_missing) const;
unsigned int get_blocks_with_voxel_data(
Box3i p_blocks_box, unsigned int lod_index, Span<std::shared_ptr<VoxelBufferInternal>> out_blocks) const;
void get_blocks_grid(VoxelDataGrid &grid, Box3i box_in_voxels, unsigned int lod_index) const;
private:
void reset_maps_no_lock();
struct Lod {
// Storage for edited and cached voxels.
VoxelDataMap map;
// This lock should be locked in write mode only when the map gets modified (adding or removing blocks).
// Otherwise it may be locked in read mode.
// It is possible to unlock it after we are done querying the map.
RWLock map_lock;
};
static void pre_generate_box(Box3i voxel_box, Span<Lod> lods, unsigned int data_block_size, bool streaming,
unsigned int lod_count, Ref<VoxelGenerator> generator, VoxelModifierStack &modifiers);
static inline std::shared_ptr<VoxelBufferInternal> try_get_voxel_buffer_with_lock(
const Lod &data_lod, Vector3i block_pos, bool &out_generate) {
RWLockRead rlock(data_lod.map_lock);
const VoxelDataBlock *block = data_lod.map.get_block(block_pos);
if (block == nullptr) {
return nullptr;
}
// TODO Thread-safety: this checking presence of voxels is not safe.
// It can change while meshing takes place if a modifier is moved in the same area,
// because it invalidates cached data (that doesn't require locking the map, and doesn't lock a VoxelBuffer,
// so there is no sync going on). One way to fix this is to implement a spatial lock.
if (!block->has_voxels()) {
out_generate = true;
return nullptr;
}
return block->get_voxels_shared();
}
// Each LOD works in a set of coordinates spanning 2x more voxels the higher their index is.
// LOD 0 is the primary storage for edited data. Higher indices are "mip-maps".
// A fixed array is used because it's often a small one, and it doesn't require locking by threads.
// Note that these LODs do not automatically update, it is up to users of the class to do this job.
FixedArray<Lod, constants::MAX_LOD> _lods;
Box3i _bounds_in_voxels;
uint8_t _lod_count = 1;
// If enabled, some data blocks can have the "not loaded" and "loaded" status. Which means we can't assume what they
// contain, until we load them from the stream.
// If disabled, all edits are loaded in memory, and we know if a block isn't stored, it means we can use the
// generator and modifiers to obtain its data.
// This mostly changes how this class is used, streaming itself is not directly implemented in this class.
bool _streaming_enabled = true;
// Procedural generation stack
VoxelModifierStack _modifiers;
Ref<VoxelGenerator> _generator;
// Persistent storage (file(s)).
Ref<VoxelStream> _stream;
// This should be locked when accessing configuration members.
RWLock _rw_lock;
};
} // namespace zylann::voxel
#endif // VOXEL_DATA_H