summaryrefslogtreecommitdiffstats
path: root/core/object/object.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/object/object.cpp')
-rw-r--r--core/object/object.cpp275
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--;
}