diff options
Diffstat (limited to 'core/object')
| -rw-r--r-- | core/object/class_db.cpp | 183 | ||||
| -rw-r--r-- | core/object/class_db.h | 37 | ||||
| -rw-r--r-- | core/object/make_virtuals.py | 43 | ||||
| -rw-r--r-- | core/object/method_bind.cpp | 8 | ||||
| -rw-r--r-- | core/object/method_bind.h | 4 | ||||
| -rw-r--r-- | core/object/object.cpp | 169 | ||||
| -rw-r--r-- | core/object/object.h | 41 | ||||
| -rw-r--r-- | core/object/ref_counted.h | 2 | ||||
| -rw-r--r-- | core/object/script_instance.cpp | 71 | ||||
| -rw-r--r-- | core/object/script_instance.h | 98 | ||||
| -rw-r--r-- | core/object/script_language.cpp | 37 | ||||
| -rw-r--r-- | core/object/script_language.h | 71 | ||||
| -rw-r--r-- | core/object/script_language_extension.cpp | 4 | ||||
| -rw-r--r-- | core/object/script_language_extension.h | 56 | ||||
| -rw-r--r-- | core/object/undo_redo.cpp | 8 | ||||
| -rw-r--r-- | core/object/worker_thread_pool.cpp | 4 |
16 files changed, 652 insertions, 184 deletions
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp index c8c50fb957..c594f4a9b4 100644 --- a/core/object/class_db.cpp +++ b/core/object/class_db.cpp @@ -31,6 +31,7 @@ #include "class_db.h" #include "core/config/engine.h" +#include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "core/os/mutex.h" #include "core/version.h" @@ -144,7 +145,7 @@ StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class) StringName ClassDB::_get_parent_class(const StringName &p_class) { ClassInfo *ti = classes.getptr(p_class); - ERR_FAIL_COND_V_MSG(!ti, StringName(), "Cannot get class '" + String(p_class) + "'."); + ERR_FAIL_NULL_V_MSG(ti, StringName(), "Cannot get class '" + String(p_class) + "'."); return ti->inherits; } @@ -159,7 +160,7 @@ ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) { ClassInfo *ti = classes.getptr(p_class); - ERR_FAIL_COND_V_MSG(!ti, API_NONE, "Cannot get class '" + String(p_class) + "'."); + ERR_FAIL_NULL_V_MSG(ti, API_NONE, "Cannot get class '" + String(p_class) + "'."); return ti->api; } @@ -180,7 +181,7 @@ uint32_t ClassDB::get_api_hash(APIType p_api) { for (const StringName &E : class_list) { ClassInfo *t = classes.getptr(E); - ERR_FAIL_COND_V_MSG(!t, 0, "Cannot get class '" + String(E) + "'."); + ERR_FAIL_NULL_V_MSG(t, 0, "Cannot get class '" + String(E) + "'."); if (t->api != p_api || !t->exposed) { continue; } @@ -221,10 +222,11 @@ uint32_t ClassDB::get_api_hash(APIType p_api) { hash = hash_murmur3_one_64(mb->get_default_argument_count(), hash); - for (int i = 0; i < mb->get_default_argument_count(); i++) { - //hash should not change, i hope for tis - Variant da = mb->get_default_argument(i); - hash = hash_murmur3_one_64(da.hash(), hash); + for (int i = 0; i < mb->get_argument_count(); i++) { + if (mb->has_default_argument(i)) { + Variant da = mb->get_default_argument(i); + hash = hash_murmur3_one_64(da.hash(), hash); + } } hash = hash_murmur3_one_64(mb->get_hint_flags(), hash); @@ -278,7 +280,7 @@ uint32_t ClassDB::get_api_hash(APIType p_api) { for (const StringName &F : snames) { PropertySetGet *psg = t->property_setget.getptr(F); - ERR_FAIL_COND_V(!psg, 0); + ERR_FAIL_NULL_V(psg, 0); hash = hash_murmur3_one_64(F.hash(), hash); hash = hash_murmur3_one_64(psg->setter.hash(), hash); @@ -336,9 +338,9 @@ Object *ClassDB::instantiate(const StringName &p_class) { ti = classes.getptr(compat_classes[p_class]); } } - ERR_FAIL_COND_V_MSG(!ti, nullptr, "Cannot get class '" + String(p_class) + "'."); + ERR_FAIL_NULL_V_MSG(ti, nullptr, "Cannot get class '" + String(p_class) + "'."); ERR_FAIL_COND_V_MSG(ti->disabled, nullptr, "Class '" + String(p_class) + "' is disabled."); - ERR_FAIL_COND_V_MSG(!ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated."); + ERR_FAIL_NULL_V_MSG(ti->creation_func, nullptr, "Class '" + String(p_class) + "' or its base class cannot be instantiated."); } #ifdef TOOLS_ENABLED if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) { @@ -347,14 +349,20 @@ Object *ClassDB::instantiate(const StringName &p_class) { } #endif if (ti->gdextension && ti->gdextension->create_instance) { - return (Object *)ti->gdextension->create_instance(ti->gdextension->class_userdata); + Object *obj = (Object *)ti->gdextension->create_instance(ti->gdextension->class_userdata); +#ifdef TOOLS_ENABLED + if (ti->gdextension->track_instance) { + ti->gdextension->track_instance(ti->gdextension->tracking_userdata, obj); + } +#endif + return obj; } else { return ti->creation_func(); } } void ClassDB::set_object_extension_instance(Object *p_object, const StringName &p_class, GDExtensionClassInstancePtr p_instance) { - ERR_FAIL_COND(!p_object); + ERR_FAIL_NULL(p_object); ClassInfo *ti; { OBJTYPE_RLOCK; @@ -364,9 +372,9 @@ void ClassDB::set_object_extension_instance(Object *p_object, const StringName & ti = classes.getptr(compat_classes[p_class]); } } - ERR_FAIL_COND_MSG(!ti, "Cannot get class '" + String(p_class) + "'."); + ERR_FAIL_NULL_MSG(ti, "Cannot get class '" + String(p_class) + "'."); ERR_FAIL_COND_MSG(ti->disabled, "Class '" + String(p_class) + "' is disabled."); - ERR_FAIL_COND_MSG(!ti->gdextension, "Class '" + String(p_class) + "' has no native extension."); + ERR_FAIL_NULL_MSG(ti->gdextension, "Class '" + String(p_class) + "' has no native extension."); } p_object->_extension = ti->gdextension; @@ -377,7 +385,14 @@ bool ClassDB::can_instantiate(const StringName &p_class) { OBJTYPE_RLOCK; ClassInfo *ti = classes.getptr(p_class); - ERR_FAIL_COND_V_MSG(!ti, false, "Cannot get class '" + String(p_class) + "'."); + if (!ti) { + if (!ScriptServer::is_global_class(p_class)) { + ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'."); + } + String path = ScriptServer::get_global_class_path(p_class); + Ref<Script> scr = ResourceLoader::load(path); + return scr.is_valid() && scr->is_valid() && !scr->is_abstract(); + } #ifdef TOOLS_ENABLED if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) { return false; @@ -394,7 +409,9 @@ bool ClassDB::is_virtual(const StringName &p_class) { if (!ScriptServer::is_global_class(p_class)) { ERR_FAIL_V_MSG(false, "Cannot get class '" + String(p_class) + "'."); } - return false; + String path = ScriptServer::get_global_class_path(p_class); + Ref<Script> scr = ResourceLoader::load(path); + return scr.is_valid() && scr->is_valid() && scr->is_abstract(); } #ifdef TOOLS_ENABLED if (ti->api == API_EDITOR && !Engine::get_singleton()->is_editor_hint()) { @@ -463,7 +480,6 @@ void ClassDB::get_method_list(const StringName &p_class, List<MethodInfo> *p_met } #ifdef DEBUG_METHODS_ENABLED - for (const MethodInfo &E : type->virtual_methods) { p_methods->push_back(E); } @@ -478,17 +494,74 @@ void ClassDB::get_method_list(const StringName &p_class, List<MethodInfo> *p_met p_methods->push_back(minfo); } - #else - for (KeyValue<StringName, MethodBind *> &E : type->method_map) { MethodBind *m = E.value; MethodInfo minfo = info_from_bind(m); p_methods->push_back(minfo); } +#endif + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + } +} + +void ClassDB::get_method_list_with_compatibility(const StringName &p_class, List<Pair<MethodInfo, uint32_t>> *p_methods, bool p_no_inheritance, bool p_exclude_from_properties) { + OBJTYPE_RLOCK; + + ClassInfo *type = classes.getptr(p_class); + + while (type) { + if (type->disabled) { + if (p_no_inheritance) { + break; + } + + type = type->inherits_ptr; + continue; + } + +#ifdef DEBUG_METHODS_ENABLED + for (const MethodInfo &E : type->virtual_methods) { + Pair<MethodInfo, uint32_t> pair(E, 0); + p_methods->push_back(pair); + } + + for (const StringName &E : type->method_order) { + if (p_exclude_from_properties && type->methods_in_properties.has(E)) { + continue; + } + + MethodBind *method = type->method_map.get(E); + MethodInfo minfo = info_from_bind(method); + + Pair<MethodInfo, uint32_t> pair(minfo, method->get_hash()); + p_methods->push_back(pair); + } +#else + for (KeyValue<StringName, MethodBind *> &E : type->method_map) { + MethodBind *method = E.value; + MethodInfo minfo = info_from_bind(method); + + Pair<MethodInfo, uint32_t> pair(minfo, method->get_hash()); + p_methods->push_back(pair); + } #endif + for (const KeyValue<StringName, LocalVector<MethodBind *, unsigned int, false, false>> &E : type->method_map_compatibility) { + LocalVector<MethodBind *> compat = E.value; + for (MethodBind *method : compat) { + MethodInfo minfo = info_from_bind(method); + + Pair<MethodInfo, uint32_t> pair(minfo, method->get_hash()); + p_methods->push_back(pair); + } + } + if (p_no_inheritance) { break; } @@ -621,7 +694,7 @@ void ClassDB::bind_integer_constant(const StringName &p_class, const StringName ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); if (type->constant_map.has(p_name)) { ERR_FAIL(); @@ -790,7 +863,7 @@ void ClassDB::set_method_error_return_values(const StringName &p_class, const St #ifdef DEBUG_METHODS_ENABLED ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); type->method_error_values[p_method] = p_values; #endif @@ -800,7 +873,7 @@ Vector<Error> ClassDB::get_method_error_return_values(const StringName &p_class, #ifdef DEBUG_METHODS_ENABLED ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND_V(!type, Vector<Error>()); + ERR_FAIL_NULL_V(type, Vector<Error>()); if (!type->method_error_values.has(p_method)) { return Vector<Error>(); @@ -853,7 +926,7 @@ void ClassDB::add_signal(const StringName &p_class, const MethodInfo &p_signal) OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); StringName sname = p_signal.name; @@ -872,7 +945,7 @@ void ClassDB::get_signal_list(const StringName &p_class, List<MethodInfo> *p_sig OBJTYPE_RLOCK; ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); ClassInfo *check = type; @@ -926,7 +999,7 @@ bool ClassDB::get_signal(const StringName &p_class, const StringName &p_signal, void ClassDB::add_property_group(const StringName &p_class, const String &p_name, const String &p_prefix, int p_indent_depth) { OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); String prefix = p_prefix; if (p_indent_depth > 0) { @@ -939,7 +1012,7 @@ void ClassDB::add_property_group(const StringName &p_class, const String &p_name void ClassDB::add_property_subgroup(const StringName &p_class, const String &p_name, const String &p_prefix, int p_indent_depth) { OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); String prefix = p_prefix; if (p_indent_depth > 0) { @@ -956,7 +1029,7 @@ void ClassDB::add_property_array_count(const StringName &p_class, const String & void ClassDB::add_property_array(const StringName &p_class, const StringName &p_path, const String &p_array_element_prefix) { OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); type->property_list.push_back(PropertyInfo(Variant::NIL, p_path, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, p_array_element_prefix)); } @@ -967,14 +1040,14 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf ClassInfo *type = classes.getptr(p_class); lock.read_unlock(); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); MethodBind *mb_set = nullptr; if (p_setter) { mb_set = get_method(p_class, p_setter); #ifdef DEBUG_METHODS_ENABLED - ERR_FAIL_COND_MSG(!mb_set, "Invalid setter '" + p_class + "::" + p_setter + "' for property '" + p_pinfo.name + "'."); + ERR_FAIL_NULL_MSG(mb_set, "Invalid setter '" + p_class + "::" + p_setter + "' for property '" + p_pinfo.name + "'."); int exp_args = 1 + (p_index >= 0 ? 1 : 0); ERR_FAIL_COND_MSG(mb_set->get_argument_count() != exp_args, "Invalid function for setter '" + p_class + "::" + p_setter + " for property '" + p_pinfo.name + "'."); @@ -986,7 +1059,7 @@ void ClassDB::add_property(const StringName &p_class, const PropertyInfo &p_pinf mb_get = get_method(p_class, p_getter); #ifdef DEBUG_METHODS_ENABLED - ERR_FAIL_COND_MSG(!mb_get, "Invalid getter '" + p_class + "::" + p_getter + "' for property '" + p_pinfo.name + "'."); + ERR_FAIL_NULL_MSG(mb_get, "Invalid getter '" + p_class + "::" + p_getter + "' for property '" + p_pinfo.name + "'."); int exp_args = 0 + (p_index >= 0 ? 1 : 0); ERR_FAIL_COND_MSG(mb_get->get_argument_count() != exp_args, "Invalid function for getter '" + p_class + "::" + p_getter + "' for property: '" + p_pinfo.name + "'."); @@ -1031,7 +1104,7 @@ void ClassDB::add_linked_property(const StringName &p_class, const String &p_pro #ifdef TOOLS_ENABLED OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); - ERR_FAIL_COND(!type); + ERR_FAIL_NULL(type); ERR_FAIL_COND(!type->property_map.has(p_property)); ERR_FAIL_COND(!type->property_map.has(p_linked_property)); @@ -1306,7 +1379,7 @@ void ClassDB::set_method_flags(const StringName &p_class, const StringName &p_me OBJTYPE_WLOCK; ClassInfo *type = classes.getptr(p_class); ClassInfo *check = type; - ERR_FAIL_COND(!check); + ERR_FAIL_NULL(check); ERR_FAIL_COND(!check->method_map.has(p_method)); check->method_map[p_method]->set_hint_flags(p_flags); } @@ -1374,7 +1447,7 @@ MethodBind *ClassDB::_bind_vararg_method(MethodBind *p_bind, const StringName &p ClassInfo *type = classes.getptr(instance_type); if (!type) { memdelete(bind); - ERR_FAIL_COND_V(!type, nullptr); + ERR_FAIL_NULL_V(type, nullptr); } if (p_compatibility) { @@ -1406,7 +1479,7 @@ MethodBind *ClassDB::bind_methodfi(uint32_t p_flags, MethodBind *p_bind, bool p_ #endif OBJTYPE_WLOCK; - ERR_FAIL_COND_V(!p_bind, nullptr); + ERR_FAIL_NULL_V(p_bind, nullptr); p_bind->set_name(mdname); String instance_type = p_bind->get_instance_class(); @@ -1532,7 +1605,7 @@ bool ClassDB::is_class_enabled(const StringName &p_class) { } } - ERR_FAIL_COND_V_MSG(!ti, false, "Cannot get class '" + String(p_class) + "'."); + ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); return !ti->disabled; } @@ -1540,10 +1613,18 @@ bool ClassDB::is_class_exposed(const StringName &p_class) { OBJTYPE_RLOCK; ClassInfo *ti = classes.getptr(p_class); - ERR_FAIL_COND_V_MSG(!ti, false, "Cannot get class '" + String(p_class) + "'."); + ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); return ti->exposed; } +bool ClassDB::is_class_reloadable(const StringName &p_class) { + OBJTYPE_RLOCK; + + ClassInfo *ti = classes.getptr(p_class); + ERR_FAIL_NULL_V_MSG(ti, false, "Cannot get class '" + String(p_class) + "'."); + return ti->reloadable; +} + void ClassDB::add_resource_base_extension(const StringName &p_extension, const StringName &p_class) { if (resource_base_extensions.has(p_extension)) { return; @@ -1661,21 +1742,39 @@ void ClassDB::register_extension_class(ObjectGDExtension *p_extension) { c.name = p_extension->class_name; c.is_virtual = p_extension->is_virtual; if (!p_extension->is_abstract) { - c.creation_func = parent->creation_func; + // Find the closest ancestor which is either non-abstract or native (or both). + ClassInfo *concrete_ancestor = parent; + while (concrete_ancestor->creation_func == nullptr && + concrete_ancestor->inherits_ptr != nullptr && + concrete_ancestor->gdextension != nullptr) { + concrete_ancestor = concrete_ancestor->inherits_ptr; + } + ERR_FAIL_NULL_MSG(concrete_ancestor->creation_func, "Extension class " + String(p_extension->class_name) + " cannot extend native abstract class " + String(concrete_ancestor->name)); + c.creation_func = concrete_ancestor->creation_func; } c.inherits = parent->name; c.class_ptr = parent->class_ptr; c.inherits_ptr = parent; - c.exposed = true; + c.exposed = p_extension->is_exposed; + if (c.exposed) { + // The parent classes should be exposed if it has an exposed child class. + while (parent && !parent->exposed) { + parent->exposed = true; + parent = classes.getptr(parent->name); + } + } + c.reloadable = p_extension->reloadable; classes[p_extension->class_name] = c; } -void ClassDB::unregister_extension_class(const StringName &p_class) { +void ClassDB::unregister_extension_class(const StringName &p_class, bool p_free_method_binds) { ClassInfo *c = classes.getptr(p_class); - ERR_FAIL_COND_MSG(!c, "Class " + p_class + "does not exist"); - for (KeyValue<StringName, MethodBind *> &F : c->method_map) { - memdelete(F.value); + ERR_FAIL_NULL_MSG(c, "Class '" + String(p_class) + "' does not exist."); + if (p_free_method_binds) { + for (KeyValue<StringName, MethodBind *> &F : c->method_map) { + memdelete(F.value); + } } classes.erase(p_class); } diff --git a/core/object/class_db.h b/core/object/class_db.h index 3aae3b452e..5c2c59d508 100644 --- a/core/object/class_db.h +++ b/core/object/class_db.h @@ -131,6 +131,7 @@ public: StringName name; bool disabled = false; bool exposed = false; + bool reloadable = false; bool is_virtual = false; Object *(*creation_func)() = nullptr; @@ -187,9 +188,10 @@ public: template <class T> static void register_class(bool p_virtual = false) { GLOBAL_LOCK_FUNCTION; + static_assert(TypesAreSame<typename T::self_type, T>::value, "Class not declared properly, please use GDCLASS."); T::initialize_class(); ClassInfo *t = classes.getptr(T::get_class_static()); - ERR_FAIL_COND(!t); + ERR_FAIL_NULL(t); t->creation_func = &creator<T>; t->exposed = true; t->is_virtual = p_virtual; @@ -201,17 +203,33 @@ public: template <class T> static void register_abstract_class() { GLOBAL_LOCK_FUNCTION; + static_assert(TypesAreSame<typename T::self_type, T>::value, "Class not declared properly, please use GDCLASS."); T::initialize_class(); ClassInfo *t = classes.getptr(T::get_class_static()); - ERR_FAIL_COND(!t); + ERR_FAIL_NULL(t); t->exposed = true; t->class_ptr = T::get_class_ptr_static(); t->api = current_api; //nothing } + template <class T> + static void register_internal_class() { + GLOBAL_LOCK_FUNCTION; + static_assert(TypesAreSame<typename T::self_type, T>::value, "Class not declared properly, please use GDCLASS."); + T::initialize_class(); + ClassInfo *t = classes.getptr(T::get_class_static()); + ERR_FAIL_NULL(t); + t->creation_func = &creator<T>; + t->exposed = false; + t->is_virtual = false; + t->class_ptr = T::get_class_ptr_static(); + t->api = current_api; + T::register_custom_data_to_otdb(); + } + static void register_extension_class(ObjectGDExtension *p_extension); - static void unregister_extension_class(const StringName &p_class); + static void unregister_extension_class(const StringName &p_class, bool p_free_method_binds = true); template <class T> static Object *_create_ptr_func() { @@ -221,9 +239,10 @@ public: template <class T> static void register_custom_instance_class() { GLOBAL_LOCK_FUNCTION; + static_assert(TypesAreSame<typename T::self_type, T>::value, "Class not declared properly, please use GDCLASS."); T::initialize_class(); ClassInfo *t = classes.getptr(T::get_class_static()); - ERR_FAIL_COND(!t); + ERR_FAIL_NULL(t); t->creation_func = &_create_ptr_func<T>; t->exposed = true; t->class_ptr = T::get_class_ptr_static(); @@ -329,7 +348,7 @@ public: GLOBAL_LOCK_FUNCTION; MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant); - ERR_FAIL_COND_V(!bind, nullptr); + ERR_FAIL_NULL_V(bind, nullptr); if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) { bind->set_return_type_is_raw_object_ptr(true); @@ -342,7 +361,7 @@ public: GLOBAL_LOCK_FUNCTION; MethodBind *bind = create_vararg_method_bind(p_method, p_info, p_return_nil_is_variant); - ERR_FAIL_COND_V(!bind, nullptr); + ERR_FAIL_NULL_V(bind, nullptr); if constexpr (std::is_same<typename member_function_traits<M>::return_type, Object *>::value) { bind->set_return_type_is_raw_object_ptr(true); @@ -380,6 +399,7 @@ public: static void set_method_flags(const StringName &p_class, const StringName &p_method, int p_flags); static void get_method_list(const StringName &p_class, List<MethodInfo> *p_methods, bool p_no_inheritance = false, bool p_exclude_from_properties = false); + static void get_method_list_with_compatibility(const StringName &p_class, List<Pair<MethodInfo, uint32_t>> *p_methods_with_hash, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static bool get_method_info(const StringName &p_class, const StringName &p_method, MethodInfo *r_info, bool p_no_inheritance = false, bool p_exclude_from_properties = false); static MethodBind *get_method(const StringName &p_class, const StringName &p_name); static MethodBind *get_method_with_compatibility(const StringName &p_class, const StringName &p_name, uint64_t p_hash, bool *r_method_exists = nullptr, bool *r_is_deprecated = nullptr); @@ -408,6 +428,7 @@ public: static bool is_class_enabled(const StringName &p_class); static bool is_class_exposed(const StringName &p_class); + static bool is_class_reloadable(const StringName &p_class); static void add_resource_base_extension(const StringName &p_extension, const StringName &p_class); static void get_resource_base_extensions(List<String> *p_extensions); @@ -480,6 +501,10 @@ _FORCE_INLINE_ Vector<Error> errarray(P... p_args) { if (m_class::_class_is_enabled) { \ ::ClassDB::register_abstract_class<m_class>(); \ } +#define GDREGISTER_INTERNAL_CLASS(m_class) \ + if (m_class::_class_is_enabled) { \ + ::ClassDB::register_internal_class<m_class>(); \ + } #define GDREGISTER_NATIVE_STRUCT(m_class, m_code) ClassDB::register_native_struct(#m_class, m_code, sizeof(m_class)) diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py index 5be9650b32..0f3cf3916a 100644 --- a/core/object/make_virtuals.py +++ b/core/object/make_virtuals.py @@ -2,7 +2,7 @@ proto = """ #define GDVIRTUAL$VER($RET m_name $ARG) \\ StringName _gdvirtual_##m_name##_sn = #m_name;\\ mutable bool _gdvirtual_##m_name##_initialized = false;\\ -mutable GDExtensionClassCallVirtual _gdvirtual_##m_name = nullptr;\\ +mutable void* _gdvirtual_##m_name = nullptr;\\ template<bool required>\\ _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ ScriptInstance *_script_instance = ((Object*)(this))->get_script_instance();\\ @@ -16,15 +16,25 @@ _FORCE_INLINE_ bool _gdvirtual_##m_name##_call($CALLARGS) $CONST { \\ } \\ }\\ if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ - /* TODO: C-style cast because GDExtensionStringNamePtr's const qualifier is broken (see https://github.com/godotengine/godot/pull/67751) */\\ - _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, (GDExtensionStringNamePtr)&_gdvirtual_##m_name##_sn) : (GDExtensionClassCallVirtual) nullptr;\\ + _gdvirtual_##m_name = nullptr;\\ + if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ + _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + } else if (_get_extension()->get_virtual) {\\ + _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + }\\ + GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized); \\ _gdvirtual_##m_name##_initialized = true;\\ }\\ if (_gdvirtual_##m_name) {\\ $CALLPTRARGS\\ $CALLPTRRETDEF\\ - _gdvirtual_##m_name(_get_extension_instance(),$CALLPTRARGPASS,$CALLPTRRETPASS);\\ - $CALLPTRRET\\ + if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ + _get_extension()->call_virtual_with_data(_get_extension_instance(), &_gdvirtual_##m_name##_sn, _gdvirtual_##m_name, $CALLPTRARGPASS,$CALLPTRRETPASS);\\ + $CALLPTRRET\\ + } else {\\ + ((GDExtensionClassCallVirtual)_gdvirtual_##m_name)(_get_extension_instance(),$CALLPTRARGPASS,$CALLPTRRETPASS);\\ + $CALLPTRRET\\ + }\\ return true;\\ }\\ \\ @@ -41,8 +51,13 @@ _FORCE_INLINE_ bool _gdvirtual_##m_name##_overridden() const { \\ return _script_instance->has_method(_gdvirtual_##m_name##_sn);\\ }\\ if (unlikely(_get_extension() && !_gdvirtual_##m_name##_initialized)) {\\ - /* TODO: C-style cast because GDExtensionStringNamePtr's const qualifier is broken (see https://github.com/godotengine/godot/pull/67751) */\\ - _gdvirtual_##m_name = (_get_extension() && _get_extension()->get_virtual) ? _get_extension()->get_virtual(_get_extension()->class_userdata, (GDExtensionStringNamePtr)&_gdvirtual_##m_name##_sn) : (GDExtensionClassCallVirtual) nullptr;\\ + _gdvirtual_##m_name = nullptr;\\ + if (_get_extension()->get_virtual_call_data && _get_extension()->call_virtual_with_data) {\\ + _gdvirtual_##m_name = _get_extension()->get_virtual_call_data(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + } else if (_get_extension()->get_virtual) {\\ + _gdvirtual_##m_name = (void *)_get_extension()->get_virtual(_get_extension()->class_userdata, &_gdvirtual_##m_name##_sn);\\ + }\\ + GDVIRTUAL_TRACK(_gdvirtual_##m_name, _gdvirtual_##m_name##_initialized); \\ _gdvirtual_##m_name##_initialized = true;\\ }\\ if (_gdvirtual_##m_name) {\\ @@ -160,6 +175,20 @@ def run(target, source, env): #ifndef GDVIRTUAL_GEN_H #define GDVIRTUAL_GEN_H +#include "core/object/script_instance.h" + +#ifdef TOOLS_ENABLED +#define GDVIRTUAL_TRACK(m_virtual, m_initialized) \\ + if (_get_extension()->reloadable) {\\ + VirtualMethodTracker *tracker = memnew(VirtualMethodTracker);\\ + tracker->method = (void **)&m_virtual;\\ + tracker->initialized = &m_initialized;\\ + tracker->next = virtual_method_list;\\ + virtual_method_list = tracker;\\ + } +#else +#define GDVIRTUAL_TRACK(m_virtual, m_initialized) +#endif """ diff --git a/core/object/method_bind.cpp b/core/object/method_bind.cpp index aa0cdedafc..b530101058 100644 --- a/core/object/method_bind.cpp +++ b/core/object/method_bind.cpp @@ -47,9 +47,11 @@ uint32_t MethodBind::get_hash() const { } hash = hash_murmur3_one_32(get_default_argument_count(), hash); - for (int i = 0; i < get_default_argument_count(); i++) { - Variant v = get_default_argument(i); - hash = hash_murmur3_one_32(v.hash(), hash); + for (int i = 0; i < get_argument_count(); i++) { + if (has_default_argument(i)) { + Variant v = get_default_argument(i); + hash = hash_murmur3_one_32(v.hash(), hash); + } } hash = hash_murmur3_one_32(is_const(), hash); diff --git a/core/object/method_bind.h b/core/object/method_bind.h index 84f0941b94..d67fd003c8 100644 --- a/core/object/method_bind.h +++ b/core/object/method_bind.h @@ -111,6 +111,10 @@ public: _FORCE_INLINE_ int get_argument_count() const { return argument_count; }; +#ifdef TOOLS_ENABLED + virtual bool is_valid() const { return true; } +#endif + virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const = 0; virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const = 0; diff --git a/core/object/object.cpp b/core/object/object.cpp index 4ae0ecdefd..0a6cd32f3c 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -31,6 +31,7 @@ #include "object.h" #include "core/core_string_names.h" +#include "core/extension/gdextension_manager.h" #include "core/io/resource.h" #include "core/object/class_db.h" #include "core/object/message_queue.h" @@ -485,7 +486,7 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons if (_extension) { const ObjectGDExtension *current_extension = _extension; while (current_extension) { - p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); + p_list->push_back(PropertyInfo(Variant::NIL, current_extension->class_name, PROPERTY_HINT_NONE, current_extension->class_name, PROPERTY_USAGE_CATEGORY)); ClassDB::get_property_list(current_extension->class_name, p_list, true, this); @@ -526,6 +527,31 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons void Object::validate_property(PropertyInfo &p_property) const { _validate_propertyv(p_property); + + if (_extension && _extension->validate_property) { + // GDExtension uses a StringName rather than a String for property name. + StringName prop_name = p_property.name; + GDExtensionPropertyInfo gdext_prop = { + (GDExtensionVariantType)p_property.type, + &prop_name, + &p_property.class_name, + (uint32_t)p_property.hint, + &p_property.hint_string, + p_property.usage, + }; + if (_extension->validate_property(_extension_instance, &gdext_prop)) { + p_property.type = (Variant::Type)gdext_prop.type; + p_property.name = *reinterpret_cast<StringName *>(gdext_prop.name); + p_property.class_name = *reinterpret_cast<StringName *>(gdext_prop.class_name); + p_property.hint = (PropertyHint)gdext_prop.hint; + p_property.hint_string = *reinterpret_cast<String *>(gdext_prop.hint_string); + p_property.usage = gdext_prop.usage; + }; + } + + if (script_instance) { // Call it last to allow user altering already validated properties. + script_instance->validate_property(p_property); + } } bool Object::property_can_revert(const StringName &p_name) const { @@ -795,14 +821,30 @@ Variant Object::call_const(const StringName &p_method, const Variant **p_args, i } void Object::notification(int p_notification, bool p_reversed) { - _notificationv(p_notification, p_reversed); + if (p_reversed) { + if (script_instance) { + script_instance->notification(p_notification, p_reversed); + } + } else { + _notificationv(p_notification, p_reversed); + } - if (script_instance) { - script_instance->notification(p_notification); + if (_extension) { + if (_extension->notification2) { + _extension->notification2(_extension_instance, p_notification, static_cast<GDExtensionBool>(p_reversed)); +#ifndef DISABLE_DEPRECATED + } else if (_extension->notification) { + _extension->notification(_extension_instance, p_notification); +#endif // DISABLE_DEPRECATED + } } - if (_extension && _extension->notification) { - _extension->notification(_extension_instance, p_notification); + if (p_reversed) { + _notificationv(p_notification, p_reversed); + } else { + if (script_instance) { + script_instance->notification(p_notification, p_reversed); + } } } @@ -826,7 +868,7 @@ String Object::to_string() { void Object::set_script_and_instance(const Variant &p_script, ScriptInstance *p_instance) { //this function is not meant to be used in any of these ways ERR_FAIL_COND(p_script.is_null()); - ERR_FAIL_COND(!p_instance); + ERR_FAIL_NULL(p_instance); ERR_FAIL_COND(script_instance != nullptr || !script.is_null()); script = p_script; @@ -839,7 +881,10 @@ void Object::set_script(const Variant &p_script) { } Ref<Script> s = p_script; - ERR_FAIL_COND_MSG(s.is_null() && !p_script.is_null(), "Invalid parameter, it should be a reference to a valid script (or null)."); + if (!p_script.is_null()) { + ERR_FAIL_COND_MSG(s.is_null(), "Cannot set object script. Parameter should be null or a reference to a valid script."); + ERR_FAIL_COND_MSG(s->is_abstract(), vformat("Cannot set object script. Script '%s' should not be abstract.", s->get_path())); + } script = p_script; @@ -1259,7 +1304,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "': the provided callable is null."); Object *target_object = p_callable.get_object(); - ERR_FAIL_COND_V_MSG(!target_object, ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null."); + ERR_FAIL_NULL_V_MSG(target_object, ERR_INVALID_PARAMETER, "Cannot connect to '" + p_signal + "' to callable '" + p_callable + "': the callable object is null."); SignalData *s = signal_map.getptr(p_signal); if (!s) { @@ -1344,7 +1389,7 @@ bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, "Cannot disconnect from '" + p_signal + "': the provided callable is null."); Object *target_object = p_callable.get_object(); - ERR_FAIL_COND_V_MSG(!target_object, false, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null."); + ERR_FAIL_NULL_V_MSG(target_object, false, "Cannot disconnect '" + p_signal + "' from callable '" + p_callable + "': the callable object is null."); SignalData *s = signal_map.getptr(p_signal); if (!s) { @@ -1352,7 +1397,7 @@ bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, (!script.is_null() && Ref<Script>(script)->has_script_signal(p_signal)); ERR_FAIL_COND_V_MSG(signal_is_valid, false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'."); } - ERR_FAIL_COND_V_MSG(!s, false, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string())); + ERR_FAIL_NULL_V_MSG(s, false, vformat("Disconnecting nonexistent signal '%s' in %s.", p_signal, to_string())); ERR_FAIL_COND_V_MSG(!s->slot_map.has(*p_callable.get_base_comparator()), false, "Attempt to disconnect a nonexistent connection from '" + to_string() + "'. Signal: '" + p_signal + "', callable: '" + p_callable + "'."); @@ -1604,6 +1649,8 @@ void Object::_bind_methods() { plget.return_val.hint_string = "Dictionary"; BIND_OBJ_CORE_METHOD(plget); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::NIL, "_validate_property", PropertyInfo(Variant::DICTIONARY, "property"))); + BIND_OBJ_CORE_METHOD(MethodInfo(Variant::BOOL, "_property_can_revert", PropertyInfo(Variant::STRING_NAME, "property"))); MethodInfo mipgr("_property_get_revert", PropertyInfo(Variant::STRING_NAME, "property")); mipgr.return_val.name = "Variant"; @@ -1748,14 +1795,17 @@ StringName Object::get_class_name_for_extension(const GDExtension *p_library) co } void Object::set_instance_binding(void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks) { - // This is only meant to be used on creation by the binder. - ERR_FAIL_COND(_instance_bindings != nullptr); - _instance_bindings = (InstanceBinding *)memalloc(sizeof(InstanceBinding)); + // This is only meant to be used on creation by the binder, but we also + // need to account for reloading (where the 'binding' will be cleared). + ERR_FAIL_COND(_instance_bindings != nullptr && _instance_bindings[0].binding != nullptr); + if (_instance_bindings == nullptr) { + _instance_bindings = (InstanceBinding *)memalloc(sizeof(InstanceBinding)); + _instance_binding_count = 1; + } _instance_bindings[0].binding = p_binding; _instance_bindings[0].free_callback = p_callbacks->free_callback; _instance_bindings[0].reference_callback = p_callbacks->reference_callback; _instance_bindings[0].token = p_token; - _instance_binding_count = 1; } void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindingCallbacks *p_callbacks) { @@ -1782,6 +1832,12 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi binding = p_callbacks->create_callback(p_token, this); _instance_bindings[_instance_binding_count].binding = binding; +#ifdef TOOLS_ENABLED + if (!_extension && Engine::get_singleton()->is_extension_reloading_enabled()) { + GDExtensionManager::get_singleton()->track_instance_binding(p_token, this); + } +#endif + _instance_binding_count++; } @@ -1805,6 +1861,71 @@ bool Object::has_instance_binding(void *p_token) { return found; } +#ifdef TOOLS_ENABLED +void Object::free_instance_binding(void *p_token) { + bool found = false; + _instance_binding_mutex.lock(); + for (uint32_t i = 0; i < _instance_binding_count; i++) { + if (!found && _instance_bindings[i].token == p_token) { + if (_instance_bindings[i].free_callback) { + _instance_bindings[i].free_callback(_instance_bindings[i].token, this, _instance_bindings[i].binding); + } + found = true; + } + if (found) { + if (i + 1 < _instance_binding_count) { + _instance_bindings[i] = _instance_bindings[i + 1]; + } else { + _instance_bindings[i] = { nullptr }; + } + } + } + if (found) { + _instance_binding_count--; + } + _instance_binding_mutex.unlock(); +} + +void Object::clear_internal_extension() { + ERR_FAIL_NULL(_extension); + + // Free the instance inside the GDExtension. + if (_extension->free_instance) { + _extension->free_instance(_extension->class_userdata, _extension_instance); + } + _extension = nullptr; + _extension_instance = nullptr; + + // Clear the instance bindings. + _instance_binding_mutex.lock(); + if (_instance_bindings[0].free_callback) { + _instance_bindings[0].free_callback(_instance_bindings[0].token, this, _instance_bindings[0].binding); + } + _instance_bindings[0].binding = nullptr; + _instance_bindings[0].token = nullptr; + _instance_bindings[0].free_callback = nullptr; + _instance_bindings[0].reference_callback = nullptr; + _instance_binding_mutex.unlock(); + + // Clear the virtual methods. + while (virtual_method_list) { + (*virtual_method_list->method) = nullptr; + (*virtual_method_list->initialized) = false; + virtual_method_list = virtual_method_list->next; + } +} + +void Object::reset_internal_extension(ObjectGDExtension *p_extension) { + ERR_FAIL_COND(_extension != nullptr); + + if (p_extension) { + _extension_instance = p_extension->recreate_instance ? p_extension->recreate_instance(p_extension->class_userdata, (GDExtensionObjectPtr)this) : nullptr; + ERR_FAIL_NULL_MSG(_extension_instance, "Unable to recreate GDExtension instance - does this extension support hot reloading?"); + _extension = p_extension; + } +} +#endif + void Object::_construct_object(bool p_reference) { type_is_reference = p_reference; _instance_id = ObjectDB::add_instance(this); @@ -1835,11 +1956,25 @@ Object::~Object() { } script_instance = nullptr; - if (_extension && _extension->free_instance) { - _extension->free_instance(_extension->class_userdata, _extension_instance); + if (_extension) { +#ifdef TOOLS_ENABLED + if (_extension->untrack_instance) { + _extension->untrack_instance(_extension->tracking_userdata, this); + } +#endif + if (_extension->free_instance) { + _extension->free_instance(_extension->class_userdata, _extension_instance); + } _extension = nullptr; _extension_instance = nullptr; } +#ifdef TOOLS_ENABLED + else if (_instance_bindings != nullptr && Engine::get_singleton()->is_extension_reloading_enabled()) { + for (uint32_t i = 0; i < _instance_binding_count; i++) { + GDExtensionManager::get_singleton()->untrack_instance_binding(_instance_bindings[i].token, this); + } + } +#endif if (_emitting) { //@todo this may need to actually reach the debugger prioritarily somehow because it may crash before diff --git a/core/object/object.h b/core/object/object.h index 318dbf98de..60dc031d4d 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -49,7 +49,7 @@ class TypedArray; enum PropertyHint { PROPERTY_HINT_NONE, ///< no hint provided. - PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_less][,hide_slider][,radians][,degrees][,exp][,suffix:<keyword>] range. + PROPERTY_HINT_RANGE, ///< hint_text = "min,max[,step][,or_greater][,or_less][,hide_slider][,radians_as_degrees][,degrees][,exp][,suffix:<keyword>] range. PROPERTY_HINT_ENUM, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_ENUM_SUGGESTION, ///< hint_text= "val1,val2,val3,etc" PROPERTY_HINT_EXP_EASING, /// exponential easing function (Math::ease) use "attenuation" hint string to revert (flip h), "positive_only" to exclude in-out and out-in. (ie: "attenuation,positive_only") @@ -313,15 +313,21 @@ struct ObjectGDExtension { StringName parent_class_name; StringName class_name; bool editor_class = false; + bool reloadable = false; bool is_virtual = false; bool is_abstract = false; + bool is_exposed = true; GDExtensionClassSet set; GDExtensionClassGet get; GDExtensionClassGetPropertyList get_property_list; GDExtensionClassFreePropertyList free_property_list; GDExtensionClassPropertyCanRevert property_can_revert; GDExtensionClassPropertyGetRevert property_get_revert; + GDExtensionClassValidateProperty validate_property; +#ifndef DISABLE_DEPRECATED GDExtensionClassNotification notification; +#endif // DISABLE_DEPRECATED + GDExtensionClassNotification2 notification2; GDExtensionClassToString to_string; GDExtensionClassReference reference; GDExtensionClassReference unreference; @@ -342,6 +348,15 @@ struct ObjectGDExtension { GDExtensionClassCreateInstance create_instance; GDExtensionClassFreeInstance free_instance; GDExtensionClassGetVirtual get_virtual; + GDExtensionClassGetVirtualCallData get_virtual_call_data; + GDExtensionClassCallVirtualWithData call_virtual_with_data; + GDExtensionClassRecreateInstance recreate_instance; + +#ifdef TOOLS_ENABLED + void *tracking_userdata = nullptr; + void (*track_instance)(void *p_userdata, void *p_instance); + void (*untrack_instance)(void *p_userdata, void *p_instance); +#endif }; #define GDVIRTUAL_CALL(m_name, ...) _gdvirtual_##m_name##_call<false>(__VA_ARGS__) @@ -382,6 +397,7 @@ private: friend class ::ClassDB; \ \ public: \ + typedef m_class self_type; \ static constexpr bool _class_is_enabled = !bool(GD_IS_DEFINED(ClassDB_Disable_##m_class)) && m_inherits::_class_is_enabled; \ virtual String get_class() const override { \ if (_get_extension()) { \ @@ -485,7 +501,7 @@ protected: if (!p_reversed) { \ m_inherits::_get_property_listv(p_list, p_reversed); \ } \ - p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, String(), PROPERTY_USAGE_CATEGORY)); \ + p_list->push_back(PropertyInfo(Variant::NIL, get_class_static(), PROPERTY_HINT_NONE, get_class_static(), PROPERTY_USAGE_CATEGORY)); \ if (!_is_gpl_reversed()) { \ ::ClassDB::get_property_list(#m_class, p_list, true, this); \ } \ @@ -557,6 +573,8 @@ class ScriptInstance; class Object { public: + typedef Object self_type; + enum ConnectFlags { CONNECT_DEFERRED = 1, CONNECT_PERSIST = 2, // hint for scene to save this connection @@ -740,6 +758,16 @@ protected: bool _disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force = false); +#ifdef TOOLS_ENABLED + struct VirtualMethodTracker { + void **method; + bool *initialized; + VirtualMethodTracker *next; + }; + + mutable VirtualMethodTracker *virtual_method_list = nullptr; +#endif + public: // Should be protected, but bug in clang++. static void initialize_class(); _FORCE_INLINE_ static void register_custom_data_to_otdb() {} @@ -771,7 +799,8 @@ public: enum { NOTIFICATION_POSTINITIALIZE = 0, - NOTIFICATION_PREDELETE = 1 + NOTIFICATION_PREDELETE = 1, + NOTIFICATION_EXTENSION_RELOADED = 2, }; /* TYPE API */ @@ -942,6 +971,12 @@ public: void set_instance_binding(void *p_token, void *p_binding, const GDExtensionInstanceBindingCallbacks *p_callbacks); bool has_instance_binding(void *p_token); +#ifdef TOOLS_ENABLED + void free_instance_binding(void *p_token); + void clear_internal_extension(); + void reset_internal_extension(ObjectGDExtension *p_extension); +#endif + void clear_internal_resource_paths(); _ALWAYS_INLINE_ bool is_ref_counted() const { return type_is_reference; } diff --git a/core/object/ref_counted.h b/core/object/ref_counted.h index 3386514706..228373d662 100644 --- a/core/object/ref_counted.h +++ b/core/object/ref_counted.h @@ -71,7 +71,7 @@ class Ref { } void ref_pointer(T *p_ref) { - ERR_FAIL_COND(!p_ref); + ERR_FAIL_NULL(p_ref); if (p_ref->init_ref()) { reference = p_ref; diff --git a/core/object/script_instance.cpp b/core/object/script_instance.cpp new file mode 100644 index 0000000000..303b127db1 --- /dev/null +++ b/core/object/script_instance.cpp @@ -0,0 +1,71 @@ +/**************************************************************************/ +/* script_instance.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "script_instance.h" + +#include "core/object/script_language.h" + +Variant ScriptInstance::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { + return callp(p_method, p_args, p_argcount, r_error); +} + +void ScriptInstance::get_property_state(List<Pair<StringName, Variant>> &state) { + List<PropertyInfo> pinfo; + get_property_list(&pinfo); + for (const PropertyInfo &E : pinfo) { + if (E.usage & PROPERTY_USAGE_STORAGE) { + Pair<StringName, Variant> p; + p.first = E.name; + if (get(p.first, p.second)) { + state.push_back(p); + } + } + } +} + +void ScriptInstance::property_set_fallback(const StringName &, const Variant &, bool *r_valid) { + if (r_valid) { + *r_valid = false; + } +} + +Variant ScriptInstance::property_get_fallback(const StringName &, bool *r_valid) { + if (r_valid) { + *r_valid = false; + } + return Variant(); +} + +const Variant ScriptInstance::get_rpc_config() const { + return get_script()->get_rpc_config(); +} + +ScriptInstance::~ScriptInstance() { +} diff --git a/core/object/script_instance.h b/core/object/script_instance.h new file mode 100644 index 0000000000..df978a25ea --- /dev/null +++ b/core/object/script_instance.h @@ -0,0 +1,98 @@ +/**************************************************************************/ +/* script_instance.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef SCRIPT_INSTANCE_H +#define SCRIPT_INSTANCE_H + +#include "core/object/ref_counted.h" + +class Script; +class ScriptLanguage; + +class ScriptInstance { +public: + virtual bool set(const StringName &p_name, const Variant &p_value) = 0; + virtual bool get(const StringName &p_name, Variant &r_ret) const = 0; + virtual void get_property_list(List<PropertyInfo> *p_properties) const = 0; + virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const = 0; + virtual void validate_property(PropertyInfo &p_property) const = 0; + + virtual bool property_can_revert(const StringName &p_name) const = 0; + virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const = 0; + + virtual Object *get_owner() { return nullptr; } + virtual void get_property_state(List<Pair<StringName, Variant>> &state); + + virtual void get_method_list(List<MethodInfo> *p_list) const = 0; + virtual bool has_method(const StringName &p_method) const = 0; + + virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0; + + template <typename... VarArgs> + Variant call(const StringName &p_method, VarArgs... p_args) { + Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. + const Variant *argptrs[sizeof...(p_args) + 1]; + for (uint32_t i = 0; i < sizeof...(p_args); i++) { + argptrs[i] = &args[i]; + } + Callable::CallError cerr; + return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr); + } + + virtual Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); // implement if language supports const functions + virtual void notification(int p_notification, bool p_reversed = false) = 0; + virtual String to_string(bool *r_valid) { + if (r_valid) { + *r_valid = false; + } + return String(); + } + + //this is used by script languages that keep a reference counter of their own + //you can make make Ref<> not die when it reaches zero, so deleting the reference + //depends entirely from the script + + virtual void refcount_incremented() {} + virtual bool refcount_decremented() { return true; } //return true if it can die + + virtual Ref<Script> get_script() const = 0; + + virtual bool is_placeholder() const { return false; } + + virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid); + virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid); + + virtual const Variant get_rpc_config() const; + + virtual ScriptLanguage *get_language() = 0; + virtual ~ScriptInstance(); +}; + +#endif // SCRIPT_INSTANCE_H diff --git a/core/object/script_language.cpp b/core/object/script_language.cpp index a8b0e426ae..011f4203ea 100644 --- a/core/object/script_language.cpp +++ b/core/object/script_language.cpp @@ -146,6 +146,7 @@ void Script::_bind_methods() { ClassDB::bind_method(D_METHOD("get_property_default_value", "property"), &Script::_get_property_default_value); ClassDB::bind_method(D_METHOD("is_tool"), &Script::is_tool); + ClassDB::bind_method(D_METHOD("is_abstract"), &Script::is_abstract); ADD_PROPERTY(PropertyInfo(Variant::STRING, "source_code", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_source_code", "get_source_code"); } @@ -388,40 +389,6 @@ String ScriptServer::get_global_class_cache_file_path() { //////////////////// -Variant ScriptInstance::call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { - return callp(p_method, p_args, p_argcount, r_error); -} - -void ScriptInstance::get_property_state(List<Pair<StringName, Variant>> &state) { - List<PropertyInfo> pinfo; - get_property_list(&pinfo); - for (const PropertyInfo &E : pinfo) { - if (E.usage & PROPERTY_USAGE_STORAGE) { - Pair<StringName, Variant> p; - p.first = E.name; - if (get(p.first, p.second)) { - state.push_back(p); - } - } - } -} - -void ScriptInstance::property_set_fallback(const StringName &, const Variant &, bool *r_valid) { - if (r_valid) { - *r_valid = false; - } -} - -Variant ScriptInstance::property_get_fallback(const StringName &, bool *r_valid) { - if (r_valid) { - *r_valid = false; - } - return Variant(); -} - -ScriptInstance::~ScriptInstance() { -} - ScriptCodeCompletionCache *ScriptCodeCompletionCache::singleton = nullptr; ScriptCodeCompletionCache::ScriptCodeCompletionCache() { singleton = this; @@ -482,7 +449,6 @@ TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_characteristics } charac.push_back(matches.size()); charac.push_back((matches[0].first == 0) ? 0 : 1); - charac.push_back(location); const char32_t *target_char = &p_base[0]; int bad_case = 0; for (const Pair<int, int> &match_segment : matches) { @@ -494,6 +460,7 @@ TypedArray<int> ScriptLanguage::CodeCompletionOption::get_option_characteristics } } charac.push_back(bad_case); + charac.push_back(location); charac.push_back(matches[0].first); last_matches = matches; return charac; diff --git a/core/object/script_language.h b/core/object/script_language.h index 3ea6a6e4c3..ca08e9837a 100644 --- a/core/object/script_language.h +++ b/core/object/script_language.h @@ -33,6 +33,7 @@ #include "core/doc_data.h" #include "core/io/resource.h" +#include "core/object/script_instance.h" #include "core/templates/pair.h" #include "core/templates/rb_map.h" #include "core/templates/safe_refcount.h" @@ -101,7 +102,6 @@ public: static bool are_languages_finished() { return languages_finished.is_set(); } }; -class ScriptInstance; class PlaceHolderScriptInstance; class Script : public Resource { @@ -141,6 +141,7 @@ public: #ifdef TOOLS_ENABLED virtual Vector<DocData::ClassDoc> get_documentation() const = 0; + virtual String get_class_icon_path() const = 0; virtual PropertyInfo get_class_category() const; #endif // TOOLS_ENABLED @@ -149,6 +150,7 @@ public: virtual bool is_tool() const = 0; virtual bool is_valid() const = 0; + virtual bool is_abstract() const = 0; virtual ScriptLanguage *get_language() const = 0; @@ -173,64 +175,6 @@ public: Script() {} }; -class ScriptInstance { -public: - virtual bool set(const StringName &p_name, const Variant &p_value) = 0; - virtual bool get(const StringName &p_name, Variant &r_ret) const = 0; - virtual void get_property_list(List<PropertyInfo> *p_properties) const = 0; - virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const = 0; - - virtual bool property_can_revert(const StringName &p_name) const = 0; - virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const = 0; - - virtual Object *get_owner() { return nullptr; } - virtual void get_property_state(List<Pair<StringName, Variant>> &state); - - virtual void get_method_list(List<MethodInfo> *p_list) const = 0; - virtual bool has_method(const StringName &p_method) const = 0; - - virtual Variant callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0; - - template <typename... VarArgs> - Variant call(const StringName &p_method, VarArgs... p_args) { - Variant args[sizeof...(p_args) + 1] = { p_args..., Variant() }; // +1 makes sure zero sized arrays are also supported. - const Variant *argptrs[sizeof...(p_args) + 1]; - for (uint32_t i = 0; i < sizeof...(p_args); i++) { - argptrs[i] = &args[i]; - } - Callable::CallError cerr; - return callp(p_method, sizeof...(p_args) == 0 ? nullptr : (const Variant **)argptrs, sizeof...(p_args), cerr); - } - - virtual Variant call_const(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error); // implement if language supports const functions - virtual void notification(int p_notification) = 0; - virtual String to_string(bool *r_valid) { - if (r_valid) { - *r_valid = false; - } - return String(); - } - - //this is used by script languages that keep a reference counter of their own - //you can make make Ref<> not die when it reaches zero, so deleting the reference - //depends entirely from the script - - virtual void refcount_incremented() {} - virtual bool refcount_decremented() { return true; } //return true if it can die - - virtual Ref<Script> get_script() const = 0; - - virtual bool is_placeholder() const { return false; } - - virtual void property_set_fallback(const StringName &p_name, const Variant &p_value, bool *r_valid); - virtual Variant property_get_fallback(const StringName &p_name, bool *r_valid); - - virtual const Variant get_rpc_config() const { return get_script()->get_rpc_config(); } - - virtual ScriptLanguage *get_language() = 0; - virtual ~ScriptInstance(); -}; - class ScriptCodeCompletionCache { static ScriptCodeCompletionCache *singleton; @@ -299,7 +243,9 @@ public: virtual bool validate(const String &p_script, const String &p_path = "", List<String> *r_functions = nullptr, List<ScriptError> *r_errors = nullptr, List<Warning> *r_warnings = nullptr, HashSet<int> *r_safe_lines = nullptr) const = 0; virtual String validate_path(const String &p_path) const { return ""; } virtual Script *create_script() const = 0; +#ifndef DISABLE_DEPRECATED virtual bool has_named_classes() const = 0; +#endif virtual bool supports_builtin_mode() const = 0; virtual bool supports_documentation() const { return false; } virtual bool can_inherit_from_file() const { return false; } @@ -342,14 +288,16 @@ public: Vector<Pair<int, int>> matches; Vector<Pair<int, int>> last_matches = { { -1, -1 } }; // This value correspond to an impossible match int location = LOCATION_OTHER; + String theme_color_name; CodeCompletionOption() {} - CodeCompletionOption(const String &p_text, CodeCompletionKind p_kind, int p_location = LOCATION_OTHER) { + CodeCompletionOption(const String &p_text, CodeCompletionKind p_kind, int p_location = LOCATION_OTHER, const String &p_theme_color_name = "") { display = p_text; insert_text = p_text; kind = p_kind; location = p_location; + theme_color_name = p_theme_color_name; } TypedArray<int> get_option_characteristics(const String &p_base); @@ -462,6 +410,7 @@ public: virtual bool get(const StringName &p_name, Variant &r_ret) const override; virtual void get_property_list(List<PropertyInfo> *p_properties) const override; virtual Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid = nullptr) const override; + virtual void validate_property(PropertyInfo &p_property) const override {} virtual bool property_can_revert(const StringName &p_name) const override { return false; }; virtual bool property_get_revert(const StringName &p_name, Variant &r_ret) const override { return false; }; @@ -473,7 +422,7 @@ public: r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; return Variant(); } - virtual void notification(int p_notification) override {} + virtual void notification(int p_notification, bool p_reversed = false) override {} virtual Ref<Script> get_script() const override { return script; } diff --git a/core/object/script_language_extension.cpp b/core/object/script_language_extension.cpp index 0df9d58334..ce1109781a 100644 --- a/core/object/script_language_extension.cpp +++ b/core/object/script_language_extension.cpp @@ -52,12 +52,14 @@ void ScriptExtension::_bind_methods() { GDVIRTUAL_BIND(_reload, "keep_state"); GDVIRTUAL_BIND(_get_documentation); + GDVIRTUAL_BIND(_get_class_icon_path); GDVIRTUAL_BIND(_has_method, "method"); GDVIRTUAL_BIND(_get_method_info, "method"); GDVIRTUAL_BIND(_is_tool); GDVIRTUAL_BIND(_is_valid); + GDVIRTUAL_BIND(_is_abstract); GDVIRTUAL_BIND(_get_language); GDVIRTUAL_BIND(_has_script_signal, "signal"); @@ -97,7 +99,9 @@ void ScriptLanguageExtension::_bind_methods() { GDVIRTUAL_BIND(_validate_path, "path"); GDVIRTUAL_BIND(_create_script); +#ifndef DISABLE_DEPRECATED GDVIRTUAL_BIND(_has_named_classes); +#endif GDVIRTUAL_BIND(_supports_builtin_mode); GDVIRTUAL_BIND(_supports_documentation); GDVIRTUAL_BIND(_can_inherit_from_file); diff --git a/core/object/script_language_extension.h b/core/object/script_language_extension.h index 1a0ec29479..3e8284670c 100644 --- a/core/object/script_language_extension.h +++ b/core/object/script_language_extension.h @@ -77,6 +77,7 @@ public: EXBIND1R(Error, reload, bool) GDVIRTUAL0RC(TypedArray<Dictionary>, _get_documentation) + GDVIRTUAL0RC(String, _get_class_icon_path) #ifdef TOOLS_ENABLED virtual Vector<DocData::ClassDoc> get_documentation() const override { TypedArray<Dictionary> doc; @@ -89,6 +90,12 @@ public: return class_doc; } + + virtual String get_class_icon_path() const override { + String ret; + GDVIRTUAL_CALL(_get_class_icon_path, ret); + return ret; + } #endif // TOOLS_ENABLED EXBIND1RC(bool, has_method, const StringName &) @@ -103,6 +110,12 @@ public: EXBIND0RC(bool, is_tool) EXBIND0RC(bool, is_valid) + virtual bool is_abstract() const override { + bool abst; + return GDVIRTUAL_CALL(_is_abstract, abst) && abst; + } + GDVIRTUAL0RC(bool, _is_abstract) + EXBIND0RC(ScriptLanguage *, get_language) EXBIND1RC(bool, has_script_signal, const StringName &) @@ -337,7 +350,9 @@ public: GDVIRTUAL_REQUIRED_CALL(_create_script, ret); return Object::cast_to<Script>(ret); } +#ifndef DISABLE_DEPRECATED EXBIND0RC(bool, has_named_classes) +#endif EXBIND0RC(bool, supports_builtin_mode) EXBIND0RC(bool, supports_documentation) EXBIND0RC(bool, can_inherit_from_file) @@ -623,7 +638,12 @@ VARIANT_ENUM_CAST(ScriptLanguageExtension::CodeCompletionLocation) class ScriptInstanceExtension : public ScriptInstance { public: - const GDExtensionScriptInstanceInfo *native_info; + const GDExtensionScriptInstanceInfo2 *native_info; + bool free_native_info = false; + struct { + GDExtensionScriptInstanceNotification notification_func = nullptr; + } deprecated_native_info; + GDExtensionScriptInstanceDataPtr instance = nullptr; // There should not be warnings on explicit casts. @@ -675,6 +695,28 @@ public: } return Variant::NIL; } + virtual void validate_property(PropertyInfo &p_property) const override { + if (native_info->validate_property_func) { + // GDExtension uses a StringName rather than a String for property name. + StringName prop_name = p_property.name; + GDExtensionPropertyInfo gdext_prop = { + (GDExtensionVariantType)p_property.type, + &prop_name, + &p_property.class_name, + (uint32_t)p_property.hint, + &p_property.hint_string, + p_property.usage, + }; + if (native_info->validate_property_func(instance, &gdext_prop)) { + p_property.type = (Variant::Type)gdext_prop.type; + p_property.name = *reinterpret_cast<StringName *>(gdext_prop.name); + p_property.class_name = *reinterpret_cast<StringName *>(gdext_prop.class_name); + p_property.hint = (PropertyHint)gdext_prop.hint; + p_property.hint_string = *reinterpret_cast<String *>(gdext_prop.hint_string); + p_property.usage = gdext_prop.usage; + } + } + } virtual bool property_can_revert(const StringName &p_name) const override { if (native_info->property_can_revert_func) { @@ -736,11 +778,16 @@ public: return ret; } - virtual void notification(int p_notification) override { + virtual void notification(int p_notification, bool p_reversed = false) override { if (native_info->notification_func) { - native_info->notification_func(instance, p_notification); + native_info->notification_func(instance, p_notification, p_reversed); +#ifndef DISABLE_DEPRECATED + } else if (deprecated_native_info.notification_func) { + deprecated_native_info.notification_func(instance, p_notification); +#endif // DISABLE_DEPRECATED } } + virtual String to_string(bool *r_valid) override { if (native_info->to_string_func) { GDExtensionBool valid; @@ -812,6 +859,9 @@ public: if (native_info->free_func) { native_info->free_func(instance); } + if (free_native_info) { + memfree(const_cast<GDExtensionScriptInstanceInfo2 *>(native_info)); + } } #if defined(__GNUC__) && !defined(__clang__) diff --git a/core/object/undo_redo.cpp b/core/object/undo_redo.cpp index f04961c760..b0f9501985 100644 --- a/core/object/undo_redo.cpp +++ b/core/object/undo_redo.cpp @@ -178,7 +178,7 @@ void UndoRedo::add_undo_method(const Callable &p_callable) { } void UndoRedo::add_do_property(Object *p_object, const StringName &p_property, const Variant &p_value) { - ERR_FAIL_COND(p_object == nullptr); + ERR_FAIL_NULL(p_object); ERR_FAIL_COND(action_level <= 0); ERR_FAIL_COND((current_action + 1) >= actions.size()); Operation do_op; @@ -194,7 +194,7 @@ void UndoRedo::add_do_property(Object *p_object, const StringName &p_property, c } void UndoRedo::add_undo_property(Object *p_object, const StringName &p_property, const Variant &p_value) { - ERR_FAIL_COND(p_object == nullptr); + ERR_FAIL_NULL(p_object); ERR_FAIL_COND(action_level <= 0); ERR_FAIL_COND((current_action + 1) >= actions.size()); @@ -217,7 +217,7 @@ void UndoRedo::add_undo_property(Object *p_object, const StringName &p_property, } void UndoRedo::add_do_reference(Object *p_object) { - ERR_FAIL_COND(p_object == nullptr); + ERR_FAIL_NULL(p_object); ERR_FAIL_COND(action_level <= 0); ERR_FAIL_COND((current_action + 1) >= actions.size()); Operation do_op; @@ -231,7 +231,7 @@ void UndoRedo::add_do_reference(Object *p_object) { } void UndoRedo::add_undo_reference(Object *p_object) { - ERR_FAIL_COND(p_object == nullptr); + ERR_FAIL_NULL(p_object); ERR_FAIL_COND(action_level <= 0); ERR_FAIL_COND((current_action + 1) >= actions.size()); diff --git a/core/object/worker_thread_pool.cpp b/core/object/worker_thread_pool.cpp index 5ec3e1a1a8..cfc3ac3bcf 100644 --- a/core/object/worker_thread_pool.cpp +++ b/core/object/worker_thread_pool.cpp @@ -34,8 +34,8 @@ #include "core/os/thread_safe.h" void WorkerThreadPool::Task::free_template_userdata() { - ERR_FAIL_COND(!template_userdata); - ERR_FAIL_COND(native_func_userdata == nullptr); + ERR_FAIL_NULL(template_userdata); + ERR_FAIL_NULL(native_func_userdata); BaseTemplateUserdata *btu = (BaseTemplateUserdata *)native_func_userdata; memdelete(btu); } |
