diff options
Diffstat (limited to 'core')
-rw-r--r-- | core/config/project_settings.cpp | 3 | ||||
-rw-r--r-- | core/core_bind.cpp | 15 | ||||
-rw-r--r-- | core/core_bind.h | 11 | ||||
-rw-r--r-- | core/io/image.cpp | 24 | ||||
-rw-r--r-- | core/io/image.h | 1 | ||||
-rw-r--r-- | core/io/resource_loader.cpp | 2 | ||||
-rw-r--r-- | core/math/a_star.cpp | 16 | ||||
-rw-r--r-- | core/math/a_star.h | 4 | ||||
-rw-r--r-- | core/math/a_star_grid_2d.cpp | 8 | ||||
-rw-r--r-- | core/math/a_star_grid_2d.h | 2 | ||||
-rw-r--r-- | core/object/object.cpp | 11 | ||||
-rw-r--r-- | core/object/script_language.cpp | 2 | ||||
-rw-r--r-- | core/object/script_language.h | 2 | ||||
-rw-r--r-- | core/object/script_language_extension.h | 2 | ||||
-rw-r--r-- | core/object/worker_thread_pool.cpp | 178 | ||||
-rw-r--r-- | core/object/worker_thread_pool.h | 28 | ||||
-rw-r--r-- | core/register_core_types.cpp | 8 | ||||
-rw-r--r-- | core/variant/dictionary.cpp | 59 | ||||
-rw-r--r-- | core/variant/dictionary.h | 1 | ||||
-rw-r--r-- | core/variant/variant_setget.cpp | 44 |
20 files changed, 293 insertions, 128 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 32f36e01f9..7951ee9edd 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1016,7 +1016,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust } } // Check for the existence of a csproj file. - if (_csproj_exists(p_path.get_base_dir())) { + if (_csproj_exists(get_resource_path())) { // If there is a csproj file, add the C# feature if it doesn't already exist. if (!project_features.has("C#")) { project_features.append("C#"); @@ -1574,6 +1574,7 @@ ProjectSettings::ProjectSettings() { ProjectSettings::ProjectSettings(const String &p_path) { if (load_custom(p_path) == OK) { + resource_path = p_path.get_base_dir(); project_loaded = true; } } diff --git a/core/core_bind.cpp b/core/core_bind.cpp index b27981d56b..bbfbb6e3cd 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1419,6 +1419,11 @@ Variant ClassDB::instantiate(const StringName &p_class) const { } } +ClassDB::APIType ClassDB::class_get_api_type(const StringName &p_class) const { + ::ClassDB::APIType api_type = ::ClassDB::get_api_type(p_class); + return (APIType)api_type; +} + bool ClassDB::class_has_signal(const StringName &p_class, const StringName &p_signal) const { return ::ClassDB::has_signal(p_class, p_signal); } @@ -1615,7 +1620,7 @@ void ClassDB::get_argument_options(const StringName &p_function, int p_idx, List pf == "class_has_method" || pf == "class_get_method_list" || pf == "class_get_integer_constant_list" || pf == "class_has_integer_constant" || pf == "class_get_integer_constant" || pf == "class_has_enum" || pf == "class_get_enum_list" || pf == "class_get_enum_constants" || pf == "class_get_integer_constant_enum" || - pf == "is_class_enabled" || pf == "is_class_enum_bitfield"); + pf == "is_class_enabled" || pf == "is_class_enum_bitfield" || pf == "class_get_api_type"); } if (first_argument_is_class || pf == "is_parent_class") { for (const String &E : get_class_list()) { @@ -1636,6 +1641,8 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("can_instantiate", "class"), &ClassDB::can_instantiate); ::ClassDB::bind_method(D_METHOD("instantiate", "class"), &ClassDB::instantiate); + ::ClassDB::bind_method(D_METHOD("class_get_api_type", "class"), &ClassDB::class_get_api_type); + ::ClassDB::bind_method(D_METHOD("class_has_signal", "class", "signal"), &ClassDB::class_has_signal); ::ClassDB::bind_method(D_METHOD("class_get_signal", "class", "signal"), &ClassDB::class_get_signal); ::ClassDB::bind_method(D_METHOD("class_get_signal_list", "class", "no_inheritance"), &ClassDB::class_get_signal_list, DEFVAL(false)); @@ -1669,6 +1676,12 @@ void ClassDB::_bind_methods() { ::ClassDB::bind_method(D_METHOD("is_class_enum_bitfield", "class", "enum", "no_inheritance"), &ClassDB::is_class_enum_bitfield, DEFVAL(false)); ::ClassDB::bind_method(D_METHOD("is_class_enabled", "class"), &ClassDB::is_class_enabled); + + BIND_ENUM_CONSTANT(API_CORE); + BIND_ENUM_CONSTANT(API_EDITOR); + BIND_ENUM_CONSTANT(API_EXTENSION); + BIND_ENUM_CONSTANT(API_EDITOR_EXTENSION); + BIND_ENUM_CONSTANT(API_NONE); } } // namespace special diff --git a/core/core_bind.h b/core/core_bind.h index 7e2686c848..2a31e64425 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -447,6 +447,14 @@ protected: static void _bind_methods(); public: + enum APIType { + API_CORE, + API_EDITOR, + API_EXTENSION, + API_EDITOR_EXTENSION, + API_NONE, + }; + PackedStringArray get_class_list() const; PackedStringArray get_inheriters_from_class(const StringName &p_class) const; StringName get_parent_class(const StringName &p_class) const; @@ -455,6 +463,7 @@ public: bool can_instantiate(const StringName &p_class) const; Variant instantiate(const StringName &p_class) const; + APIType class_get_api_type(const StringName &p_class) const; bool class_has_signal(const StringName &p_class, const StringName &p_signal) const; Dictionary class_get_signal(const StringName &p_class, const StringName &p_signal) const; TypedArray<Dictionary> class_get_signal_list(const StringName &p_class, bool p_no_inheritance = false) const; @@ -634,4 +643,6 @@ VARIANT_ENUM_CAST(core_bind::Geometry2D::PolyEndType); VARIANT_ENUM_CAST(core_bind::Thread::Priority); +VARIANT_ENUM_CAST(core_bind::special::ClassDB::APIType); + #endif // CORE_BIND_H diff --git a/core/io/image.cpp b/core/io/image.cpp index fcbe483e38..aa391b77dd 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -866,12 +866,10 @@ static void _scale_cubic(const uint8_t *__restrict p_src, uint8_t *__restrict p_ template <int CC, typename T> static void _scale_bilinear(const uint8_t *__restrict p_src, uint8_t *__restrict p_dst, uint32_t p_src_width, uint32_t p_src_height, uint32_t p_dst_width, uint32_t p_dst_height) { - enum { - FRAC_BITS = 8, - FRAC_LEN = (1 << FRAC_BITS), - FRAC_HALF = (FRAC_LEN >> 1), - FRAC_MASK = FRAC_LEN - 1 - }; + constexpr uint32_t FRAC_BITS = 8; + constexpr uint32_t FRAC_LEN = (1 << FRAC_BITS); + constexpr uint32_t FRAC_HALF = (FRAC_LEN >> 1); + constexpr uint32_t FRAC_MASK = FRAC_LEN - 1; for (uint32_t i = 0; i < p_dst_height; i++) { // Add 0.5 in order to interpolate based on pixel center @@ -2751,6 +2749,19 @@ Error Image::compress_from_channels(CompressMode p_mode, UsedChannels p_channels } break; + case COMPRESS_S3TC: { + // BC3 is unsupported currently. + if ((p_channels == USED_CHANNELS_RGB || p_channels == USED_CHANNELS_L) && _image_compress_bc_rd_func) { + Error result = _image_compress_bc_rd_func(this, p_channels); + + // If the image was compressed successfully, we return here. If not, we fall back to the default compression scheme. + if (result == OK) { + return OK; + } + } + + } break; + default: { } } @@ -3138,6 +3149,7 @@ void (*Image::_image_compress_etc1_func)(Image *) = nullptr; void (*Image::_image_compress_etc2_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_compress_astc_func)(Image *, Image::ASTCFormat) = nullptr; Error (*Image::_image_compress_bptc_rd_func)(Image *, Image::UsedChannels) = nullptr; +Error (*Image::_image_compress_bc_rd_func)(Image *, Image::UsedChannels) = nullptr; void (*Image::_image_decompress_bc)(Image *) = nullptr; void (*Image::_image_decompress_bptc)(Image *) = nullptr; void (*Image::_image_decompress_etc1)(Image *) = nullptr; diff --git a/core/io/image.h b/core/io/image.h index 4461ae71a6..78757246e0 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -160,6 +160,7 @@ public: static void (*_image_compress_astc_func)(Image *, ASTCFormat p_format); static Error (*_image_compress_bptc_rd_func)(Image *, UsedChannels p_channels); + static Error (*_image_compress_bc_rd_func)(Image *, UsedChannels p_channels); static void (*_image_decompress_bc)(Image *); static void (*_image_decompress_bptc)(Image *); diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index f29f9eef98..94f6caf6eb 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -860,7 +860,7 @@ Ref<Resource> ResourceLoader::_load_complete_inner(LoadToken &p_load_token, Erro } } core_bind::Semaphore done; - MessageQueue::get_main_singleton()->push_callable(callable_mp(&done, &core_bind::Semaphore::post)); + MessageQueue::get_main_singleton()->push_callable(callable_mp(&done, &core_bind::Semaphore::post).bind(1)); done.wait(); } } diff --git a/core/math/a_star.cpp b/core/math/a_star.cpp index c53fd3d330..c85201a973 100644 --- a/core/math/a_star.cpp +++ b/core/math/a_star.cpp @@ -319,11 +319,11 @@ Vector3 AStar3D::get_closest_position_in_segment(const Vector3 &p_point) const { return closest_point; } -bool AStar3D::_solve(Point *begin_point, Point *end_point) { +bool AStar3D::_solve(Point *begin_point, Point *end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } @@ -443,7 +443,7 @@ Vector<Vector3> AStar3D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<Vector3>(); @@ -497,7 +497,7 @@ Vector<int64_t> AStar3D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<int64_t>(); @@ -726,7 +726,7 @@ Vector<Vector2> AStar2D::get_point_path(int64_t p_from_id, int64_t p_to_id, bool AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector<Vector2>(); @@ -780,7 +780,7 @@ Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ AStar3D::Point *begin_point = a; AStar3D::Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || astar.last_closest_point == nullptr) { return Vector<int64_t>(); @@ -816,11 +816,11 @@ Vector<int64_t> AStar2D::get_id_path(int64_t p_from_id, int64_t p_to_id, bool p_ return path; } -bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point) { +bool AStar2D::_solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path) { astar.last_closest_point = nullptr; astar.pass++; - if (!end_point->enabled) { + if (!end_point->enabled && !p_allow_partial_path) { return false; } diff --git a/core/math/a_star.h b/core/math/a_star.h index 143a3bec61..cbaafc1018 100644 --- a/core/math/a_star.h +++ b/core/math/a_star.h @@ -115,7 +115,7 @@ class AStar3D : public RefCounted { HashSet<Segment, Segment> segments; Point *last_closest_point = nullptr; - bool _solve(Point *begin_point, Point *end_point); + bool _solve(Point *begin_point, Point *end_point, bool p_allow_partial_path); protected: static void _bind_methods(); @@ -171,7 +171,7 @@ class AStar2D : public RefCounted { GDCLASS(AStar2D, RefCounted); AStar3D astar; - bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point); + bool _solve(AStar3D::Point *begin_point, AStar3D::Point *end_point, bool p_allow_partial_path); protected: static void _bind_methods(); diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp index c40ee5b4d7..7e0e982c62 100644 --- a/core/math/a_star_grid_2d.cpp +++ b/core/math/a_star_grid_2d.cpp @@ -491,11 +491,11 @@ void AStarGrid2D::_get_nbors(Point *p_point, LocalVector<Point *> &r_nbors) { } } -bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { +bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path) { last_closest_point = nullptr; pass++; - if (_get_solid_unchecked(p_end_point->id)) { + if (_get_solid_unchecked(p_end_point->id) && !p_allow_partial_path) { return false; } @@ -647,7 +647,7 @@ Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vec Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return Vector<Vector2>(); @@ -700,7 +700,7 @@ TypedArray<Vector2i> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const V Point *begin_point = a; Point *end_point = b; - bool found_route = _solve(begin_point, end_point); + bool found_route = _solve(begin_point, end_point, p_allow_partial_path); if (!found_route) { if (!p_allow_partial_path || last_closest_point == nullptr) { return TypedArray<Vector2i>(); diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h index f5ac472f09..0536b8109b 100644 --- a/core/math/a_star_grid_2d.h +++ b/core/math/a_star_grid_2d.h @@ -159,8 +159,8 @@ private: // Internal routines. void _get_nbors(Point *p_point, LocalVector<Point *> &r_nbors); Point *_jump(Point *p_from, Point *p_to); + bool _solve(Point *p_begin_point, Point *p_end_point, bool p_allow_partial_path); Point *_forced_successor(int32_t p_x, int32_t p_y, int32_t p_dx, int32_t p_dy, bool p_inclusive = false); - bool _solve(Point *p_begin_point, Point *p_end_point); protected: static void _bind_methods(); diff --git a/core/object/object.cpp b/core/object/object.cpp index 000d5328b4..da3ca6bc61 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -44,14 +44,17 @@ #ifdef DEBUG_ENABLED struct _ObjectDebugLock { - Object *obj; + ObjectID obj_id; _ObjectDebugLock(Object *p_obj) { - obj = p_obj; - obj->_lock_index.ref(); + obj_id = p_obj->get_instance_id(); + p_obj->_lock_index.ref(); } ~_ObjectDebugLock() { - obj->_lock_index.unref(); + Object *obj_ptr = ObjectDB::get_instance(obj_id); + if (likely(obj_ptr)) { + obj_ptr->_lock_index.unref(); + } } }; diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index d5b7bc768d..d2fc7392c8 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -174,6 +174,8 @@ void Script::_bind_methods() { ClassDB::bind_method(D_METHOD("is_tool"), &Script::is_tool); ClassDB::bind_method(D_METHOD("is_abstract"), &Script::is_abstract); + ClassDB::bind_method(D_METHOD("get_rpc_config"), &Script::get_rpc_config); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_source_code", "get_source_code"); } diff --git a/core/object/script_language.h b/core/object/script_language.h index d9e2ab1d3c..d0023d70e8 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -182,7 +182,7 @@ public: virtual bool is_placeholder_fallback_enabled() const { return false; } - virtual const Variant get_rpc_config() const = 0; + virtual Variant get_rpc_config() const = 0; Script() {} }; diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index c9344f5799..bc773c5ad3 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -205,7 +205,7 @@ public: GDVIRTUAL0RC(Variant, _get_rpc_config) - virtual const Variant get_rpc_config() const override { + virtual Variant get_rpc_config() const override { Variant ret; GDVIRTUAL_REQUIRED_CALL(_get_rpc_config, ret); return ret; diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index cf396c2676..08903d6196 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -180,13 +180,17 @@ void WorkerThreadPool::_process_task(Task *p_task) { void WorkerThreadPool::_thread_function(void *p_user) { ThreadData *thread_data = (ThreadData *)p_user; + while (true) { Task *task_to_process = nullptr; { MutexLock lock(singleton->task_mutex); - if (singleton->exit_threads) { - return; + + bool exit = singleton->_handle_runlevel(thread_data, lock); + if (unlikely(exit)) { + break; } + thread_data->signaled = false; if (singleton->task_queue.first()) { @@ -194,7 +198,6 @@ void WorkerThreadPool::_thread_function(void *p_user) { singleton->task_queue.remove(singleton->task_queue.first()); } else { thread_data->cond_var.wait(lock); - DEV_ASSERT(singleton->exit_threads || thread_data->signaled); } } @@ -204,19 +207,24 @@ void WorkerThreadPool::_thread_function(void *p_user) { } } -void WorkerThreadPool::_post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, bool p_high_priority) { +void WorkerThreadPool::_post_tasks(Task **p_tasks, uint32_t p_count, bool p_high_priority, MutexLock<BinaryMutex> &p_lock) { // Fall back to processing on the calling thread if there are no worker threads. // Separated into its own variable to make it easier to extend this logic // in custom builds. bool process_on_calling_thread = threads.size() == 0; if (process_on_calling_thread) { - task_mutex.unlock(); + p_lock.temp_unlock(); for (uint32_t i = 0; i < p_count; i++) { _process_task(p_tasks[i]); } + p_lock.temp_relock(); return; } + while (runlevel == RUNLEVEL_EXIT_LANGUAGES) { + control_cond_var.wait(p_lock); + } + uint32_t to_process = 0; uint32_t to_promote = 0; @@ -238,8 +246,6 @@ void WorkerThreadPool::_post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, } _notify_threads(caller_pool_thread, to_process, to_promote); - - task_mutex.unlock(); } void WorkerThreadPool::_notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count) { @@ -323,9 +329,8 @@ WorkerThreadPool::TaskID WorkerThreadPool::add_native_task(void (*p_func)(void * } WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable, void (*p_func)(void *), void *p_userdata, BaseTemplateUserdata *p_template_userdata, bool p_high_priority, const String &p_description) { - ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a task because the WorkerThreadPool is either not initialized yet or already terminated."); + MutexLock<BinaryMutex> lock(task_mutex); - task_mutex.lock(); // Get a free task Task *task = task_allocator.alloc(); TaskID id = last_task++; @@ -337,7 +342,7 @@ WorkerThreadPool::TaskID WorkerThreadPool::_add_task(const Callable &p_callable, task->template_userdata = p_template_userdata; tasks.insert(id, task); - _post_tasks_and_unlock(&task, 1, p_high_priority); + _post_tasks(&task, 1, p_high_priority, lock); return id; } @@ -444,22 +449,34 @@ void WorkerThreadPool::_unlock_unlockable_mutexes() { void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task) { // Keep processing tasks until the condition to stop waiting is met. -#define IS_WAIT_OVER (unlikely(p_task == ThreadData::YIELDING) ? p_caller_pool_thread->yield_is_over : p_task->completed) - while (true) { Task *task_to_process = nullptr; bool relock_unlockables = false; { MutexLock lock(task_mutex); + bool was_signaled = p_caller_pool_thread->signaled; p_caller_pool_thread->signaled = false; - if (IS_WAIT_OVER) { - if (unlikely(p_task == ThreadData::YIELDING)) { + bool exit = _handle_runlevel(p_caller_pool_thread, lock); + if (unlikely(exit)) { + break; + } + + bool wait_is_over = false; + if (unlikely(p_task == ThreadData::YIELDING)) { + if (p_caller_pool_thread->yield_is_over) { p_caller_pool_thread->yield_is_over = false; + wait_is_over = true; } + } else { + if (p_task->completed) { + wait_is_over = true; + } + } - if (!exit_threads && was_signaled) { + if (wait_is_over) { + if (was_signaled) { // This thread was awaken for some additional reason, but it's about to exit. // Let's find out what may be pending and forward the requests. uint32_t to_process = task_queue.first() ? 1 : 0; @@ -474,28 +491,26 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T break; } - if (!exit_threads) { - if (p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) { - if (_try_promote_low_priority_task()) { - _notify_threads(p_caller_pool_thread, 1, 0); - } + if (p_caller_pool_thread->current_task->low_priority && low_priority_task_queue.first()) { + if (_try_promote_low_priority_task()) { + _notify_threads(p_caller_pool_thread, 1, 0); } + } - if (singleton->task_queue.first()) { - task_to_process = task_queue.first()->self(); - task_queue.remove(task_queue.first()); - } + if (singleton->task_queue.first()) { + task_to_process = task_queue.first()->self(); + task_queue.remove(task_queue.first()); + } - if (!task_to_process) { - p_caller_pool_thread->awaited_task = p_task; + if (!task_to_process) { + p_caller_pool_thread->awaited_task = p_task; - _unlock_unlockable_mutexes(); - relock_unlockables = true; - p_caller_pool_thread->cond_var.wait(lock); + _unlock_unlockable_mutexes(); + relock_unlockables = true; - DEV_ASSERT(exit_threads || p_caller_pool_thread->signaled || IS_WAIT_OVER); - p_caller_pool_thread->awaited_task = nullptr; - } + p_caller_pool_thread->cond_var.wait(lock); + + p_caller_pool_thread->awaited_task = nullptr; } } @@ -509,16 +524,65 @@ void WorkerThreadPool::_wait_collaboratively(ThreadData *p_caller_pool_thread, T } } +void WorkerThreadPool::_switch_runlevel(Runlevel p_runlevel) { + DEV_ASSERT(p_runlevel > runlevel); + runlevel = p_runlevel; + memset(&runlevel_data, 0, sizeof(runlevel_data)); + for (uint32_t i = 0; i < threads.size(); i++) { + threads[i].cond_var.notify_one(); + threads[i].signaled = true; + } + control_cond_var.notify_all(); +} + +// Returns whether threads have to exit. This may perform the check about handling needed. +bool WorkerThreadPool::_handle_runlevel(ThreadData *p_thread_data, MutexLock<BinaryMutex> &p_lock) { + bool exit = false; + switch (runlevel) { + case RUNLEVEL_NORMAL: { + } break; + case RUNLEVEL_PRE_EXIT_LANGUAGES: { + if (!p_thread_data->pre_exited_languages) { + if (!task_queue.first() && !low_priority_task_queue.first()) { + p_thread_data->pre_exited_languages = true; + runlevel_data.pre_exit_languages.num_idle_threads++; + control_cond_var.notify_all(); + } + } + } break; + case RUNLEVEL_EXIT_LANGUAGES: { + if (!p_thread_data->exited_languages) { + p_lock.temp_unlock(); + ScriptServer::thread_exit(); + p_lock.temp_relock(); + p_thread_data->exited_languages = true; + runlevel_data.exit_languages.num_exited_threads++; + control_cond_var.notify_all(); + } + } break; + case RUNLEVEL_EXIT: { + exit = true; + } break; + } + return exit; +} + void WorkerThreadPool::yield() { int th_index = get_thread_index(); ERR_FAIL_COND_MSG(th_index == -1, "This function can only be called from a worker thread."); _wait_collaboratively(&threads[th_index], ThreadData::YIELDING); - // If this long-lived task started before the scripting server was initialized, - // now is a good time to have scripting languages ready for the current thread. - // Otherwise, such a piece of setup won't happen unless another task has been - // run during the collaborative wait. - ScriptServer::thread_enter(); + task_mutex.lock(); + if (runlevel < RUNLEVEL_EXIT_LANGUAGES) { + // If this long-lived task started before the scripting server was initialized, + // now is a good time to have scripting languages ready for the current thread. + // Otherwise, such a piece of setup won't happen unless another task has been + // run during the collaborative wait. + task_mutex.unlock(); + ScriptServer::thread_enter(); + } else { + task_mutex.unlock(); + } } void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { @@ -543,13 +607,13 @@ void WorkerThreadPool::notify_yield_over(TaskID p_task_id) { } WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_callable, void (*p_func)(void *, uint32_t), void *p_userdata, BaseTemplateUserdata *p_template_userdata, int p_elements, int p_tasks, bool p_high_priority, const String &p_description) { - ERR_FAIL_COND_V_MSG(threads.is_empty(), INVALID_TASK_ID, "Can't add a group task because the WorkerThreadPool is either not initialized yet or already terminated."); ERR_FAIL_COND_V(p_elements < 0, INVALID_TASK_ID); if (p_tasks < 0) { p_tasks = MAX(1u, threads.size()); } - task_mutex.lock(); + MutexLock<BinaryMutex> lock(task_mutex); + Group *group = group_allocator.alloc(); GroupID id = last_task++; group->max = p_elements; @@ -584,7 +648,7 @@ WorkerThreadPool::GroupID WorkerThreadPool::_add_group_task(const Callable &p_ca groups[id] = group; - _post_tasks_and_unlock(tasks_posted, p_tasks, p_high_priority); + _post_tasks(tasks_posted, p_tasks, p_high_priority, lock); return id; } @@ -687,6 +751,9 @@ void WorkerThreadPool::thread_exit_unlock_allowance_zone(uint32_t p_zone_id) { void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) { ERR_FAIL_COND(threads.size() > 0); + + runlevel = RUNLEVEL_NORMAL; + if (p_thread_count < 0) { p_thread_count = OS::get_singleton()->get_default_thread_pool_size(); } @@ -704,6 +771,26 @@ void WorkerThreadPool::init(int p_thread_count, float p_low_priority_task_ratio) } } +void WorkerThreadPool::exit_languages_threads() { + if (threads.size() == 0) { + return; + } + + MutexLock lock(task_mutex); + + // Wait until all threads are idle. + _switch_runlevel(RUNLEVEL_PRE_EXIT_LANGUAGES); + while (runlevel_data.pre_exit_languages.num_idle_threads != threads.size()) { + control_cond_var.wait(lock); + } + + // Wait until all threads have detached from scripting languages. + _switch_runlevel(RUNLEVEL_EXIT_LANGUAGES); + while (runlevel_data.exit_languages.num_exited_threads != threads.size()) { + control_cond_var.wait(lock); + } +} + void WorkerThreadPool::finish() { if (threads.size() == 0) { return; @@ -716,15 +803,10 @@ void WorkerThreadPool::finish() { print_error("Task waiting was never re-claimed: " + E->self()->description); E = E->next(); } - } - { - MutexLock lock(task_mutex); - exit_threads = true; - } - for (ThreadData &data : threads) { - data.cond_var.notify_one(); + _switch_runlevel(RUNLEVEL_EXIT); } + for (ThreadData &data : threads) { data.thread.wait_to_finish(); } @@ -755,5 +837,5 @@ WorkerThreadPool::WorkerThreadPool() { } WorkerThreadPool::~WorkerThreadPool() { - DEV_ASSERT(threads.size() == 0 && "finish() hasn't been called!"); + finish(); } diff --git a/core/object/worker_thread_pool.h b/core/object/worker_thread_pool.h index 6374dbe8c7..62296ac040 100644 --- a/core/object/worker_thread_pool.h +++ b/core/object/worker_thread_pool.h @@ -114,17 +114,35 @@ private: Thread thread; bool signaled : 1; bool yield_is_over : 1; + bool pre_exited_languages : 1; + bool exited_languages : 1; Task *current_task = nullptr; Task *awaited_task = nullptr; // Null if not awaiting the condition variable, or special value (YIELDING). ConditionVariable cond_var; ThreadData() : signaled(false), - yield_is_over(false) {} + yield_is_over(false), + pre_exited_languages(false), + exited_languages(false) {} }; TightLocalVector<ThreadData> threads; - bool exit_threads = false; + enum Runlevel { + RUNLEVEL_NORMAL, + RUNLEVEL_PRE_EXIT_LANGUAGES, // Block adding new tasks + RUNLEVEL_EXIT_LANGUAGES, // All threads detach from scripting threads. + RUNLEVEL_EXIT, + } runlevel = RUNLEVEL_NORMAL; + union { // Cleared on every runlevel change. + struct { + uint32_t num_idle_threads; + } pre_exit_languages; + struct { + uint32_t num_exited_threads; + } exit_languages; + } runlevel_data; + ConditionVariable control_cond_var; HashMap<Thread::ID, int> thread_ids; HashMap< @@ -152,7 +170,7 @@ private: void _process_task(Task *task); - void _post_tasks_and_unlock(Task **p_tasks, uint32_t p_count, bool p_high_priority); + void _post_tasks(Task **p_tasks, uint32_t p_count, bool p_high_priority, MutexLock<BinaryMutex> &p_lock); void _notify_threads(const ThreadData *p_current_thread_data, uint32_t p_process_count, uint32_t p_promote_count); bool _try_promote_low_priority_task(); @@ -193,6 +211,9 @@ private: void _wait_collaboratively(ThreadData *p_caller_pool_thread, Task *p_task); + void _switch_runlevel(Runlevel p_runlevel); + bool _handle_runlevel(ThreadData *p_thread_data, MutexLock<BinaryMutex> &p_lock); + #ifdef THREADS_ENABLED static uint32_t _thread_enter_unlock_allowance_zone(THREADING_NAMESPACE::unique_lock<THREADING_NAMESPACE::mutex> &p_ulock); #endif @@ -256,6 +277,7 @@ public: #endif void init(int p_thread_count = -1, float p_low_priority_task_ratio = 0.3); + void exit_languages_threads(); void finish(); WorkerThreadPool(); ~WorkerThreadPool(); diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 220ed9da31..c866ff0415 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -107,6 +107,8 @@ static Time *_time = nullptr; static core_bind::Geometry2D *_geometry_2d = nullptr; static core_bind::Geometry3D *_geometry_3d = nullptr; +static WorkerThreadPool *worker_thread_pool = nullptr; + extern Mutex _global_mutex; static GDExtensionManager *gdextension_manager = nullptr; @@ -295,6 +297,8 @@ void register_core_types() { GDREGISTER_NATIVE_STRUCT(AudioFrame, "float left;float right"); GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time"); + worker_thread_pool = memnew(WorkerThreadPool); + OS::get_singleton()->benchmark_end_measure("Core", "Register Types"); } @@ -345,7 +349,7 @@ void register_core_singletons() { Engine::get_singleton()->add_singleton(Engine::Singleton("Time", Time::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("GDExtensionManager", GDExtensionManager::get_singleton())); Engine::get_singleton()->add_singleton(Engine::Singleton("ResourceUID", ResourceUID::get_singleton())); - Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", WorkerThreadPool::get_singleton())); + Engine::get_singleton()->add_singleton(Engine::Singleton("WorkerThreadPool", worker_thread_pool)); OS::get_singleton()->benchmark_end_measure("Core", "Register Singletons"); } @@ -378,6 +382,8 @@ void unregister_core_types() { // Destroy singletons in reverse order to ensure dependencies are not broken. + memdelete(worker_thread_pool); + memdelete(_engine_debugger); memdelete(_marshalls); memdelete(_classdb); diff --git a/core/variant/dictionary.cpp b/core/variant/dictionary.cpp index 0754814d35..2db754438f 100644 --- a/core/variant/dictionary.cpp +++ b/core/variant/dictionary.cpp @@ -83,35 +83,64 @@ Variant Dictionary::get_value_at_index(int p_index) const { return Variant(); } +// WARNING: This operator does not validate the value type. For scripting/extensions this is +// done in `variant_setget.cpp`. Consider using `set()` if the data might be invalid. Variant &Dictionary::operator[](const Variant &p_key) { - if (unlikely(_p->read_only)) { - if (likely(_p->variant_map.has(p_key))) { - *_p->read_only = _p->variant_map[p_key]; + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "use `operator[]`"))) { + if (unlikely(!_p->typed_fallback)) { + _p->typed_fallback = memnew(Variant); + } + VariantInternal::initialize(_p->typed_fallback, _p->typed_value.type); + return *_p->typed_fallback; + } else if (unlikely(_p->read_only)) { + if (likely(_p->variant_map.has(key))) { + *_p->read_only = _p->variant_map[key]; } else { - *_p->read_only = Variant(); + VariantInternal::initialize(_p->read_only, _p->typed_value.type); } - return *_p->read_only; } else { - return _p->variant_map[p_key]; + if (unlikely(!_p->variant_map.has(key))) { + VariantInternal::initialize(&_p->variant_map[key], _p->typed_value.type); + } + return _p->variant_map[key]; } } const Variant &Dictionary::operator[](const Variant &p_key) const { - // Will not insert key, so no conversion is necessary. - return _p->variant_map[p_key]; + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "use `operator[]`"))) { + if (unlikely(!_p->typed_fallback)) { + _p->typed_fallback = memnew(Variant); + } + VariantInternal::initialize(_p->typed_fallback, _p->typed_value.type); + return *_p->typed_fallback; + } else { + // Will not insert key, so no initialization is necessary. + return _p->variant_map[key]; + } } const Variant *Dictionary::getptr(const Variant &p_key) const { - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(p_key)); + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "getptr"))) { + return nullptr; + } + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::ConstIterator E(_p->variant_map.find(key)); if (!E) { return nullptr; } return &E->value; } +// WARNING: This method does not validate the value type. Variant *Dictionary::getptr(const Variant &p_key) { - HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E(_p->variant_map.find(p_key)); + Variant key = p_key; + if (unlikely(!_p->typed_key.validate(key, "getptr"))) { + return nullptr; + } + HashMap<Variant, Variant, VariantHasher, StringLikeVariantComparator>::Iterator E(_p->variant_map.find(key)); if (!E) { return nullptr; } @@ -158,6 +187,16 @@ Variant Dictionary::get_or_add(const Variant &p_key, const Variant &p_default) { return *result; } +bool Dictionary::set(const Variant &p_key, const Variant &p_value) { + ERR_FAIL_COND_V_MSG(_p->read_only, false, "Dictionary is in read-only state."); + Variant key = p_key; + ERR_FAIL_COND_V(!_p->typed_key.validate(key, "set"), false); + Variant value = p_value; + ERR_FAIL_COND_V(!_p->typed_value.validate(value, "set"), false); + _p->variant_map[key] = value; + return true; +} + int Dictionary::size() const { return _p->variant_map.size(); } diff --git a/core/variant/dictionary.h b/core/variant/dictionary.h index 57fbefc8f2..5f3ce40219 100644 --- a/core/variant/dictionary.h +++ b/core/variant/dictionary.h @@ -59,6 +59,7 @@ public: Variant get_valid(const Variant &p_key) const; Variant get(const Variant &p_key, const Variant &p_default) const; Variant get_or_add(const Variant &p_key, const Variant &p_default); + bool set(const Variant &p_key, const Variant &p_value); int size() const; bool is_empty() const; diff --git a/core/variant/variant_setget.cpp b/core/variant/variant_setget.cpp index b60ff83cf1..1652f81d99 100644 --- a/core/variant/variant_setget.cpp +++ b/core/variant/variant_setget.cpp @@ -252,20 +252,7 @@ void Variant::set_named(const StringName &p_member, const Variant &p_value, bool } } else if (type == Variant::DICTIONARY) { Dictionary &dict = *VariantGetInternalPtr<Dictionary>::get_ptr(this); - - if (dict.is_read_only()) { - r_valid = false; - return; - } - - Variant *v = dict.getptr(p_member); - if (v) { - *v = p_value; - } else { - dict[p_member] = p_value; - } - - r_valid = true; + r_valid = dict.set(p_member, p_value); } else { r_valid = false; } @@ -721,26 +708,16 @@ struct VariantIndexedSetGet_Dictionary { PtrToArg<Variant>::encode(*ptr, member); } static void set(Variant *base, int64_t index, const Variant *value, bool *valid, bool *oob) { - if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { - *valid = false; - *oob = true; - return; - } - (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value; - *oob = false; - *valid = true; + *valid = VariantGetInternalPtr<Dictionary>::get_ptr(base)->set(index, *value); + *oob = VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only(); } static void validated_set(Variant *base, int64_t index, const Variant *value, bool *oob) { - if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { - *oob = true; - return; - } - (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[index] = *value; - *oob = false; + VariantGetInternalPtr<Dictionary>::get_ptr(base)->set(index, *value); + *oob = VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only(); } static void ptr_set(void *base, int64_t index, const void *member) { Dictionary &v = *reinterpret_cast<Dictionary *>(base); - v[index] = PtrToArg<Variant>::convert(member); + v.set(index, PtrToArg<Variant>::convert(member)); } static Variant::Type get_index_type() { return Variant::NIL; } static uint32_t get_index_usage() { return PROPERTY_USAGE_DEFAULT; } @@ -1010,16 +987,11 @@ struct VariantKeyedSetGetDictionary { PtrToArg<Variant>::encode(*ptr, value); } static void set(Variant *base, const Variant *key, const Variant *value, bool *r_valid) { - if (VariantGetInternalPtr<Dictionary>::get_ptr(base)->is_read_only()) { - *r_valid = false; - return; - } - (*VariantGetInternalPtr<Dictionary>::get_ptr(base))[*key] = *value; - *r_valid = true; + *r_valid = VariantGetInternalPtr<Dictionary>::get_ptr(base)->set(*key, *value); } static void ptr_set(void *base, const void *key, const void *value) { Dictionary &v = *reinterpret_cast<Dictionary *>(base); - v[PtrToArg<Variant>::convert(key)] = PtrToArg<Variant>::convert(value); + v.set(PtrToArg<Variant>::convert(key), PtrToArg<Variant>::convert(value)); } static bool has(const Variant *base, const Variant *key, bool *r_valid) { |