Migrate VoxelTerrain to use the new VoxelData. UNTESTED

master
Marc Gilleron 2022-08-29 23:34:00 +01:00
parent 7a8e605430
commit afd81e4a24
12 changed files with 685 additions and 293 deletions

View File

@ -2,6 +2,7 @@
#include "../meshers/blocky/voxel_mesher_blocky.h"
#include "../meshers/cubes/voxel_mesher_cubes.h"
#include "../storage/voxel_buffer_gd.h"
#include "../storage/voxel_data.h"
#include "../storage/voxel_metadata_variant.h"
#include "../terrain/fixed_lod/voxel_terrain.h"
#include "../util/godot/ref_counted.h"
@ -21,38 +22,45 @@ VoxelToolTerrain::VoxelToolTerrain(VoxelTerrain *terrain) {
bool VoxelToolTerrain::is_area_editable(const Box3i &box) const {
ERR_FAIL_COND_V(_terrain == nullptr, false);
// TODO Take volume bounds into account
return _terrain->get_storage().is_area_fully_loaded(box);
return _terrain->get_storage().is_area_loaded(box);
}
Ref<VoxelRaycastResult> VoxelToolTerrain::raycast(
Vector3 p_pos, Vector3 p_dir, float p_max_distance, uint32_t p_collision_mask) {
// TODO Implement broad-phase on blocks to minimize locking and increase performance
// TODO Optimization: voxel raycast uses `get_voxel` which is the slowest, but could be made faster.
// See `VoxelToolLodTerrain` for information about how to implement improvements.
struct RaycastPredicateColor {
const VoxelDataMap &map;
const VoxelData &data;
bool operator()(const VoxelRaycastState &rs) const {
const uint64_t v = map.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_COLOR);
VoxelSingleValue defval;
defval.i = 0;
const uint64_t v = data.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_COLOR, defval).i;
return v != 0;
}
};
struct RaycastPredicateSDF {
const VoxelDataMap &map;
const VoxelData &data;
bool operator()(const VoxelRaycastState &rs) const {
const float v = map.get_voxel_f(rs.hit_position, VoxelBufferInternal::CHANNEL_SDF);
const float v = data.get_voxel_f(rs.hit_position, VoxelBufferInternal::CHANNEL_SDF);
return v < 0;
}
};
struct RaycastPredicateBlocky {
const VoxelDataMap &map;
const VoxelData &data;
const VoxelBlockyLibrary &library;
const uint32_t collision_mask;
bool operator()(const VoxelRaycastState &rs) const {
const int v = map.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_TYPE);
VoxelSingleValue defval;
defval.i = 0;
const int v = data.get_voxel(rs.hit_position, VoxelBufferInternal::CHANNEL_TYPE, defval).i;
if (library.has_voxel(v) == false) {
return false;
@ -178,9 +186,9 @@ void VoxelToolTerrain::do_sphere(Vector3 center, float radius) {
return;
}
VoxelDataMap &data = _terrain->get_storage();
VoxelData &data = _terrain->get_storage();
op.blocks.reference_area(data, op.box);
data.get_blocks_grid(op.blocks, op.box, 0);
op();
_post_edit(op.box);
@ -209,9 +217,9 @@ void VoxelToolTerrain::do_hemisphere(Vector3 center, float radius, Vector3 flat_
return;
}
VoxelDataMap &data = _terrain->get_storage();
VoxelData &data = _terrain->get_storage();
op.blocks.reference_area(data, op.box);
data.get_blocks_grid(op.blocks, op.box, 0);
op();
_post_edit(op.box);
@ -219,7 +227,9 @@ void VoxelToolTerrain::do_hemisphere(Vector3 center, float radius, Vector3 flat_
uint64_t VoxelToolTerrain::_get_voxel(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, 0);
return _terrain->get_storage().get_voxel(pos, _channel);
VoxelSingleValue defval;
defval.i = 0;
return _terrain->get_storage().get_voxel(pos, _channel, defval).i;
}
float VoxelToolTerrain::_get_voxel_f(Vector3i pos) const {
@ -229,12 +239,12 @@ float VoxelToolTerrain::_get_voxel_f(Vector3i pos) const {
void VoxelToolTerrain::_set_voxel(Vector3i pos, uint64_t v) {
ERR_FAIL_COND(_terrain == nullptr);
_terrain->get_storage().set_voxel(v, pos, _channel);
_terrain->get_storage().try_set_voxel(v, pos, _channel);
}
void VoxelToolTerrain::_set_voxel_f(Vector3i pos, float v) {
ERR_FAIL_COND(_terrain == nullptr);
_terrain->get_storage().set_voxel_f(v, pos, _channel);
_terrain->get_storage().try_set_voxel_f(v, pos, _channel);
}
void VoxelToolTerrain::_post_edit(const Box3i &box) {
@ -244,40 +254,25 @@ void VoxelToolTerrain::_post_edit(const Box3i &box) {
void VoxelToolTerrain::set_voxel_metadata(Vector3i pos, Variant meta) {
ERR_FAIL_COND(_terrain == nullptr);
VoxelDataMap &map = _terrain->get_storage();
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
ERR_FAIL_COND_MSG(block == nullptr, "Area not editable");
// TODO In this situation, the generator would need to be invoked to fill in the blank
ERR_FAIL_COND_MSG(!block->has_voxels(), "Area not cached");
RWLockWrite lock(block->get_voxels().get_lock());
VoxelMetadata *meta_storage = block->get_voxels().get_or_create_voxel_metadata(map.to_local(pos));
ERR_FAIL_COND(meta_storage == nullptr);
gd::set_as_variant(*meta_storage, meta);
VoxelData &data = _terrain->get_storage();
data.set_voxel_metadata(pos, meta);
}
Variant VoxelToolTerrain::get_voxel_metadata(Vector3i pos) const {
ERR_FAIL_COND_V(_terrain == nullptr, Variant());
VoxelDataMap &map = _terrain->get_storage();
VoxelDataBlock *block = map.get_block(map.voxel_to_block(pos));
ERR_FAIL_COND_V_MSG(block == nullptr, Variant(), "Area not editable");
// TODO In this situation, the generator would need to be invoked to fill in the blank
ERR_FAIL_COND_V_MSG(!block->has_voxels(), Variant(), "Area not cached");
RWLockRead lock(block->get_voxels().get_lock());
const VoxelMetadata *meta = block->get_voxels_const().get_voxel_metadata(map.to_local(pos));
if (meta == nullptr) {
return Variant();
}
return gd::get_as_variant(*meta);
VoxelData &data = _terrain->get_storage();
return data.get_voxel_metadata(pos);
}
void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i voxel_box, const VoxelBlockyLibrary &lib,
void VoxelToolTerrain::run_blocky_random_tick_static(VoxelData &data, Box3i voxel_box, const VoxelBlockyLibrary &lib,
int voxel_count, int batch_count, void *callback_data, bool (*callback)(void *, Vector3i, int64_t)) {
ERR_FAIL_COND(batch_count <= 0);
ERR_FAIL_COND(voxel_count < 0);
ERR_FAIL_COND(!math::is_valid_size(voxel_box.size));
ERR_FAIL_COND(callback == nullptr);
const Box3i block_box = voxel_box.downscaled(map.get_block_size());
const unsigned int block_size = data.get_block_size();
const Box3i block_box = voxel_box.downscaled(block_size);
const int block_count = voxel_count / batch_count;
//const int bs_mask = map.get_block_size_mask();
@ -290,7 +285,7 @@ void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i vo
static thread_local std::vector<Pick> picks;
picks.reserve(batch_count);
const float block_volume = map.get_block_size() * map.get_block_size() * map.get_block_size();
const float block_volume = math::cubed(block_size);
CRASH_COND(block_volume < 0.1f);
struct L {
@ -309,15 +304,15 @@ void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i vo
for (int bi = 0; bi < block_count; ++bi) {
const Vector3i block_pos = block_box.pos + L::urand_vec3i(block_box.size);
const Vector3i block_origin = map.block_to_voxel(block_pos);
const Vector3i block_origin = data.block_to_voxel(block_pos);
VoxelDataBlock *block = map.get_block(block_pos);
std::shared_ptr<VoxelBufferInternal> voxels_ptr = data.try_get_block_voxels(block_pos);
if (block != nullptr && block->has_voxels()) {
if (voxels_ptr != nullptr) {
// Doing ONLY reads here.
{
RWLockRead lock(block->get_voxels().get_lock());
const VoxelBufferInternal &voxels = block->get_voxels_const();
RWLockRead lock(voxels_ptr->get_lock());
const VoxelBufferInternal &voxels = *voxels_ptr;
if (voxels.get_channel_compression(channel) == VoxelBufferInternal::COMPRESSION_UNIFORM) {
const uint64_t v = voxels.get_voxel(0, 0, 0, channel);
@ -330,7 +325,7 @@ void VoxelToolTerrain::run_blocky_random_tick_static(VoxelDataMap &map, Box3i vo
}
}
Box3i block_voxel_box(block_origin, Vector3iUtil::create(map.get_block_size()));
const Box3i block_voxel_box(block_origin, Vector3iUtil::create(block_size));
Box3i local_voxel_box = voxel_box.clipped(block_voxel_box);
local_voxel_box.pos -= block_origin;
const float volume_ratio = Vector3iUtil::get_volume(local_voxel_box.size) / block_volume;
@ -398,11 +393,11 @@ void VoxelToolTerrain::run_blocky_random_tick(
CallbackData cb_self{ callback };
const VoxelBlockyLibrary &lib = **get_voxel_library(*_terrain);
VoxelDataMap &map = _terrain->get_storage();
VoxelData &data = _terrain->get_storage();
const Box3i voxel_box(math::floor_to_int(voxel_area.position), math::floor_to_int(voxel_area.size));
run_blocky_random_tick_static(
map, voxel_box, lib, voxel_count, batch_count, &cb_self, [](void *self, Vector3i pos, int64_t val) {
data, voxel_box, lib, voxel_count, batch_count, &cb_self, [](void *self, Vector3i pos, int64_t val) {
const Variant vpos = pos;
const Variant vv = val;
const Variant *args[2];
@ -430,20 +425,20 @@ void VoxelToolTerrain::for_each_voxel_metadata_in_area(AABB voxel_area, const Ca
const Box3i data_block_box = voxel_box.downscaled(_terrain->get_data_block_size());
VoxelDataMap &map = _terrain->get_storage();
VoxelData &data = _terrain->get_storage();
data_block_box.for_each_cell([&map, &callback, voxel_box](Vector3i block_pos) {
VoxelDataBlock *block = map.get_block(block_pos);
data_block_box.for_each_cell([&data, &callback, voxel_box](Vector3i block_pos) {
std::shared_ptr<VoxelBufferInternal> voxels_ptr = data.try_get_block_voxels(block_pos);
if (block == nullptr || !block->has_voxels()) {
if (voxels_ptr == nullptr) {
return;
}
const Vector3i block_origin = block_pos * map.get_block_size();
const Vector3i block_origin = block_pos * data.get_block_size();
const Box3i rel_voxel_box(voxel_box.pos - block_origin, voxel_box.size);
// TODO Worth it locking blocks for metadata?
block->get_voxels().for_each_voxel_metadata_in_area(
voxels_ptr->for_each_voxel_metadata_in_area(
rel_voxel_box, [&callback, block_origin](Vector3i rel_pos, const VoxelMetadata &meta) {
Variant v = gd::get_as_variant(meta);
const Variant key = rel_pos + block_origin;

View File

@ -7,7 +7,7 @@ namespace zylann::voxel {
class VoxelTerrain;
class VoxelBlockyLibrary;
class VoxelDataMap;
class VoxelData;
class VoxelToolTerrain : public VoxelTool {
GDCLASS(VoxelToolTerrain, VoxelTool)
@ -37,7 +37,7 @@ public:
// For easier unit testing (the regular one needs a terrain setup etc, harder to test atm)
// The `_static` suffix is because it otherwise conflicts with the non-static method when registering the class
static void run_blocky_random_tick_static(VoxelDataMap &map, Box3i voxel_box, const VoxelBlockyLibrary &lib,
static void run_blocky_random_tick_static(VoxelData &data, Box3i voxel_box, const VoxelBlockyLibrary &lib,
int voxel_count, int batch_count, void *callback_data, bool (*callback)(void *, Vector3i, int64_t));
void for_each_voxel_metadata_in_area(AABB voxel_area, const Callable &callback);

View File

@ -2,9 +2,61 @@
#include "../util/dstack.h"
#include "../util/math/conv.h"
#include "voxel_data_grid.h"
#include "voxel_metadata_variant.h"
namespace zylann::voxel {
namespace {
struct BeforeUnloadSaveAction {
std::vector<VoxelData::BlockToSave> *to_save;
Vector3i position;
unsigned int lod_index;
inline void operator()(VoxelDataBlock &block) {
if (block.is_modified()) {
// If a modified block has no voxels, it is equivalent to removing the block from the stream
VoxelData::BlockToSave b;
b.position = position;
b.lod_index = lod_index;
if (block.has_voxels()) {
// No copy is necessary because the block will be removed anyways
b.voxels = block.get_voxels_shared();
}
to_save->push_back(b);
}
}
};
struct ScheduleSaveAction {
std::vector<VoxelData::BlockToSave> &blocks_to_save;
uint8_t lod_index;
bool with_copy;
void operator()(const Vector3i &bpos, VoxelDataBlock &block) {
if (block.is_modified()) {
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
VoxelData::BlockToSave b;
// If a modified block has no voxels, it is equivalent to removing the block from the stream
if (block.has_voxels()) {
if (with_copy) {
RWLockRead lock(block.get_voxels().get_lock());
b.voxels = make_shared_instance<VoxelBufferInternal>();
block.get_voxels_const().duplicate_to(*b.voxels, true);
} else {
b.voxels = block.get_voxels_shared();
}
}
b.position = bpos;
b.lod_index = lod_index;
blocks_to_save.push_back(b);
block.set_modified(false);
}
}
};
} //namespace
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
VoxelData::VoxelData() {}
VoxelData::~VoxelData() {}
@ -385,8 +437,7 @@ void VoxelData::mark_area_modified(Box3i p_voxel_box, std::vector<Vector3i> *lod
//RWLockWrite wlock(block->get_voxels_shared()->get_lock());
block->set_modified(true);
// TODO call `set_edited(true)` as well? Apparently it wasn't needed so far, but it's a bit confusing
block->set_edited(true);
// TODO That boolean is also modified by the threaded update task (always set to false)
if (!block->get_needs_lodding()) {
@ -401,8 +452,8 @@ void VoxelData::mark_area_modified(Box3i p_voxel_box, std::vector<Vector3i> *lod
}
}
bool VoxelData::try_set_block_buffer(
Vector3i block_position, unsigned int lod_index, std::shared_ptr<VoxelBufferInternal> buffer, bool edited) {
VoxelDataBlock *VoxelData::try_set_block_buffer(Vector3i block_position, unsigned int lod_index,
std::shared_ptr<VoxelBufferInternal> buffer, bool edited, bool overwrite) {
Lod &data_lod = _lods[lod_index];
if (buffer->get_size() != Vector3iUtil::create(get_block_size())) {
@ -414,11 +465,11 @@ bool VoxelData::try_set_block_buffer(
// Store buffer
RWLockWrite wlock(data_lod.map_lock);
// TODO Expose `overwrite` as parameter?
VoxelDataBlock *block = data_lod.map.set_block_buffer(block_position, buffer, false);
CRASH_COND(block == nullptr);
VoxelDataBlock *block = data_lod.map.set_block_buffer(block_position, buffer, overwrite);
ZN_ASSERT(block != nullptr);
block->set_edited(edited);
return true;
return block;
}
void VoxelData::set_empty_block_buffer(Vector3i block_position, unsigned int lod_index) {
@ -435,6 +486,24 @@ bool VoxelData::has_block(Vector3i bpos, unsigned int lod_index) const {
return data_lod.map.has_block(bpos);
}
// VoxelDataBlock *VoxelData::get_block(Vector3i bpos) {
// Lod &data_lod = _lods[0];
// RWLockRead rlock(data_lod.map_lock);
// return data_lod.map.get_block(bpos);
// }
bool VoxelData::has_all_blocks_in_area(Box3i data_blocks_box) const {
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
data_blocks_box = data_blocks_box.clipped(bounds_in_blocks);
const Lod &data_lod = _lods[0];
RWLockRead rlock(data_lod.map_lock);
return data_blocks_box.all_cells_match([&data_lod](Vector3i bpos) { //
return data_lod.map.has_block(bpos);
});
}
unsigned int VoxelData::get_block_count() const {
unsigned int sum = 0;
const unsigned int lod_count = get_lod_count();
@ -585,6 +654,64 @@ void VoxelData::update_lods(Span<const Vector3i> modified_lod0_blocks, std::vect
// }
}
void VoxelData::unload_blocks(Box3i bbox, unsigned int lod_index, std::vector<BlockToSave> *to_save) {
Lod &lod = _lods[lod_index];
RWLockWrite wlock(lod.map_lock);
if (to_save == nullptr) {
bbox.for_each_cell_zxy([&lod](Vector3i bpos) { //
lod.map.remove_block(bpos, VoxelDataMap::NoAction());
});
} else {
bbox.for_each_cell_zxy([&lod, lod_index, to_save](Vector3i bpos) {
lod.map.remove_block(bpos, BeforeUnloadSaveAction{ to_save, bpos, lod_index });
});
}
}
void VoxelData::unload_blocks(Span<const Vector3i> positions, std::vector<BlockToSave> *to_save) {
Lod &lod = _lods[0];
RWLockWrite wlock(lod.map_lock);
if (to_save == nullptr) {
for (Vector3i bpos : positions) {
lod.map.remove_block(bpos, VoxelDataMap::NoAction());
}
} else {
for (Vector3i bpos : positions) {
lod.map.remove_block(bpos, BeforeUnloadSaveAction{ to_save, bpos, 0 });
}
}
}
bool VoxelData::consume_block_modifications(Vector3i bpos, VoxelData::BlockToSave &out_to_save) {
Lod &lod = _lods[0];
RWLockRead rlock(lod.map_lock);
VoxelDataBlock *block = lod.map.get_block(bpos);
if (block == nullptr) {
return false;
}
if (block->is_modified()) {
if (block->has_voxels()) {
RWLockRead lock(block->get_voxels().get_lock());
out_to_save.voxels = make_shared_instance<VoxelBufferInternal>();
block->get_voxels_const().duplicate_to(*out_to_save.voxels, true);
}
out_to_save.position = bpos;
out_to_save.lod_index = 0;
block->set_modified(false);
return true;
}
return false;
}
void VoxelData::consume_all_modifications(std::vector<BlockToSave> &to_save, bool with_copy) {
const unsigned int lod_count = get_lod_count();
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(ScheduleSaveAction{ to_save, uint8_t(lod_index), with_copy });
}
}
void VoxelData::get_missing_blocks(
Span<const Vector3i> block_positions, unsigned int lod_index, std::vector<Vector3i> &out_missing) const {
const Lod &lod = _lods[lod_index];
@ -600,7 +727,7 @@ void VoxelData::get_missing_blocks(
Box3i p_blocks_box, unsigned int lod_index, std::vector<Vector3i> &out_missing) const {
const Lod &data_lod = _lods[lod_index];
const Box3i bounds_in_blocks = _bounds_in_voxels.downscaled(get_block_size());
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
const Box3i blocks_box = p_blocks_box.clipped(bounds_in_blocks);
RWLockRead rlock(data_lod.map_lock);
@ -612,27 +739,25 @@ void VoxelData::get_missing_blocks(
});
}
unsigned int VoxelData::get_blocks_with_voxel_data(
void VoxelData::get_blocks_with_voxel_data(
Box3i p_blocks_box, unsigned int lod_index, Span<std::shared_ptr<VoxelBufferInternal>> out_blocks) const {
ZN_ASSERT(int64_t(out_blocks.size()) >= Vector3iUtil::get_volume(p_blocks_box.size));
const Lod &data_lod = _lods[lod_index];
RWLockRead rlock(data_lod.map_lock);
unsigned int count = 0;
unsigned int index = 0;
// Iteration order matters for thread access.
p_blocks_box.for_each_cell_zxy([&count, &data_lod, &out_blocks](Vector3i data_block_pos) {
p_blocks_box.for_each_cell_zxy([&index, &data_lod, &out_blocks](Vector3i data_block_pos) {
const VoxelDataBlock *nblock = data_lod.map.get_block(data_block_pos);
// The block can actually be null on some occasions. Not sure yet if it's that bad
//CRASH_COND(nblock == nullptr);
if (nblock != nullptr && nblock->has_voxels()) {
out_blocks[count] = nblock->get_voxels_shared();
out_blocks[index] = nblock->get_voxels_shared();
}
++count;
++index;
});
return count;
}
void VoxelData::get_blocks_grid(VoxelDataGrid &grid, Box3i box_in_voxels, unsigned int lod_index) const {
@ -641,4 +766,93 @@ void VoxelData::get_blocks_grid(VoxelDataGrid &grid, Box3i box_in_voxels, unsign
grid.reference_area(data_lod.map, box_in_voxels);
}
void VoxelData::view_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
std::vector<Vector3i> &found_blocks_positions, std::vector<VoxelDataBlock *> &found_blocks) {
ZN_PROFILE_SCOPE();
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
blocks_box = blocks_box.clipped(bounds_in_blocks);
Lod &lod = _lods[0];
RWLockRead rlock(lod.map_lock);
blocks_box.for_each_cell_zxy([&lod, &found_blocks_positions, &found_blocks, &missing_blocks](Vector3i bpos) {
VoxelDataBlock *block = lod.map.get_block(bpos);
if (block != nullptr) {
block->viewers.add();
found_blocks.push_back(block);
found_blocks_positions.push_back(bpos);
} else {
missing_blocks.push_back(bpos);
}
});
}
void VoxelData::unview_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
std::vector<Vector3i> &found_blocks, std::vector<BlockToSave> *to_save) {
ZN_PROFILE_SCOPE();
const Box3i bounds_in_blocks = get_bounds().downscaled(get_block_size());
blocks_box = blocks_box.clipped(bounds_in_blocks);
Lod &lod = _lods[0];
RWLockRead rlock(lod.map_lock);
blocks_box.for_each_cell_zxy([&lod, &missing_blocks, &found_blocks, to_save](Vector3i bpos) {
VoxelDataBlock *block = lod.map.get_block(bpos);
if (block != nullptr) {
block->viewers.remove();
if (block->viewers.get() == 0) {
if (to_save == nullptr) {
lod.map.remove_block(bpos, VoxelDataMap::NoAction());
} else {
lod.map.remove_block(bpos, BeforeUnloadSaveAction{ to_save, bpos, 0 });
}
}
found_blocks.push_back(bpos);
} else {
missing_blocks.push_back(bpos);
}
});
}
std::shared_ptr<VoxelBufferInternal> VoxelData::try_get_block_voxels(Vector3i bpos) {
Lod &lod = _lods[0];
RWLockRead rlock(lod.map_lock);
VoxelDataBlock *block = lod.map.get_block(bpos);
if (block == nullptr) {
return nullptr;
}
if (block->has_voxels()) {
return block->get_voxels_shared();
}
return nullptr;
}
void VoxelData::set_voxel_metadata(Vector3i pos, Variant meta) {
Lod &lod = _lods[0];
RWLockRead rlock(lod.map_lock);
VoxelDataBlock *block = lod.map.get_block(lod.map.voxel_to_block(pos));
ZN_ASSERT_RETURN_MSG(block != nullptr, "Area not editable");
// TODO Ability to have metadata in areas where voxels have not been allocated?
// Otherwise we have to generate the block, because that's where it is stored at the moment.
ZN_ASSERT_RETURN_MSG(block->has_voxels(), "Area not cached");
RWLockWrite lock(block->get_voxels().get_lock());
VoxelMetadata *meta_storage = block->get_voxels().get_or_create_voxel_metadata(lod.map.to_local(pos));
ZN_ASSERT_RETURN(meta_storage != nullptr);
gd::set_as_variant(*meta_storage, meta);
}
Variant VoxelData::get_voxel_metadata(Vector3i pos) {
Lod &lod = _lods[0];
RWLockRead rlock(lod.map_lock);
VoxelDataBlock *block = lod.map.get_block(lod.map.voxel_to_block(pos));
ZN_ASSERT_RETURN_V_MSG(block != nullptr, Variant(), "Area not editable");
ZN_ASSERT_RETURN_V_MSG(block->has_voxels(), Variant(), "Area not cached");
RWLockRead lock(block->get_voxels().get_lock());
const VoxelMetadata *meta = block->get_voxels_const().get_voxel_metadata(lod.map.to_local(pos));
if (meta == nullptr) {
return Variant();
}
return gd::get_as_variant(*meta);
}
} // namespace zylann::voxel

View File

@ -36,6 +36,10 @@ public:
return _lods[0].map.voxel_to_block(pos);
}
inline Vector3i block_to_voxel(Vector3i pos) const {
return _lods[0].map.block_to_voxel(pos);
}
void set_lod_count(unsigned int p_lod_count);
// Clears voxel data. Keeps modifiers, generator and settings.
@ -82,7 +86,7 @@ public:
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Queries.
// Voxel queries.
// When not specified, the used LOD index is 0.
VoxelSingleValue get_voxel(Vector3i pos, unsigned int channel_index, VoxelSingleValue defval) const;
@ -102,6 +106,8 @@ public:
void paste(Vector3i min_pos, const VoxelBufferInternal &src_buffer, unsigned int channels_mask, bool use_mask,
uint64_t mask_value, bool create_new_blocks);
// Tests if the given area is loaded at LOD0.
// This is necessary for editing destructively.
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.
@ -118,6 +124,7 @@ public:
Ref<VoxelGenerator> generator = _generator;
VoxelDataLodMap::Lod &data_lod0 = _data->lods[0];
{
// New blocks can be created in the map so we have to lock for writing
RWLockWrite wlock(data_lod0.map_lock);
data_lod0.map.write_box(
voxel_box, channel_index, action, [&generator](VoxelBufferInternal &voxels, Vector3i pos) {
@ -144,6 +151,7 @@ public:
Ref<VoxelGenerator> generator = _generator;
VoxelDataLodMap::Lod &data_lod0 = _data->lods[0];
{
// New blocks can be created in the map so we have to lock for writing
RWLockWrite wlock(data_lod0.map_lock);
data_lod0.map.write_box_2(voxel_box, channel1_index, channel2_index, action,
[&generator](VoxelBufferInternal &voxels, Vector3i pos) {
@ -165,6 +173,7 @@ public:
// Clears voxel data from blocks that are pure results of generators and modifiers.
// WARNING: this does not check if the area is editable.
// TODO Rename `clear_cached_voxel_data_in_area`
void clear_cached_blocks_in_voxel_area(Box3i p_voxel_box);
// Flags all blocks in the given area as modified at LOD0.
@ -172,14 +181,17 @@ public:
// 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);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Block-aware API
// 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);
// If the block has different size than expected, returns nullptr and doesn't set the data.
// If the block already exists, it will be overwritten if `overwrite` is `true`.
// The block is returned if it exists.
// WARNING: the returned pointer may only be used if you know that this block won't get removed by another thread.
VoxelDataBlock *try_set_block_buffer(Vector3i block_position, unsigned int lod_index,
std::shared_ptr<VoxelBufferInternal> buffer, bool edited, bool overwrite);
// Sets empty voxel data at a block position. It means this block is known to have no edits and no cached generator
// data.
@ -187,18 +199,18 @@ public:
// TODO Might need to expose a parameter for the overwriting behavior.
void set_empty_block_buffer(Vector3i block_position, unsigned int lod_index);
// void op(Vector3i bpos, VoxelDataBlock &block)
// void op(Vector3i bpos, const VoxelDataBlock &block)
template <typename F>
void for_each_block(F op) {
void for_each_block(F op) const {
const unsigned int lod_count = get_lod_count();
for (unsigned int lod_index = 0; lod_index < lod_count; ++lod_index) {
Lod &lod = _lods[lod_index];
const Lod &lod = _lods[lod_index];
RWLockRead rlock(lod.map_lock);
lod.map.for_each_block(op);
}
}
// void op(Vector3i bpos, VoxelDataBlock &block)
// void op(Vector3i bpos, const VoxelDataBlock &block)
template <typename F>
void for_each_block_at_lod(F op, unsigned int lod_index) const {
const Lod &lod = _lods[lod_index];
@ -210,6 +222,14 @@ public:
// 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 a block directly from LOD0.
// WARNING: the returned pointer may only be used if you know that this block won't get removed by another thread.
// Prefer using other safe functions if possible.
//VoxelDataBlock *get_block(Vector3i bpos);
// Tests if all blocks in a LOD0 area are loaded. If any isn't, returns false. Otherwise, returns true.
bool has_all_blocks_in_area(Box3i data_blocks_box) const;
// Gets the total amount of allocated blocks. This includes blocks having no voxel data.
unsigned int get_block_count() const;
@ -222,20 +242,33 @@ public:
// 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); });
});
}
struct BlockToSave {
std::shared_ptr<VoxelBufferInternal> voxels;
Vector3i position;
uint32_t lod_index;
};
// Unloads data blocks in the specified area. If some of them were modified and `to_save` is not null, their data
// will be returned for the caller to save.
void unload_blocks(Box3i bbox, unsigned int lod_index, std::vector<BlockToSave> *to_save);
// Unloads data blocks at specified positions of LOD0. If some of them were modified and `to_save` is not null,
// their data will be returned for the caller to save.
void unload_blocks(Span<const Vector3i> positions, std::vector<BlockToSave> *to_save);
// If the block at the specified LOD0 position exists and is modified, marks it as non-modified and returns a copy
// of its data to save. Returns true if there is something to save.
bool consume_block_modifications(Vector3i bpos, BlockToSave &out_to_save);
// Marks all modified blocks as unmodified and returns their data to save. if `with_copy` is true, the returned data
// will be a copy, otherwise it will reference voxel data. Prefer using references when about to quit for example.
void consume_all_modifications(std::vector<BlockToSave> &to_save, bool with_copy);
// 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).
// 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;
@ -243,11 +276,37 @@ public:
// 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(
// Gets blocks with voxel data in the given area in block coordinates.
// Voxel data references are returned in an array big enough to contain a grid of the size of the area.
// Blocks found will be placed at an index computed as if the array was a flat grid (ZXY).
// Entries without voxel data will be left to null.
void 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;
std::shared_ptr<VoxelBufferInternal> try_get_block_voxels(Vector3i bpos);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Reference-counted API (LOD0 only)
void view_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
std::vector<Vector3i> &found_blocks_positions, std::vector<VoxelDataBlock *> &found_blocks);
// void unview_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks,
// std::vector<Vector3i> &blocks_without_viewers_positions,
// std::vector<VoxelDataBlock *> &blocks_without_viewers);
void unview_area(Box3i blocks_box, std::vector<Vector3i> &missing_blocks, std::vector<Vector3i> &found_blocks,
std::vector<BlockToSave> *to_save);
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Metadata queries.
// Only at LOD0.
void set_voxel_metadata(Vector3i pos, Variant meta);
Variant get_voxel_metadata(Vector3i pos);
private:
void reset_maps_no_settings_lock();
@ -288,19 +347,21 @@ private:
//
// TODO Optimize: (low priority) this takes more than 5Kb in the object, even when not using LODs.
// Each LOD contains an RWLock, which is 242 bytes, so *24 it adds up quickly.
// A solution would be to allocate LODs dynamically in the constructor (the potential presence of LODs doesnt need
// to change after being constructed, there is no use case for that so far)
// A solution would be to allocate LODs dynamically in the constructor (the potential presence of LODs doesnt
// need to change after being constructed, there is no use case for that so far)
FixedArray<Lod, constants::MAX_LOD> _lods;
// Area within which voxels can exist.
// Note, these bounds might not be exactly represented. Volumes are chunk-based, so the result may be
// approximated to the closest chunk.
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.
// 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
@ -317,7 +378,8 @@ private:
// It is not a RWLock because it may be locked for VERY short periods of time (just reading small values).
// In comparison, RWLock uses a `shared_timed_mutex` under the hood, and locking that for reading locks a
// mutex internally either way.
// There are times where locking can take longer, but it only happens rarely, when changing LOD count for example.
// There are times where locking can take longer, but it only happens rarely, when changing LOD count for
// example.
Mutex _settings_mutex;
};

View File

@ -1,6 +1,7 @@
#include "voxel_box_mover.h"
#include "../../meshers/blocky/voxel_mesher_blocky.h"
#include "../../meshers/cubes/voxel_mesher_cubes.h"
#include "../../storage/voxel_data.h"
#include "../../util/godot/ref_counted.h"
#include "voxel_terrain.h"
@ -183,7 +184,7 @@ static bool intersects(Span<const AABB> aabbs, const AABB &box) {
static void collect_boxes(
VoxelTerrain &p_terrain, AABB query_box, uint32_t collision_nask, std::vector<AABB> &potential_boxes) {
const VoxelDataMap &voxels = p_terrain.get_storage();
const VoxelData &voxels = p_terrain.get_storage();
const int min_x = int(Math::floor(query_box.position.x));
const int min_y = int(Math::floor(query_box.position.y));
@ -204,11 +205,14 @@ static void collect_boxes(
ERR_FAIL_COND_MSG(library_ref.is_null(), "VoxelMesherBlocky has no library assigned");
VoxelBlockyLibrary &library = **library_ref;
const int channel = VoxelBufferInternal::CHANNEL_TYPE;
VoxelSingleValue defval;
defval.i = 0;
// TODO Optimization: read the whole box of voxels at once, querying individually is slower
for (i.z = min_z; i.z < max_z; ++i.z) {
for (i.y = min_y; i.y < max_y; ++i.y) {
for (i.x = min_x; i.x < max_x; ++i.x) {
const int type_id = voxels.get_voxel(i, channel);
const int type_id = voxels.get_voxel(i, channel, defval).i;
if (library.has_voxel(type_id)) {
const VoxelBlockyModel &voxel_type = library.get_voxel_const(type_id);
@ -231,11 +235,14 @@ static void collect_boxes(
} else if (try_get_as(p_terrain.get_mesher(), mesher_cubes)) {
const int channel = VoxelBufferInternal::CHANNEL_COLOR;
VoxelSingleValue defval;
defval.i = 0;
// TODO Optimization: read the whole box of voxels at once, querying individually is slower
for (i.z = min_z; i.z < max_z; ++i.z) {
for (i.y = min_y; i.y < max_y; ++i.y) {
for (i.x = min_x; i.x < max_x; ++i.x) {
const int color_data = voxels.get_voxel(i, channel);
const int color_data = voxels.get_voxel(i, channel, defval).i;
if (color_data != 0) {
potential_boxes.push_back(AABB(i, Vector3(1, 1, 1)));
}

View File

@ -10,6 +10,7 @@
#include "../../engine/voxel_engine_updater.h"
#include "../../meshers/blocky/voxel_mesher_blocky.h"
#include "../../storage/voxel_buffer_gd.h"
#include "../../storage/voxel_data.h"
#include "../../util/container_funcs.h"
#include "../../util/macros.h"
#include "../../util/math/conv.h"
@ -36,9 +37,11 @@ VoxelTerrain::VoxelTerrain() {
set_notify_transform(true);
_data = make_shared_instance<VoxelData>();
// TODO Should it actually be finite for better discovery?
// Infinite by default
_bounds_in_voxels = Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT));
_data->set_bounds(Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT)));
_streaming_dependency = make_shared_instance<StreamingDependency>();
_meshing_dependency = make_shared_instance<MeshingDependency>();
@ -106,18 +109,18 @@ Ref<Material> VoxelTerrain::get_material_override() const {
}
void VoxelTerrain::set_stream(Ref<VoxelStream> p_stream) {
if (p_stream == _stream) {
if (p_stream == get_stream()) {
return;
}
_stream = p_stream;
_data->set_stream(p_stream);
StreamingDependency::reset(_streaming_dependency, _stream, _generator);
StreamingDependency::reset(_streaming_dependency, p_stream, get_generator());
#ifdef TOOLS_ENABLED
if (_stream.is_valid()) {
if (p_stream.is_valid()) {
if (Engine::get_singleton()->is_editor_hint()) {
Ref<Script> script = _stream->get_script();
Ref<Script> script = p_stream->get_script();
if (script.is_valid()) {
// Safety check. It's too easy to break threads by making a script reload.
// You can turn it back on, but be careful.
@ -132,23 +135,23 @@ void VoxelTerrain::set_stream(Ref<VoxelStream> p_stream) {
}
Ref<VoxelStream> VoxelTerrain::get_stream() const {
return _stream;
return _data->get_stream();
}
void VoxelTerrain::set_generator(Ref<VoxelGenerator> p_generator) {
if (p_generator == _generator) {
if (p_generator == get_generator()) {
return;
}
_generator = p_generator;
_data->set_generator(p_generator);
MeshingDependency::reset(_meshing_dependency, _mesher, p_generator);
StreamingDependency::reset(_streaming_dependency, _stream, p_generator);
StreamingDependency::reset(_streaming_dependency, get_stream(), p_generator);
#ifdef TOOLS_ENABLED
if (_generator.is_valid()) {
if (p_generator.is_valid()) {
if (Engine::get_singleton()->is_editor_hint()) {
Ref<Script> script = _generator->get_script();
Ref<Script> script = p_generator->get_script();
if (script.is_valid()) {
// Safety check. It's too easy to break threads by making a script reload.
// You can turn it back on, but be careful.
@ -163,7 +166,7 @@ void VoxelTerrain::set_generator(Ref<VoxelGenerator> p_generator) {
}
Ref<VoxelGenerator> VoxelTerrain::get_generator() const {
return _generator;
return _data->get_generator();
}
/*void VoxelTerrain::set_data_block_size_po2(unsigned int p_block_size_po2) {
@ -183,12 +186,12 @@ Ref<VoxelGenerator> VoxelTerrain::get_generator() const {
_on_stream_params_changed();
}*/
void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) {
_data_map.create(0);
}
// void VoxelTerrain::_set_block_size_po2(int p_block_size_po2) {
// _data_map.create(0);
// }
unsigned int VoxelTerrain::get_data_block_size_pow2() const {
return _data_map.get_block_size_pow2();
return _data->get_block_size_po2();
}
unsigned int VoxelTerrain::get_mesh_block_size_pow2() const {
@ -250,10 +253,10 @@ void VoxelTerrain::_on_stream_params_changed() {
stop_streamer();
stop_updater();
if (_stream.is_valid()) {
const int stream_block_size_po2 = _stream->get_block_size_po2();
_set_block_size_po2(stream_block_size_po2);
}
// if (_stream.is_valid()) {
// const int stream_block_size_po2 = _stream->get_block_size_po2();
// _set_block_size_po2(stream_block_size_po2);
// }
// VoxelEngine::get_singleton().set_volume_data_block_size(_volume_id, 1 << get_data_block_size_pow2());
// VoxelEngine::get_singleton().set_volume_render_block_size(_volume_id, 1 << get_mesh_block_size_pow2());
@ -261,7 +264,7 @@ void VoxelTerrain::_on_stream_params_changed() {
// The whole map might change, so regenerate it
reset_map();
if ((_stream.is_valid() || _generator.is_valid()) &&
if ((get_stream().is_valid() || get_generator().is_valid()) &&
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor)) {
start_streamer();
start_updater();
@ -288,7 +291,7 @@ void VoxelTerrain::set_mesher(Ref<VoxelMesher> mesher) {
_mesher = mesher;
MeshingDependency::reset(_meshing_dependency, _mesher, _generator);
MeshingDependency::reset(_meshing_dependency, _mesher, get_generator());
stop_updater();
@ -302,7 +305,7 @@ void VoxelTerrain::set_mesher(Ref<VoxelMesher> mesher) {
}
void VoxelTerrain::get_viewers_in_area(std::vector<int> &out_viewer_ids, Box3i voxel_box) const {
const Box3i block_box = voxel_box.downscaled(_data_map.get_block_size());
const Box3i block_box = voxel_box.downscaled(get_data_block_size());
for (auto it = _paired_viewers.begin(); it != _paired_viewers.end(); ++it) {
const PairedViewer &viewer = *it;
@ -405,7 +408,8 @@ void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlockVT &mesh_block) {
}
const int render_to_data_factor = get_mesh_block_size() / get_data_block_size();
const Box3i bounds_in_data_blocks = _bounds_in_voxels.downscaled(get_data_block_size());
/*const Box3i bounds_in_data_blocks = _bounds_in_voxels.downscaled(get_data_block_size());
// Pad by 1 because meshing needs neighbors
const Box3i data_box =
Box3i(mesh_block.position * render_to_data_factor, Vector3iUtil::create(render_to_data_factor))
@ -418,7 +422,15 @@ void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlockVT &mesh_block) {
// Check if we have the data
const bool data_available = data_box.all_cells_match([this](Vector3i bpos) { //
return _data_map.has_block(bpos);
});
});*/
const Box3i data_box =
Box3i(mesh_block.position * render_to_data_factor, Vector3iUtil::create(render_to_data_factor)).padded(1);
// If we get an empty box at this point, something is wrong with the caller
ZN_ASSERT_RETURN(!data_box.is_empty());
const bool data_available = _data->has_all_blocks_in_area(data_box);
if (data_available) {
// Regardless of if the updater is updating the block already,
@ -428,7 +440,7 @@ void VoxelTerrain::try_schedule_mesh_update(VoxelMeshBlockVT &mesh_block) {
}
}
void VoxelTerrain::view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification) {
/*void VoxelTerrain::view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification) {
VoxelDataBlock *block = _data_map.get_block(bpos);
if (block == nullptr) {
@ -469,7 +481,7 @@ void VoxelTerrain::view_data_block(Vector3i bpos, uint32_t viewer_id, bool requi
// TODO viewers with varying flags during the game is not supported at the moment.
// They have to be re-created, which may cause world re-load...
}
}
}*/
void VoxelTerrain::view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag) {
if (mesh_flag == false && collision_flag == false) {
@ -503,7 +515,7 @@ void VoxelTerrain::view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision
// They have to be re-created, which may cause world re-load...
}
void VoxelTerrain::unview_data_block(Vector3i bpos) {
/*void VoxelTerrain::unview_data_block(Vector3i bpos) {
VoxelDataBlock *block = _data_map.get_block(bpos);
if (block == nullptr) {
@ -540,7 +552,7 @@ void VoxelTerrain::unview_data_block(Vector3i bpos) {
unload_data_block(bpos);
}
}
}
}*/
void VoxelTerrain::unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag) {
VoxelMeshBlockVT *block = _mesh_map.get_block(bpos);
@ -569,35 +581,7 @@ void VoxelTerrain::unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collisi
}
}
namespace {
struct ScheduleSaveAction {
std::vector<VoxelTerrain::BlockToSave> &blocks_to_save;
bool with_copy;
void operator()(const Vector3i &bpos, VoxelDataBlock &block) {
// TODO Don't ask for save if the stream doesn't support it!
if (block.is_modified()) {
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
VoxelTerrain::BlockToSave b;
// If a modified block has no voxels, it is equivalent to removing the block from the stream
if (block.has_voxels()) {
if (with_copy) {
RWLockRead lock(block.get_voxels().get_lock());
b.voxels = make_shared_instance<VoxelBufferInternal>();
block.get_voxels_const().duplicate_to(*b.voxels, true);
} else {
b.voxels = block.get_voxels_shared();
}
}
b.position = bpos;
blocks_to_save.push_back(b);
block.set_modified(false);
}
}
};
} // namespace
void VoxelTerrain::unload_data_block(Vector3i bpos) {
/*void VoxelTerrain::unload_data_block(Vector3i bpos) {
const bool save = _stream.is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor);
_data_map.remove_block(bpos, [this, save, bpos](VoxelDataBlock &block) {
@ -612,7 +596,7 @@ void VoxelTerrain::unload_data_block(Vector3i bpos) {
// Blocks in the update queue will be cancelled in _process,
// because it's too expensive to linear-search all blocks for each block
}
}*/
void VoxelTerrain::unload_mesh_block(Vector3i bpos) {
std::vector<Vector3i> &blocks_pending_update = _blocks_pending_update;
@ -631,10 +615,14 @@ void VoxelTerrain::unload_mesh_block(Vector3i bpos) {
}
void VoxelTerrain::save_all_modified_blocks(bool with_copy) {
// That may cause a stutter, so should be used when the player won't notice
_data_map.for_each_block(ScheduleSaveAction{ _blocks_to_save, with_copy });
ZN_PROFILE_SCOPE();
Ref<VoxelStream> stream = get_stream();
ERR_FAIL_COND_MSG(stream.is_null(), "Attempting to save modified blocks, but there is no stream to save them to.");
if (_stream.is_valid() && _instancer != nullptr && _stream->supports_instance_blocks()) {
// That may cause a stutter, so should be used when the player won't notice
_data->consume_all_modifications(_blocks_to_save, with_copy);
if (stream.is_valid() && _instancer != nullptr && stream->supports_instance_blocks()) {
_instancer->save_all_modified_blocks();
}
@ -714,7 +702,7 @@ void VoxelTerrain::start_updater() {
void VoxelTerrain::stop_updater() {
// Invalidate pending tasks
MeshingDependency::reset(_meshing_dependency, _mesher, _generator);
MeshingDependency::reset(_meshing_dependency, _mesher, get_generator());
// VoxelEngine::get_singleton().set_volume_mesher(_volume_id, Ref<VoxelMesher>());
@ -738,7 +726,7 @@ void VoxelTerrain::remesh_all_blocks() {
// At the moment, this function is for client-side use case in multiplayer scenarios
void VoxelTerrain::generate_block_async(Vector3i block_position) {
if (_data_map.has_block(block_position)) {
if (_data->has_block(block_position, 0)) {
// Already exists
return;
}
@ -752,7 +740,7 @@ void VoxelTerrain::generate_block_async(Vector3i block_position) {
// }
LoadingBlock new_loading_block;
const Box3i block_box(_data_map.block_to_voxel(block_position), Vector3iUtil::create(_data_map.get_block_size()));
const Box3i block_box(_data->block_to_voxel(block_position), Vector3iUtil::create(_data->get_block_size()));
for (size_t i = 0; i < _paired_viewers.size(); ++i) {
const PairedViewer &viewer = _paired_viewers[i];
if (viewer.state.data_box.intersects(block_box)) {
@ -777,7 +765,7 @@ void VoxelTerrain::start_streamer() {
void VoxelTerrain::stop_streamer() {
// Invalidate pending tasks
StreamingDependency::reset(_streaming_dependency, _stream, _generator);
StreamingDependency::reset(_streaming_dependency, get_stream(), get_generator());
// VoxelEngine::get_singleton().set_volume_stream(_volume_id, Ref<VoxelStream>());
// VoxelEngine::get_singleton().set_volume_generator(_volume_id, Ref<VoxelGenerator>());
_loading_blocks.clear();
@ -787,10 +775,10 @@ void VoxelTerrain::stop_streamer() {
void VoxelTerrain::reset_map() {
// Discard everything, to reload it all
_data_map.for_each_block([this](const Vector3i &bpos, VoxelDataBlock &block) { //
emit_data_block_unloaded(block, bpos);
_data->for_each_block([this](const Vector3i &bpos, const VoxelDataBlock &block) { //
emit_data_block_unloaded(bpos);
});
_data_map.create(0);
_data->reset_maps();
_mesh_map.clear();
@ -821,10 +809,12 @@ void VoxelTerrain::try_schedule_mesh_update_from_data(const Box3i &box_in_voxels
}
void VoxelTerrain::post_edit_area(Box3i box_in_voxels) {
box_in_voxels.clip(_bounds_in_voxels);
_data->mark_area_modified(box_in_voxels, nullptr);
box_in_voxels.clip(_data->get_bounds());
// Mark data as modified
const Box3i data_box = box_in_voxels.downscaled(get_data_block_size());
/*const Box3i data_box = box_in_voxels.downscaled(get_data_block_size());
data_box.for_each_cell([this](Vector3i pos) {
VoxelDataBlock *block = _data_map.get_block(pos);
// The edit can happen next to a boundary
@ -832,7 +822,7 @@ void VoxelTerrain::post_edit_area(Box3i box_in_voxels) {
block->set_modified(true);
block->set_edited(true);
}
});
});*/
if (_area_edit_notification_enabled) {
GDVIRTUAL_CALL(_on_area_edited, box_in_voxels.pos, box_in_voxels.size);
@ -1002,17 +992,17 @@ void VoxelTerrain::send_block_data_requests() {
// Blocks to load
for (size_t i = 0; i < _blocks_pending_load.size(); ++i) {
const Vector3i block_pos = _blocks_pending_load[i];
// TODO Batch request
// TODO Optimization: Batch request
request_block_load(_volume_id, _streaming_dependency, get_data_block_size(), block_pos, shared_viewers_data,
volume_transform, _instancer != nullptr);
}
// Blocks to save
if (_stream.is_valid()) {
if (get_stream().is_valid()) {
for (unsigned int i = 0; i < _blocks_to_save.size(); ++i) {
ZN_PRINT_VERBOSE(format("Requesting save of block {}", _blocks_to_save[i].position));
const BlockToSave b = _blocks_to_save[i];
// TODO Batch request
const VoxelData::BlockToSave b = _blocks_to_save[i];
// TODO Optimization: Batch request
request_voxel_block_save(_volume_id, b.voxels, b.position, _streaming_dependency, get_data_block_size());
}
} else {
@ -1026,7 +1016,7 @@ void VoxelTerrain::send_block_data_requests() {
_blocks_to_save.clear();
}
void VoxelTerrain::emit_data_block_loaded(const VoxelDataBlock &block, Vector3i bpos) {
void VoxelTerrain::emit_data_block_loaded(Vector3i bpos) {
// Not sure about exposing buffers directly... some stuff on them is useful to obtain directly,
// but also it allows scripters to mess with voxels in a way they should not.
// Example: modifying voxels without locking them first, while another thread may be reading them at the same
@ -1039,9 +1029,7 @@ void VoxelTerrain::emit_data_block_loaded(const VoxelDataBlock &block, Vector3i
emit_signal(VoxelStringNames::get_singleton().block_loaded, bpos);
}
void VoxelTerrain::emit_data_block_unloaded(const VoxelDataBlock &block, Vector3i bpos) {
// const Variant vbuffer = block->voxels;
// const Variant *args[2] = { &vpos, &vbuffer };
void VoxelTerrain::emit_data_block_unloaded(Vector3i bpos) {
emit_signal(VoxelStringNames::get_singleton().block_unloaded, bpos);
}
@ -1057,7 +1045,7 @@ bool VoxelTerrain::try_get_paired_viewer_index(uint32_t id, size_t &out_i) const
}
// TODO It is unclear yet if this API will stay. I have a feeling it might consume a lot of CPU
void VoxelTerrain::notify_data_block_enter(VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id) {
void VoxelTerrain::notify_data_block_enter(const VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id) {
if (!VoxelEngine::get_singleton().viewer_exists(viewer_id)) {
// The viewer might have been removed between the moment we requested the block and the moment we finished
// loading it
@ -1113,8 +1101,10 @@ void VoxelTerrain::process_viewers() {
// TODO There is probably a better way to do this
const float view_distance_scale = world_to_local_transform.basis.xform(Vector3(1, 0, 0)).length();
const Box3i bounds_in_data_blocks = _bounds_in_voxels.downscaled(get_data_block_size());
const Box3i bounds_in_mesh_blocks = _bounds_in_voxels.downscaled(get_mesh_block_size());
const Box3i bounds_in_voxels = _data->get_bounds();
const Box3i bounds_in_data_blocks = bounds_in_voxels.downscaled(get_data_block_size());
const Box3i bounds_in_mesh_blocks = bounds_in_voxels.downscaled(get_mesh_block_size());
struct UpdatePairedViewer {
VoxelTerrain &self;
@ -1186,7 +1176,8 @@ void VoxelTerrain::process_viewers() {
VoxelEngine::get_singleton().for_each_viewer(u);
}
const bool can_load_blocks = (_automatic_loading_enabled && (_stream.is_valid() || _generator.is_valid())) &&
const bool can_load_blocks =
(_automatic_loading_enabled && (get_stream().is_valid() || get_generator().is_valid())) &&
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor);
// Find out which blocks need to appear and which need to be unloaded
@ -1201,28 +1192,7 @@ void VoxelTerrain::process_viewers() {
const Box3i &prev_data_box = viewer.prev_state.data_box;
if (prev_data_box != new_data_box) {
ZN_PROFILE_SCOPE();
const bool require_notifications = _block_enter_notification_enabled &&
VoxelEngine::get_singleton().is_viewer_requiring_data_block_notifications(viewer.id);
// Unview blocks that just fell out of range
prev_data_box.difference(new_data_box, [this](Box3i out_of_range_box) {
out_of_range_box.for_each_cell([this](Vector3i bpos) { //
unview_data_block(bpos);
});
});
// View blocks that just entered the range
if (can_load_blocks) {
new_data_box.difference(
prev_data_box, [this, &viewer, require_notifications](Box3i box_to_load) {
box_to_load.for_each_cell([this, &viewer, require_notifications](Vector3i bpos) {
// Load or update block
view_data_block(bpos, viewer.id, require_notifications);
});
});
}
process_viewer_data_box_change(viewer.id, prev_data_box, new_data_box, can_load_blocks);
}
}
@ -1303,6 +1273,140 @@ void VoxelTerrain::process_viewers() {
_stats.time_request_blocks_to_load = profiling_clock.restart();
}
void VoxelTerrain::process_viewer_data_box_change(
uint32_t viewer_id, Box3i prev_data_box, Box3i new_data_box, bool can_load_blocks) {
ZN_PROFILE_SCOPE();
/*
// Unview blocks that just fell out of range
prev_data_box.difference(new_data_box, [this](Box3i out_of_range_box) {
out_of_range_box.for_each_cell([this](Vector3i bpos) { //
unview_data_block(bpos);
});
});
// View blocks that just entered the range
if (can_load_blocks) {
new_data_box.difference(
prev_data_box, [this, &viewer, require_notifications](Box3i box_to_load) {
box_to_load.for_each_cell([this, &viewer, require_notifications](Vector3i bpos) {
// Load or update block
view_data_block(bpos, viewer.id, require_notifications);
});
});
}
*/
static thread_local std::vector<Vector3i> tls_missing_blocks;
static thread_local std::vector<Vector3i> tls_found_blocks_positions;
// Unview blocks that just fell out of range
{
const bool may_save =
get_stream().is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor);
tls_missing_blocks.clear();
tls_found_blocks_positions.clear();
// Decrement refcounts from loaded blocks, and unload them
prev_data_box.difference(new_data_box, [this, may_save](Box3i out_of_range_box) {
_data->unview_area(out_of_range_box, tls_missing_blocks, tls_found_blocks_positions, &_blocks_to_save);
});
// Remove loading blocks (those were loaded and had their refcount reach zero)
for (const Vector3i bpos : tls_found_blocks_positions) {
emit_data_block_unloaded(bpos);
_loading_blocks.erase(bpos);
}
// Remove refcount from loading blocks, and cancel loading if it reaches zero
for (const Vector3i bpos : tls_missing_blocks) {
auto loading_block_it = _loading_blocks.find(bpos);
if (loading_block_it == _loading_blocks.end()) {
ZN_PRINT_VERBOSE("Request to unview a loading block that was never requested");
// Not expected, but fine I guess
return;
}
LoadingBlock &loading_block = loading_block_it->second;
loading_block.viewers.remove();
if (loading_block.viewers.get() == 0) {
// No longer want to load it
_loading_blocks.erase(loading_block_it);
// TODO Do we really need that vector after all?
for (size_t i = 0; i < _blocks_pending_load.size(); ++i) {
if (_blocks_pending_load[i] == bpos) {
_blocks_pending_load[i] = _blocks_pending_load.back();
_blocks_pending_load.pop_back();
break;
}
}
}
}
}
// View blocks coming into range
if (can_load_blocks) {
const bool require_notifications = _block_enter_notification_enabled &&
VoxelEngine::get_singleton().is_viewer_requiring_data_block_notifications(viewer_id);
static thread_local std::vector<VoxelDataBlock *> tls_found_blocks;
tls_missing_blocks.clear();
tls_found_blocks.clear();
tls_found_blocks_positions.clear();
new_data_box.difference(prev_data_box, [this](Box3i box_to_load) {
_data->view_area(box_to_load, tls_missing_blocks, tls_found_blocks_positions, tls_found_blocks);
});
// Schedule loading of missing blocks
for (const Vector3i missing_bpos : tls_missing_blocks) {
auto loading_block_it = _loading_blocks.find(missing_bpos);
if (loading_block_it == _loading_blocks.end()) {
// First viewer to request it
LoadingBlock new_loading_block;
new_loading_block.viewers.add();
if (require_notifications) {
new_loading_block.viewers_to_notify.push_back(viewer_id);
}
// Schedule a loading request
_loading_blocks.insert({ missing_bpos, new_loading_block });
_blocks_pending_load.push_back(missing_bpos);
} else {
// More viewers
LoadingBlock &loading_block = loading_block_it->second;
loading_block.viewers.add();
if (require_notifications) {
loading_block.viewers_to_notify.push_back(viewer_id);
}
}
}
if (require_notifications) {
// Notifications for blocks that were already loaded
// Note, we carry pointers to blocks up to here because we assume it won't get removed or invalidated by a
// different thread. The only place where we remove blocks is in the current thread.
for (unsigned int i = 0; i < tls_found_blocks.size(); ++i) {
const Vector3i bpos = tls_found_blocks_positions[i];
const VoxelDataBlock *block = tls_found_blocks[i];
ZN_ASSERT(block != nullptr);
notify_data_block_enter(*block, bpos, viewer_id);
}
}
// TODO viewers with varying flags during the game is not supported at the moment.
// They have to be re-created, which may cause world re-load...
}
}
void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
ZN_PROFILE_SCOPE();
@ -1352,7 +1456,7 @@ void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
CRASH_COND(ob.voxels == nullptr);
const Vector3i expected_block_size = Vector3iUtil::create(_data_map.get_block_size());
/*const Vector3i expected_block_size = Vector3iUtil::create(_data->get_block_size());
if (ob.voxels->get_size() != expected_block_size) {
// Voxel block size is incorrect, drop it
ERR_PRINT(String("Block size obtained from stream is different from expected size. "
@ -1360,22 +1464,25 @@ void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
.format(varray(expected_block_size, ob.voxels->get_size())));
++_stats.dropped_block_loads;
return;
}
}*/
// Create or update block data
VoxelDataBlock *block = _data_map.get_block(block_pos);
const bool was_not_loaded = block == nullptr;
block = _data_map.set_block_buffer(block_pos, ob.voxels, true);
CRASH_COND(block == nullptr);
block->set_edited(ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED);
const bool was_not_loaded = _data->has_block(block_pos, 0);
VoxelDataBlock *block = _data->try_set_block_buffer(
block_pos, 0, ob.voxels, ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED, true);
if (block == nullptr) {
// An error occurred
ERR_PRINT("Error occured when applying loaded data block.");
++_stats.dropped_block_loads;
return;
}
if (was_not_loaded) {
// Set viewers count that are currently expecting the block
block->viewers = loading_block.viewers;
}
emit_data_block_loaded(*block, block_pos);
emit_data_block_loaded(block_pos);
for (unsigned int i = 0; i < loading_block.viewers_to_notify.size(); ++i) {
const uint32_t viewer_id = loading_block.viewers_to_notify[i];
@ -1386,7 +1493,7 @@ void VoxelTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob) {
{
ZN_PROFILE_SCOPE();
try_schedule_mesh_update_from_data(
Box3i(_data_map.block_to_voxel(block_pos), Vector3iUtil::create(get_data_block_size())));
Box3i(_data->block_to_voxel(block_pos), Vector3iUtil::create(get_data_block_size())));
}
// We might have requested some blocks again (if we got a dropped one while we still need them)
@ -1406,7 +1513,7 @@ bool VoxelTerrain::try_set_block_data(Vector3i position, std::shared_ptr<VoxelBu
ZN_PROFILE_SCOPE();
ERR_FAIL_COND_V(voxel_data == nullptr, false);
const Vector3i expected_block_size = Vector3iUtil::create(_data_map.get_block_size());
const Vector3i expected_block_size = Vector3iUtil::create(_data->get_block_size());
ERR_FAIL_COND_V_MSG(voxel_data->get_size() != expected_block_size, false,
String("Block size is different from expected size. "
"Expected {0}, got {1}")
@ -1432,20 +1539,21 @@ bool VoxelTerrain::try_set_block_data(Vector3i position, std::shared_ptr<VoxelBu
_loading_blocks.erase(position);
// Create or update block data
VoxelDataBlock *block = _data_map.set_block_buffer(position, voxel_data, true);
// TODO How to set the `edited` flag? Does it matter in use cases for this function?
const bool edited = true;
VoxelDataBlock *block = _data->try_set_block_buffer(position, 0, voxel_data, edited, true);
CRASH_COND(block == nullptr);
block->viewers = refcount;
// TODO How to set the `edited` flag? Does it matter in use cases for this function?
// The block itself might not be suitable for meshing yet, but blocks surrounding it might be now
try_schedule_mesh_update_from_data(
Box3i(_data_map.block_to_voxel(position), Vector3iUtil::create(get_data_block_size())));
Box3i(_data->block_to_voxel(position), Vector3iUtil::create(get_data_block_size())));
return true;
}
bool VoxelTerrain::has_data_block(Vector3i position) const {
return _data_map.has_block(position);
return _data->has_block(position, 0);
}
void VoxelTerrain::process_meshing() {
@ -1481,8 +1589,7 @@ void VoxelTerrain::process_meshing() {
// We must have picked up a valid data block
{
const Vector3i anchor_pos = data_box.pos + Vector3i(1, 1, 1);
const VoxelDataBlock *data_block = _data_map.get_block(anchor_pos);
ERR_CONTINUE(data_block == nullptr);
ERR_CONTINUE(!_data->has_block(anchor_pos, 0));
}
#endif
@ -1497,14 +1604,16 @@ void VoxelTerrain::process_meshing() {
task->collision_hint = _generate_collisions;
// This iteration order is specifically chosen to match VoxelEngine and threaded access
task->blocks_count = 0;
_data->get_blocks_with_voxel_data(data_box, 0, to_span(task->blocks));
task->blocks_count = Vector3iUtil::get_volume(data_box.size);
/*task->blocks_count = 0;
data_box.for_each_cell_zxy([this, task](Vector3i data_block_pos) {
VoxelDataBlock *data_block = _data_map.get_block(data_block_pos);
if (data_block != nullptr && data_block->has_voxels()) {
task->blocks[task->blocks_count] = data_block->get_voxels_shared();
}
++task->blocks_count;
});
});*/
#ifdef DEBUG_ENABLED
{
@ -1659,11 +1768,13 @@ bool VoxelTerrain::is_stream_running_in_editor() const {
}
void VoxelTerrain::set_bounds(Box3i box) {
_bounds_in_voxels =
Box3i bounds_in_voxels =
box.clipped(Box3i::from_center_extents(Vector3i(), Vector3iUtil::create(constants::MAX_VOLUME_EXTENT)));
// Round to block size
_bounds_in_voxels = _bounds_in_voxels.snapped(get_data_block_size());
bounds_in_voxels = bounds_in_voxels.snapped(get_data_block_size());
_data->set_bounds(bounds_in_voxels);
const unsigned int largest_dimension =
static_cast<unsigned int>(math::max(math::max(box.size.x, box.size.y), box.size.z));
@ -1678,15 +1789,15 @@ void VoxelTerrain::set_bounds(Box3i box) {
}
Box3i VoxelTerrain::get_bounds() const {
return _bounds_in_voxels;
return _data->get_bounds();
}
Vector3i VoxelTerrain::_b_voxel_to_data_block(Vector3 pos) const {
return _data_map.voxel_to_block(math::floor_to_int(pos));
return _data->voxel_to_block(math::floor_to_int(pos));
}
Vector3i VoxelTerrain::_b_data_block_to_voxel(Vector3i pos) const {
return _data_map.block_to_voxel(pos);
return _data->block_to_voxel(pos);
}
void VoxelTerrain::_b_save_modified_blocks() {
@ -1695,14 +1806,18 @@ void VoxelTerrain::_b_save_modified_blocks() {
// Explicitely ask to save a block if it was modified
void VoxelTerrain::_b_save_block(Vector3i p_block_pos) {
VoxelDataBlock *block = _data_map.get_block(p_block_pos);
VoxelData::BlockToSave to_save;
if (_data->consume_block_modifications(p_block_pos, to_save)) {
_blocks_to_save.push_back(to_save);
}
/*VoxelDataBlock *block = _data->get_block(p_block_pos);
ERR_FAIL_COND(block == nullptr);
if (!block->is_modified()) {
return;
}
ScheduleSaveAction{ _blocks_to_save, true }(p_block_pos, *block);
ScheduleSaveAction{ _blocks_to_save, true }(p_block_pos, *block);*/
}
void VoxelTerrain::_b_set_bounds(AABB aabb) {

View File

@ -1,9 +1,11 @@
#ifndef VOXEL_TERRAIN_H
#define VOXEL_TERRAIN_H
#include "../../constants/voxel_constants.h"
#include "../../engine/meshing_dependency.h"
#include "../../storage/voxel_data_map.h"
#include "../../storage/voxel_data.h"
#include "../../util/godot/memory.h"
#include "../../util/math/box3i.h"
#include "../voxel_data_block_enter_info.h"
#include "../voxel_mesh_map.h"
#include "../voxel_node.h"
@ -80,11 +82,13 @@ public:
void set_material_override(Ref<Material> material);
Ref<Material> get_material_override() const;
VoxelDataMap &get_storage() {
return _data_map;
VoxelData &get_storage() const {
ZN_ASSERT(_data != nullptr);
return *_data;
}
const VoxelDataMap &get_storage() const {
return _data_map;
std::shared_ptr<VoxelData> get_storage_shared() const {
return _data;
}
Ref<VoxelTool> get_voxel_tool() override;
@ -122,10 +126,10 @@ public:
const Stats &get_stats() const;
struct BlockToSave {
std::shared_ptr<VoxelBufferInternal> voxels;
Vector3i position;
};
// struct BlockToSave {
// std::shared_ptr<VoxelBufferInternal> voxels;
// Vector3i position;
// };
// Internal
@ -149,13 +153,15 @@ protected:
private:
void _process();
void process_viewers();
void process_viewer_data_box_change(
uint32_t viewer_id, Box3i prev_data_box, Box3i new_data_box, bool can_load_blocks);
//void process_received_data_blocks();
void process_meshing();
void apply_mesh_update(const VoxelEngine::BlockMeshOutput &ob);
void apply_data_block_response(VoxelEngine::BlockDataOutput &ob);
void _on_stream_params_changed();
void _set_block_size_po2(int p_block_size_po2);
// void _set_block_size_po2(int p_block_size_po2);
//void make_all_view_dirty();
void start_updater();
void stop_updater();
@ -163,11 +169,11 @@ private:
void stop_streamer();
void reset_map();
void view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification);
// void view_data_block(Vector3i bpos, uint32_t viewer_id, bool require_notification);
void view_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag);
void unview_data_block(Vector3i bpos);
// void unview_data_block(Vector3i bpos);
void unview_mesh_block(Vector3i bpos, bool mesh_flag, bool collision_flag);
void unload_data_block(Vector3i bpos);
// void unload_data_block(Vector3i bpos);
void unload_mesh_block(Vector3i bpos);
//void make_data_block_dirty(Vector3i bpos);
void try_schedule_mesh_update(VoxelMeshBlockVT &block);
@ -177,12 +183,12 @@ private:
void get_viewer_pos_and_direction(Vector3 &out_pos, Vector3 &out_direction) const;
void send_block_data_requests();
void emit_data_block_loaded(const VoxelDataBlock &block, Vector3i bpos);
void emit_data_block_unloaded(const VoxelDataBlock &block, Vector3i bpos);
void emit_data_block_loaded(Vector3i bpos);
void emit_data_block_unloaded(Vector3i bpos);
bool try_get_paired_viewer_index(uint32_t id, size_t &out_i) const;
void notify_data_block_enter(VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id);
void notify_data_block_enter(const VoxelDataBlock &block, Vector3i bpos, uint32_t viewer_id);
void get_viewers_in_area(std::vector<int> &out_viewer_ids, Box3i voxel_box) const;
@ -227,17 +233,13 @@ private:
std::vector<PairedViewer> _paired_viewers;
// Voxel storage
VoxelDataMap _data_map;
// Voxel storage. Using a shared_ptr so threaded tasks can use it safely.
std::shared_ptr<VoxelData> _data;
// Mesh storage
VoxelMeshMap<VoxelMeshBlockVT> _mesh_map;
uint32_t _mesh_block_size_po2 = constants::DEFAULT_BLOCK_SIZE_PO2;
// Area within which voxels can exist.
// Note, these bounds might not be exactly represented. This volume is chunk-based, so the result will be
// approximated to the closest chunk.
Box3i _bounds_in_voxels;
unsigned int _max_view_distance_voxels = 128;
// TODO Terrains only need to handle the visible portion of voxels, which reduces the bounds blocks to handle.
@ -259,11 +261,11 @@ private:
std::vector<Vector3i> _blocks_pending_update;
// Blocks that should be saved on the next process call.
// The order in that list does not matter.
std::vector<BlockToSave> _blocks_to_save;
std::vector<VoxelData::BlockToSave> _blocks_to_save;
Ref<VoxelStream> _stream;
// Ref<VoxelStream> _stream;
Ref<VoxelMesher> _mesher;
Ref<VoxelGenerator> _generator;
// Ref<VoxelGenerator> _generator;
// Data stored with a shared pointer so it can be sent to asynchronous tasks
std::shared_ptr<StreamingDependency> _streaming_dependency;

View File

@ -50,15 +50,15 @@ struct BeforeUnloadMeshAction {
}
};
struct ScheduleSaveAction {
std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save;
/*struct ScheduleSaveAction {
std::vector<VoxelData::BlockToSave> &blocks_to_save;
void operator()(const Vector3i &bpos, VoxelDataBlock &block) {
// Save if modified
// TODO Don't ask for save if the stream doesn't support it!
if (block.is_modified()) {
//print_line(String("Scheduling save for block {0}").format(varray(block->position.to_vec3())));
VoxelLodTerrainUpdateData::BlockToSave b;
VoxelData::BlockToSave b;
// If a modified block has no voxels, it is equivalent to removing the block from the stream
if (block.has_voxels()) {
@ -70,12 +70,12 @@ struct ScheduleSaveAction {
}
b.position = bpos;
b.lod = block.get_lod_index();
b.lod_index = block.get_lod_index();
blocks_to_save.push_back(b);
block.set_modified(false);
}
}
};
};*/
static inline uint64_t get_ticks_msec() {
return Time::get_singleton()->get_ticks_msec();
@ -1566,8 +1566,8 @@ void VoxelLodTerrain::apply_data_block_response(VoxelEngine::BlockDataOutput &ob
}
if (ob.voxels != nullptr) {
if (!_data->try_set_block_buffer(
ob.position, ob.lod, ob.voxels, ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED)) {
if (_data->try_set_block_buffer(ob.position, ob.lod, ob.voxels,
ob.type == VoxelEngine::BlockDataOutput::TYPE_LOADED, false) == nullptr) {
++_stats.dropped_block_loads;
return;
}
@ -2116,12 +2116,13 @@ void VoxelLodTerrain::save_all_modified_blocks(bool with_copy) {
VoxelLodTerrainUpdateTask::flush_pending_lod_edits(_update_data->state, *_data, get_mesh_block_size());
std::vector<VoxelLodTerrainUpdateData::BlockToSave> blocks_to_save;
std::vector<VoxelData::BlockToSave> blocks_to_save;
Ref<VoxelStream> stream = get_stream();
if (stream.is_valid()) {
// That may cause a stutter, so should be used when the player won't notice
_data->for_each_block(ScheduleSaveAction{ blocks_to_save });
_data->consume_all_modifications(blocks_to_save, with_copy);
/*for (unsigned int i = 0; i < _data->lod_count; ++i) {
VoxelDataLodMap::Lod &data_lod = _data->lods[i];
RWLockRead rlock(data_lod.map_lock);

View File

@ -36,11 +36,11 @@ struct VoxelLodTerrainUpdateData {
uint8_t lod;
};
struct BlockToSave {
std::shared_ptr<VoxelBufferInternal> voxels;
Vector3i position;
uint8_t lod;
};
// struct BlockToSave {
// std::shared_ptr<VoxelBufferInternal> voxels;
// Vector3i position;
// uint8_t lod;
// };
// These values don't change during the update task.
struct Settings {

View File

@ -209,7 +209,7 @@ void VoxelLodTerrainUpdateTask::flush_pending_lod_edits(
*/
}
struct BeforeUnloadDataAction {
/*struct BeforeUnloadDataAction {
std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save;
const Vector3i bpos;
bool save;
@ -230,7 +230,7 @@ struct BeforeUnloadDataAction {
blocks_to_save.push_back(b);
}
}
};
};*/
/*static void unload_data_block_no_lock(VoxelLodTerrainUpdateData::Lod &lod, VoxelDataLodMap::Lod &data_lod,
Vector3i block_pos, std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save, bool can_save) {
@ -252,7 +252,7 @@ struct BeforeUnloadDataAction {
}*/
static void process_unload_data_blocks_sliding_box(VoxelLodTerrainUpdateData::State &state, VoxelData &data,
Vector3 p_viewer_pos, std::vector<VoxelLodTerrainUpdateData::BlockToSave> &blocks_to_save, bool can_save,
Vector3 p_viewer_pos, std::vector<VoxelData::BlockToSave> &blocks_to_save, bool can_save,
const VoxelLodTerrainUpdateData::Settings &settings) {
ZN_PROFILE_SCOPE_NAMED("Sliding box data unload");
// TODO Could it actually be enough to have a rolling update on all blocks?
@ -310,10 +310,7 @@ static void process_unload_data_blocks_sliding_box(VoxelLodTerrainUpdateData::St
prev_box.difference_to_vec(new_box, tls_to_remove);
for (const Box3i bbox : tls_to_remove) {
data.unload_blocks(bbox, lod_index, //
[&blocks_to_save, can_save](VoxelDataBlock &block, Vector3i bpos) {
BeforeUnloadDataAction{ blocks_to_save, bpos, can_save }(block);
});
data.unload_blocks(bbox, lod_index, &blocks_to_save);
}
/*prev_box.difference(new_box, [&lod, &data_lod, &blocks_to_save, can_save](Box3i out_of_range_box) {
@ -1268,14 +1265,13 @@ static void request_voxel_block_save(uint32_t volume_id, std::shared_ptr<VoxelBu
}
void VoxelLodTerrainUpdateTask::send_block_save_requests(uint32_t volume_id,
Span<VoxelLodTerrainUpdateData::BlockToSave> blocks_to_save,
std::shared_ptr<StreamingDependency> &stream_dependency, unsigned int data_block_size,
BufferedTaskScheduler &task_scheduler) {
Span<VoxelData::BlockToSave> blocks_to_save, std::shared_ptr<StreamingDependency> &stream_dependency,
unsigned int data_block_size, BufferedTaskScheduler &task_scheduler) {
for (unsigned int i = 0; i < blocks_to_save.size(); ++i) {
VoxelLodTerrainUpdateData::BlockToSave &b = blocks_to_save[i];
ZN_PRINT_VERBOSE(format("Requesting save of block {} lod {}", b.position, b.lod));
VoxelData::BlockToSave &b = blocks_to_save[i];
ZN_PRINT_VERBOSE(format("Requesting save of block {} lod {}", b.position, b.lod_index));
request_voxel_block_save(
volume_id, b.voxels, b.position, b.lod, stream_dependency, data_block_size, task_scheduler);
volume_id, b.voxels, b.position, b.lod_index, stream_dependency, data_block_size, task_scheduler);
}
}
@ -1342,7 +1338,8 @@ static void send_mesh_requests(uint32_t volume_id, VoxelLodTerrainUpdateData::St
// Iteration order matters for thread access.
// The array also implicitely encodes block position due to the convention being used,
// so there is no need to also include positions in the request
task->blocks_count = data.get_blocks_with_voxel_data(data_box, lod_index, to_span(task->blocks));
data.get_blocks_with_voxel_data(data_box, lod_index, to_span(task->blocks));
task->blocks_count = Vector3iUtil::get_volume(data_box.size);
/*const VoxelDataLodMap::Lod &data_lod = data.lods[lod_index];
RWLockRead rlock(data_lod.map_lock);
@ -1600,7 +1597,7 @@ void VoxelLodTerrainUpdateTask::run(ThreadedTaskContext ctx) {
// Other mesh updates
process_changed_generated_areas(state, settings, lod_count);
static thread_local std::vector<VoxelLodTerrainUpdateData::BlockToSave> data_blocks_to_save;
static thread_local std::vector<VoxelData::BlockToSave> data_blocks_to_save;
static thread_local std::vector<VoxelLodTerrainUpdateData::BlockLocation> data_blocks_to_load;
data_blocks_to_load.clear();

View File

@ -62,8 +62,7 @@ public:
}
}
static void send_block_save_requests(uint32_t volume_id,
Span<VoxelLodTerrainUpdateData::BlockToSave> blocks_to_save,
static void send_block_save_requests(uint32_t volume_id, Span<VoxelData::BlockToSave> blocks_to_save,
std::shared_ptr<StreamingDependency> &stream_dependency, unsigned int data_block_size,
BufferedTaskScheduler &task_scheduler);

View File

@ -19,7 +19,7 @@ class VoxelDataBlockEnterInfo : public Object {
public:
int network_peer_id = -1;
Vector3i block_position;
VoxelDataBlock *voxel_block = nullptr;
const VoxelDataBlock *voxel_block = nullptr;
private:
int _b_get_network_peer_id() const;