diff options
Diffstat (limited to 'core/object/object.cpp')
-rw-r--r-- | core/object/object.cpp | 275 |
1 files changed, 189 insertions, 86 deletions
diff --git a/core/object/object.cpp b/core/object/object.cpp index 2e5b897bce..f8d2feb5a8 100644 --- a/core/object/object.cpp +++ b/core/object/object.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "object.h" +#include "object.compat.inc" #include "core/core_string_names.h" #include "core/extension/gdextension_manager.h" @@ -141,16 +142,16 @@ MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) { args = p_dict["args"]; } - for (int i = 0; i < args.size(); i++) { - Dictionary d = args[i]; + for (const Variant &arg : args) { + Dictionary d = arg; mi.arguments.push_back(PropertyInfo::from_dict(d)); } Array defargs; if (p_dict.has("default_args")) { defargs = p_dict["default_args"]; } - for (int i = 0; i < defargs.size(); i++) { - mi.default_arguments.push_back(defargs[i]); + for (const Variant &defarg : defargs) { + mi.default_arguments.push_back(defarg); } if (p_dict.has("return")) { @@ -198,6 +199,7 @@ bool Object::_predelete() { notification(NOTIFICATION_PREDELETE, true); if (_predelete_ok) { _class_name_ptr = nullptr; // Must restore, so constructors/destructors have proper class name access at each stage. + notification(NOTIFICATION_PREDELETE_CLEANUP, true); } return _predelete_ok; } @@ -491,14 +493,22 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons ClassDB::get_property_list(current_extension->class_name, p_list, true, this); if (current_extension->get_property_list) { - uint32_t pcount; - const GDExtensionPropertyInfo *pinfo = current_extension->get_property_list(_extension_instance, &pcount); - for (uint32_t i = 0; i < pcount; i++) { - p_list->push_back(PropertyInfo(pinfo[i])); - } - if (current_extension->free_property_list) { - current_extension->free_property_list(_extension_instance, pinfo); +#ifdef TOOLS_ENABLED + // If this is a placeholder, we can't call into the GDExtension on the parent class, + // because we don't have a real instance of the class to give it. + if (likely(!_extension->is_placeholder)) { +#endif + uint32_t pcount; + const GDExtensionPropertyInfo *pinfo = current_extension->get_property_list(_extension_instance, &pcount); + for (uint32_t i = 0; i < pcount; i++) { + p_list->push_back(PropertyInfo(pinfo[i])); + } + if (current_extension->free_property_list) { + current_extension->free_property_list(_extension_instance, pinfo); + } +#ifdef TOOLS_ENABLED } +#endif } current_extension = current_extension->parent; @@ -678,6 +688,59 @@ bool Object::has_method(const StringName &p_method) const { return false; } +int Object::_get_method_argument_count_bind(const StringName &p_method) const { + return get_method_argument_count(p_method); +} + +int Object::get_method_argument_count(const StringName &p_method, bool *r_is_valid) const { + if (p_method == CoreStringNames::get_singleton()->_free) { + if (r_is_valid) { + *r_is_valid = true; + } + return 0; + } + + if (script_instance) { + bool valid = false; + int ret = script_instance->get_method_argument_count(p_method, &valid); + if (valid) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret; + } + } + + { + bool valid = false; + int ret = ClassDB::get_method_argument_count(get_class_name(), p_method, &valid); + if (valid) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret; + } + } + + const Script *scr = Object::cast_to<Script>(this); + while (scr != nullptr) { + bool valid = false; + int ret = scr->get_script_method_argument_count(p_method, &valid); + if (valid) { + if (r_is_valid) { + *r_is_valid = true; + } + return ret; + } + scr = scr->get_base_script().ptr(); + } + + if (r_is_valid) { + *r_is_valid = false; + } + return 0; +} + Variant Object::getvar(const Variant &p_key, bool *r_valid) const { if (r_valid) { *r_valid = false; @@ -727,7 +790,7 @@ Variant Object::callp(const StringName &p_method, const Variant **p_args, int p_ r_error.expected = 0; return Variant(); } - if (Object::cast_to<RefCounted>(this)) { + if (is_ref_counted()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; ERR_FAIL_V_MSG(Variant(), "Can't 'free' a reference."); } @@ -1037,10 +1100,19 @@ bool Object::_has_user_signal(const StringName &p_name) const { return signal_map[p_name].user.name.length() > 0; } -struct _ObjectSignalDisconnectData { - StringName signal; - Callable callable; -}; +void Object::_remove_user_signal(const StringName &p_name) { + SignalData *s = signal_map.getptr(p_name); + ERR_FAIL_NULL_MSG(s, "Provided signal does not exist."); + ERR_FAIL_COND_MSG(!s->removable, "Signal is not removable (not added with add_user_signal)."); + for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) { + Object *target = slot_kv.key.get_object(); + if (likely(target)) { + target->connections.erase(slot_kv.value.cE); + } + } + + signal_map.erase(p_name); +} Error Object::_emit_signal(const Variant **p_args, int p_argcount, Callable::CallError &r_error) { if (unlikely(p_argcount < 1)) { @@ -1090,26 +1162,43 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int // which is needed in certain edge cases; e.g., https://github.com/godotengine/godot/issues/73889. Ref<RefCounted> rc = Ref<RefCounted>(Object::cast_to<RefCounted>(this)); - List<_ObjectSignalDisconnectData> disconnect_data; - // Ensure that disconnecting the signal or even deleting the object // will not affect the signal calling. - LocalVector<Connection> slot_conns; - slot_conns.resize(s->slot_map.size()); - { - uint32_t idx = 0; - for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) { - slot_conns[idx++] = slot_kv.value.conn; + Callable *slot_callables = (Callable *)alloca(sizeof(Callable) * s->slot_map.size()); + uint32_t *slot_flags = (uint32_t *)alloca(sizeof(uint32_t) * s->slot_map.size()); + uint32_t slot_count = 0; + + for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) { + memnew_placement(&slot_callables[slot_count], Callable(slot_kv.value.conn.callable)); + slot_flags[slot_count] = slot_kv.value.conn.flags; + ++slot_count; + } + + DEV_ASSERT(slot_count == s->slot_map.size()); + + // Disconnect all one-shot connections before emitting to prevent recursion. + for (uint32_t i = 0; i < slot_count; ++i) { + bool disconnect = slot_flags[i] & CONNECT_ONE_SHOT; +#ifdef TOOLS_ENABLED + if (disconnect && (slot_flags[i] & CONNECT_PERSIST) && Engine::get_singleton()->is_editor_hint()) { + // This signal was connected from the editor, and is being edited. Just don't disconnect for now. + disconnect = false; + } +#endif + if (disconnect) { + _disconnect(p_name, slot_callables[i]); } - DEV_ASSERT(idx == s->slot_map.size()); } OBJ_DEBUG_LOCK Error err = OK; - for (const Connection &c : slot_conns) { - if (!c.callable.is_valid()) { + for (uint32_t i = 0; i < slot_count; ++i) { + const Callable &callable = slot_callables[i]; + const uint32_t &flags = slot_flags[i]; + + if (!callable.is_valid()) { // Target might have been deleted during signal callback, this is expected and OK. continue; } @@ -1117,51 +1206,34 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int const Variant **args = p_args; int argc = p_argcount; - if (c.flags & CONNECT_DEFERRED) { - MessageQueue::get_singleton()->push_callablep(c.callable, args, argc, true); + if (flags & CONNECT_DEFERRED) { + MessageQueue::get_singleton()->push_callablep(callable, args, argc, true); } else { Callable::CallError ce; _emitting = true; Variant ret; - c.callable.callp(args, argc, ret, ce); + callable.callp(args, argc, ret, ce); _emitting = false; if (ce.error != Callable::CallError::CALL_OK) { #ifdef DEBUG_ENABLED - if (c.flags & CONNECT_PERSIST && Engine::get_singleton()->is_editor_hint() && (script.is_null() || !Ref<Script>(script)->is_tool())) { + if (flags & CONNECT_PERSIST && Engine::get_singleton()->is_editor_hint() && (script.is_null() || !Ref<Script>(script)->is_tool())) { continue; } #endif - Object *target = c.callable.get_object(); + Object *target = callable.get_object(); if (ce.error == Callable::CallError::CALL_ERROR_INVALID_METHOD && target && !ClassDB::class_exists(target->get_class_name())) { //most likely object is not initialized yet, do not throw error. } else { - ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(c.callable, args, argc, ce) + "."); + ERR_PRINT("Error calling from signal '" + String(p_name) + "' to callable: " + Variant::get_callable_error_text(callable, args, argc, ce) + "."); err = ERR_METHOD_NOT_FOUND; } } } - - bool disconnect = c.flags & CONNECT_ONE_SHOT; -#ifdef TOOLS_ENABLED - if (disconnect && (c.flags & CONNECT_PERSIST) && Engine::get_singleton()->is_editor_hint()) { - //this signal was connected from the editor, and is being edited. just don't disconnect for now - disconnect = false; - } -#endif - if (disconnect) { - _ObjectSignalDisconnectData dd; - dd.signal = p_name; - dd.callable = c.callable; - disconnect_data.push_back(dd); - } } - while (!disconnect_data.is_empty()) { - const _ObjectSignalDisconnectData &dd = disconnect_data.front()->get(); - - _disconnect(dd.signal, dd.callable); - disconnect_data.pop_front(); + for (uint32_t i = 0; i < slot_count; ++i) { + slot_callables[i].~Callable(); } return err; @@ -1175,8 +1247,8 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { MethodInfo mi; mi.name = p_name; - for (int i = 0; i < p_args.size(); i++) { - Dictionary d = p_args[i]; + for (const Variant &arg : p_args) { + Dictionary d = arg; PropertyInfo param; if (d.has("name")) { @@ -1190,6 +1262,10 @@ void Object::_add_user_signal(const String &p_name, const Array &p_args) { } add_user_signal(mi); + + if (signal_map.has(p_name)) { + signal_map.getptr(p_name)->removable = true; + } } TypedArray<Dictionary> Object::_get_signal_list() const { @@ -1346,12 +1422,10 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui s = &signal_map[p_signal]; } - Callable target = p_callable; - //compare with the base callable, so binds can be ignored - if (s->slot_map.has(*target.get_base_comparator())) { + if (s->slot_map.has(*p_callable.get_base_comparator())) { if (p_flags & CONNECT_REFERENCE_COUNTED) { - s->slot_map[*target.get_base_comparator()].reference_count++; + s->slot_map[*p_callable.get_base_comparator()].reference_count++; return OK; } else { ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, "Signal '" + p_signal + "' is already connected to given callable '" + p_callable + "' in that object."); @@ -1363,7 +1437,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui SignalData::Slot slot; Connection conn; - conn.callable = target; + conn.callable = p_callable; conn.signal = ::Signal(this, p_signal); conn.flags = p_flags; slot.conn = conn; @@ -1375,7 +1449,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui } //use callable version as key, so binds can be ignored - s->slot_map[*target.get_base_comparator()] = slot; + s->slot_map[*p_callable.get_base_comparator()] = slot; return OK; } @@ -1396,9 +1470,7 @@ bool Object::is_connected(const StringName &p_signal, const Callable &p_callable ERR_FAIL_V_MSG(false, "Nonexistent signal: " + p_signal + "."); } - Callable target = p_callable; - - return s->slot_map.has(*target.get_base_comparator()); + return s->slot_map.has(*p_callable.get_base_comparator()); } void Object::disconnect(const StringName &p_signal, const Callable &p_callable) { @@ -1467,6 +1539,7 @@ void Object::initialize_class() { } ClassDB::_add_class<Object>(); _bind_methods(); + _bind_compatibility_methods(); initialized = true; } @@ -1475,11 +1548,16 @@ String Object::tr(const StringName &p_message, const StringName &p_context) cons return p_message; } - if (Engine::get_singleton()->is_editor_hint()) { + if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) { + String tr_msg = TranslationServer::get_singleton()->extractable_translate(p_message, p_context); + if (!tr_msg.is_empty() && tr_msg != p_message) { + return tr_msg; + } + return TranslationServer::get_singleton()->tool_translate(p_message, p_context); - } else { - return TranslationServer::get_singleton()->translate(p_message, p_context); } + + return TranslationServer::get_singleton()->translate(p_message, p_context); } String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { @@ -1491,11 +1569,16 @@ String Object::tr_n(const StringName &p_message, const StringName &p_message_plu return p_message_plural; } - if (Engine::get_singleton()->is_editor_hint()) { + if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) { + String tr_msg = TranslationServer::get_singleton()->extractable_translate_plural(p_message, p_message_plural, p_n, p_context); + if (!tr_msg.is_empty() && tr_msg != p_message && tr_msg != p_message_plural) { + return tr_msg; + } + return TranslationServer::get_singleton()->tool_translate_plural(p_message, p_message_plural, p_n, p_context); - } else { - return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context); } + + return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context); } void Object::_clear_internal_resource_paths(const Variant &p_var) { @@ -1520,8 +1603,8 @@ void Object::_clear_internal_resource_paths(const Variant &p_var) { } break; case Variant::ARRAY: { Array a = p_var; - for (int i = 0; i < a.size(); i++) { - _clear_internal_resource_paths(a[i]); + for (const Variant &var : a) { + _clear_internal_resource_paths(var); } } break; @@ -1596,6 +1679,7 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("add_user_signal", "signal", "arguments"), &Object::_add_user_signal, DEFVAL(Array())); ClassDB::bind_method(D_METHOD("has_user_signal", "signal"), &Object::_has_user_signal); + ClassDB::bind_method(D_METHOD("remove_user_signal", "signal"), &Object::_remove_user_signal); { MethodInfo mi; @@ -1627,6 +1711,8 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("has_method", "method"), &Object::has_method); + ClassDB::bind_method(D_METHOD("get_method_argument_count", "method"), &Object::_get_method_argument_count_bind); + ClassDB::bind_method(D_METHOD("has_signal", "signal"), &Object::has_signal); ClassDB::bind_method(D_METHOD("get_signal_list"), &Object::_get_signal_list); ClassDB::bind_method(D_METHOD("get_signal_connection_list", "signal"), &Object::_get_signal_connection_list); @@ -1642,8 +1728,8 @@ void Object::_bind_methods() { ClassDB::bind_method(D_METHOD("set_message_translation", "enable"), &Object::set_message_translation); ClassDB::bind_method(D_METHOD("can_translate_messages"), &Object::can_translate_messages); - ClassDB::bind_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL("")); - ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL("")); + ClassDB::bind_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(StringName())); + ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(StringName())); ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion); ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free); @@ -1686,6 +1772,7 @@ void Object::_bind_methods() { BIND_CONSTANT(NOTIFICATION_POSTINITIALIZE); BIND_CONSTANT(NOTIFICATION_PREDELETE); + BIND_CONSTANT(NOTIFICATION_EXTENSION_RELOADED); BIND_ENUM_CONSTANT(CONNECT_DEFERRED); BIND_ENUM_CONSTANT(CONNECT_PERSIST); @@ -1794,6 +1881,16 @@ uint32_t Object::get_edited_version() const { #endif StringName Object::get_class_name_for_extension(const GDExtension *p_library) const { +#ifdef TOOLS_ENABLED + // If this is the library this extension comes from and it's a placeholder, we + // have to return the closest native parent's class name, so that it doesn't try to + // use this like the real object. + if (unlikely(_extension && _extension->library == p_library && _extension->is_placeholder)) { + const StringName *class_name = _get_class_namev(); + return *class_name; + } +#endif + // Only return the class name per the extension if it matches the given p_library. if (_extension && _extension->library == p_library) { return _extension->class_name; @@ -1921,13 +2018,15 @@ void Object::clear_internal_extension() { // 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); + if (_instance_bindings) { + 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_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. @@ -2066,15 +2165,17 @@ void ObjectDB::debug_objects(DebugFunc p_func) { spin_lock.unlock(); } +#ifdef TOOLS_ENABLED void Object::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const { + const String pf = p_function; if (p_idx == 0) { - if (p_function == "connect" || p_function == "is_connected" || p_function == "disconnect" || p_function == "emit_signal" || p_function == "has_signal") { + if (pf == "connect" || pf == "is_connected" || pf == "disconnect" || pf == "emit_signal" || pf == "has_signal") { List<MethodInfo> signals; get_signal_list(&signals); for (const MethodInfo &E : signals) { r_options->push_back(E.name.quote()); } - } else if (p_function == "call" || p_function == "call_deferred" || p_function == "callv" || p_function == "has_method") { + } else if (pf == "call" || pf == "call_deferred" || pf == "callv" || pf == "has_method") { List<MethodInfo> methods; get_method_list(&methods); for (const MethodInfo &E : methods) { @@ -2083,7 +2184,7 @@ void Object::get_argument_options(const StringName &p_function, int p_idx, List< } r_options->push_back(E.name.quote()); } - } else if (p_function == "set" || p_function == "set_deferred" || p_function == "get") { + } else if (pf == "set" || pf == "set_deferred" || pf == "get") { List<PropertyInfo> properties; get_property_list(&properties); for (const PropertyInfo &E : properties) { @@ -2091,13 +2192,13 @@ void Object::get_argument_options(const StringName &p_function, int p_idx, List< r_options->push_back(E.name.quote()); } } - } else if (p_function == "set_meta" || p_function == "get_meta" || p_function == "has_meta" || p_function == "remove_meta") { + } else if (pf == "set_meta" || pf == "get_meta" || pf == "has_meta" || pf == "remove_meta") { for (const KeyValue<StringName, Variant> &K : metadata) { r_options->push_back(String(K.key).quote()); } } } else if (p_idx == 2) { - if (p_function == "connect") { + if (pf == "connect") { // Ideally, the constants should be inferred by the parameter. // But a parameter's PropertyInfo does not store the enum they come from, so this will do for now. List<StringName> constants; @@ -2108,6 +2209,7 @@ void Object::get_argument_options(const StringName &p_function, int p_idx, List< } } } +#endif SpinLock ObjectDB::spin_lock; uint32_t ObjectDB::slot_count = 0; @@ -2225,8 +2327,9 @@ void ObjectDB::cleanup() { extra_info = " - Resource path: " + String(resource_get_path->call(obj, nullptr, 0, call_error)); } - uint64_t id = uint64_t(i) | (uint64_t(object_slots[i].validator) << OBJECTDB_VALIDATOR_BITS) | (object_slots[i].is_ref_counted ? OBJECTDB_REFERENCE_BIT : 0); - print_line("Leaked instance: " + String(obj->get_class()) + ":" + itos(id) + extra_info); + uint64_t id = uint64_t(i) | (uint64_t(object_slots[i].validator) << OBJECTDB_SLOT_MAX_COUNT_BITS) | (object_slots[i].is_ref_counted ? OBJECTDB_REFERENCE_BIT : 0); + DEV_ASSERT(id == (uint64_t)obj->get_instance_id()); // We could just use the id from the object, but this check may help catching memory corruption catastrophes. + print_line("Leaked instance: " + String(obj->get_class()) + ":" + uitos(id) + extra_info); count--; } |