Refactor generators and streams

- VoxelGenerator no longer inherit VoxelStream
- VoxelStream is now more focused on files
- Nodes have separate stream and generator properties
- Generators use 2 dedicated threads instead of sharing a single one with streams
- TODO Image.lock() is problematic for multithreading
- TODO Voxel graph can cause RWLock contention if edited while it runs
- TODO Saving generator output no longer works, need to put it back
This commit is contained in:
Marc Gilleron 2021-01-17 17:18:05 +00:00
parent fb1442e444
commit 4ec60074bb
23 changed files with 547 additions and 304 deletions

View File

@ -14,6 +14,7 @@ class VoxelTerrainEditorTaskIndicator : public HBoxContainer {
private:
enum StatID {
STAT_STREAM_TASKS,
STAT_GENERATE_TASKS,
STAT_MESH_TASKS,
STAT_MAIN_THREAD_TASKS,
STAT_COUNT
@ -22,6 +23,7 @@ private:
public:
VoxelTerrainEditorTaskIndicator() {
create_stat(STAT_STREAM_TASKS, TTR("Streaming tasks"));
create_stat(STAT_GENERATE_TASKS, TTR("Generation tasks"));
create_stat(STAT_MESH_TASKS, TTR("Meshing tasks"));
create_stat(STAT_MAIN_THREAD_TASKS, TTR("Main thread tasks"));
}
@ -29,6 +31,7 @@ public:
void _notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED:
// Set a monospace font.
// Can't do this in constructor, fonts are not available then. Also the theme can change.
for (unsigned int i = 0; i < _stats.size(); ++i) {
_stats[i].label->add_font_override("font", get_font("source", "EditorFonts"));
@ -40,6 +43,7 @@ public:
void update_stats(int main_thread_tasks) {
const VoxelServer::Stats stats = VoxelServer::get_singleton()->get_stats();
set_stat(STAT_STREAM_TASKS, stats.streaming.tasks);
set_stat(STAT_GENERATE_TASKS, stats.generation.tasks);
set_stat(STAT_MESH_TASKS, stats.meshing.tasks);
set_stat(STAT_MAIN_THREAD_TASKS, main_thread_tasks);
}
@ -53,7 +57,7 @@ private:
name_label->set_text(name);
add_child(name_label);
stat.label = memnew(Label);
stat.label->set_custom_minimum_size(Vector2(80 * EDSCALE, 0));
stat.label->set_custom_minimum_size(Vector2(60 * EDSCALE, 0));
stat.label->set_text("---");
add_child(stat.label);
}

View File

@ -198,6 +198,10 @@ int VoxelGeneratorGraph::get_used_channels_mask() const {
}
void VoxelGeneratorGraph::generate_block(VoxelBlockRequest &input) {
// TODO Find a way to not require read lock for the whole duration of the block.
// If the user tries to edit (and recompile) the graph while it's still generating stuff in the editor,
// the editor will freeze until generation has completed.
// Use std::shared_ptr?
RWLockRead rlock(_runtime_lock);
const VoxelGraphRuntime *runtime = _runtime;

View File

@ -1138,6 +1138,9 @@ VoxelGraphNodeDB::VoxelGraphNodeDB() {
// TODO Allow to use bilinear filtering?
const Params p = ctx.get_params<Params>();
Image &im = *p.image;
// TODO Because of this shitty locking system, images aren't read-only and as a result can't be used with more than one thread!
// - Copy data in a custom structure?
// - Lock all images after compilation and unlock them in destructor?
im.lock();
for (uint32_t i = 0; i < out.size; ++i) {
out.data[i] = sdf_sphere_heightmap(x.data[i], y.data[i], z.data[i],

View File

@ -8,17 +8,8 @@ void VoxelGenerator::generate_block(VoxelBlockRequest &input) {
ERR_FAIL_COND(input.voxel_buffer.is_null());
}
//bool VoxelGenerator::is_thread_safe() const {
// return false;
//}
//bool VoxelGenerator::is_cloneable() const {
// return false;
//}
void VoxelGenerator::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
VoxelBlockRequest r = { out_buffer, Vector3i(origin_in_voxels), lod };
generate_block(r);
int VoxelGenerator::get_used_channels_mask() const {
return 0;
}
void VoxelGenerator::_b_generate_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod) {

View File

@ -1,26 +1,21 @@
#ifndef VOXEL_GENERATOR_H
#define VOXEL_GENERATOR_H
#include "../streams/voxel_stream.h"
// TODO I would like VoxelGenerator to not inherit VoxelStream
// because it gets members that make no sense with generators
#include "../streams/voxel_block_request.h"
#include <core/resource.h>
// Provides access to read-only generated voxels.
// Must be implemented in a multi-thread-safe way.
class VoxelGenerator : public VoxelStream {
GDCLASS(VoxelGenerator, VoxelStream)
class VoxelGenerator : public Resource {
GDCLASS(VoxelGenerator, Resource)
public:
VoxelGenerator();
virtual void generate_block(VoxelBlockRequest &input);
// TODO Single sample
// virtual bool is_thread_safe() const;
// virtual bool is_cloneable() const;
private:
void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
// Declares the channels this generator will use
virtual int get_used_channels_mask() const;
protected:
static void _bind_methods();

View File

@ -5,6 +5,7 @@
#include "../voxel_constants.h"
#include <core/os/memory.h>
#include <scene/main/viewport.h>
#include <thread>
namespace {
VoxelServer *g_voxel_server = nullptr;
@ -44,14 +45,20 @@ void VoxelServer::destroy_singleton() {
}
VoxelServer::VoxelServer() {
// TODO Can't set more than 1 thread yet, streams aren't well made for it... unless we can separate file ones
// This pool can work on larger periods, it doesn't require low latency
const unsigned int hw_threads_hint = std::thread::hardware_concurrency();
PRINT_VERBOSE(String("HW threads hint: {0}").format(varray(hw_threads_hint)));
// TODO Automatic thread assignment and project settings
// Can't be more than 1 thread. File access with more threads isn't worth it.
_streaming_thread_pool.set_thread_count(1);
_streaming_thread_pool.set_priority_update_period(300);
_streaming_thread_pool.set_batch_count(16);
// TODO Try more threads, it should be possible
// This pool works on visuals so it must have low latency
_generation_thread_pool.set_thread_count(2);
_generation_thread_pool.set_priority_update_period(300);
_generation_thread_pool.set_batch_count(1);
// This pool works on visuals so it must have lower latency
_meshing_thread_pool.set_thread_count(2);
_meshing_thread_pool.set_priority_update_period(64);
_meshing_thread_pool.set_batch_count(1);
@ -75,6 +82,7 @@ VoxelServer::~VoxelServer() {
void VoxelServer::wait_and_clear_all_tasks(bool warn) {
_streaming_thread_pool.wait_for_all_tasks();
_meshing_thread_pool.wait_for_all_tasks();
_generation_thread_pool.wait_for_all_tasks();
_streaming_thread_pool.dequeue_completed_tasks([warn](IVoxelTask *task) {
if (warn) {
@ -87,6 +95,14 @@ void VoxelServer::wait_and_clear_all_tasks(bool warn) {
_meshing_thread_pool.dequeue_completed_tasks([](IVoxelTask *task) {
memdelete(task);
});
_generation_thread_pool.dequeue_completed_tasks([warn](IVoxelTask *task) {
if (warn) {
WARN_PRINT("Generator tasks remain on module cleanup, "
"this could become a problem if they reference scripts");
}
memdelete(task);
});
}
int VoxelServer::get_priority(const PriorityDependency &dep, uint8_t lod, float *out_closest_distance_sq) {
@ -142,19 +158,28 @@ void VoxelServer::set_volume_stream(uint32_t volume_id, Ref<VoxelStream> stream)
Volume &volume = _world.volumes.get(volume_id);
volume.stream = stream;
// Commit a new stream to process requests with
// Commit a new dependency to process requests with
if (volume.stream_dependency != nullptr) {
volume.stream_dependency->valid = false;
}
if (stream.is_valid()) {
volume.stream_dependency = gd_make_shared<StreamingDependency>();
for (size_t i = 0; i < _streaming_thread_pool.get_thread_count(); ++i) {
volume.stream_dependency->streams[i] = stream->duplicate();
}
} else {
volume.stream_dependency = nullptr;
volume.stream_dependency = gd_make_shared<StreamingDependency>();
volume.stream_dependency->generator = volume.generator;
volume.stream_dependency->stream = volume.stream;
}
void VoxelServer::set_volume_generator(uint32_t volume_id, Ref<VoxelGenerator> generator) {
Volume &volume = _world.volumes.get(volume_id);
volume.generator = generator;
// Commit a new dependency to process requests with
if (volume.stream_dependency != nullptr) {
volume.stream_dependency->valid = false;
}
volume.stream_dependency = gd_make_shared<StreamingDependency>();
volume.stream_dependency->generator = volume.generator;
volume.stream_dependency->stream = volume.stream;
}
void VoxelServer::set_volume_mesher(uint32_t volume_id, Ref<VoxelMesher> mesher) {
@ -212,8 +237,6 @@ void VoxelServer::init_priority_dependency(
void VoxelServer::request_block_mesh(uint32_t volume_id, BlockMeshInput &input) {
const Volume &volume = _world.volumes.get(volume_id);
ERR_FAIL_COND(volume.stream.is_null());
CRASH_COND(volume.stream_dependency == nullptr);
ERR_FAIL_COND(volume.meshing_dependency == nullptr);
BlockMeshRequest *r = memnew(BlockMeshRequest);
@ -231,21 +254,38 @@ void VoxelServer::request_block_mesh(uint32_t volume_id, BlockMeshInput &input)
void VoxelServer::request_block_load(uint32_t volume_id, Vector3i block_pos, int lod) {
const Volume &volume = _world.volumes.get(volume_id);
ERR_FAIL_COND(volume.stream.is_null());
CRASH_COND(volume.stream_dependency == nullptr);
ERR_FAIL_COND(volume.stream_dependency == nullptr);
BlockDataRequest r;
r.volume_id = volume_id;
r.position = block_pos;
r.lod = lod;
r.type = BlockDataRequest::TYPE_LOAD;
r.block_size = volume.block_size;
r.stream_dependency = volume.stream_dependency;
if (volume.stream_dependency->stream.is_valid()) {
BlockDataRequest r;
r.volume_id = volume_id;
r.position = block_pos;
r.lod = lod;
r.type = BlockDataRequest::TYPE_LOAD;
r.block_size = volume.block_size;
r.stream_dependency = volume.stream_dependency;
init_priority_dependency(r.priority_dependency, block_pos, lod, volume);
init_priority_dependency(r.priority_dependency, block_pos, lod, volume);
BlockDataRequest *rp = memnew(BlockDataRequest(r));
_streaming_thread_pool.enqueue(rp);
BlockDataRequest *rp = memnew(BlockDataRequest(r));
_streaming_thread_pool.enqueue(rp);
} else {
// Directly generate the block without checking the stream
ERR_FAIL_COND(volume.stream_dependency->generator.is_null());
BlockGenerateRequest r;
r.volume_id = volume_id;
r.position = block_pos;
r.lod = lod;
r.block_size = volume.block_size;
r.stream_dependency = volume.stream_dependency;
init_priority_dependency(r.priority_dependency, block_pos, lod, volume);
BlockGenerateRequest *rp = memnew(BlockGenerateRequest(r));
_generation_thread_pool.enqueue(rp);
}
}
void VoxelServer::request_block_save(uint32_t volume_id, Ref<VoxelBuffer> voxels, Vector3i block_pos, int lod) {
@ -268,6 +308,22 @@ void VoxelServer::request_block_save(uint32_t volume_id, Ref<VoxelBuffer> voxels
_streaming_thread_pool.enqueue(rp);
}
void VoxelServer::request_block_generate_from_data_request(BlockDataRequest *src) {
// This can be called from another thread
BlockGenerateRequest r;
r.voxels = src->voxels;
r.volume_id = src->volume_id;
r.position = src->position;
r.lod = src->lod;
r.block_size = src->block_size;
r.stream_dependency = src->stream_dependency;
r.priority_dependency = src->priority_dependency;
BlockGenerateRequest *rp = memnew(BlockGenerateRequest(r));
_generation_thread_pool.enqueue(rp);
}
void VoxelServer::remove_volume(uint32_t volume_id) {
{
Volume &volume = _world.volumes.get(volume_id);
@ -351,7 +407,8 @@ void VoxelServer::process() {
// TODO Comparing pointer may not be guaranteed
// The request response must match the dependency it would have been requested with.
// If it doesn't match, we are no longer interested in the result.
if (r->stream_dependency == volume->stream_dependency) {
if (r->stream_dependency == volume->stream_dependency &&
r->type != BlockDataRequest::TYPE_FALLBACK_ON_GENERATOR) {
BlockDataOutput o;
o.voxels = r->voxels;
o.position = r->position;
@ -376,7 +433,34 @@ void VoxelServer::process() {
} else {
// This can happen if the user removes the volume while requests are still about to return
PRINT_VERBOSE("Data request response came back but volume wasn't found");
PRINT_VERBOSE("Stream data request response came back but volume wasn't found");
}
memdelete(r);
});
// Receive generation updates
_generation_thread_pool.dequeue_completed_tasks([this](IVoxelTask *task) {
BlockGenerateRequest *r = must_be_cast<BlockGenerateRequest>(task);
Volume *volume = _world.volumes.try_get(r->volume_id);
if (volume != nullptr) {
// TODO Comparing pointer may not be guaranteed
// The request response must match the dependency it would have been requested with.
// If it doesn't match, we are no longer interested in the result.
if (r->stream_dependency == volume->stream_dependency) {
BlockDataOutput o;
o.voxels = r->voxels;
o.position = r->position;
o.lod = r->lod;
o.dropped = !r->has_run;
o.type = BlockDataOutput::TYPE_LOAD;
volume->reception_buffers->data_output.push_back(o);
}
} else {
// This can happen if the user removes the volume while requests are still about to return
PRINT_VERBOSE("Gemerated data request response came back but volume wasn't found");
}
memdelete(r);
@ -486,6 +570,7 @@ static VoxelServer::Stats::ThreadPoolStats debug_get_pool_stats(const VoxelThrea
VoxelServer::Stats VoxelServer::get_stats() const {
Stats s;
s.streaming = debug_get_pool_stats(_streaming_thread_pool);
s.generation = debug_get_pool_stats(_generation_thread_pool);
s.meshing = debug_get_pool_stats(_meshing_thread_pool);
return s;
}
@ -504,17 +589,24 @@ void VoxelServer::BlockDataRequest::run(VoxelTaskContext ctx) {
VOXEL_PROFILE_SCOPE();
CRASH_COND(stream_dependency == nullptr);
Ref<VoxelStream> stream = stream_dependency->streams[ctx.thread_index];
Ref<VoxelStream> stream = stream_dependency->stream;
CRASH_COND(stream.is_null());
const Vector3i origin_in_voxels = (position << lod) * block_size;
switch (type) {
case TYPE_LOAD:
case TYPE_LOAD: {
voxels.instance();
voxels->create(block_size, block_size, block_size);
stream->emerge_block(voxels, origin_in_voxels, lod);
break;
const VoxelStream::Result result = stream->emerge_block(voxels, origin_in_voxels, lod);
if (result == VoxelStream::RESULT_BLOCK_NOT_FOUND) {
Ref<VoxelGenerator> generator = stream_dependency->generator;
if (generator.is_valid()) {
VoxelServer::get_singleton()->request_block_generate_from_data_request(this);
type = TYPE_FALLBACK_ON_GENERATOR;
}
}
} break;
case TYPE_SAVE: {
Ref<VoxelBuffer> voxels_copy;
@ -549,6 +641,40 @@ bool VoxelServer::BlockDataRequest::is_cancelled() {
//----------------------------------------------------------------------------------------------------------------------
void VoxelServer::BlockGenerateRequest::run(VoxelTaskContext ctx) {
VOXEL_PROFILE_SCOPE();
CRASH_COND(stream_dependency == nullptr);
Ref<VoxelGenerator> generator = stream_dependency->generator;
ERR_FAIL_COND(generator.is_null());
const Vector3i origin_in_voxels = (position << lod) * block_size;
if (voxels.is_null()) {
voxels.instance();
voxels->create(block_size, block_size, block_size);
}
VoxelBlockRequest r{ voxels, origin_in_voxels, lod };
generator->generate_block(r);
// TODO Request save if the option is enabled
has_run = true;
}
int VoxelServer::BlockGenerateRequest::get_priority() {
float closest_viewer_distance_sq;
const int p = VoxelServer::get_priority(priority_dependency, lod, &closest_viewer_distance_sq);
too_far = closest_viewer_distance_sq > priority_dependency.drop_distance_squared;
return p;
}
bool VoxelServer::BlockGenerateRequest::is_cancelled() {
return !stream_dependency->valid || too_far; // || stream_dependency->stream->get_fallback_generator().is_null();
}
//----------------------------------------------------------------------------------------------------------------------
static void copy_block_and_neighbors(const FixedArray<Ref<VoxelBuffer>, Cube::MOORE_AREA_3D_COUNT> &moore_blocks,
VoxelBuffer &dst, int min_padding, int max_padding, int channels_mask) {

View File

@ -1,6 +1,7 @@
#ifndef VOXEL_SERVER_H
#define VOXEL_SERVER_H
#include "../generators/voxel_generator.h"
#include "../meshers/blocky/voxel_mesher_blocky.h"
#include "../streams/voxel_stream.h"
#include "../util/file_locker.h"
@ -82,6 +83,7 @@ public:
void set_volume_transform(uint32_t volume_id, Transform t);
void set_volume_block_size(uint32_t volume_id, uint32_t block_size);
void set_volume_stream(uint32_t volume_id, Ref<VoxelStream> stream);
void set_volume_generator(uint32_t volume_id, Ref<VoxelGenerator> generator);
void set_volume_mesher(uint32_t volume_id, Ref<VoxelMesher> mesher);
void set_volume_octree_split_scale(uint32_t volume_id, float split_scale);
void invalidate_volume_mesh_requests(uint32_t volume_id);
@ -134,19 +136,21 @@ public:
Dictionary to_dict() {
Dictionary d;
d["tasks"] = thread_count;
d["tasks"] = tasks;
d["active_threads"] = active_threads;
d["thread_count"] = tasks;
d["thread_count"] = thread_count;
return d;
}
};
ThreadPoolStats streaming;
ThreadPoolStats generation;
ThreadPoolStats meshing;
Dictionary to_dict() {
Dictionary d;
d["streaming"] = streaming.to_dict();
d["generation"] = generation.to_dict();
d["meshing"] = meshing.to_dict();
return d;
}
@ -155,6 +159,10 @@ public:
Stats get_stats() const;
private:
class BlockDataRequest;
void request_block_generate_from_data_request(BlockDataRequest *src);
Dictionary _b_get_stats();
static void _bind_methods();
@ -171,13 +179,12 @@ private:
// If such data sets change structurally (like their size, or other non-dirty-readable fields),
// then a new instance is created and old references are left to "die out".
// Data common to all requests about a particular volume
struct StreamingDependency {
FixedArray<Ref<VoxelStream>, VoxelThreadPool::MAX_THREADS> streams;
Ref<VoxelStream> stream;
Ref<VoxelGenerator> generator;
bool valid = true;
};
// Data common to all requests about a particular volume
struct MeshingDependency {
Ref<VoxelMesher> mesher;
bool valid = true;
@ -188,6 +195,7 @@ private:
ReceptionBuffers *reception_buffers = nullptr;
Transform transform;
Ref<VoxelStream> stream;
Ref<VoxelGenerator> generator;
Ref<VoxelMesher> mesher;
uint32_t block_size = 16;
float octree_split_scale = 0;
@ -226,7 +234,8 @@ private:
public:
enum Type {
TYPE_LOAD = 0,
TYPE_SAVE
TYPE_SAVE,
TYPE_FALLBACK_ON_GENERATOR
};
void run(VoxelTaskContext ctx) override;
@ -246,6 +255,23 @@ private:
// TODO Find a way to separate save, it doesnt need sorting
};
class BlockGenerateRequest : public IVoxelTask {
public:
void run(VoxelTaskContext ctx) override;
int get_priority() override;
bool is_cancelled() override;
Ref<VoxelBuffer> voxels;
Vector3i position;
uint32_t volume_id;
uint8_t lod;
uint8_t block_size;
bool has_run = false;
bool too_far = false;
PriorityDependency priority_dependency;
std::shared_ptr<StreamingDependency> stream_dependency;
};
class BlockMeshRequest : public IVoxelTask {
public:
void run(VoxelTaskContext ctx) override;
@ -267,6 +293,7 @@ private:
World _world;
VoxelThreadPool _streaming_thread_pool;
VoxelThreadPool _generation_thread_pool;
VoxelThreadPool _meshing_thread_pool;
VoxelFileLocker _file_locker;

View File

@ -3,56 +3,98 @@
#include <core/script_language.h>
VoxelStream::VoxelStream() {
_parameters_lock = RWLock::create();
}
void VoxelStream::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND(out_buffer.is_null());
VoxelStream::~VoxelStream() {
memdelete(_parameters_lock);
}
VoxelStream::Result VoxelStream::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND_V(out_buffer.is_null(), RESULT_ERROR);
// Can be implemented in subclasses
return RESULT_BLOCK_NOT_FOUND;
}
void VoxelStream::immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND(buffer.is_null());
// Can be implemented in subclasses
}
void VoxelStream::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
void VoxelStream::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vector<Result> &out_results) {
// Default implementation. May matter for some stream types to optimize loading.
for (int i = 0; i < p_blocks.size(); ++i) {
VoxelBlockRequest &r = p_blocks.write[i];
emerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
const Result res = emerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
out_results.push_back(res);
}
}
void VoxelStream::immerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
void VoxelStream::immerge_blocks(const Vector<VoxelBlockRequest> &p_blocks) {
for (int i = 0; i < p_blocks.size(); ++i) {
VoxelBlockRequest &r = p_blocks.write[i];
const VoxelBlockRequest &r = p_blocks[i];
immerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
}
}
void VoxelStream::_emerge_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND(lod < 0);
emerge_block(out_buffer, Vector3i(origin_in_voxels), lod);
}
void VoxelStream::_immerge_block(Ref<VoxelBuffer> buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND(lod < 0);
immerge_block(buffer, Vector3i(origin_in_voxels), lod);
}
int VoxelStream::get_used_channels_mask() const {
return 0;
}
int VoxelStream::_get_used_channels_mask() const {
void VoxelStream::set_save_generator_output(bool enabled) {
RWLockWrite wlock(_parameters_lock);
_parameters.save_generator_output = enabled;
}
bool VoxelStream::get_save_generator_output() const {
RWLockRead rlock(_parameters_lock);
return _parameters.save_generator_output;
}
int VoxelStream::get_block_size_po2() const {
return 4;
}
int VoxelStream::get_lod_count() const {
return 1;
}
// Binding land
void VoxelStream::_b_emerge_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND(lod < 0);
emerge_block(out_buffer, Vector3i(origin_in_voxels), lod);
}
void VoxelStream::_b_immerge_block(Ref<VoxelBuffer> buffer, Vector3 origin_in_voxels, int lod) {
ERR_FAIL_COND(lod < 0);
immerge_block(buffer, Vector3i(origin_in_voxels), lod);
}
int VoxelStream::_b_get_used_channels_mask() const {
return get_used_channels_mask();
}
bool VoxelStream::has_script() const {
Ref<Script> s = get_script();
return s.is_valid();
Vector3 VoxelStream::_b_get_block_size() const {
return Vector3i(1 << get_block_size_po2()).to_vec3();
}
void VoxelStream::_bind_methods() {
ClassDB::bind_method(D_METHOD("emerge_block", "out_buffer", "origin_in_voxels", "lod"), &VoxelStream::_emerge_block);
ClassDB::bind_method(D_METHOD("immerge_block", "buffer", "origin_in_voxels", "lod"), &VoxelStream::_immerge_block);
ClassDB::bind_method(D_METHOD("get_used_channels_mask"), &VoxelStream::_get_used_channels_mask);
ClassDB::bind_method(D_METHOD("emerge_block", "out_buffer", "origin_in_voxels", "lod"),
&VoxelStream::_b_emerge_block);
ClassDB::bind_method(D_METHOD("immerge_block", "buffer", "origin_in_voxels", "lod"),
&VoxelStream::_b_immerge_block);
ClassDB::bind_method(D_METHOD("get_used_channels_mask"), &VoxelStream::_b_get_used_channels_mask);
ClassDB::bind_method(D_METHOD("set_save_generator_output", "enabled"), &VoxelStream::set_save_generator_output);
ClassDB::bind_method(D_METHOD("get_save_generator_output"), &VoxelStream::get_save_generator_output);
ClassDB::bind_method(D_METHOD("get_block_size"), &VoxelStream::_b_get_block_size);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_generator_output"),
"set_save_generator_output", "get_save_generator_output");
BIND_ENUM_CONSTANT(RESULT_ERROR);
BIND_ENUM_CONSTANT(RESULT_BLOCK_FOUND);
BIND_ENUM_CONSTANT(RESULT_BLOCK_NOT_FOUND);
}

View File

@ -1,45 +1,80 @@
#ifndef VOXEL_STREAM_H
#define VOXEL_STREAM_H
#include "../generators/voxel_generator.h"
#include "voxel_block_request.h"
#include <core/resource.h>
// Provides access to a source of paged voxel data, which may load and save.
// Must be implemented in a multi-thread-safe way.
// If you are looking for a more specialized API to generate voxels, use VoxelGenerator.
// This is intented for files, so it may run in a single background thread and gets requests in batches.
// Must be implemented in a thread-safe way.
//
// If you are looking for a more specialized API to generate voxels with more threads, use VoxelGenerator.
//
class VoxelStream : public Resource {
GDCLASS(VoxelStream, Resource)
public:
VoxelStream();
~VoxelStream();
enum Result {
// Something went wrong, the request should be aborted
RESULT_ERROR,
// The block could not be found in the stream. The requester may fallback on the generator.
RESULT_BLOCK_NOT_FOUND,
// The block was found, so the requester won't use the generator.
RESULT_BLOCK_FOUND,
_RESULT_COUNT
};
// TODO Rename load_block()
// Queries a block of voxels beginning at the given world-space voxel position and LOD.
// If you use LOD, the result at a given coordinate must always remain the same regardless of it.
// In other words, voxels values must solely depend on their coordinates or fixed parameters.
virtual void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod);
virtual Result emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod);
// TODO Rename unload_block(), or save_block() ?
// TODO Rename save_block()
virtual void immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod);
// Note: vector is passed by ref for performance. Don't reorder it.
virtual void emerge_blocks(Vector<VoxelBlockRequest> &p_blocks);
virtual void emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vector<Result> &out_results);
// Returns multiple blocks of voxels to the stream.
// Generators usually don't implement it.
// This function is recommended if you save to files, because you can batch their access.
virtual void immerge_blocks(Vector<VoxelBlockRequest> &p_blocks);
virtual void immerge_blocks(const Vector<VoxelBlockRequest> &p_blocks);
// Declares the format expected from this stream
virtual int get_used_channels_mask() const;
virtual bool has_script() const;
// Gets which block size this stream will provide, as a power of two.
// File streams are likely to impose a specific block size,
// and changing it can be very expensive so the API is usually specific too
virtual int get_block_size_po2() const;
protected:
// Gets at how many levels of details blocks can be queried.
virtual int get_lod_count() const;
// Should generated blocks be saved immediately? If not, they will be saved only when modified.
void set_save_generator_output(bool enabled);
bool get_save_generator_output() const;
private:
static void _bind_methods();
void _emerge_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod);
void _immerge_block(Ref<VoxelBuffer> buffer, Vector3 origin_in_voxels, int lod);
int _get_used_channels_mask() const;
void _b_emerge_block(Ref<VoxelBuffer> out_buffer, Vector3 origin_in_voxels, int lod);
void _b_immerge_block(Ref<VoxelBuffer> buffer, Vector3 origin_in_voxels, int lod);
int _b_get_used_channels_mask() const;
Vector3 _b_get_block_size() const;
struct Parameters {
bool save_generator_output = true;
};
Parameters _parameters;
RWLock *_parameters_lock = nullptr;
};
VARIANT_ENUM_CAST(VoxelStream::Result);
#endif // VOXEL_STREAM_H

View File

@ -22,17 +22,18 @@ VoxelStreamBlockFiles::VoxelStreamBlockFiles() {
// TODO Have configurable block size
void VoxelStreamBlockFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND(out_buffer.is_null());
VoxelStream::Result VoxelStreamBlockFiles::emerge_block(
Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND_V(out_buffer.is_null(), RESULT_ERROR);
if (_directory_path.empty()) {
emerge_block_fallback(out_buffer, origin_in_voxels, lod);
return;
return RESULT_BLOCK_NOT_FOUND;
}
if (!_meta_loaded) {
if (load_meta() != VOXEL_FILE_OK) {
return;
return RESULT_ERROR;
}
}
@ -40,8 +41,8 @@ void VoxelStreamBlockFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i o
const Vector3i block_size(1 << _meta.block_size_po2);
ERR_FAIL_COND(lod >= _meta.lod_count);
ERR_FAIL_COND(block_size != out_buffer->get_size());
ERR_FAIL_COND_V(lod >= _meta.lod_count, RESULT_ERROR);
ERR_FAIL_COND_V(block_size != out_buffer->get_size(), RESULT_ERROR);
Vector3i block_pos = get_block_position(origin_in_voxels) >> lod;
String file_path = get_block_file_path(block_pos, lod);
@ -53,12 +54,11 @@ void VoxelStreamBlockFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i o
f = open_file(file_path, FileAccess::READ, &err);
// Had to add ERR_FILE_CANT_OPEN because that's what Godot actually returns when the file doesn't exist...
if (f == nullptr && (err == ERR_FILE_NOT_FOUND || err == ERR_FILE_CANT_OPEN)) {
emerge_block_fallback(out_buffer, origin_in_voxels, lod);
return;
return RESULT_BLOCK_NOT_FOUND;
}
}
ERR_FAIL_COND(f == nullptr);
ERR_FAIL_COND_V(f == nullptr, RESULT_ERROR);
{
{
@ -67,7 +67,7 @@ void VoxelStreamBlockFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i o
if (err != VOXEL_FILE_OK) {
memdelete(f);
ERR_PRINT(String("Invalid file header: ") + ::to_string(err));
return;
return RESULT_ERROR;
}
}
@ -85,6 +85,8 @@ void VoxelStreamBlockFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i o
f->close();
memdelete(f);
return RESULT_BLOCK_FOUND;
}
void VoxelStreamBlockFiles::immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) {

View File

@ -14,7 +14,7 @@ class VoxelStreamBlockFiles : public VoxelStreamFile {
public:
VoxelStreamBlockFiles();
void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
Result emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
void immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) override;
String get_directory() const;

View File

@ -4,74 +4,7 @@
#include <core/os/file_access.h>
#include <core/os/os.h>
VoxelStreamFile::VoxelStreamFile() {
_parameters_lock = RWLock::create();
}
VoxelStreamFile::~VoxelStreamFile() {
memdelete(_parameters_lock);
}
void VoxelStreamFile::set_save_fallback_output(bool enabled) {
RWLockWrite wlock(_parameters_lock);
_parameters.save_fallback_output = enabled;
}
bool VoxelStreamFile::get_save_fallback_output() const {
RWLockRead rlock(_parameters_lock);
return _parameters.save_fallback_output;
}
Ref<VoxelStream> VoxelStreamFile::get_fallback_stream() const {
RWLockRead rlock(_parameters_lock);
return _parameters.fallback_stream;
}
void VoxelStreamFile::set_fallback_stream(Ref<VoxelStream> stream) {
ERR_FAIL_COND(*stream == this);
RWLockWrite wlock(_parameters_lock);
_parameters.fallback_stream = stream;
}
void VoxelStreamFile::emerge_block_fallback(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
// This function is just a helper around the true thing, really. I might remove it in the future.
VoxelBlockRequest r;
r.voxel_buffer = out_buffer;
r.origin_in_voxels = origin_in_voxels;
r.lod = lod;
Vector<VoxelBlockRequest> requests;
requests.push_back(r);
emerge_blocks_fallback(requests);
}
void VoxelStreamFile::emerge_blocks_fallback(Vector<VoxelBlockRequest> &requests) {
VOXEL_PROFILE_SCOPE();
Parameters params;
{
RWLockRead rlock(_parameters_lock);
params = _parameters;
}
if (params.fallback_stream.is_valid()) {
params.fallback_stream->emerge_blocks(requests);
if (params.save_fallback_output) {
immerge_blocks(requests);
}
}
}
int VoxelStreamFile::get_used_channels_mask() const {
RWLockRead rlock(_parameters_lock);
if (_parameters.fallback_stream.is_valid()) {
return _parameters.fallback_stream->get_used_channels_mask();
}
return VoxelStream::get_used_channels_mask();
}
thread_local VoxelBlockSerializerInternal VoxelStreamFile::_block_serializer;
FileAccess *VoxelStreamFile::open_file(const String &fpath, int mode_flags, Error *err) {
VOXEL_PROFILE_SCOPE();
@ -83,44 +16,5 @@ FileAccess *VoxelStreamFile::open_file(const String &fpath, int mode_flags, Erro
return f;
}
void VoxelStreamFile::close_file(FileAccess *f) {
ERR_FAIL_COND(f == nullptr);
f->close();
memdelete(f);
VoxelServer::get_singleton()->get_file_locker().unlock(f->get_path());
}
int VoxelStreamFile::get_block_size_po2() const {
return 4;
}
int VoxelStreamFile::get_lod_count() const {
return 1;
}
Vector3 VoxelStreamFile::_get_block_size() const {
return Vector3i(1 << get_block_size_po2()).to_vec3();
}
bool VoxelStreamFile::has_script() const {
RWLockRead rlock(_parameters_lock);
if (_parameters.fallback_stream.is_valid()) {
return _parameters.fallback_stream->has_script();
}
return VoxelStream::has_script();
}
void VoxelStreamFile::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_save_fallback_output", "enabled"), &VoxelStreamFile::set_save_fallback_output);
ClassDB::bind_method(D_METHOD("get_save_fallback_output"), &VoxelStreamFile::get_save_fallback_output);
ClassDB::bind_method(D_METHOD("set_fallback_stream", "stream"), &VoxelStreamFile::set_fallback_stream);
ClassDB::bind_method(D_METHOD("get_fallback_stream"), &VoxelStreamFile::get_fallback_stream);
ClassDB::bind_method(D_METHOD("get_block_size"), &VoxelStreamFile::_get_block_size);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "fallback_stream", PROPERTY_HINT_RESOURCE_TYPE, "VoxelStream"),
"set_fallback_stream", "get_fallback_stream");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "save_fallback_output"),
"set_save_fallback_output", "get_save_fallback_output");
}

View File

@ -6,52 +6,17 @@
class FileAccess;
// TODO Could be worth integrating with Godot ResourceLoader
// Loads and saves blocks to the filesystem.
// If a block is not found, a fallback stream can be used (usually to generate the block).
// Look at subclasses of this for a specific format.
// TODO Might be removed in the future, it doesn't add much.
// Helper common base for some file streams.
class VoxelStreamFile : public VoxelStream {
GDCLASS(VoxelStreamFile, VoxelStream)
public:
VoxelStreamFile();
~VoxelStreamFile();
void set_save_fallback_output(bool enabled);
bool get_save_fallback_output() const;
Ref<VoxelStream> get_fallback_stream() const;
void set_fallback_stream(Ref<VoxelStream> stream);
// File streams are likely to impose a specific block size,
// and changing it can be very expensive so the API is usually specific too
virtual int get_block_size_po2() const;
virtual int get_lod_count() const;
int get_used_channels_mask() const override;
bool has_script() const override;
protected:
static void _bind_methods();
void emerge_block_fallback(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod);
void emerge_blocks_fallback(Vector<VoxelBlockRequest> &requests);
FileAccess *open_file(const String &fpath, int mode_flags, Error *err);
void close_file(FileAccess *f);
VoxelBlockSerializerInternal _block_serializer;
private:
Vector3 _get_block_size() const;
struct Parameters {
Ref<VoxelStream> fallback_stream;
bool save_fallback_output = true;
};
Parameters _parameters;
RWLock *_parameters_lock = nullptr;
static thread_local VoxelBlockSerializerInternal _block_serializer;
};
#endif // VOXEL_STREAM_FILE_H

View File

@ -33,14 +33,16 @@ VoxelStreamRegionFiles::~VoxelStreamRegionFiles() {
memdelete(_mutex);
}
void VoxelStreamRegionFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
VoxelStream::Result VoxelStreamRegionFiles::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
VoxelBlockRequest r;
r.voxel_buffer = out_buffer;
r.origin_in_voxels = origin_in_voxels;
r.lod = lod;
Vector<VoxelBlockRequest> requests;
Vector<Result> results;
requests.push_back(r);
emerge_blocks(requests);
emerge_blocks(requests, results);
return results[0];
}
void VoxelStreamRegionFiles::immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) {
@ -53,7 +55,7 @@ void VoxelStreamRegionFiles::immerge_block(Ref<VoxelBuffer> buffer, Vector3i ori
immerge_blocks(requests);
}
void VoxelStreamRegionFiles::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
void VoxelStreamRegionFiles::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vector<Result> &out_results) {
VOXEL_PROFILE_SCOPE();
// In order to minimize opening/closing files, requests are grouped according to their region.
@ -70,16 +72,25 @@ void VoxelStreamRegionFiles::emerge_blocks(Vector<VoxelBlockRequest> &p_blocks)
for (int i = 0; i < sorted_blocks.size(); ++i) {
VoxelBlockRequest &r = sorted_blocks.write[i];
EmergeResult result = _emerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
if (result == EMERGE_OK_FALLBACK) {
fallback_requests.push_back(r);
const EmergeResult result = _emerge_block(r.voxel_buffer, r.origin_in_voxels, r.lod);
switch (result) {
case EMERGE_OK:
out_results.push_back(RESULT_BLOCK_FOUND);
break;
case EMERGE_OK_FALLBACK:
out_results.push_back(RESULT_BLOCK_NOT_FOUND);
break;
case EMERGE_FAILED:
out_results.push_back(RESULT_ERROR);
break;
default:
CRASH_NOW();
break;
}
}
emerge_blocks_fallback(fallback_requests);
}
void VoxelStreamRegionFiles::immerge_blocks(Vector<VoxelBlockRequest> &p_blocks) {
void VoxelStreamRegionFiles::immerge_blocks(const Vector<VoxelBlockRequest> &p_blocks) {
VOXEL_PROFILE_SCOPE();
// Had to copy input to sort it, as some areas in the module break if they get responses in different order

View File

@ -23,11 +23,11 @@ public:
VoxelStreamRegionFiles();
~VoxelStreamRegionFiles();
void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
Result emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
void immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) override;
void emerge_blocks(Vector<VoxelBlockRequest> &p_blocks) override;
void immerge_blocks(Vector<VoxelBlockRequest> &p_blocks) override;
void emerge_blocks(Vector<VoxelBlockRequest> &p_blocks, Vector<Result> &out_results) override;
void immerge_blocks(const Vector<VoxelBlockRequest> &p_blocks) override;
String get_directory() const;
void set_directory(String dirpath);
@ -55,6 +55,7 @@ private:
struct CachedRegion;
struct RegionHeader;
// TODO Redundant with VoxelStream::Result. May be replaced
enum EmergeResult {
EMERGE_OK,
EMERGE_OK_FALLBACK,

View File

@ -1,10 +1,16 @@
#include "voxel_stream_script.h"
#include "../voxel_string_names.h"
void VoxelStreamScript::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND(out_buffer.is_null());
try_call_script(this, VoxelStringNames::get_singleton()->_emerge_block,
out_buffer, origin_in_voxels.to_vec3(), lod, nullptr);
VoxelStream::Result VoxelStreamScript::emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) {
ERR_FAIL_COND_V(out_buffer.is_null(), RESULT_ERROR);
Variant output;
if (try_call_script(this, VoxelStringNames::get_singleton()->_emerge_block,
out_buffer, origin_in_voxels.to_vec3(), lod, &output)) {
int res = output;
ERR_FAIL_INDEX_V(res, _RESULT_COUNT, RESULT_ERROR);
return static_cast<Result>(res);
}
return RESULT_ERROR;
}
void VoxelStreamScript::immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) {

View File

@ -9,7 +9,7 @@
class VoxelStreamScript : public VoxelStream {
GDCLASS(VoxelStreamScript, VoxelStream)
public:
void emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
Result emerge_block(Ref<VoxelBuffer> out_buffer, Vector3i origin_in_voxels, int lod) override;
void immerge_block(Ref<VoxelBuffer> buffer, Vector3i origin_in_voxels, int lod) override;
int get_used_channels_mask() const override;

View File

@ -169,10 +169,6 @@ void VoxelLodTerrain::set_material(Ref<Material> p_material) {
_material = p_material;
}
Ref<VoxelStream> VoxelLodTerrain::get_stream() const {
return _stream;
}
unsigned int VoxelLodTerrain::get_block_size() const {
return _lods[0].map.get_block_size();
}
@ -191,7 +187,8 @@ void VoxelLodTerrain::set_stream(Ref<VoxelStream> p_stream) {
#ifdef TOOLS_ENABLED
if (_stream.is_valid()) {
if (Engine::get_singleton()->is_editor_hint()) {
if (_stream->has_script()) {
Ref<Script> script = _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.
_run_stream_in_editor = false;
@ -204,8 +201,36 @@ void VoxelLodTerrain::set_stream(Ref<VoxelStream> p_stream) {
_on_stream_params_changed();
}
Ref<VoxelMesher> VoxelLodTerrain::get_mesher() const {
return _mesher;
Ref<VoxelStream> VoxelLodTerrain::get_stream() const {
return _stream;
}
void VoxelLodTerrain::set_generator(Ref<VoxelGenerator> p_generator) {
if (p_generator == _generator) {
return;
}
_generator = p_generator;
#ifdef TOOLS_ENABLED
if (_generator.is_valid()) {
if (Engine::get_singleton()->is_editor_hint()) {
Ref<Script> script = _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.
_run_stream_in_editor = false;
_change_notify();
}
}
}
#endif
_on_stream_params_changed();
}
Ref<VoxelGenerator> VoxelLodTerrain::get_generator() const {
return _generator;
}
void VoxelLodTerrain::set_mesher(Ref<VoxelMesher> p_mesher) {
@ -225,16 +250,19 @@ void VoxelLodTerrain::set_mesher(Ref<VoxelMesher> p_mesher) {
update_configuration_warning();
}
Ref<VoxelMesher> VoxelLodTerrain::get_mesher() const {
return _mesher;
}
void VoxelLodTerrain::_on_stream_params_changed() {
stop_streamer();
stop_updater();
Ref<VoxelStreamFile> file_stream = _stream;
if (file_stream.is_valid()) {
const int stream_block_size_po2 = file_stream->get_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);
const int stream_lod_count = file_stream->get_lod_count();
const int stream_lod_count = _stream->get_lod_count();
_set_lod_count(min(stream_lod_count, get_lod_count()));
}
@ -242,7 +270,8 @@ void VoxelLodTerrain::_on_stream_params_changed() {
reset_maps();
if (_stream.is_valid() && (!Engine::get_singleton()->is_editor_hint() || _run_stream_in_editor)) {
if ((_stream.is_valid() || _generator.is_valid()) &&
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor)) {
start_streamer();
start_updater();
}
@ -349,10 +378,12 @@ void VoxelLodTerrain::stop_updater() {
void VoxelLodTerrain::start_streamer() {
VoxelServer::get_singleton()->set_volume_stream(_volume_id, _stream);
VoxelServer::get_singleton()->set_volume_generator(_volume_id, _generator);
}
void VoxelLodTerrain::stop_streamer() {
VoxelServer::get_singleton()->set_volume_stream(_volume_id, Ref<VoxelStream>());
VoxelServer::get_singleton()->set_volume_generator(_volume_id, Ref<VoxelGenerator>());
for (unsigned int i = 0; i < _lods.size(); ++i) {
Lod &lod = _lods[i];
@ -1045,8 +1076,11 @@ void VoxelLodTerrain::_process() {
_stats.time_detect_required_blocks = profiling_clock.restart();
const bool stream_enabled = (_stream.is_valid() || _generator.is_valid()) &&
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor);
// It's possible the user didn't set a stream yet, or it is turned off
if (_stream.is_valid() && (Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor)) {
if (stream_enabled) {
send_block_data_requests();
}

View File

@ -32,6 +32,9 @@ public:
Ref<VoxelStream> get_stream() const override;
void set_stream(Ref<VoxelStream> p_stream) override;
Ref<VoxelGenerator> get_generator() const override;
void set_generator(Ref<VoxelGenerator> p_stream) override;
Ref<VoxelMesher> get_mesher() const override;
void set_mesher(Ref<VoxelMesher> p_mesher) override;
@ -185,6 +188,7 @@ private:
//Rect3i _prev_bounds_in_voxels;
Ref<VoxelStream> _stream;
Ref<VoxelGenerator> _generator;
Ref<VoxelMesher> _mesher;
std::vector<BlockToSave> _blocks_to_save;

View File

@ -1,25 +1,35 @@
#include "voxel_node.h"
#include "../generators/voxel_generator.h"
#include "../meshers/voxel_mesher.h"
#include "../streams/voxel_stream.h"
void VoxelNode::set_mesher(Ref<VoxelMesher> mesher) {
// Not implemented
// Implemented in subclasses
}
Ref<VoxelMesher> VoxelNode::get_mesher() const {
// Not implemented
// Implemented in subclasses
return Ref<VoxelMesher>();
}
void VoxelNode::set_stream(Ref<VoxelStream> stream) {
// Not implemented
// Implemented in subclasses
}
Ref<VoxelStream> VoxelNode::get_stream() const {
// Not implemented
// Implemented in subclasses
return Ref<VoxelStream>();
}
void VoxelNode::set_generator(Ref<VoxelGenerator> generator) {
// Implemented in subclasses
}
Ref<VoxelGenerator> VoxelNode::get_generator() const {
// Implemented in subclasses
return Ref<VoxelGenerator>();
}
void VoxelNode::restart_stream() {
// Not implemented
}
@ -31,6 +41,7 @@ void VoxelNode::remesh_all_blocks() {
String VoxelNode::get_configuration_warning() const {
Ref<VoxelMesher> mesher = get_mesher();
Ref<VoxelStream> stream = get_stream();
Ref<VoxelGenerator> generator = get_generator();
if (mesher.is_null()) {
return TTR("This node has no mesher assigned, it wont produce any mesh visuals. "
@ -43,7 +54,7 @@ String VoxelNode::get_configuration_warning() const {
if (script.is_valid()) {
if (script->is_tool()) {
// TODO This is very annoying. Probably needs an issue or proposal in Godot so we can handle this properly?
return TTR("Be careful! Don't edit your custom stream while it's running, "
return TTR("Careful, don't edit your custom stream while it's running, "
"it can cause crashes. Turn off `run_stream_in_editor` before doing so.");
} else {
return TTR("The custom stream is not tool, the editor won't be able to use it.");
@ -54,23 +65,63 @@ String VoxelNode::get_configuration_warning() const {
const int mesher_channels = mesher->get_used_channels_mask();
if ((stream_channels & mesher_channels) == 0) {
return TTR("The current stream is providing voxel data only on channels that are not used by the current mesher. "
"This will result in nothing being visible.");
return TTR("The current stream is providing voxel data only on channels that are not used by "
"the current mesher. This will result in nothing being visible.");
}
}
if (generator.is_valid()) {
Ref<Script> script = generator->get_script();
if (script.is_valid()) {
if (script->is_tool()) {
// TODO This is very annoying. Probably needs an issue or proposal in Godot so we can handle this properly?
return TTR("Careful, don't edit your custom generator while it's running, "
"it can cause crashes. Turn off `run_stream_in_editor` before doing so.");
} else {
return TTR("The custom generator is not tool, the editor won't be able to use it.");
}
}
const int generator_channels = generator->get_used_channels_mask();
const int mesher_channels = mesher->get_used_channels_mask();
if ((generator_channels & mesher_channels) == 0) {
return TTR("The current generator is providing voxel data only on channels that are not used by "
"the current mesher. This will result in nothing being visible.");
}
}
return String();
}
int VoxelNode::get_used_channels_mask() const {
Ref<VoxelGenerator> generator = get_generator();
Ref<VoxelStream> stream = get_stream();
int used_channels_mask = 0;
if (generator.is_valid()) {
used_channels_mask |= stream->get_used_channels_mask();
}
if (stream.is_valid()) {
used_channels_mask |= stream->get_used_channels_mask();
}
return used_channels_mask;
}
void VoxelNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_stream", "stream"), &VoxelNode::_b_set_stream);
ClassDB::bind_method(D_METHOD("get_stream"), &VoxelNode::_b_get_stream);
ClassDB::bind_method(D_METHOD("set_generator", "generator"), &VoxelNode::_b_set_generator);
ClassDB::bind_method(D_METHOD("get_generator"), &VoxelNode::_b_get_generator);
ClassDB::bind_method(D_METHOD("set_mesher", "mesher"), &VoxelNode::_b_set_mesher);
ClassDB::bind_method(D_METHOD("get_mesher"), &VoxelNode::_b_get_mesher);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "VoxelStream"),
"set_stream", "get_stream");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "generator", PROPERTY_HINT_RESOURCE_TYPE, "VoxelGenerator"),
"set_generator", "get_generator");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesher", PROPERTY_HINT_RESOURCE_TYPE, "VoxelMesher"),
"set_mesher", "get_mesher");
}

View File

@ -5,6 +5,7 @@
class VoxelMesher;
class VoxelStream;
class VoxelGenerator;
// Base class for voxel volumes
class VoxelNode : public Spatial {
@ -16,11 +17,17 @@ public:
virtual void set_stream(Ref<VoxelStream> stream);
virtual Ref<VoxelStream> get_stream() const;
virtual void set_generator(Ref<VoxelGenerator> generator);
virtual Ref<VoxelGenerator> get_generator() const;
virtual void restart_stream();
virtual void remesh_all_blocks();
String get_configuration_warning() const override;
protected:
int get_used_channels_mask() const;
private:
Ref<VoxelMesher> _b_get_mesher() { return get_mesher(); }
void _b_set_mesher(Ref<VoxelMesher> mesher) { set_mesher(mesher); }
@ -28,6 +35,9 @@ private:
Ref<VoxelStream> _b_get_stream() { return get_stream(); }
void _b_set_stream(Ref<VoxelStream> stream) { set_stream(stream); }
Ref<VoxelGenerator> _b_get_generator() { return get_generator(); }
void _b_set_generator(Ref<VoxelGenerator> g) { set_generator(g); }
static void _bind_methods();
};

View File

@ -22,6 +22,7 @@ VoxelTerrain::VoxelTerrain() {
set_notify_transform(true);
// TODO Should it actually be finite for better discovery?
// Infinite by default
_bounds_in_voxels = Rect3i::from_center_extents(Vector3i(0), Vector3i(VoxelConstants::MAX_VOLUME_EXTENT));
@ -84,7 +85,8 @@ void VoxelTerrain::set_stream(Ref<VoxelStream> p_stream) {
#ifdef TOOLS_ENABLED
if (_stream.is_valid()) {
if (Engine::get_singleton()->is_editor_hint()) {
if (_stream->has_script()) {
Ref<Script> script = _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.
_run_stream_in_editor = false;
@ -101,6 +103,34 @@ Ref<VoxelStream> VoxelTerrain::get_stream() const {
return _stream;
}
void VoxelTerrain::set_generator(Ref<VoxelGenerator> p_generator) {
if (p_generator == _generator) {
return;
}
_generator = p_generator;
#ifdef TOOLS_ENABLED
if (_generator.is_valid()) {
if (Engine::get_singleton()->is_editor_hint()) {
Ref<Script> script = _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.
_run_stream_in_editor = false;
_change_notify();
}
}
}
#endif
_on_stream_params_changed();
}
Ref<VoxelGenerator> VoxelTerrain::get_generator() const {
return _generator;
}
void VoxelTerrain::set_block_size_po2(unsigned int p_block_size_po2) {
ERR_FAIL_COND(p_block_size_po2 < 1);
ERR_FAIL_COND(p_block_size_po2 > 32);
@ -134,9 +164,8 @@ void VoxelTerrain::_on_stream_params_changed() {
stop_streamer();
stop_updater();
Ref<VoxelStreamFile> file_stream = _stream;
if (file_stream.is_valid()) {
int stream_block_size_po2 = file_stream->get_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);
}
@ -145,7 +174,8 @@ void VoxelTerrain::_on_stream_params_changed() {
// The whole map might change, so regenerate it
reset_map();
if (_stream.is_valid() && (Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor)) {
if ((_stream.is_valid() || _generator.is_valid()) &&
(Engine::get_singleton()->is_editor_hint() == false || _run_stream_in_editor)) {
start_streamer();
start_updater();
}
@ -493,10 +523,12 @@ void VoxelTerrain::remesh_all_blocks() {
void VoxelTerrain::start_streamer() {
VoxelServer::get_singleton()->set_volume_stream(_volume_id, _stream);
VoxelServer::get_singleton()->set_volume_generator(_volume_id, _generator);
}
void VoxelTerrain::stop_streamer() {
VoxelServer::get_singleton()->set_volume_stream(_volume_id, Ref<VoxelStream>());
VoxelServer::get_singleton()->set_volume_generator(_volume_id, Ref<VoxelGenerator>());
_loading_blocks.clear();
_blocks_pending_load.clear();
_reception_buffers.data_output.clear();
@ -870,7 +902,7 @@ void VoxelTerrain::_process() {
});
}
const bool stream_enabled = _stream.is_valid() &&
const bool stream_enabled = (_stream.is_valid() || _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
@ -1089,14 +1121,16 @@ void VoxelTerrain::_process() {
{
VOXEL_PROFILE_SCOPE();
const int used_channels_mask = get_used_channels_mask();
for (size_t bi = 0; bi < _blocks_pending_update.size(); ++bi) {
const Vector3i block_pos = _blocks_pending_update[bi];
// Check if the block is worth meshing
// Smooth meshing works on more neighbors, so checking a single block isn't enough to ignore it,
// but that will slow down meshing a lot.
// TODO This is one reason to separate terrain systems between blocky and smooth (other reason is LOD)
if (!(_stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF))) {
// TODO Query mesher instead?
if (!(used_channels_mask & (1 << VoxelBuffer::CHANNEL_SDF))) {
VoxelBlock *block = _map.get_block(block_pos);
if (block == nullptr) {
continue;
@ -1249,11 +1283,11 @@ void VoxelTerrain::_process() {
Ref<VoxelTool> VoxelTerrain::get_voxel_tool() {
Ref<VoxelTool> vt = memnew(VoxelToolTerrain(this));
if (_stream.is_valid()) {
if (_stream->get_used_channels_mask() & (1 << VoxelBuffer::CHANNEL_SDF)) {
vt->set_channel(VoxelBuffer::CHANNEL_SDF);
} else {
vt->set_channel(VoxelBuffer::CHANNEL_TYPE);
const int used_channels_mask = get_used_channels_mask();
// Auto-pick first used channel
for (int channel = 0; channel < VoxelBuffer::MAX_CHANNELS; ++channel) {
if ((used_channels_mask & (1 << channel)) != 0) {
vt->set_channel(channel);
}
}
return vt;

View File

@ -23,6 +23,9 @@ public:
void set_stream(Ref<VoxelStream> p_stream) override;
Ref<VoxelStream> get_stream() const override;
void set_generator(Ref<VoxelGenerator> p_generator) override;
Ref<VoxelGenerator> get_generator() const override;
void set_mesher(Ref<VoxelMesher> mesher) override;
Ref<VoxelMesher> get_mesher() const override;
@ -171,6 +174,7 @@ private:
Ref<VoxelStream> _stream;
Ref<VoxelMesher> _mesher;
Ref<VoxelGenerator> _generator;
bool _generate_collisions = true;
bool _run_stream_in_editor = true;