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:
parent
fb1442e444
commit
4ec60074bb
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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],
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user