/**************************************************************************/ /* class_db.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 "class_db.h" #include "core/config/engine.h" #include "core/core_string_names.h" #include "core/io/resource_loader.h" #include "core/object/script_language.h" #include "core/os/mutex.h" #include "core/version.h" #define OBJTYPE_RLOCK RWLockRead _rw_lockr_(lock); #define OBJTYPE_WLOCK RWLockWrite _rw_lockw_(lock); #ifdef DEBUG_METHODS_ENABLED MethodDefinition D_METHODP(const char *p_name, const char *const **p_args, uint32_t p_argcount) { MethodDefinition md; md.name = StaticCString::create(p_name); md.args.resize(p_argcount); for (uint32_t i = 0; i < p_argcount; i++) { md.args.write[i] = StaticCString::create(*p_args[i]); } return md; } #endif ClassDB::APIType ClassDB::current_api = API_CORE; HashMap ClassDB::api_hashes_cache; void ClassDB::set_current_api(APIType p_api) { DEV_ASSERT(!api_hashes_cache.has(p_api)); // This API type may not be suitable for caching of hash if it can change later. current_api = p_api; } ClassDB::APIType ClassDB::get_current_api() { return current_api; } HashMap ClassDB::classes; HashMap ClassDB::resource_base_extensions; HashMap ClassDB::compat_classes; bool ClassDB::_is_parent_class(const StringName &p_class, const StringName &p_inherits) { if (!classes.has(p_class)) { return false; } StringName inherits = p_class; while (inherits.operator String().length()) { if (inherits == p_inherits) { return true; } inherits = _get_parent_class(inherits); } return false; } bool ClassDB::is_parent_class(const StringName &p_class, const StringName &p_inherits) { OBJTYPE_RLOCK; return _is_parent_class(p_class, p_inherits); } void ClassDB::get_class_list(List *p_classes) { OBJTYPE_RLOCK; for (const KeyValue &E : classes) { p_classes->push_back(E.key); } p_classes->sort_custom(); } #ifdef TOOLS_ENABLED void ClassDB::get_extensions_class_list(List *p_classes) { OBJTYPE_RLOCK; for (const KeyValue &E : classes) { if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) { continue; } p_classes->push_back(E.key); } p_classes->sort_custom(); } #endif void ClassDB::get_inheriters_from_class(const StringName &p_class, List *p_classes) { OBJTYPE_RLOCK; for (const KeyValue &E : classes) { if (E.key != p_class && _is_parent_class(E.key, p_class)) { p_classes->push_back(E.key); } } } void ClassDB::get_direct_inheriters_from_class(const StringName &p_class, List *p_classes) { OBJTYPE_RLOCK; for (const KeyValue &E : classes) { if (E.key != p_class && _get_parent_class(E.key) == p_class) { p_classes->push_back(E.key); } } } StringName ClassDB::get_parent_class_nocheck(const StringName &p_class) { OBJTYPE_RLOCK; ClassInfo *ti = classes.getptr(p_class); if (!ti) { return StringName(); } return ti->inherits; } StringName ClassDB::get_compatibility_remapped_class(const StringName &p_class) { if (classes.has(p_class)) { return p_class; } if (compat_classes.has(p_class)) { return compat_classes[p_class]; } return p_class; } StringName ClassDB::_get_parent_class(const StringName &p_class) { ClassInfo *ti = classes.getptr(p_class); ERR_FAIL_NULL_V_MSG(ti, StringName(), "Cannot get class '" + String(p_class) + "'."); return ti->inherits; } StringName ClassDB::get_parent_class(const StringName &p_class) { OBJTYPE_RLOCK; return _get_parent_class(p_class); } ClassDB::APIType ClassDB::get_api_type(const StringName &p_class) { OBJTYPE_RLOCK; ClassInfo *ti = classes.getptr(p_class); ERR_FAIL_NULL_V_MSG(ti, API_NONE, "Cannot get class '" + String(p_class) + "'."); return ti->api; } uint32_t ClassDB::get_api_hash(APIType p_api) { #ifdef DEBUG_METHODS_ENABLED OBJTYPE_WLOCK; if (api_hashes_cache.has(p_api)) { return api_hashes_cache[p_api]; } uint64_t hash = hash_murmur3_one_64(HashMapHasherDefault::hash(VERSION_FULL_CONFIG)); List class_list; for (const KeyValue &E : classes) { class_list.push_back(E.key); } // Must be alphabetically sorted for hash to compute. class_list.sort_custom(); for (const StringName &E : class_list) { ClassInfo *t = classes.getptr(E); ERR_FAIL_NULL_V_MSG(t, 0, "Cannot get class '" + String(E) + "'."); if (t->api != p_api || !t->exposed) { continue; } hash = hash_murmur3_one_64(t->name.hash(), hash); hash = hash_murmur3_one_64(t->inherits.hash(), hash); { //methods List snames; for (const KeyValue &F : t->method_map) { String name = F.key.operator String(); ERR_CONTINUE(name.is_empty()); if (name[0] == '_') { continue; // Ignore non-virtual methods that start with an underscore } snames.push_back(F.key); } snames.sort_custom(); for (const StringName &F : snames) { MethodBind *mb = t->method_map[F]; hash = hash_murmur3_one_64(mb->get_name().hash(), hash); hash = hash_murmur3_one_64(mb->get_argument_count(), hash); hash = hash_murmur3_one_64(mb->get_argument_type(-1), hash); //return for (int i = 0; i < mb->get_argument_count(); i++) { const PropertyInfo info = mb->get_argument_info(i); hash = hash_murmur3_one_64(info.type, hash); hash = hash_murmur3_one_64(info.name.hash(), hash); hash = hash_murmur3_one_64(info.hint, hash); hash = hash_murmur3_one_64(info.hint_string.hash(), hash); } hash = hash_murmur3_one_64(mb->get_default_argument_count(), 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); } } { //constants List snames; for (const KeyValue &F : t->constant_map) { snames.push_back(F.key); } snames.sort_custom(); for (const StringName &F : snames) { hash = hash_murmur3_one_64(F.hash(), hash); hash = hash_murmur3_one_64(t->constant_map[F], hash); } } { //signals List snames; for (const KeyValue &F : t->signal_map) { snames.push_back(F.key); } snames.sort_custom(); for (const StringName &F : snames) { MethodInfo &mi = t->signal_map[F]; hash = hash_murmur3_one_64(F.hash(), hash); for (int i = 0; i < mi.arguments.size(); i++) { hash = hash_murmur3_one_64(mi.arguments[i].type, hash); } } } { //properties List snames; for (const KeyValue &F : t->property_setget) { snames.push_back(F.key); } snames.sort_custom(); for (const StringName &F : snames) { PropertySetGet *psg = t->property_setget.getptr(F); ERR_FAIL_NULL_V(psg, 0); hash = hash_murmur3_one_64(F.hash(), hash); hash = hash_murmur3_one_64(psg->setter.hash(), hash); hash = hash_murmur3_one_64(psg->getter.hash(), hash); } } //property list for (const PropertyInfo &F : t->property_list) { hash = hash_murmur3_one_64(F.name.hash(), hash); hash = hash_murmur3_one_64(F.type, hash); hash = hash_murmur3_one_64(F.hint, hash); hash = hash_murmur3_one_64(F.hint_string.hash(), hash); hash = hash_murmur3_one_64(F.usage, hash); } } hash = hash_fmix32(hash); // Extension API changes at runtime; let's just not cache them by now. if (p_api != API_EXTENSION && p_api != API_EDITOR_EXTENSION) { api_hashes_cache[p_api] = hash; } return hash; #else return 0; #endif } bool ClassDB::class_exists(const StringName &p_class) { OBJTYPE_RLOCK; return classes.has(p_class); } void ClassDB::add_compatibility_class(const StringName &p_class, const StringName &p_fallback) { OBJTYPE_WLOCK; compat_classes[p_class] = p_fallback; } StringName ClassDB::get_compatibility_class(const StringName &p_class) { if (compat_classes.has(p_class)) { return compat_classes[p_class]; } return StringName(); } Object *ClassDB::instantiate(const StringName &p_class) { ClassInfo *ti; { OBJTYPE_RLOCK; ti = classes.getptr(p_class); if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[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_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()) { ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor."); return nullptr; } #endif if (ti->gdextension && ti->gdextension->create_instance) { 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_NULL(p_object); ClassInfo *ti; { OBJTYPE_RLOCK; ti = classes.getptr(p_class); if (!ti || ti->disabled || !ti->creation_func || (ti->gdextension && !ti->gdextension->create_instance)) { if (compat_classes.has(p_class)) { ti = classes.getptr(compat_classes[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_NULL_MSG(ti->gdextension, "Class '" + String(p_class) + "' has no native extension."); } p_object->_extension = ti->gdextension; p_object->_extension_instance = p_instance; } bool ClassDB::can_instantiate(const StringName &p_class) { OBJTYPE_RLOCK; ClassInfo *ti = classes.getptr(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