| 1 | /**************************************************************************/ |
| 2 | /* gdextension.cpp */ |
| 3 | /**************************************************************************/ |
| 4 | /* This file is part of: */ |
| 5 | /* GODOT ENGINE */ |
| 6 | /* https://godotengine.org */ |
| 7 | /**************************************************************************/ |
| 8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
| 9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
| 10 | /* */ |
| 11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
| 12 | /* a copy of this software and associated documentation files (the */ |
| 13 | /* "Software"), to deal in the Software without restriction, including */ |
| 14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
| 15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
| 16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
| 17 | /* the following conditions: */ |
| 18 | /* */ |
| 19 | /* The above copyright notice and this permission notice shall be */ |
| 20 | /* included in all copies or substantial portions of the Software. */ |
| 21 | /* */ |
| 22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
| 23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
| 24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
| 25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
| 26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
| 27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
| 28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
| 29 | /**************************************************************************/ |
| 30 | |
| 31 | #include "gdextension.h" |
| 32 | #include "core/config/project_settings.h" |
| 33 | #include "core/io/dir_access.h" |
| 34 | #include "core/object/class_db.h" |
| 35 | #include "core/object/method_bind.h" |
| 36 | #include "core/os/os.h" |
| 37 | #include "core/version.h" |
| 38 | |
| 39 | extern void gdextension_setup_interface(); |
| 40 | extern GDExtensionInterfaceFunctionPtr gdextension_get_proc_address(const char *p_name); |
| 41 | |
| 42 | typedef GDExtensionBool (*GDExtensionLegacyInitializationFunction)(void *p_interface, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization); |
| 43 | |
| 44 | String GDExtension::get_extension_list_config_file() { |
| 45 | return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg" ); |
| 46 | } |
| 47 | |
| 48 | String GDExtension::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) { |
| 49 | // First, check the explicit libraries. |
| 50 | if (p_config->has_section("libraries" )) { |
| 51 | List<String> libraries; |
| 52 | p_config->get_section_keys("libraries" , &libraries); |
| 53 | |
| 54 | // Iterate the libraries, finding the best matching tags. |
| 55 | String best_library_path; |
| 56 | Vector<String> best_library_tags; |
| 57 | for (const String &E : libraries) { |
| 58 | Vector<String> tags = E.split("." ); |
| 59 | bool all_tags_met = true; |
| 60 | for (int i = 0; i < tags.size(); i++) { |
| 61 | String tag = tags[i].strip_edges(); |
| 62 | if (!p_has_feature(tag)) { |
| 63 | all_tags_met = false; |
| 64 | break; |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | if (all_tags_met && tags.size() > best_library_tags.size()) { |
| 69 | best_library_path = p_config->get_value("libraries" , E); |
| 70 | best_library_tags = tags; |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | if (!best_library_path.is_empty()) { |
| 75 | if (best_library_path.is_relative_path()) { |
| 76 | best_library_path = p_path.get_base_dir().path_join(best_library_path); |
| 77 | } |
| 78 | if (r_tags != nullptr) { |
| 79 | r_tags->append_array(best_library_tags); |
| 80 | } |
| 81 | return best_library_path; |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | // Second, try to autodetect |
| 86 | String autodetect_library_prefix; |
| 87 | if (p_config->has_section_key("configuration" , "autodetect_library_prefix" )) { |
| 88 | autodetect_library_prefix = p_config->get_value("configuration" , "autodetect_library_prefix" ); |
| 89 | } |
| 90 | if (!autodetect_library_prefix.is_empty()) { |
| 91 | String autodetect_path = autodetect_library_prefix; |
| 92 | if (autodetect_path.is_relative_path()) { |
| 93 | autodetect_path = p_path.get_base_dir().path_join(autodetect_path); |
| 94 | } |
| 95 | |
| 96 | // Find the folder and file parts of the prefix. |
| 97 | String folder; |
| 98 | String file_prefix; |
| 99 | if (DirAccess::dir_exists_absolute(autodetect_path)) { |
| 100 | folder = autodetect_path; |
| 101 | } else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) { |
| 102 | folder = autodetect_path.get_base_dir(); |
| 103 | file_prefix = autodetect_path.get_file(); |
| 104 | } else { |
| 105 | ERR_FAIL_V_MSG(String(), vformat("Error in extension: %s. Could not find folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"" , p_path, autodetect_library_prefix)); |
| 106 | } |
| 107 | |
| 108 | // Open the folder. |
| 109 | Ref<DirAccess> dir = DirAccess::open(folder); |
| 110 | ERR_FAIL_COND_V_MSG(!dir.is_valid(), String(), vformat("Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix=\"%s\"" , p_path, autodetect_library_prefix)); |
| 111 | |
| 112 | // Iterate the files and check the prefixes, finding the best matching file. |
| 113 | String best_file; |
| 114 | Vector<String> best_file_tags; |
| 115 | dir->list_dir_begin(); |
| 116 | String file_name = dir->_get_next(); |
| 117 | while (file_name != "" ) { |
| 118 | if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) { |
| 119 | // Check if the files matches all requested feature tags. |
| 120 | String tags_str = file_name.trim_prefix(file_prefix); |
| 121 | tags_str = tags_str.trim_suffix(tags_str.get_extension()); |
| 122 | |
| 123 | Vector<String> tags = tags_str.split("." , false); |
| 124 | bool all_tags_met = true; |
| 125 | for (int i = 0; i < tags.size(); i++) { |
| 126 | String tag = tags[i].strip_edges(); |
| 127 | if (!p_has_feature(tag)) { |
| 128 | all_tags_met = false; |
| 129 | break; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | // If all tags are found in the feature list, and we found more tags than before, use this file. |
| 134 | if (all_tags_met && tags.size() > best_file_tags.size()) { |
| 135 | best_file_tags = tags; |
| 136 | best_file = file_name; |
| 137 | } |
| 138 | } |
| 139 | file_name = dir->_get_next(); |
| 140 | } |
| 141 | |
| 142 | if (!best_file.is_empty()) { |
| 143 | String library_path = folder.path_join(best_file); |
| 144 | if (r_tags != nullptr) { |
| 145 | r_tags->append_array(best_file_tags); |
| 146 | } |
| 147 | return library_path; |
| 148 | } |
| 149 | } |
| 150 | return String(); |
| 151 | } |
| 152 | |
| 153 | class GDExtensionMethodBind : public MethodBind { |
| 154 | GDExtensionClassMethodCall call_func; |
| 155 | GDExtensionClassMethodValidatedCall validated_call_func; |
| 156 | GDExtensionClassMethodPtrCall ptrcall_func; |
| 157 | void *method_userdata; |
| 158 | bool vararg; |
| 159 | uint32_t argument_count; |
| 160 | PropertyInfo return_value_info; |
| 161 | GodotTypeInfo::Metadata return_value_metadata; |
| 162 | List<PropertyInfo> arguments_info; |
| 163 | List<GodotTypeInfo::Metadata> arguments_metadata; |
| 164 | |
| 165 | protected: |
| 166 | virtual Variant::Type _gen_argument_type(int p_arg) const override { |
| 167 | if (p_arg < 0) { |
| 168 | return return_value_info.type; |
| 169 | } else { |
| 170 | return arguments_info[p_arg].type; |
| 171 | } |
| 172 | } |
| 173 | virtual PropertyInfo _gen_argument_type_info(int p_arg) const override { |
| 174 | if (p_arg < 0) { |
| 175 | return return_value_info; |
| 176 | } else { |
| 177 | return arguments_info[p_arg]; |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | public: |
| 182 | #ifdef DEBUG_METHODS_ENABLED |
| 183 | virtual GodotTypeInfo::Metadata get_argument_meta(int p_arg) const override { |
| 184 | if (p_arg < 0) { |
| 185 | return return_value_metadata; |
| 186 | } else { |
| 187 | return arguments_metadata[p_arg]; |
| 188 | } |
| 189 | } |
| 190 | #endif |
| 191 | |
| 192 | virtual Variant call(Object *p_object, const Variant **p_args, int p_arg_count, Callable::CallError &r_error) const override { |
| 193 | Variant ret; |
| 194 | GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance(); |
| 195 | GDExtensionCallError ce{ GDEXTENSION_CALL_OK, 0, 0 }; |
| 196 | call_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstVariantPtr *>(p_args), p_arg_count, (GDExtensionVariantPtr)&ret, &ce); |
| 197 | r_error.error = Callable::CallError::Error(ce.error); |
| 198 | r_error.argument = ce.argument; |
| 199 | r_error.expected = ce.expected; |
| 200 | return ret; |
| 201 | } |
| 202 | virtual void validated_call(Object *p_object, const Variant **p_args, Variant *r_ret) const override { |
| 203 | ERR_FAIL_COND_MSG(vararg, "Validated methods don't have ptrcall support. This is most likely an engine bug." ); |
| 204 | GDExtensionClassInstancePtr extension_instance = is_static() ? nullptr : p_object->_get_extension_instance(); |
| 205 | |
| 206 | if (validated_call_func) { |
| 207 | // This is added here, but it's unlikely to be provided by most extensions. |
| 208 | validated_call_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstVariantPtr *>(p_args), (GDExtensionVariantPtr)r_ret); |
| 209 | } else { |
| 210 | #if 1 |
| 211 | // Slow code-path, but works for the time being. |
| 212 | Callable::CallError ce; |
| 213 | call(p_object, p_args, argument_count, ce); |
| 214 | #else |
| 215 | // This is broken, because it needs more information to do the calling properly |
| 216 | |
| 217 | // If not provided, go via ptrcall, which is faster than resorting to regular call. |
| 218 | const void **argptrs = (const void **)alloca(argument_count * sizeof(void *)); |
| 219 | for (uint32_t i = 0; i < argument_count; i++) { |
| 220 | argptrs[i] = VariantInternal::get_opaque_pointer(p_args[i]); |
| 221 | } |
| 222 | |
| 223 | bool returns = true; |
| 224 | void *ret_opaque; |
| 225 | if (returns) { |
| 226 | ret_opaque = VariantInternal::get_opaque_pointer(r_ret); |
| 227 | } else { |
| 228 | ret_opaque = nullptr; // May be unnecessary as this is ignored, but just in case. |
| 229 | } |
| 230 | |
| 231 | ptrcall(p_object, argptrs, ret_opaque); |
| 232 | #endif |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | virtual void ptrcall(Object *p_object, const void **p_args, void *r_ret) const override { |
| 237 | ERR_FAIL_COND_MSG(vararg, "Vararg methods don't have ptrcall support. This is most likely an engine bug." ); |
| 238 | GDExtensionClassInstancePtr extension_instance = p_object->_get_extension_instance(); |
| 239 | ptrcall_func(method_userdata, extension_instance, reinterpret_cast<GDExtensionConstTypePtr *>(p_args), (GDExtensionTypePtr)r_ret); |
| 240 | } |
| 241 | |
| 242 | virtual bool is_vararg() const override { |
| 243 | return false; |
| 244 | } |
| 245 | |
| 246 | explicit GDExtensionMethodBind(const GDExtensionClassMethodInfo *p_method_info) { |
| 247 | method_userdata = p_method_info->method_userdata; |
| 248 | call_func = p_method_info->call_func; |
| 249 | validated_call_func = nullptr; |
| 250 | ptrcall_func = p_method_info->ptrcall_func; |
| 251 | set_name(*reinterpret_cast<StringName *>(p_method_info->name)); |
| 252 | |
| 253 | if (p_method_info->has_return_value) { |
| 254 | return_value_info = PropertyInfo(*p_method_info->return_value_info); |
| 255 | return_value_metadata = GodotTypeInfo::Metadata(p_method_info->return_value_metadata); |
| 256 | } |
| 257 | |
| 258 | for (uint32_t i = 0; i < p_method_info->argument_count; i++) { |
| 259 | arguments_info.push_back(PropertyInfo(p_method_info->arguments_info[i])); |
| 260 | arguments_metadata.push_back(GodotTypeInfo::Metadata(p_method_info->arguments_metadata[i])); |
| 261 | } |
| 262 | |
| 263 | set_hint_flags(p_method_info->method_flags); |
| 264 | argument_count = p_method_info->argument_count; |
| 265 | vararg = p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_VARARG; |
| 266 | _set_returns(p_method_info->has_return_value); |
| 267 | _set_const(p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_CONST); |
| 268 | _set_static(p_method_info->method_flags & GDEXTENSION_METHOD_FLAG_STATIC); |
| 269 | #ifdef DEBUG_METHODS_ENABLED |
| 270 | _generate_argument_types(p_method_info->argument_count); |
| 271 | #endif |
| 272 | set_argument_count(p_method_info->argument_count); |
| 273 | |
| 274 | Vector<Variant> defargs; |
| 275 | defargs.resize(p_method_info->default_argument_count); |
| 276 | for (uint32_t i = 0; i < p_method_info->default_argument_count; i++) { |
| 277 | defargs.write[i] = *static_cast<Variant *>(p_method_info->default_arguments[i]); |
| 278 | } |
| 279 | |
| 280 | set_default_arguments(defargs); |
| 281 | } |
| 282 | }; |
| 283 | |
| 284 | #ifndef DISABLE_DEPRECATED |
| 285 | void GDExtension::_register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs) { |
| 286 | const GDExtensionClassCreationInfo2 class_info2 = { |
| 287 | p_extension_funcs->is_virtual, // GDExtensionBool is_virtual; |
| 288 | p_extension_funcs->is_abstract, // GDExtensionBool is_abstract; |
| 289 | true, // GDExtensionBool is_exposed; |
| 290 | p_extension_funcs->set_func, // GDExtensionClassSet set_func; |
| 291 | p_extension_funcs->get_func, // GDExtensionClassGet get_func; |
| 292 | p_extension_funcs->get_property_list_func, // GDExtensionClassGetPropertyList get_property_list_func; |
| 293 | p_extension_funcs->free_property_list_func, // GDExtensionClassFreePropertyList free_property_list_func; |
| 294 | p_extension_funcs->property_can_revert_func, // GDExtensionClassPropertyCanRevert property_can_revert_func; |
| 295 | p_extension_funcs->property_get_revert_func, // GDExtensionClassPropertyGetRevert property_get_revert_func; |
| 296 | nullptr, // GDExtensionClassValidateProperty validate_property_func; |
| 297 | nullptr, // GDExtensionClassNotification2 notification_func; |
| 298 | p_extension_funcs->to_string_func, // GDExtensionClassToString to_string_func; |
| 299 | p_extension_funcs->reference_func, // GDExtensionClassReference reference_func; |
| 300 | p_extension_funcs->unreference_func, // GDExtensionClassUnreference unreference_func; |
| 301 | p_extension_funcs->create_instance_func, // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */ |
| 302 | p_extension_funcs->free_instance_func, // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */ |
| 303 | p_extension_funcs->get_virtual_func, // GDExtensionClassGetVirtual get_virtual_func; |
| 304 | p_extension_funcs->get_rid_func, // GDExtensionClassGetRID get_rid; |
| 305 | p_extension_funcs->class_userdata, // void *class_userdata; |
| 306 | }; |
| 307 | |
| 308 | const ClassCreationDeprecatedInfo legacy = { |
| 309 | p_extension_funcs->notification_func, |
| 310 | }; |
| 311 | _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, &class_info2, &legacy); |
| 312 | } |
| 313 | #endif // DISABLE_DEPRECATED |
| 314 | |
| 315 | void GDExtension::_register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs) { |
| 316 | _register_extension_class_internal(p_library, p_class_name, p_parent_class_name, p_extension_funcs); |
| 317 | } |
| 318 | |
| 319 | void GDExtension::_register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs) { |
| 320 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 321 | |
| 322 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 323 | StringName parent_class_name = *reinterpret_cast<const StringName *>(p_parent_class_name); |
| 324 | ERR_FAIL_COND_MSG(!String(class_name).is_valid_identifier(), "Attempt to register extension class '" + class_name + "', which is not a valid class identifier." ); |
| 325 | ERR_FAIL_COND_MSG(ClassDB::class_exists(class_name), "Attempt to register extension class '" + class_name + "', which appears to be already registered." ); |
| 326 | |
| 327 | Extension *parent_extension = nullptr; |
| 328 | |
| 329 | if (self->extension_classes.has(parent_class_name)) { |
| 330 | parent_extension = &self->extension_classes[parent_class_name]; |
| 331 | } else if (ClassDB::class_exists(parent_class_name)) { |
| 332 | if (ClassDB::get_api_type(parent_class_name) == ClassDB::API_EXTENSION || ClassDB::get_api_type(parent_class_name) == ClassDB::API_EDITOR_EXTENSION) { |
| 333 | ERR_PRINT("Unimplemented yet" ); |
| 334 | //inheriting from another extension |
| 335 | } else { |
| 336 | //inheriting from engine class |
| 337 | } |
| 338 | } else { |
| 339 | ERR_FAIL_MSG("Attempt to register an extension class '" + String(class_name) + "' using non-existing parent class '" + String(parent_class_name) + "'" ); |
| 340 | } |
| 341 | |
| 342 | self->extension_classes[class_name] = Extension(); |
| 343 | |
| 344 | Extension *extension = &self->extension_classes[class_name]; |
| 345 | |
| 346 | if (parent_extension) { |
| 347 | extension->gdextension.parent = &parent_extension->gdextension; |
| 348 | parent_extension->gdextension.children.push_back(&extension->gdextension); |
| 349 | } |
| 350 | |
| 351 | extension->gdextension.library = self; |
| 352 | extension->gdextension.parent_class_name = parent_class_name; |
| 353 | extension->gdextension.class_name = class_name; |
| 354 | extension->gdextension.editor_class = self->level_initialized == INITIALIZATION_LEVEL_EDITOR; |
| 355 | extension->gdextension.is_virtual = p_extension_funcs->is_virtual; |
| 356 | extension->gdextension.is_abstract = p_extension_funcs->is_abstract; |
| 357 | extension->gdextension.is_exposed = p_extension_funcs->is_exposed; |
| 358 | extension->gdextension.set = p_extension_funcs->set_func; |
| 359 | extension->gdextension.get = p_extension_funcs->get_func; |
| 360 | extension->gdextension.get_property_list = p_extension_funcs->get_property_list_func; |
| 361 | extension->gdextension.free_property_list = p_extension_funcs->free_property_list_func; |
| 362 | extension->gdextension.property_can_revert = p_extension_funcs->property_can_revert_func; |
| 363 | extension->gdextension.property_get_revert = p_extension_funcs->property_get_revert_func; |
| 364 | extension->gdextension.validate_property = p_extension_funcs->validate_property_func; |
| 365 | #ifndef DISABLE_DEPRECATED |
| 366 | if (p_deprecated_funcs) { |
| 367 | extension->gdextension.notification = p_deprecated_funcs->notification_func; |
| 368 | } |
| 369 | #endif // DISABLE_DEPRECATED |
| 370 | extension->gdextension.notification2 = p_extension_funcs->notification_func; |
| 371 | extension->gdextension.to_string = p_extension_funcs->to_string_func; |
| 372 | extension->gdextension.reference = p_extension_funcs->reference_func; |
| 373 | extension->gdextension.unreference = p_extension_funcs->unreference_func; |
| 374 | extension->gdextension.class_userdata = p_extension_funcs->class_userdata; |
| 375 | extension->gdextension.create_instance = p_extension_funcs->create_instance_func; |
| 376 | extension->gdextension.free_instance = p_extension_funcs->free_instance_func; |
| 377 | extension->gdextension.get_virtual = p_extension_funcs->get_virtual_func; |
| 378 | extension->gdextension.get_rid = p_extension_funcs->get_rid_func; |
| 379 | |
| 380 | ClassDB::register_extension_class(&extension->gdextension); |
| 381 | } |
| 382 | |
| 383 | void GDExtension::_register_extension_class_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info) { |
| 384 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 385 | |
| 386 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 387 | StringName method_name = *reinterpret_cast<const StringName *>(p_method_info->name); |
| 388 | ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension method '" + String(method_name) + "' for unexisting class '" + class_name + "'." ); |
| 389 | |
| 390 | //Extension *extension = &self->extension_classes[class_name]; |
| 391 | |
| 392 | GDExtensionMethodBind *method = memnew(GDExtensionMethodBind(p_method_info)); |
| 393 | method->set_instance_class(class_name); |
| 394 | |
| 395 | ClassDB::bind_method_custom(class_name, method); |
| 396 | } |
| 397 | void GDExtension::_register_extension_class_integer_constant(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_enum_name, GDExtensionConstStringNamePtr p_constant_name, GDExtensionInt p_constant_value, GDExtensionBool p_is_bitfield) { |
| 398 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 399 | |
| 400 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 401 | StringName enum_name = *reinterpret_cast<const StringName *>(p_enum_name); |
| 402 | StringName constant_name = *reinterpret_cast<const StringName *>(p_constant_name); |
| 403 | ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension constant '" + constant_name + "' for unexisting class '" + class_name + "'." ); |
| 404 | |
| 405 | ClassDB::bind_integer_constant(class_name, enum_name, constant_name, p_constant_value, p_is_bitfield); |
| 406 | } |
| 407 | |
| 408 | void GDExtension::_register_extension_class_property(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter) { |
| 409 | _register_extension_class_property_indexed(p_library, p_class_name, p_info, p_setter, p_getter, -1); |
| 410 | } |
| 411 | |
| 412 | void GDExtension::_register_extension_class_property_indexed(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter, GDExtensionInt p_index) { |
| 413 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 414 | |
| 415 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 416 | StringName setter = *reinterpret_cast<const StringName *>(p_setter); |
| 417 | StringName getter = *reinterpret_cast<const StringName *>(p_getter); |
| 418 | String property_name = *reinterpret_cast<const StringName *>(p_info->name); |
| 419 | ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property '" + property_name + "' for unexisting class '" + class_name + "'." ); |
| 420 | |
| 421 | PropertyInfo pinfo(*p_info); |
| 422 | |
| 423 | ClassDB::add_property(class_name, pinfo, setter, getter, p_index); |
| 424 | } |
| 425 | |
| 426 | void GDExtension::_register_extension_class_property_group(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_group_name, GDExtensionConstStringPtr p_prefix) { |
| 427 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 428 | |
| 429 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 430 | String group_name = *reinterpret_cast<const String *>(p_group_name); |
| 431 | String prefix = *reinterpret_cast<const String *>(p_prefix); |
| 432 | ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property group '" + group_name + "' for unexisting class '" + class_name + "'." ); |
| 433 | |
| 434 | ClassDB::add_property_group(class_name, group_name, prefix); |
| 435 | } |
| 436 | |
| 437 | void GDExtension::_register_extension_class_property_subgroup(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringPtr p_subgroup_name, GDExtensionConstStringPtr p_prefix) { |
| 438 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 439 | |
| 440 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 441 | String subgroup_name = *reinterpret_cast<const String *>(p_subgroup_name); |
| 442 | String prefix = *reinterpret_cast<const String *>(p_prefix); |
| 443 | ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class property subgroup '" + subgroup_name + "' for unexisting class '" + class_name + "'." ); |
| 444 | |
| 445 | ClassDB::add_property_subgroup(class_name, subgroup_name, prefix); |
| 446 | } |
| 447 | |
| 448 | void GDExtension::_register_extension_class_signal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count) { |
| 449 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 450 | |
| 451 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 452 | StringName signal_name = *reinterpret_cast<const StringName *>(p_signal_name); |
| 453 | ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to register extension class signal '" + signal_name + "' for unexisting class '" + class_name + "'." ); |
| 454 | |
| 455 | MethodInfo s; |
| 456 | s.name = signal_name; |
| 457 | for (int i = 0; i < p_argument_count; i++) { |
| 458 | PropertyInfo arg(p_argument_info[i]); |
| 459 | s.arguments.push_back(arg); |
| 460 | } |
| 461 | ClassDB::add_signal(class_name, s); |
| 462 | } |
| 463 | |
| 464 | void GDExtension::_unregister_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name) { |
| 465 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 466 | |
| 467 | StringName class_name = *reinterpret_cast<const StringName *>(p_class_name); |
| 468 | ERR_FAIL_COND_MSG(!self->extension_classes.has(class_name), "Attempt to unregister unexisting extension class '" + class_name + "'." ); |
| 469 | Extension *ext = &self->extension_classes[class_name]; |
| 470 | ERR_FAIL_COND_MSG(ext->gdextension.children.size(), "Attempt to unregister class '" + class_name + "' while other extension classes inherit from it." ); |
| 471 | |
| 472 | ClassDB::unregister_extension_class(class_name); |
| 473 | if (ext->gdextension.parent != nullptr) { |
| 474 | ext->gdextension.parent->children.erase(&ext->gdextension); |
| 475 | } |
| 476 | self->extension_classes.erase(class_name); |
| 477 | } |
| 478 | |
| 479 | void GDExtension::_get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionUninitializedStringPtr r_path) { |
| 480 | GDExtension *self = reinterpret_cast<GDExtension *>(p_library); |
| 481 | |
| 482 | memnew_placement(r_path, String(self->library_path)); |
| 483 | } |
| 484 | |
| 485 | HashMap<StringName, GDExtensionInterfaceFunctionPtr> gdextension_interface_functions; |
| 486 | |
| 487 | void GDExtension::register_interface_function(StringName p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer) { |
| 488 | ERR_FAIL_COND_MSG(gdextension_interface_functions.has(p_function_name), "Attempt to register interface function '" + p_function_name + "', which appears to be already registered." ); |
| 489 | gdextension_interface_functions.insert(p_function_name, p_function_pointer); |
| 490 | } |
| 491 | |
| 492 | GDExtensionInterfaceFunctionPtr GDExtension::get_interface_function(StringName p_function_name) { |
| 493 | GDExtensionInterfaceFunctionPtr *function = gdextension_interface_functions.getptr(p_function_name); |
| 494 | ERR_FAIL_NULL_V_MSG(function, nullptr, "Attempt to get non-existent interface function: " + String(p_function_name) + "." ); |
| 495 | return *function; |
| 496 | } |
| 497 | |
| 498 | Error GDExtension::open_library(const String &p_path, const String &p_entry_symbol) { |
| 499 | Error err = OS::get_singleton()->open_dynamic_library(p_path, library, true, &library_path); |
| 500 | if (err != OK) { |
| 501 | ERR_PRINT("GDExtension dynamic library not found: " + p_path); |
| 502 | return err; |
| 503 | } |
| 504 | |
| 505 | void *entry_funcptr = nullptr; |
| 506 | |
| 507 | err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, p_entry_symbol, entry_funcptr, false); |
| 508 | |
| 509 | if (err != OK) { |
| 510 | ERR_PRINT("GDExtension entry point '" + p_entry_symbol + "' not found in library " + p_path); |
| 511 | OS::get_singleton()->close_dynamic_library(library); |
| 512 | return err; |
| 513 | } |
| 514 | |
| 515 | GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr; |
| 516 | GDExtensionBool ret = initialization_function(&gdextension_get_proc_address, this, &initialization); |
| 517 | |
| 518 | if (ret) { |
| 519 | level_initialized = -1; |
| 520 | return OK; |
| 521 | } else { |
| 522 | ERR_PRINT("GDExtension initialization function '" + p_entry_symbol + "' returned an error." ); |
| 523 | return FAILED; |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | void GDExtension::close_library() { |
| 528 | ERR_FAIL_NULL(library); |
| 529 | OS::get_singleton()->close_dynamic_library(library); |
| 530 | |
| 531 | #if defined(TOOLS_ENABLED) && defined(WINDOWS_ENABLED) |
| 532 | // Delete temporary copy of library if it exists. |
| 533 | if (!temp_lib_path.is_empty() && Engine::get_singleton()->is_editor_hint()) { |
| 534 | DirAccess::remove_absolute(temp_lib_path); |
| 535 | } |
| 536 | #endif |
| 537 | |
| 538 | library = nullptr; |
| 539 | } |
| 540 | |
| 541 | bool GDExtension::is_library_open() const { |
| 542 | return library != nullptr; |
| 543 | } |
| 544 | |
| 545 | GDExtension::InitializationLevel GDExtension::get_minimum_library_initialization_level() const { |
| 546 | ERR_FAIL_NULL_V(library, INITIALIZATION_LEVEL_CORE); |
| 547 | return InitializationLevel(initialization.minimum_initialization_level); |
| 548 | } |
| 549 | |
| 550 | void GDExtension::initialize_library(InitializationLevel p_level) { |
| 551 | ERR_FAIL_NULL(library); |
| 552 | ERR_FAIL_COND_MSG(p_level <= int32_t(level_initialized), vformat("Level '%d' must be higher than the current level '%d'" , p_level, level_initialized)); |
| 553 | |
| 554 | level_initialized = int32_t(p_level); |
| 555 | |
| 556 | ERR_FAIL_COND(initialization.initialize == nullptr); |
| 557 | |
| 558 | initialization.initialize(initialization.userdata, GDExtensionInitializationLevel(p_level)); |
| 559 | } |
| 560 | void GDExtension::deinitialize_library(InitializationLevel p_level) { |
| 561 | ERR_FAIL_NULL(library); |
| 562 | ERR_FAIL_COND(p_level > int32_t(level_initialized)); |
| 563 | |
| 564 | level_initialized = int32_t(p_level) - 1; |
| 565 | initialization.deinitialize(initialization.userdata, GDExtensionInitializationLevel(p_level)); |
| 566 | } |
| 567 | |
| 568 | void GDExtension::_bind_methods() { |
| 569 | ClassDB::bind_method(D_METHOD("open_library" , "path" , "entry_symbol" ), &GDExtension::open_library); |
| 570 | ClassDB::bind_method(D_METHOD("close_library" ), &GDExtension::close_library); |
| 571 | ClassDB::bind_method(D_METHOD("is_library_open" ), &GDExtension::is_library_open); |
| 572 | |
| 573 | ClassDB::bind_method(D_METHOD("get_minimum_library_initialization_level" ), &GDExtension::get_minimum_library_initialization_level); |
| 574 | ClassDB::bind_method(D_METHOD("initialize_library" , "level" ), &GDExtension::initialize_library); |
| 575 | |
| 576 | BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_CORE); |
| 577 | BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_SERVERS); |
| 578 | BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_SCENE); |
| 579 | BIND_ENUM_CONSTANT(INITIALIZATION_LEVEL_EDITOR); |
| 580 | } |
| 581 | |
| 582 | GDExtension::GDExtension() { |
| 583 | } |
| 584 | |
| 585 | GDExtension::~GDExtension() { |
| 586 | if (library != nullptr) { |
| 587 | close_library(); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | void GDExtension::initialize_gdextensions() { |
| 592 | gdextension_setup_interface(); |
| 593 | |
| 594 | #ifndef DISABLE_DEPRECATED |
| 595 | register_interface_function("classdb_register_extension_class" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class); |
| 596 | #endif // DISABLE_DEPRECATED |
| 597 | register_interface_function("classdb_register_extension_class2" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class2); |
| 598 | register_interface_function("classdb_register_extension_class_method" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_method); |
| 599 | register_interface_function("classdb_register_extension_class_integer_constant" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_integer_constant); |
| 600 | register_interface_function("classdb_register_extension_class_property" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property); |
| 601 | register_interface_function("classdb_register_extension_class_property_indexed" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_indexed); |
| 602 | register_interface_function("classdb_register_extension_class_property_group" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_group); |
| 603 | register_interface_function("classdb_register_extension_class_property_subgroup" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_property_subgroup); |
| 604 | register_interface_function("classdb_register_extension_class_signal" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_register_extension_class_signal); |
| 605 | register_interface_function("classdb_unregister_extension_class" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_unregister_extension_class); |
| 606 | register_interface_function("get_library_path" , (GDExtensionInterfaceFunctionPtr)&GDExtension::_get_library_path); |
| 607 | } |
| 608 | |
| 609 | Ref<Resource> GDExtensionResourceLoader::load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads, float *r_progress, CacheMode p_cache_mode) { |
| 610 | Ref<ConfigFile> config; |
| 611 | config.instantiate(); |
| 612 | |
| 613 | Error err = config->load(p_path); |
| 614 | |
| 615 | if (r_error) { |
| 616 | *r_error = err; |
| 617 | } |
| 618 | |
| 619 | if (err != OK) { |
| 620 | ERR_PRINT("Error loading GDExtension configuration file: " + p_path); |
| 621 | return Ref<Resource>(); |
| 622 | } |
| 623 | |
| 624 | if (!config->has_section_key("configuration" , "entry_symbol" )) { |
| 625 | if (r_error) { |
| 626 | *r_error = ERR_INVALID_DATA; |
| 627 | } |
| 628 | ERR_PRINT("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: " + p_path); |
| 629 | return Ref<Resource>(); |
| 630 | } |
| 631 | |
| 632 | String entry_symbol = config->get_value("configuration" , "entry_symbol" ); |
| 633 | |
| 634 | uint32_t compatibility_minimum[3] = { 0, 0, 0 }; |
| 635 | if (config->has_section_key("configuration" , "compatibility_minimum" )) { |
| 636 | String compat_string = config->get_value("configuration" , "compatibility_minimum" ); |
| 637 | Vector<int> parts = compat_string.split_ints("." ); |
| 638 | for (int i = 0; i < parts.size(); i++) { |
| 639 | if (i >= 3) { |
| 640 | break; |
| 641 | } |
| 642 | if (parts[i] >= 0) { |
| 643 | compatibility_minimum[i] = parts[i]; |
| 644 | } |
| 645 | } |
| 646 | } else { |
| 647 | if (r_error) { |
| 648 | *r_error = ERR_INVALID_DATA; |
| 649 | } |
| 650 | ERR_PRINT("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: " + p_path); |
| 651 | return Ref<Resource>(); |
| 652 | } |
| 653 | |
| 654 | if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) { |
| 655 | if (r_error) { |
| 656 | *r_error = ERR_INVALID_DATA; |
| 657 | } |
| 658 | ERR_PRINT(vformat("GDExtension's compatibility_minimum (%d.%d.%d) must be at least 4.1.0: %s" , compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); |
| 659 | return Ref<Resource>(); |
| 660 | } |
| 661 | |
| 662 | bool compatible = true; |
| 663 | // Check version lexicographically. |
| 664 | if (VERSION_MAJOR != compatibility_minimum[0]) { |
| 665 | compatible = VERSION_MAJOR > compatibility_minimum[0]; |
| 666 | } else if (VERSION_MINOR != compatibility_minimum[1]) { |
| 667 | compatible = VERSION_MINOR > compatibility_minimum[1]; |
| 668 | } else { |
| 669 | compatible = VERSION_PATCH >= compatibility_minimum[2]; |
| 670 | } |
| 671 | if (!compatible) { |
| 672 | if (r_error) { |
| 673 | *r_error = ERR_INVALID_DATA; |
| 674 | } |
| 675 | ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s" , compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path)); |
| 676 | return Ref<Resource>(); |
| 677 | } |
| 678 | |
| 679 | String library_path = GDExtension::find_extension_library(p_path, config, [](String p_feature) { return OS::get_singleton()->has_feature(p_feature); }); |
| 680 | |
| 681 | if (library_path.is_empty()) { |
| 682 | if (r_error) { |
| 683 | *r_error = ERR_FILE_NOT_FOUND; |
| 684 | } |
| 685 | const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name(); |
| 686 | ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s" , os_arch, p_path)); |
| 687 | return Ref<Resource>(); |
| 688 | } |
| 689 | |
| 690 | if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { |
| 691 | library_path = p_path.get_base_dir().path_join(library_path); |
| 692 | } |
| 693 | |
| 694 | Ref<GDExtension> lib; |
| 695 | lib.instantiate(); |
| 696 | String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path); |
| 697 | |
| 698 | #if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) |
| 699 | // If running on the editor on Windows, we copy the library and open the copy. |
| 700 | // This is so the original file isn't locked and can be updated by a compiler. |
| 701 | if (Engine::get_singleton()->is_editor_hint()) { |
| 702 | if (!FileAccess::exists(abs_path)) { |
| 703 | if (r_error) { |
| 704 | *r_error = ERR_FILE_NOT_FOUND; |
| 705 | } |
| 706 | ERR_PRINT("GDExtension library not found: " + library_path); |
| 707 | return Ref<Resource>(); |
| 708 | } |
| 709 | |
| 710 | // Copy the file to the same directory as the original with a prefix in the name. |
| 711 | // This is so relative path to dependencies are satisfied. |
| 712 | String copy_path = abs_path.get_base_dir().path_join("~" + abs_path.get_file()); |
| 713 | |
| 714 | // If there's a left-over copy (possibly from a crash) then delete it first. |
| 715 | if (FileAccess::exists(copy_path)) { |
| 716 | DirAccess::remove_absolute(copy_path); |
| 717 | } |
| 718 | |
| 719 | Error copy_err = DirAccess::copy_absolute(abs_path, copy_path); |
| 720 | if (copy_err) { |
| 721 | if (r_error) { |
| 722 | *r_error = ERR_CANT_CREATE; |
| 723 | } |
| 724 | ERR_PRINT("Error copying GDExtension library: " + library_path); |
| 725 | return Ref<Resource>(); |
| 726 | } |
| 727 | FileAccess::set_hidden_attribute(copy_path, true); |
| 728 | |
| 729 | // Save the copied path so it can be deleted later. |
| 730 | lib->set_temp_library_path(copy_path); |
| 731 | |
| 732 | // Use the copy to open the library. |
| 733 | abs_path = copy_path; |
| 734 | } |
| 735 | #endif |
| 736 | err = lib->open_library(abs_path, entry_symbol); |
| 737 | |
| 738 | if (r_error) { |
| 739 | *r_error = err; |
| 740 | } |
| 741 | |
| 742 | if (err != OK) { |
| 743 | #if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED) |
| 744 | // If the DLL fails to load, make sure that temporary DLL copies are cleaned up. |
| 745 | if (Engine::get_singleton()->is_editor_hint()) { |
| 746 | DirAccess::remove_absolute(lib->get_temp_library_path()); |
| 747 | } |
| 748 | #endif |
| 749 | // Errors already logged in open_library() |
| 750 | return Ref<Resource>(); |
| 751 | } |
| 752 | |
| 753 | // Handle icons if any are specified. |
| 754 | if (config->has_section("icons" )) { |
| 755 | List<String> keys; |
| 756 | config->get_section_keys("icons" , &keys); |
| 757 | for (const String &key : keys) { |
| 758 | lib->class_icon_paths[key] = config->get_value("icons" , key); |
| 759 | } |
| 760 | } |
| 761 | |
| 762 | return lib; |
| 763 | } |
| 764 | |
| 765 | void GDExtensionResourceLoader::get_recognized_extensions(List<String> *p_extensions) const { |
| 766 | p_extensions->push_back("gdextension" ); |
| 767 | } |
| 768 | |
| 769 | bool GDExtensionResourceLoader::handles_type(const String &p_type) const { |
| 770 | return p_type == "GDExtension" ; |
| 771 | } |
| 772 | |
| 773 | String GDExtensionResourceLoader::get_resource_type(const String &p_path) const { |
| 774 | String el = p_path.get_extension().to_lower(); |
| 775 | if (el == "gdextension" ) { |
| 776 | return "GDExtension" ; |
| 777 | } |
| 778 | return "" ; |
| 779 | } |
| 780 | |
| 781 | #ifdef TOOLS_ENABLED |
| 782 | Vector<StringName> GDExtensionEditorPlugins::extension_classes; |
| 783 | GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_add_plugin = nullptr; |
| 784 | GDExtensionEditorPlugins::EditorPluginRegisterFunc GDExtensionEditorPlugins::editor_node_remove_plugin = nullptr; |
| 785 | |
| 786 | void GDExtensionEditorPlugins::add_extension_class(const StringName &p_class_name) { |
| 787 | if (editor_node_add_plugin) { |
| 788 | editor_node_add_plugin(p_class_name); |
| 789 | } else { |
| 790 | extension_classes.push_back(p_class_name); |
| 791 | } |
| 792 | } |
| 793 | |
| 794 | void GDExtensionEditorPlugins::remove_extension_class(const StringName &p_class_name) { |
| 795 | if (editor_node_remove_plugin) { |
| 796 | editor_node_remove_plugin(p_class_name); |
| 797 | } else { |
| 798 | extension_classes.erase(p_class_name); |
| 799 | } |
| 800 | } |
| 801 | #endif // TOOLS_ENABLED |
| 802 | |