#include "generate_block_task.h" #include "../storage/voxel_buffer_internal.h" #include "../storage/voxel_data_map.h" #include "../util/godot/funcs.h" #include "../util/log.h" #include "../util/profiling.h" #include "../util/string_funcs.h" #include "save_block_data_task.h" #include "voxel_server.h" namespace zylann::voxel { namespace { std::atomic_int g_debug_generate_tasks_count; } GenerateBlockTask::GenerateBlockTask() { ++g_debug_generate_tasks_count; } GenerateBlockTask::~GenerateBlockTask() { --g_debug_generate_tasks_count; } int GenerateBlockTask::debug_get_running_count() { return g_debug_generate_tasks_count; } void GenerateBlockTask::run(zylann::ThreadedTaskContext ctx) { ZN_PROFILE_SCOPE(); CRASH_COND(stream_dependency == nullptr); Ref generator = stream_dependency->generator; ERR_FAIL_COND(generator.is_null()); const Vector3i origin_in_voxels = (position << lod) * block_size; if (voxels == nullptr) { voxels = make_shared_instance(); voxels->create(block_size, block_size, block_size); } VoxelGenerator::VoxelQueryData query_data{ *voxels, origin_in_voxels, lod }; const VoxelGenerator::Result result = generator->generate_block(query_data); max_lod_hint = result.max_lod_hint; if (data != nullptr) { data->modifiers.apply( query_data.voxel_buffer, AABB(query_data.origin_in_voxels, query_data.voxel_buffer.get_size() << lod)); } if (stream_dependency->valid) { Ref stream = stream_dependency->stream; // TODO In some cases we dont want this to run all the time, do we? // Like in full load mode, where non-edited blocks remain generated on the fly... if (stream.is_valid() && stream->get_save_generator_output()) { ZN_PRINT_VERBOSE(format("Requesting save of generator output for block {} lod {}", position, lod)); // TODO Optimization: `voxels` doesnt actually need to be shared std::shared_ptr voxels_copy = make_shared_instance(); voxels->duplicate_to(*voxels_copy, true); // No instances, generators are not designed to produce them at this stage yet. // No priority data, saving doesnt need sorting SaveBlockDataTask *save_task = memnew(SaveBlockDataTask(volume_id, position, lod, block_size, voxels_copy, stream_dependency)); VoxelServer::get_singleton().push_async_task(save_task); } } has_run = true; } int GenerateBlockTask::get_priority() { float closest_viewer_distance_sq; const int p = priority_dependency.evaluate(lod, &closest_viewer_distance_sq); too_far = drop_beyond_max_distance && closest_viewer_distance_sq > priority_dependency.drop_distance_squared; return p; } bool GenerateBlockTask::is_cancelled() { return !stream_dependency->valid || too_far; // || stream_dependency->stream->get_fallback_generator().is_null(); } void GenerateBlockTask::apply_result() { bool aborted = true; if (VoxelServer::get_singleton().is_volume_valid(volume_id)) { // 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 (stream_dependency->valid) { Ref stream = stream_dependency->stream; VoxelServer::BlockDataOutput o; o.voxels = voxels; o.position = position; o.lod = lod; o.dropped = !has_run; if (stream.is_valid() && stream->get_save_generator_output()) { // We can't consider the block as "generated" since there is no state to tell that once saved, // so it has to be considered an edited block o.type = VoxelServer::BlockDataOutput::TYPE_LOADED; } else { o.type = VoxelServer::BlockDataOutput::TYPE_GENERATED; } o.max_lod_hint = max_lod_hint; o.initial_load = false; VoxelServer::VolumeCallbacks callbacks = VoxelServer::get_singleton().get_volume_callbacks(volume_id); ERR_FAIL_COND(callbacks.data_output_callback == nullptr); callbacks.data_output_callback(callbacks.data, o); aborted = !has_run; } } else { // This can happen if the user removes the volume while requests are still about to return ZN_PRINT_VERBOSE("Gemerated data request response came back but volume wasn't found"); } // TODO We could complete earlier inside run() if we had access to the data structure to write the block into. // This would reduce latency a little. The rest of things the terrain needs to do with the generated block could // run later. if (tracker != nullptr) { if (aborted) { tracker->abort(); } else { tracker->post_complete(); } } } } // namespace zylann::voxel