diff --git a/register_types.cpp b/register_types.cpp index c729545a..96f30981 100644 --- a/register_types.cpp +++ b/register_types.cpp @@ -48,6 +48,7 @@ #endif #include +#include #ifdef TOOLS_ENABLED #include "editor/editor_plugin.h" @@ -68,6 +69,52 @@ #include "tests/tests.h" #endif +namespace zylann::voxel { + +static VoxelServer::ThreadsConfig get_config_from_godot(unsigned int &out_main_thread_time_budget_usec) { + CRASH_COND(ProjectSettings::get_singleton() == nullptr); + + VoxelServer::ThreadsConfig config; + + // Compute thread count for general pool. + // Note that the I/O thread counts as one used thread and will always be present. + + // "RST" means changing the property requires an editor restart (or game restart) + GLOBAL_DEF_RST("voxel/threads/count/minimum", 1); + ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/count/minimum", + PropertyInfo(Variant::INT, "voxel/threads/count/minimum", PROPERTY_HINT_RANGE, "1,64")); + + GLOBAL_DEF_RST("voxel/threads/count/margin_below_max", 1); + ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/count/margin_below_max", + PropertyInfo(Variant::INT, "voxel/threads/count/margin_below_max", PROPERTY_HINT_RANGE, "1,64")); + + GLOBAL_DEF_RST("voxel/threads/count/ratio_over_max", 0.5f); + ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/count/ratio_over_max", + PropertyInfo(Variant::FLOAT, "voxel/threads/count/ratio_over_max", PROPERTY_HINT_RANGE, "0,1,0.1")); + + GLOBAL_DEF_RST("voxel/threads/main/time_budget_ms", 8); + ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/main/time_budget_ms", + PropertyInfo(Variant::INT, "voxel/threads/main/time_budget_ms", PROPERTY_HINT_RANGE, "0,1000")); + + out_main_thread_time_budget_usec = + 1000 * int(ProjectSettings::get_singleton()->get("voxel/threads/main/time_budget_ms")); + + config.thread_count_minimum = + math::max(1, int(ProjectSettings::get_singleton()->get("voxel/threads/count/minimum"))); + + // How many threads below available count on the CPU should we set as limit + config.thread_count_margin_below_max = + math::max(1, int(ProjectSettings::get_singleton()->get("voxel/threads/count/margin_below_max"))); + + // Portion of available CPU threads to attempt using + config.thread_count_ratio_over_max = zylann::math::clamp( + float(ProjectSettings::get_singleton()->get("voxel/threads/count/ratio_over_max")), 0.f, 1.f); + + return config; +} + +} // namespace zylann::voxel + void initialize_voxel_module(ModuleInitializationLevel p_level) { using namespace zylann; using namespace voxel; @@ -76,9 +123,13 @@ void initialize_voxel_module(ModuleInitializationLevel p_level) { VoxelMemoryPool::create_singleton(); VoxelStringNames::create_singleton(); VoxelGraphNodeDB::create_singleton(); - VoxelServer::create_singleton(); - gd::VoxelServer::create_singleton(); + unsigned int main_thread_budget_usec; + const VoxelServer::ThreadsConfig threads_config = get_config_from_godot(main_thread_budget_usec); + VoxelServer::create_singleton(threads_config); + VoxelServer::get_singleton().set_main_thread_time_budget_usec(main_thread_budget_usec); + + gd::VoxelServer::create_singleton(); Engine::get_singleton()->add_singleton(Engine::Singleton("VoxelServer", gd::VoxelServer::get_singleton())); VoxelMetadataFactory::get_singleton().add_constructor_by_type( diff --git a/server/voxel_server.cpp b/server/voxel_server.cpp index d58486e5..4dca13d4 100644 --- a/server/voxel_server.cpp +++ b/server/voxel_server.cpp @@ -1,6 +1,5 @@ #include "voxel_server.h" #include "../constants/voxel_constants.h" -#include "../storage/voxel_memory_pool.h" #include "../util/log.h" #include "../util/macros.h" #include "../util/profiling.h" @@ -11,8 +10,6 @@ #include "mesh_block_task.h" #include "save_block_data_task.h" -#include - namespace zylann::voxel { VoxelServer *g_voxel_server = nullptr; @@ -22,65 +19,39 @@ VoxelServer &VoxelServer::get_singleton() { return *g_voxel_server; } -void VoxelServer::create_singleton() { +void VoxelServer::create_singleton(ThreadsConfig threads_config) { ZN_ASSERT_MSG(g_voxel_server == nullptr, "Creating singleton twice"); - g_voxel_server = memnew(VoxelServer); + g_voxel_server = ZN_NEW(VoxelServer(threads_config)); } void VoxelServer::destroy_singleton() { ZN_ASSERT_MSG(g_voxel_server != nullptr, "Destroying singleton twice"); - memdelete(g_voxel_server); + ZN_DELETE(g_voxel_server); g_voxel_server = nullptr; } -VoxelServer::VoxelServer() { - CRASH_COND(ProjectSettings::get_singleton() == nullptr); - +VoxelServer::VoxelServer(ThreadsConfig threads_config) { const int hw_threads_hint = Thread::get_hardware_concurrency(); ZN_PRINT_VERBOSE(format("Voxel: HW threads hint: {}", hw_threads_hint)); + ZN_ASSERT(threads_config.thread_count_margin_below_max >= 0); + ZN_ASSERT(threads_config.thread_count_minimum >= 1); + ZN_ASSERT(threads_config.thread_count_ratio_over_max >= 0.f); + // Compute thread count for general pool. // Note that the I/O thread counts as one used thread and will always be present. - // "RST" means changing the property requires an editor restart (or game restart) - GLOBAL_DEF_RST("voxel/threads/count/minimum", 1); - ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/count/minimum", - PropertyInfo(Variant::INT, "voxel/threads/count/minimum", PROPERTY_HINT_RANGE, "1,64")); - - GLOBAL_DEF_RST("voxel/threads/count/margin_below_max", 1); - ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/count/margin_below_max", - PropertyInfo(Variant::INT, "voxel/threads/count/margin_below_max", PROPERTY_HINT_RANGE, "1,64")); - - GLOBAL_DEF_RST("voxel/threads/count/ratio_over_max", 0.5f); - ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/count/ratio_over_max", - PropertyInfo(Variant::FLOAT, "voxel/threads/count/ratio_over_max", PROPERTY_HINT_RANGE, "0,1,0.1")); - - GLOBAL_DEF_RST("voxel/threads/main/time_budget_ms", 8); - ProjectSettings::get_singleton()->set_custom_property_info("voxel/threads/main/time_budget_ms", - PropertyInfo(Variant::INT, "voxel/threads/main/time_budget_ms", PROPERTY_HINT_RANGE, "0,1000")); - - _main_thread_time_budget_usec = - 1000 * int(ProjectSettings::get_singleton()->get("voxel/threads/main/time_budget_ms")); - - const int minimum_thread_count = - math::max(1, int(ProjectSettings::get_singleton()->get("voxel/threads/count/minimum"))); - - // How many threads below available count on the CPU should we set as limit - const int thread_count_margin = - math::max(1, int(ProjectSettings::get_singleton()->get("voxel/threads/count/margin_below_max"))); - - // Portion of available CPU threads to attempt using - const float threads_ratio = - math::clamp(float(ProjectSettings::get_singleton()->get("voxel/threads/count/ratio_over_max")), 0.f, 1.f); - - const int maximum_thread_count = math::max(hw_threads_hint - thread_count_margin, minimum_thread_count); + const int maximum_thread_count = math::max( + hw_threads_hint - threads_config.thread_count_margin_below_max, threads_config.thread_count_minimum); // `-1` is for the stream thread - const int thread_count_by_ratio = int(Math::round(float(threads_ratio) * hw_threads_hint)) - 1; - const int thread_count = math::clamp(thread_count_by_ratio, minimum_thread_count, maximum_thread_count); + const int thread_count_by_ratio = + int(Math::round(float(threads_config.thread_count_ratio_over_max) * hw_threads_hint)) - 1; + const int thread_count = + math::clamp(thread_count_by_ratio, threads_config.thread_count_minimum, maximum_thread_count); ZN_PRINT_VERBOSE(format("Voxel: automatic thread count set to {}", thread_count)); if (thread_count > hw_threads_hint) { - WARN_PRINT("Configured thread count exceeds hardware thread count. Performance may not be optimal"); + ZN_PRINT_WARNING("Configured thread count exceeds hardware thread count. Performance may not be optimal"); } // I/O can't be more than 1 thread. File access with more threads isn't worth it. @@ -244,6 +215,10 @@ int VoxelServer::get_main_thread_time_budget_usec() const { return _main_thread_time_budget_usec; } +void VoxelServer::set_main_thread_time_budget_usec(unsigned int usec) { + _main_thread_time_budget_usec = usec; +} + void VoxelServer::push_async_task(zylann::IThreadedTask *task) { _general_thread_pool.enqueue(task); } @@ -327,30 +302,6 @@ static VoxelServer::Stats::ThreadPoolStats debug_get_pool_stats(const zylann::Th return d; } -Dictionary VoxelServer::Stats::to_dict() { - Dictionary pools; - pools["streaming"] = streaming.to_dict(); - pools["general"] = general.to_dict(); - - Dictionary tasks; - tasks["streaming"] = streaming_tasks; - tasks["generation"] = generation_tasks; - tasks["meshing"] = meshing_tasks; - tasks["main_thread"] = main_thread_tasks; - - // This part is additional for scripts because VoxelMemoryPool is not exposed - Dictionary mem; - mem["voxel_total"] = ZN_SIZE_T_TO_VARIANT(VoxelMemoryPool::get_singleton().debug_get_total_memory()); - mem["voxel_used"] = ZN_SIZE_T_TO_VARIANT(VoxelMemoryPool::get_singleton().debug_get_used_memory()); - mem["block_count"] = VoxelMemoryPool::get_singleton().debug_get_used_blocks(); - - Dictionary d; - d["thread_pools"] = pools; - d["tasks"] = tasks; - d["memory_pools"] = mem; - return d; -} - VoxelServer::Stats VoxelServer::get_stats() const { Stats s; s.streaming = debug_get_pool_stats(_streaming_thread_pool); diff --git a/server/voxel_server.h b/server/voxel_server.h index 81d01e4f..5d46b106 100644 --- a/server/voxel_server.h +++ b/server/voxel_server.h @@ -78,12 +78,17 @@ public: int network_peer_id = -1; }; - static VoxelServer &get_singleton(); - static void create_singleton(); - static void destroy_singleton(); + struct ThreadsConfig { + int thread_count_minimum = 1; + // How many threads below available count on the CPU should we set as limit + int thread_count_margin_below_max = 1; + // Portion of available CPU threads to attempt using + float thread_count_ratio_over_max = 0.5; + }; - VoxelServer(); - ~VoxelServer(); + static VoxelServer &get_singleton(); + static void create_singleton(ThreadsConfig threads_config); + static void destroy_singleton(); uint32_t add_volume(VolumeCallbacks callbacks); VolumeCallbacks get_volume_callbacks(uint32_t volume_id) const; @@ -117,6 +122,7 @@ public: void push_main_thread_time_spread_task(ITimeSpreadTask *task); int get_main_thread_time_budget_usec() const; + void set_main_thread_time_budget_usec(unsigned int usec); void push_main_thread_progressive_task(IProgressiveTask *task); @@ -129,11 +135,6 @@ public: // Thread-safe. void push_async_io_tasks(Span tasks); - // Gets by how much voxels must be padded with neighbors in order to be polygonized properly - // void get_min_max_block_padding( - // bool blocky_enabled, bool smooth_enabled, - // unsigned int &out_min_padding, unsigned int &out_max_padding) const; - void process(); void wait_and_clear_all_tasks(bool warn); @@ -153,14 +154,6 @@ public: unsigned int thread_count; unsigned int active_threads; unsigned int tasks; - - Dictionary to_dict() { - Dictionary d; - d["tasks"] = tasks; - d["active_threads"] = active_threads; - d["thread_count"] = thread_count; - return d; - } }; ThreadPoolStats streaming; @@ -169,13 +162,16 @@ public: int streaming_tasks; int meshing_tasks; int main_thread_tasks; - - Dictionary to_dict(); }; Stats get_stats() const; + // TODO Should be private, but can't because `memdelete` would be unable to call it otherwise... + ~VoxelServer(); + private: + VoxelServer(ThreadsConfig threads_config); + // Since we are going to send data to tasks running in multiple threads, a few strategies are in place: // // - Copy the data for each task. This is suitable for simple information that doesn't change after scheduling. @@ -209,7 +205,7 @@ private: ThreadedTaskRunner _general_thread_pool; // For tasks that can only run on the main thread and be spread out over frames TimeSpreadTaskRunner _time_spread_task_runner; - int _main_thread_time_budget_usec = 8000; + unsigned int _main_thread_time_budget_usec = 8000; ProgressiveTaskRunner _progressive_task_runner; FileLocker _file_locker; diff --git a/server/voxel_server_gd.cpp b/server/voxel_server_gd.cpp index b011ddd6..413fa905 100644 --- a/server/voxel_server_gd.cpp +++ b/server/voxel_server_gd.cpp @@ -1,4 +1,5 @@ #include "voxel_server_gd.h" +#include "../storage/voxel_memory_pool.h" #include "../util/macros.h" #include "../util/profiling.h" #include "../util/tasks/godot/threaded_task_gd.h" @@ -31,8 +32,40 @@ VoxelServer::VoxelServer() { #endif } +Dictionary to_dict(const zylann::voxel::VoxelServer::Stats::ThreadPoolStats &stats) { + Dictionary d; + d["tasks"] = stats.tasks; + d["active_threads"] = stats.active_threads; + d["thread_count"] = stats.thread_count; + return d; +} + +Dictionary to_dict(const zylann::voxel::VoxelServer::Stats &stats) { + Dictionary pools; + pools["streaming"] = to_dict(stats.streaming); + pools["general"] = to_dict(stats.general); + + Dictionary tasks; + tasks["streaming"] = stats.streaming_tasks; + tasks["generation"] = stats.generation_tasks; + tasks["meshing"] = stats.meshing_tasks; + tasks["main_thread"] = stats.main_thread_tasks; + + // This part is additional for scripts because VoxelMemoryPool is not exposed + Dictionary mem; + mem["voxel_total"] = ZN_SIZE_T_TO_VARIANT(VoxelMemoryPool::get_singleton().debug_get_total_memory()); + mem["voxel_used"] = ZN_SIZE_T_TO_VARIANT(VoxelMemoryPool::get_singleton().debug_get_used_memory()); + mem["block_count"] = VoxelMemoryPool::get_singleton().debug_get_used_blocks(); + + Dictionary d; + d["thread_pools"] = pools; + d["tasks"] = tasks; + d["memory_pools"] = mem; + return d; +} + Dictionary VoxelServer::get_stats() const { - return zylann::voxel::VoxelServer::get_singleton().get_stats().to_dict(); + return to_dict(zylann::voxel::VoxelServer::get_singleton().get_stats()); } void VoxelServer::schedule_task(Ref task) {