/**************************************************************************/ /* 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/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; #ifdef TOOLS_ENABLED HashMap ClassDB::placeholder_extensions; class PlaceholderExtensionInstance { StringName class_name; HashMap properties; // Checks if a property is from a runtime class, and not a non-runtime base class. bool is_runtime_property(const StringName &p_property_name) { StringName current_class_name = class_name; while (ClassDB::is_class_runtime(current_class_name)) { if (ClassDB::has_property(current_class_name, p_property_name, true)) { return true; } current_class_name = ClassDB::get_parent_class(current_class_name); } return false; } public: PlaceholderExtensionInstance(const StringName &p_class_name) { class_name = p_class_name; } ~PlaceholderExtensionInstance() {} void set(const StringName &p_name, const Variant &p_value, bool &r_valid) { r_valid = is_runtime_property(p_name); if (r_valid) { properties[p_name] = p_value; } } Variant get(const StringName &p_name, bool &r_valid) { const Variant *value = properties.getptr(p_name); Variant ret; if (value) { ret = *value; r_valid = true; } else { r_valid = is_runtime_property(p_name); if (r_valid) { ret = ClassDB::class_get_default_property_value(class_name, p_name); } } return ret; } static GDExtensionBool placeholder_instance_set(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionConstVariantPtr p_value) { PlaceholderExtensionInstance *self = (PlaceholderExtensionInstance *)p_instance; const StringName &name = *(StringName *)p_name; const Variant &value = *(const Variant *)p_value; bool valid = false; self->set(name, value, valid); return valid; } static GDExtensionBool placeholder_instance_get(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { PlaceholderExtensionInstance *self = (PlaceholderExtensionInstance *)p_instance; const StringName &name = *(StringName *)p_name; Variant *value = (Variant *)r_ret; bool valid = false; *value = self->get(name, valid); return valid; } static const GDExtensionPropertyInfo *placeholder_instance_get_property_list(GDExtensionClassInstancePtr p_instance, uint32_t *r_count) { *r_count = 0; return nullptr; } static void placeholder_instance_free_property_list(GDExtensionClassInstancePtr p_instance, const GDExtensionPropertyInfo *p_list, uint32_t p_count) { } static GDExtensionBool placeholder_instance_property_can_revert(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name) { return false; } static GDExtensionBool placeholder_instance_property_get_revert(GDExtensionClassInstancePtr p_instance, GDExtensionConstStringNamePtr p_name, GDExtensionVariantPtr r_ret) { return false; } static GDExtensionBool placeholder_instance_validate_property(GDExtensionClassInstancePtr p_instance, GDExtensionPropertyInfo *p_property) { return false; } static void placeholder_instance_notification(GDExtensionClassInstancePtr p_instance, int32_t p_what, GDExtensionBool p_reversed) { } static void placeholder_instance_to_string(GDExtensionClassInstancePtr p_instance, GDExtensionBool *r_is_valid, GDExtensionStringPtr p_out) { *r_is_valid = true; } static void placeholder_instance_reference(GDExtensionClassInstancePtr p_instance) { } static void placeholder_instance_unreference(GDExtensionClassInstancePtr p_instance) { } static uint64_t placeholder_instance_get_rid(GDExtensionClassInstancePtr p_instance) { return 0; } static GDExtensionObjectPtr placeholder_class_create_instance(void *p_class_userdata, GDExtensionBool p_notify_postinitialize) { ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; // Find the closest native parent, that isn't a runtime class. ClassDB::ClassInfo *native_parent = ti->inherits_ptr; while (native_parent->gdextension || native_parent->is_runtime) { native_parent = native_parent->inherits_ptr; } ERR_FAIL_NULL_V(native_parent->creation_func, nullptr); // Construct a placeholder. Object *obj = native_parent->creation_func(static_cast(p_notify_postinitialize)); // ClassDB::set_object_extension_instance() won't be called for placeholders. // We need need to make sure that all the things it would have done (even if // done in a different way to support placeholders) will also be done here. obj->_extension = ClassDB::get_placeholder_extension(ti->name); obj->_extension_instance = memnew(PlaceholderExtensionInstance(ti->name)); #ifdef TOOLS_ENABLED if (obj->_extension->track_instance) { obj->_extension->track_instance(obj->_extension->tracking_userdata, obj); } #endif return obj; } static GDExtensionObjectPtr placeholder_class_recreate_instance(void *p_class_userdata, GDExtensionObjectPtr p_object) { ClassDB::ClassInfo *ti = (ClassDB::ClassInfo *)p_class_userdata; return memnew(PlaceholderExtensionInstance(ti->name)); } static void placeholder_class_free_instance(void *p_class_userdata, GDExtensionClassInstancePtr p_instance) { PlaceholderExtensionInstance *instance = (PlaceholderExtensionInstance *)p_instance; memdelete(instance); } static GDExtensionClassCallVirtual placeholder_class_get_virtual(void *p_class_userdata, GDExtensionConstStringNamePtr p_name) { return nullptr; } }; #endif 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(); } void ClassDB::get_extension_class_list(const Ref &p_extension, List *p_classes) { OBJTYPE_RLOCK; for (const KeyValue &E : classes) { if (E.value.api != API_EXTENSION && E.value.api != API_EDITOR_EXTENSION) { continue; } if (!E.value.gdextension || E.value.gdextension->library != p_extension.ptr()) { 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; } bool ClassDB::get_inheritance_chain_nocheck(const StringName &p_class, Vector &r_result) { OBJTYPE_RLOCK; ClassInfo *start = classes.getptr(p_class); if (!start) { return false; } int classes_to_add = 0; for (ClassInfo *ti = start; ti; ti = ti->inherits_ptr) { classes_to_add++; } int64_t old_size = r_result.size(); r_result.resize(old_size + classes_to_add); StringName *w = r_result.ptrw() + old_size; for (ClassInfo *ti = start; ti; ti = ti->inherits_ptr) { *w++ = ti->name; } return true; } 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 (const PropertyInfo &pi : mi.arguments) { hash = hash_murmur3_one_64(pi.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_internal(const StringName &p_class, bool p_require_real_class, bool p_notify_postinitialize) { ClassInfo *ti; { OBJTYPE_RLOCK; ti = classes.getptr(p_class); if (!_can_instantiate(ti)) { 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 || ti->api == API_EDITOR_EXTENSION) && !Engine::get_singleton()->is_editor_hint()) { ERR_PRINT("Class '" + String(p_class) + "' can only be instantiated by editor."); return nullptr; } #endif #ifdef TOOLS_ENABLED // Try to create placeholder. if (!p_require_real_class && ti->is_runtime && Engine::get_singleton()->is_editor_hint()) { bool can_create_placeholder = false; if (ti->gdextension) { if (ti->gdextension->create_instance2) { can_create_placeholder = true; } #ifndef DISABLE_DEPRECATED else if (ti->gdextension->create_instance) { can_create_placeholder = true; } #endif // DISABLE_DEPRECATED } else if (!ti->inherits_ptr || !ti->inherits_ptr->creation_func) { ERR_PRINT(vformat("Cannot make a placeholder instance of runtime class %s because its parent cannot be constructed.", ti->name)); } else { can_create_placeholder = true; } if (can_create_placeholder) { ObjectGDExtension *extension = get_placeholder_extension(ti->name); return (Object *)extension->create_instance2(extension->class_userdata, p_notify_postinitialize); } } #endif // TOOLS_ENABLED if (ti->gdextension && ti->gdextension->create_instance2) { ObjectGDExtension *extension = ti->gdextension; return (Object *)extension->create_instance2(extension->class_userdata, p_notify_postinitialize); } #ifndef DISABLE_DEPRECATED else if (ti->gdextension && ti->gdextension->create_instance) { ObjectGDExtension *extension = ti->gdextension; return (Object *)extension->create_instance(extension->class_userdata); } #endif // DISABLE_DEPRECATED else { return ti->creation_func(p_notify_postinitialize); } } bool ClassDB::_can_instantiate(ClassInfo *p_class_info) { if (!p_class_info) { return false; } if (p_class_info->disabled || !p_class_info->creation_func) { return false; } if (!p_class_info->gdextension) { return true; } if (p_class_info->gdextension->create_instance2) { return true; } #ifndef DISABLE_DEPRECATED if (p_class_info->gdextension->create_instance) { return true; } #endif // DISABLE_DEPRECATED return false; } Object *ClassDB::instantiate(const StringName &p_class) { return _instantiate_internal(p_class); } Object *ClassDB::instantiate_no_placeholders(const StringName &p_class) { return _instantiate_internal(p_class, true); } Object *ClassDB::instantiate_without_postinitialization(const StringName &p_class) { return _instantiate_internal(p_class, true, false); } #ifdef TOOLS_ENABLED ObjectGDExtension *ClassDB::get_placeholder_extension(const StringName &p_class) { ObjectGDExtension *placeholder_extension = placeholder_extensions.getptr(p_class); if (placeholder_extension) { return placeholder_extension; } ClassInfo *ti; { OBJTYPE_RLOCK; ti = classes.getptr(p_class); if (!_can_instantiate(ti)) { 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."); } // Make a "fake" extension to act as a placeholder. placeholder_extensions[p_class] = ObjectGDExtension(); placeholder_extension = placeholder_extensions.getptr(p_class); placeholder_extension->is_runtime = true; placeholder_extension->is_placeholder = true; if (ti->gdextension) { placeholder_extension->library = ti->gdextension->library; placeholder_extension->parent = ti->gdextension->parent; placeholder_extension->children = ti->gdextension->children; placeholder_extension->parent_class_name = ti->gdextension->parent_class_name; placeholder_extension->class_name = ti->gdextension->class_name; placeholder_extension->editor_class = ti->gdextension->editor_class; placeholder_extension->reloadable = ti->gdextension->reloadable; placeholder_extension->is_virtual = ti->gdextension->is_virtual; placeholder_extension->is_abstract = ti->gdextension->is_abstract; placeholder_extension->is_exposed = ti->gdextension->is_exposed; placeholder_extension->tracking_userdata = ti->gdextension->tracking_userdata; placeholder_extension->track_instance = ti->gdextension->track_instance; placeholder_extension->untrack_instance = ti->gdextension->untrack_instance; } else { placeholder_extension->library = nullptr; placeholder_extension->parent = nullptr; placeholder_extension->parent_class_name = ti->inherits; placeholder_extension->class_name = ti->name; placeholder_extension->editor_class = ti->api == API_EDITOR; placeholder_extension->reloadable = false; placeholder_extension->is_virtual = ti->is_virtual; placeholder_extension->is_abstract = false; placeholder_extension->is_exposed = ti->exposed; } placeholder_extension->set = &PlaceholderExtensionInstance::placeholder_instance_set; placeholder_extension->get = &PlaceholderExtensionInstance::placeholder_instance_get; placeholder_extension->get_property_list = &PlaceholderExtensionInstance::placeholder_instance_get_property_list; placeholder_extension->free_property_list2 = &PlaceholderExtensionInstance::placeholder_instance_free_property_list; placeholder_extension->property_can_revert = &PlaceholderExtensionInstance::placeholder_instance_property_can_revert; placeholder_extension->property_get_revert = &PlaceholderExtensionInstance::placeholder_instance_property_get_revert; placeholder_extension->validate_property = &PlaceholderExtensionInstance::placeholder_instance_validate_property; #ifndef DISABLE_DEPRECATED placeholder_extension->notification = nullptr; placeholder_extension->free_property_list = nullptr; #endif // DISABLE_DEPRECATED placeholder_extension->notification2 = &PlaceholderExtensionInstance::placeholder_instance_notification; placeholder_extension->to_string = &PlaceholderExtensionInstance::placeholder_instance_to_string; placeholder_extension->reference = &PlaceholderExtensionInstance::placeholder_instance_reference; placeholder_extension->unreference = &PlaceholderExtensionInstance::placeholder_instance_unreference; placeholder_extension->get_rid = &PlaceholderExtensionInstance::placeholder_instance_get_rid; placeholder_extension->class_userdata = ti; #ifndef DISABLE_DEPRECATED placeholder_extension->create_instance = nullptr; #endif // DISABLE_DEPRECATED placeholder_extension->create_instance2 = &PlaceholderExtensionInstance::placeholder_class_create_instance; placeholder_extension->free_instance = &PlaceholderExtensionInstance::placeholder_class_free_instance; placeholder_extension->get_virtual = &PlaceholderExtensionInstance::placeholder_class_get_virtual; placeholder_extension->get_virtual_call_data = nullptr; placeholder_extension->call_virtual_with_data = nullptr; placeholder_extension->recreate_instance = &PlaceholderExtensionInstance::placeholder_class_recreate_instance; return placeholder_extension; } #endif 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 (!_can_instantiate(ti)) { 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; #ifdef TOOLS_ENABLED if (p_object->_extension->track_instance) { p_object->_extension->track_instance(p_object->_extension->tracking_userdata, p_object); } #endif } 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