feat: updated godot version

This commit is contained in:
Sara Gerretsen 2026-04-04 19:38:56 +02:00
parent 0c508b0831
commit 42b028dbb5
4694 changed files with 236470 additions and 401376 deletions

View file

@ -30,7 +30,6 @@
#include "object.h"
#include "core/config/engine.h"
#include "core/extension/gdextension_manager.h"
#include "core/io/resource.h"
#include "core/object/class_db.h"
@ -66,45 +65,163 @@ struct _ObjectDebugLock {
#endif
struct ObjectSignalLock {
Mutex *mutex1 = nullptr;
Mutex *mutex2 = nullptr;
ObjectSignalLock(const Object *p_obj1, const Object *p_obj2 = nullptr) {
mutex1 = p_obj1->signal_mutex;
if (p_obj2 != nullptr) {
mutex2 = p_obj2->signal_mutex;
if (mutex2 < mutex1) {
// We must always lock two locks in the same order.
SWAP(mutex1, mutex2);
} else if (unlikely(mutex2 == mutex1)) {
// No point locking the same lock twice.
mutex2 = nullptr;
}
}
if (mutex1) {
mutex1->lock();
}
if (mutex2) {
mutex2->lock();
struct _ObjectSignalLock {
Mutex *mutex;
_ObjectSignalLock(const Object *const p_obj) {
mutex = p_obj->signal_mutex;
if (mutex) {
mutex->lock();
}
}
~ObjectSignalLock() {
if (mutex2) {
mutex2->unlock();
}
if (mutex1) {
mutex1->unlock();
~_ObjectSignalLock() {
if (mutex) {
mutex->unlock();
}
}
};
#define OBJ_SIGNAL_LOCK _ObjectSignalLock _signal_lock(this);
PropertyInfo::operator Dictionary() const {
Dictionary d;
d["name"] = name;
d["class_name"] = class_name;
d["type"] = type;
d["hint"] = hint;
d["hint_string"] = hint_string;
d["usage"] = usage;
return d;
}
PropertyInfo PropertyInfo::from_dict(const Dictionary &p_dict) {
PropertyInfo pi;
if (p_dict.has("type")) {
pi.type = Variant::Type(int(p_dict["type"]));
}
if (p_dict.has("name")) {
pi.name = p_dict["name"];
}
if (p_dict.has("class_name")) {
pi.class_name = p_dict["class_name"];
}
if (p_dict.has("hint")) {
pi.hint = PropertyHint(int(p_dict["hint"]));
}
if (p_dict.has("hint_string")) {
pi.hint_string = p_dict["hint_string"];
}
if (p_dict.has("usage")) {
pi.usage = p_dict["usage"];
}
return pi;
}
TypedArray<Dictionary> convert_property_list(const List<PropertyInfo> *p_list) {
TypedArray<Dictionary> va;
for (const List<PropertyInfo>::Element *E = p_list->front(); E; E = E->next()) {
va.push_back(Dictionary(E->get()));
}
return va;
}
TypedArray<Dictionary> convert_property_list(const Vector<PropertyInfo> &p_vector) {
TypedArray<Dictionary> va;
for (const PropertyInfo &E : p_vector) {
va.push_back(Dictionary(E));
}
return va;
}
MethodInfo::operator Dictionary() const {
Dictionary d;
d["name"] = name;
d["args"] = convert_property_list(arguments);
Array da;
for (int i = 0; i < default_arguments.size(); i++) {
da.push_back(default_arguments[i]);
}
d["default_args"] = da;
d["flags"] = flags;
d["id"] = id;
Dictionary r = return_val;
d["return"] = r;
return d;
}
MethodInfo MethodInfo::from_dict(const Dictionary &p_dict) {
MethodInfo mi;
if (p_dict.has("name")) {
mi.name = p_dict["name"];
}
Array args;
if (p_dict.has("args")) {
args = p_dict["args"];
}
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 (const Variant &defarg : defargs) {
mi.default_arguments.push_back(defarg);
}
if (p_dict.has("return")) {
mi.return_val = PropertyInfo::from_dict(p_dict["return"]);
}
if (p_dict.has("flags")) {
mi.flags = p_dict["flags"];
}
return mi;
}
uint32_t MethodInfo::get_compatibility_hash() const {
bool has_return = (return_val.type != Variant::NIL) || (return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT);
uint32_t hash = hash_murmur3_one_32(has_return);
hash = hash_murmur3_one_32(arguments.size(), hash);
if (has_return) {
hash = hash_murmur3_one_32(return_val.type, hash);
if (return_val.class_name != StringName()) {
hash = hash_murmur3_one_32(return_val.class_name.hash(), hash);
}
}
for (const PropertyInfo &arg : arguments) {
hash = hash_murmur3_one_32(arg.type, hash);
if (arg.class_name != StringName()) {
hash = hash_murmur3_one_32(arg.class_name.hash(), hash);
}
}
hash = hash_murmur3_one_32(default_arguments.size(), hash);
for (const Variant &v : default_arguments) {
hash = hash_murmur3_one_32(v.hash(), hash);
}
hash = hash_murmur3_one_32(flags & METHOD_FLAG_CONST ? 1 : 0, hash);
hash = hash_murmur3_one_32(flags & METHOD_FLAG_VARARG ? 1 : 0, hash);
return hash_fmix32(hash);
}
Object::Connection::operator Variant() const {
Dictionary d;
d["signal"] = signal;
@ -117,7 +234,6 @@ void ObjectGDExtension::create_gdtype() {
ERR_FAIL_COND(gdtype);
gdtype = memnew(GDType(ClassDB::get_gdtype(parent_class_name), class_name));
gdtype->initialize();
}
void ObjectGDExtension::destroy_gdtype() {
@ -500,8 +616,9 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons
_get_property_listv(p_list, p_reversed);
const uint32_t base_script_usage = is_class(Script::get_class_static()) ? PROPERTY_USAGE_NO_EDITOR : PROPERTY_USAGE_DEFAULT;
p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, Script::get_class_static(), base_script_usage | PROPERTY_USAGE_INTERNAL | PROPERTY_USAGE_NEVER_DUPLICATE));
if (!is_class("Script")) { // can still be set, but this is for user-friendliness
p_list->push_back(PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NEVER_DUPLICATE));
}
if (script_instance && !p_reversed) {
script_instance->get_property_list(p_list);
@ -513,10 +630,10 @@ void Object::get_property_list(List<PropertyInfo> *p_list, bool p_reversed) cons
pi.hint = PROPERTY_HINT_RESOURCE_TYPE;
Object *obj = K.value;
if (Object::cast_to<Script>(obj)) {
pi.hint_string = Script::get_class_static();
pi.hint_string = "Script";
pi.usage |= PROPERTY_USAGE_NEVER_DUPLICATE;
} else {
pi.hint_string = Resource::get_class_static();
pi.hint_string = "Resource";
}
}
p_list->push_back(pi);
@ -1093,7 +1210,7 @@ void Object::add_user_signal(const MethodInfo &p_signal) {
ERR_FAIL_COND_MSG(p_signal.name.is_empty(), "Signal name cannot be empty.");
ERR_FAIL_COND_MSG(ClassDB::has_signal(get_class_name(), p_signal.name), vformat("User signal's name conflicts with a built-in signal of '%s'.", get_class_name()));
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
ERR_FAIL_COND_MSG(signal_map.has(p_signal.name), vformat("Trying to add already existing signal '%s'.", p_signal.name));
SignalData s;
@ -1102,7 +1219,7 @@ void Object::add_user_signal(const MethodInfo &p_signal) {
}
bool Object::_has_user_signal(const StringName &p_name) const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
if (!signal_map.has(p_name)) {
return false;
@ -1111,26 +1228,19 @@ bool Object::_has_user_signal(const StringName &p_name) const {
}
void Object::_remove_user_signal(const StringName &p_name) {
HashMap<Callable, Object::SignalData::Slot> slots_to_disconnect;
OBJ_SIGNAL_LOCK
{
ObjectSignalLock signal_lock(this);
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).");
slots_to_disconnect = std::move(s->slot_map);
signal_map.erase(p_name);
}
for (const KeyValue<Callable, SignalData::Slot> &slot_kv : slots_to_disconnect) {
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)) {
ObjectSignalLock signal_lock(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) {
@ -1176,7 +1286,7 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
uint32_t slot_count = 0;
{
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
SignalData *s = signal_map.getptr(p_name);
if (!s) {
@ -1203,19 +1313,19 @@ Error Object::emit_signalp(const StringName &p_name, const Variant **p_args, int
}
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;
// 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;
}
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]);
if (disconnect) {
_disconnect(p_name, slot_callables[i]);
}
}
}
@ -1331,16 +1441,12 @@ void Object::_reset_gdtype() const {
}
}
void Object::autorelease_gdtype(GDType **r_type) {
ClassDB::gdtype_autorelease_pool.push_back(r_type);
}
void Object::_add_user_signal(const String &p_name, const Array &p_args) {
// this version of add_user_signal is meant to be used from scripts or external apis
// without access to ADD_SIGNAL in bind_methods
// added events are per instance, as opposed to the other ones, which are global
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
MethodInfo mi;
mi.name = p_name;
@ -1394,8 +1500,6 @@ TypedArray<Dictionary> Object::_get_signal_connection_list(const StringName &p_s
}
TypedArray<Dictionary> Object::_get_incoming_connections() const {
ObjectSignalLock signal_lock(this);
TypedArray<Dictionary> ret;
for (const Object::Connection &connection : connections) {
ret.push_back(connection);
@ -1421,7 +1525,7 @@ bool Object::has_signal(const StringName &p_name) const {
}
void Object::get_signal_list(List<MethodInfo> *p_signals) const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
if (script_instance) {
script_instance->get_script()->get_script_signal_list(p_signals);
@ -1439,7 +1543,7 @@ void Object::get_signal_list(List<MethodInfo> *p_signals) const {
}
void Object::get_all_signal_connections(List<Connection> *p_connections) const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
for (const KeyValue<StringName, SignalData> &E : signal_map) {
const SignalData *s = &E.value;
@ -1451,7 +1555,7 @@ void Object::get_all_signal_connections(List<Connection> *p_connections) const {
}
void Object::get_signal_connection_list(const StringName &p_signal, List<Connection> *p_connections) const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@ -1464,7 +1568,7 @@ void Object::get_signal_connection_list(const StringName &p_signal, List<Connect
}
int Object::get_persistent_signal_connection_count() const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
int count = 0;
for (const KeyValue<StringName, SignalData> &E : signal_map) {
@ -1481,7 +1585,7 @@ int Object::get_persistent_signal_connection_count() const {
}
uint32_t Object::get_signal_connection_flags(const StringName &p_name, const Callable &p_callable) const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
const SignalData *signal_data = signal_map.getptr(p_name);
if (signal_data) {
const SignalData::Slot *slot = signal_data->slot_map.getptr(p_callable);
@ -1493,7 +1597,7 @@ uint32_t Object::get_signal_connection_flags(const StringName &p_name, const Cal
}
void Object::get_signals_connected_to_this(List<Connection> *p_connections) const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
for (const Connection &E : connections) {
p_connections->push_back(E);
@ -1502,14 +1606,13 @@ void Object::get_signals_connected_to_this(List<Connection> *p_connections) cons
Error Object::connect(const StringName &p_signal, const Callable &p_callable, uint32_t p_flags) {
ERR_FAIL_COND_V_MSG(p_callable.is_null(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is null.", p_signal));
Object *target_object = p_callable.get_object();
ObjectSignalLock signal_lock(this, target_object);
OBJ_SIGNAL_LOCK
if (p_callable.is_standard()) {
// FIXME: This branch should probably removed in favor of the `is_valid()` branch, but there exist some classes
// that call `connect()` before they are fully registered with ClassDB. Until all such classes can be found
// and registered soon enough this branch is needed to allow `connect()` to succeed.
ERR_FAIL_NULL_V_MSG(target_object, ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s' to callable '%s': the callable object is null.", p_signal, p_callable));
ERR_FAIL_NULL_V_MSG(p_callable.get_object(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s' to callable '%s': the callable object is null.", p_signal, p_callable));
} else {
ERR_FAIL_COND_V_MSG(!p_callable.is_valid(), ERR_INVALID_PARAMETER, vformat("Cannot connect to '%s': the provided callable is not valid: '%s'.", p_signal, p_callable));
}
@ -1548,6 +1651,8 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
}
}
Object *target_object = p_callable.get_object();
SignalData::Slot slot;
Connection conn;
@ -1570,7 +1675,7 @@ Error Object::connect(const StringName &p_signal, const Callable &p_callable, ui
bool Object::is_connected(const StringName &p_signal, const Callable &p_callable) const {
ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot determine if connected to '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@ -1590,7 +1695,7 @@ bool Object::is_connected(const StringName &p_signal, const Callable &p_callable
}
bool Object::has_connections(const StringName &p_signal) const {
ObjectSignalLock signal_lock(this);
OBJ_SIGNAL_LOCK
const SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@ -1615,8 +1720,7 @@ void Object::disconnect(const StringName &p_signal, const Callable &p_callable)
bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable, bool p_force) {
ERR_FAIL_COND_V_MSG(p_callable.is_null(), false, vformat("Cannot disconnect from '%s': the provided callable is null.", p_signal)); // Should use `is_null`, see note in `connect` about the use of `is_valid`.
Object *target_object = p_callable.get_object();
ObjectSignalLock signal_lock(this, target_object);
OBJ_SIGNAL_LOCK
SignalData *s = signal_map.getptr(p_signal);
if (!s) {
@ -1637,8 +1741,11 @@ bool Object::_disconnect(const StringName &p_signal, const Callable &p_callable,
}
}
if (likely(target_object)) {
target_object->connections.erase(slot->cE);
if (slot->cE) {
Object *target_object = p_callable.get_object();
if (target_object) {
target_object->connections.erase(slot->cE);
}
}
s->slot_map.erase(*p_callable.get_base_comparator());
@ -1686,18 +1793,10 @@ Variant Object::_get_indexed_bind(const NodePath &p_name) const {
void Object::initialize_class() {
static bool initialized = false;
if (likely(initialized)) {
return;
}
static BinaryMutex __init_mutex;
MutexLock lock(__init_mutex);
if (initialized) {
// Initialized on another thread while we were waiting.
return;
}
_add_class_to_classdb(get_gdtype_static_mutable(), nullptr);
get_gdtype_static_mutable().initialize();
_add_class_to_classdb(get_gdtype_static(), nullptr);
_bind_methods();
_bind_compatibility_methods();
initialized = true;
@ -1773,7 +1872,7 @@ void Object::_clear_internal_resource_paths(const Variant &p_var) {
}
}
void Object::_add_class_to_classdb(GDType &p_type, const GDType *p_inherits) {
void Object::_add_class_to_classdb(const GDType &p_type, const GDType *p_inherits) {
ClassDB::_add_class(p_type, p_inherits);
}
@ -1974,15 +2073,11 @@ void Object::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_PREDELETE);
BIND_CONSTANT(NOTIFICATION_EXTENSION_RELOADED);
BIND_BITFIELD_FLAG(CONNECT_DEFERRED);
BIND_BITFIELD_FLAG(CONNECT_PERSIST);
BIND_BITFIELD_FLAG(CONNECT_ONE_SHOT);
BIND_BITFIELD_FLAG(CONNECT_REFERENCE_COUNTED);
BIND_BITFIELD_FLAG(CONNECT_APPEND_SOURCE_OBJECT);
}
void Object::call_deferredp(const StringName &p_method, const Variant **p_args, int p_argcount, bool p_show_error) {
MessageQueue::get_singleton()->push_callp(this, p_method, p_args, p_argcount);
BIND_ENUM_CONSTANT(CONNECT_DEFERRED);
BIND_ENUM_CONSTANT(CONNECT_PERSIST);
BIND_ENUM_CONSTANT(CONNECT_ONE_SHOT);
BIND_ENUM_CONSTANT(CONNECT_REFERENCE_COUNTED);
BIND_ENUM_CONSTANT(CONNECT_APPEND_SOURCE_OBJECT);
}
void Object::set_deferred(const StringName &p_property, const Variant &p_value) {
@ -2164,8 +2259,8 @@ void *Object::get_instance_binding(void *p_token, const GDExtensionInstanceBindi
}
}
if (unlikely(!binding && p_callbacks)) {
uint32_t current_size = Math::next_power_of_2(_instance_binding_count);
uint32_t new_size = Math::next_power_of_2(_instance_binding_count + 1);
uint32_t current_size = next_power_of_2(_instance_binding_count);
uint32_t new_size = next_power_of_2(_instance_binding_count + 1);
if (current_size == 0 || new_size > current_size) {
_instance_bindings = (InstanceBinding *)memrealloc(_instance_bindings, new_size * sizeof(InstanceBinding));
@ -2308,36 +2403,56 @@ void Object::detach_from_objectdb() {
}
}
void Object::assign_type_static(GDType **type_ptr, const char *p_name, const GDType *super_type) {
static BinaryMutex _mutex;
MutexLock lock(_mutex);
GDType *type = *type_ptr;
if (type) {
// Assigned while we were waiting.
return;
}
type = memnew(GDType(super_type, StringName(p_name)));
*type_ptr = type;
ClassDB::gdtype_autorelease_pool.push_back(type_ptr);
}
Object::~Object() {
if (_emitting) {
//@todo this may need to actually reach the debugger prioritarily somehow because it may crash before
ERR_PRINT(vformat("Object '%s' was freed or unreferenced while a signal is being emitted from it. Try connecting to the signal using 'CONNECT_DEFERRED' flag, or use queue_free() to free the object (if this object is a Node) to avoid this error and potential crashes.", to_string()));
}
// Drop all connections to the signals of this object.
while (signal_map.size()) {
// Avoid regular iteration so erasing is safe.
KeyValue<StringName, SignalData> &E = *signal_map.begin();
SignalData *s = &E.value;
{
OBJ_SIGNAL_LOCK
// Drop all connections to the signals of this object.
while (signal_map.size()) {
// Avoid regular iteration so erasing is safe.
KeyValue<StringName, SignalData> &E = *signal_map.begin();
SignalData *s = &E.value;
for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
Object *target = slot_kv.value.conn.callable.get_object();
if (likely(target)) {
ObjectSignalLock signal_lock(this, target);
target->connections.erase(slot_kv.value.cE);
for (const KeyValue<Callable, SignalData::Slot> &slot_kv : s->slot_map) {
Object *target = slot_kv.value.conn.callable.get_object();
if (likely(target)) {
target->connections.erase(slot_kv.value.cE);
}
}
signal_map.erase(E.key);
}
signal_map.erase(E.key);
}
// Disconnect signals that connect to this object.
while (connections.size()) {
Connection c = connections.front()->get();
bool disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
if (unlikely(!disconnected)) {
// If the disconnect has failed, abandon the connection to avoid getting trapped in an infinite loop here.
connections.pop_front();
// Disconnect signals that connect to this object.
while (connections.size()) {
Connection c = connections.front()->get();
Object *obj = c.callable.get_object();
bool disconnected = false;
if (likely(obj)) {
disconnected = c.signal.get_object()->_disconnect(c.signal.get_name(), c.callable, true);
}
if (unlikely(!disconnected)) {
// If the disconnect has failed, abandon the connection to avoid getting trapped in an infinite loop here.
connections.pop_front();
}
}
}
@ -2523,7 +2638,7 @@ void ObjectDB::cleanup() {
spin_lock.lock();
if (slot_count > 0) {
WARN_PRINT(vformat("%d ObjectDB %s leaked at exit (run with `--verbose` for details).", slot_count, slot_count == 1 ? "instance was" : "instances were"));
WARN_PRINT("ObjectDB instances leaked at exit (run with --verbose for details).");
if (OS::get_singleton()->is_stdout_verbose()) {
// Ensure calling the native classes because if a leaked instance has a script
// that overrides any of those methods, it'd not be OK to call them at this point,