initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
18
core/extension/SCsub
Normal file
18
core/extension/SCsub
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
import make_interface_dumper
|
||||
import make_wrappers
|
||||
|
||||
env.CommandNoCache(["ext_wrappers.gen.inc"], "make_wrappers.py", env.Run(make_wrappers.run))
|
||||
env.CommandNoCache(
|
||||
"gdextension_interface_dump.gen.h",
|
||||
["gdextension_interface.h", "make_interface_dumper.py"],
|
||||
env.Run(make_interface_dumper.run),
|
||||
)
|
||||
|
||||
env_extension = env.Clone()
|
||||
|
||||
env_extension.add_source_files(env.core_sources, "*.cpp")
|
1679
core/extension/extension_api_dump.cpp
Normal file
1679
core/extension/extension_api_dump.cpp
Normal file
File diff suppressed because it is too large
Load Diff
43
core/extension/extension_api_dump.h
Normal file
43
core/extension/extension_api_dump.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/**************************************************************************/
|
||||
/* extension_api_dump.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/extension/gdextension.h"
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
class GDExtensionAPIDump {
|
||||
public:
|
||||
static Dictionary generate_extension_api(bool p_include_docs = false);
|
||||
static void generate_extension_json_file(const String &p_path, bool p_include_docs = false);
|
||||
static Error validate_extension_json_file(const String &p_path);
|
||||
};
|
||||
#endif
|
49
core/extension/gdextension.compat.inc
Normal file
49
core/extension/gdextension.compat.inc
Normal file
@@ -0,0 +1,49 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension.compat.inc */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
Error GDExtension::_open_library_bind_compat_88418(const String &p_path, const String &p_entry_symbol) {
|
||||
return ERR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
void GDExtension::_close_library_bind_compat_88418() {
|
||||
}
|
||||
|
||||
void GDExtension::_initialize_library_bind_compat_88418(InitializationLevel p_level) {
|
||||
}
|
||||
|
||||
void GDExtension::_bind_compatibility_methods() {
|
||||
ClassDB::bind_compatibility_method(D_METHOD("open_library", "path", "entry_symbol"), &GDExtension::_open_library_bind_compat_88418);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("close_library"), &GDExtension::_close_library_bind_compat_88418);
|
||||
ClassDB::bind_compatibility_method(D_METHOD("initialize_library", "level"), &GDExtension::_initialize_library_bind_compat_88418);
|
||||
}
|
||||
|
||||
#endif
|
1112
core/extension/gdextension.cpp
Normal file
1112
core/extension/gdextension.cpp
Normal file
File diff suppressed because it is too large
Load Diff
240
core/extension/gdextension.h
Normal file
240
core/extension/gdextension.h
Normal file
@@ -0,0 +1,240 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/extension/gdextension_interface.h"
|
||||
#include "core/extension/gdextension_loader.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class GDExtensionMethodBind;
|
||||
|
||||
class GDExtension : public Resource {
|
||||
GDCLASS(GDExtension, Resource)
|
||||
|
||||
friend class GDExtensionManager;
|
||||
|
||||
Ref<GDExtensionLoader> loader;
|
||||
|
||||
bool reloadable = false;
|
||||
|
||||
struct Extension {
|
||||
ObjectGDExtension gdextension;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool is_reloading = false;
|
||||
HashMap<StringName, GDExtensionMethodBind *> methods;
|
||||
HashSet<ObjectID> instances;
|
||||
|
||||
struct InstanceState {
|
||||
List<Pair<String, Variant>> properties;
|
||||
bool is_placeholder = false;
|
||||
};
|
||||
HashMap<ObjectID, InstanceState> instance_state;
|
||||
#endif
|
||||
};
|
||||
|
||||
HashMap<StringName, Extension> extension_classes;
|
||||
|
||||
struct ClassCreationDeprecatedInfo {
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
GDExtensionClassNotification notification_func = nullptr;
|
||||
GDExtensionClassFreePropertyList free_property_list_func = nullptr;
|
||||
GDExtensionClassCreateInstance create_instance_func = nullptr;
|
||||
GDExtensionClassGetRID get_rid_func = nullptr;
|
||||
GDExtensionClassGetVirtual get_virtual_func = nullptr;
|
||||
GDExtensionClassGetVirtualCallData get_virtual_call_data_func = nullptr;
|
||||
#endif // DISABLE_DEPRECATED
|
||||
};
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
static void _register_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo *p_extension_funcs);
|
||||
static void _register_extension_class2(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo2 *p_extension_funcs);
|
||||
static void _register_extension_class3(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo3 *p_extension_funcs);
|
||||
static void _register_extension_class4(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo4 *p_extension_funcs);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
static void _register_extension_class5(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo5 *p_extension_funcs);
|
||||
static void _register_extension_class_internal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_parent_class_name, const GDExtensionClassCreationInfo5 *p_extension_funcs, const ClassCreationDeprecatedInfo *p_deprecated_funcs = nullptr);
|
||||
static void _register_extension_class_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassMethodInfo *p_method_info);
|
||||
static void _register_extension_class_virtual_method(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionClassVirtualMethodInfo *p_method_info);
|
||||
static void _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);
|
||||
static void _register_extension_class_property(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, const GDExtensionPropertyInfo *p_info, GDExtensionConstStringNamePtr p_setter, GDExtensionConstStringNamePtr p_getter);
|
||||
static void _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);
|
||||
static void _register_extension_class_property_group(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_group_name, GDExtensionConstStringNamePtr p_prefix);
|
||||
static void _register_extension_class_property_subgroup(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_subgroup_name, GDExtensionConstStringNamePtr p_prefix);
|
||||
static void _register_extension_class_signal(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstStringNamePtr p_signal_name, const GDExtensionPropertyInfo *p_argument_info, GDExtensionInt p_argument_count);
|
||||
static void _unregister_extension_class(GDExtensionClassLibraryPtr p_library, GDExtensionConstStringNamePtr p_class_name);
|
||||
static void _get_library_path(GDExtensionClassLibraryPtr p_library, GDExtensionStringPtr r_path);
|
||||
static void _register_get_classes_used_callback(GDExtensionClassLibraryPtr p_library, GDExtensionEditorGetClassesUsedCallback p_callback);
|
||||
static void _register_main_loop_callbacks(GDExtensionClassLibraryPtr p_library, const GDExtensionMainLoopCallbacks *p_callbacks);
|
||||
|
||||
GDExtensionInitialization initialization;
|
||||
int32_t level_initialized = -1;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool is_reloading = false;
|
||||
Vector<GDExtensionMethodBind *> invalid_methods;
|
||||
Vector<ObjectID> instance_bindings;
|
||||
GDExtensionEditorGetClassesUsedCallback get_classes_used_callback = nullptr;
|
||||
|
||||
static void _track_instance(void *p_user_data, void *p_instance);
|
||||
static void _untrack_instance(void *p_user_data, void *p_instance);
|
||||
|
||||
void _clear_extension(Extension *p_extension);
|
||||
|
||||
// Only called by GDExtensionManager during the reload process.
|
||||
void prepare_reload();
|
||||
void finish_reload();
|
||||
void clear_instance_bindings();
|
||||
#endif
|
||||
|
||||
GDExtensionMainLoopStartupCallback startup_callback = nullptr;
|
||||
GDExtensionMainLoopShutdownCallback shutdown_callback = nullptr;
|
||||
GDExtensionMainLoopFrameCallback frame_callback = nullptr;
|
||||
|
||||
static inline HashMap<StringName, GDExtensionInterfaceFunctionPtr> gdextension_interface_functions;
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
|
||||
public:
|
||||
HashMap<String, String> class_icon_paths;
|
||||
|
||||
virtual bool editor_can_reload_from_file() override { return false; } // Reloading is handled in a special way.
|
||||
|
||||
static String get_extension_list_config_file();
|
||||
|
||||
const Ref<GDExtensionLoader> get_loader() const { return loader; }
|
||||
|
||||
Error open_library(const String &p_path, const Ref<GDExtensionLoader> &p_loader);
|
||||
void close_library();
|
||||
bool is_library_open() const;
|
||||
|
||||
enum InitializationLevel {
|
||||
INITIALIZATION_LEVEL_CORE = GDEXTENSION_INITIALIZATION_CORE,
|
||||
INITIALIZATION_LEVEL_SERVERS = GDEXTENSION_INITIALIZATION_SERVERS,
|
||||
INITIALIZATION_LEVEL_SCENE = GDEXTENSION_INITIALIZATION_SCENE,
|
||||
INITIALIZATION_LEVEL_EDITOR = GDEXTENSION_INITIALIZATION_EDITOR
|
||||
};
|
||||
|
||||
protected:
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
Error _open_library_bind_compat_88418(const String &p_path, const String &p_entry_symbol);
|
||||
void _close_library_bind_compat_88418();
|
||||
void _initialize_library_bind_compat_88418(InitializationLevel p_level);
|
||||
static void _bind_compatibility_methods();
|
||||
#endif
|
||||
|
||||
public:
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool is_reloadable() const { return reloadable; }
|
||||
void set_reloadable(bool p_reloadable) { reloadable = p_reloadable; }
|
||||
|
||||
bool has_library_changed() const;
|
||||
|
||||
void track_instance_binding(Object *p_object);
|
||||
void untrack_instance_binding(Object *p_object);
|
||||
|
||||
PackedStringArray get_classes_used() const;
|
||||
#endif
|
||||
|
||||
InitializationLevel get_minimum_library_initialization_level() const;
|
||||
void initialize_library(InitializationLevel p_level);
|
||||
void deinitialize_library(InitializationLevel p_level);
|
||||
|
||||
static void register_interface_function(const StringName &p_function_name, GDExtensionInterfaceFunctionPtr p_function_pointer);
|
||||
static GDExtensionInterfaceFunctionPtr get_interface_function(const StringName &p_function_name);
|
||||
static void initialize_gdextensions();
|
||||
static void finalize_gdextensions();
|
||||
|
||||
GDExtension();
|
||||
~GDExtension();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(GDExtension::InitializationLevel)
|
||||
|
||||
class GDExtensionResourceLoader : public ResourceFormatLoader {
|
||||
public:
|
||||
static Error load_gdextension_resource(const String &p_path, Ref<GDExtension> &p_extension);
|
||||
|
||||
virtual Ref<Resource> load(const String &p_path, const String &p_original_path, Error *r_error, bool p_use_sub_threads = false, float *r_progress = nullptr, CacheMode p_cache_mode = CACHE_MODE_REUSE) override;
|
||||
virtual void get_recognized_extensions(List<String> *p_extensions) const override;
|
||||
virtual bool handles_type(const String &p_type) const override;
|
||||
virtual String get_resource_type(const String &p_path) const override;
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual void get_classes_used(const String &p_path, HashSet<StringName> *r_classes) override;
|
||||
#endif // TOOLS_ENABLED
|
||||
};
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
class GDExtensionEditorPlugins {
|
||||
private:
|
||||
static inline Vector<StringName> extension_classes;
|
||||
|
||||
protected:
|
||||
friend class EditorNode;
|
||||
|
||||
// Since this in core, we can't directly reference EditorNode, so it will
|
||||
// set these function pointers in its constructor.
|
||||
typedef void (*EditorPluginRegisterFunc)(const StringName &p_class_name);
|
||||
static inline EditorPluginRegisterFunc editor_node_add_plugin = nullptr;
|
||||
static inline EditorPluginRegisterFunc editor_node_remove_plugin = nullptr;
|
||||
|
||||
public:
|
||||
static void add_extension_class(const StringName &p_class_name);
|
||||
static void remove_extension_class(const StringName &p_class_name);
|
||||
|
||||
static const Vector<StringName> &get_extension_classes() {
|
||||
return extension_classes;
|
||||
}
|
||||
};
|
||||
|
||||
class GDExtensionEditorHelp {
|
||||
protected:
|
||||
friend class EditorHelp;
|
||||
|
||||
// Similarly to EditorNode above, we need to be able to ask EditorHelp to parse
|
||||
// new documentation data. Note though that, differently from EditorHelp, this
|
||||
// is initialized even _before_ it gets instantiated, as we need to rely on
|
||||
// this method while initializing the engine.
|
||||
typedef void (*EditorHelpLoadXmlBufferFunc)(const uint8_t *p_buffer, int p_size);
|
||||
static inline EditorHelpLoadXmlBufferFunc editor_help_load_xml_buffer = nullptr;
|
||||
|
||||
typedef void (*EditorHelpRemoveClassFunc)(const String &p_class);
|
||||
static inline EditorHelpRemoveClassFunc editor_help_remove_class = nullptr;
|
||||
|
||||
public:
|
||||
static void load_xml_buffer(const uint8_t *p_buffer, int p_size);
|
||||
static void remove_class(const String &p_class);
|
||||
};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
1854
core/extension/gdextension_interface.cpp
Normal file
1854
core/extension/gdextension_interface.cpp
Normal file
File diff suppressed because it is too large
Load Diff
3198
core/extension/gdextension_interface.h
Normal file
3198
core/extension/gdextension_interface.h
Normal file
File diff suppressed because it is too large
Load Diff
394
core/extension/gdextension_library_loader.cpp
Normal file
394
core/extension/gdextension_library_loader.cpp
Normal file
@@ -0,0 +1,394 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_library_loader.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 "gdextension_library_loader.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/version.h"
|
||||
#include "gdextension.h"
|
||||
|
||||
Vector<SharedObject> GDExtensionLibraryLoader::find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature) {
|
||||
Vector<SharedObject> dependencies_shared_objects;
|
||||
if (p_config->has_section("dependencies")) {
|
||||
Vector<String> config_dependencies = p_config->get_section_keys("dependencies");
|
||||
|
||||
for (const String &dependency : config_dependencies) {
|
||||
Vector<String> dependency_tags = dependency.split(".");
|
||||
bool all_tags_met = true;
|
||||
for (int i = 0; i < dependency_tags.size(); i++) {
|
||||
String tag = dependency_tags[i].strip_edges();
|
||||
if (!p_has_feature(tag)) {
|
||||
all_tags_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_tags_met) {
|
||||
Dictionary dependency_value = p_config->get_value("dependencies", dependency);
|
||||
for (const Variant *key = dependency_value.next(nullptr); key; key = dependency_value.next(key)) {
|
||||
String dependency_path = *key;
|
||||
String target_path = dependency_value[*key];
|
||||
if (dependency_path.is_relative_path()) {
|
||||
dependency_path = p_path.get_base_dir().path_join(dependency_path);
|
||||
}
|
||||
dependencies_shared_objects.push_back(SharedObject(dependency_path, dependency_tags, target_path));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies_shared_objects;
|
||||
}
|
||||
|
||||
String GDExtensionLibraryLoader::find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags) {
|
||||
// First, check the explicit libraries.
|
||||
if (p_config->has_section("libraries")) {
|
||||
Vector<String> libraries = p_config->get_section_keys("libraries");
|
||||
|
||||
// Iterate the libraries, finding the best matching tags.
|
||||
String best_library_path;
|
||||
Vector<String> best_library_tags;
|
||||
for (const String &E : libraries) {
|
||||
Vector<String> tags = E.split(".");
|
||||
bool all_tags_met = true;
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
String tag = tags[i].strip_edges();
|
||||
if (!p_has_feature(tag)) {
|
||||
all_tags_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_tags_met && tags.size() > best_library_tags.size()) {
|
||||
best_library_path = p_config->get_value("libraries", E);
|
||||
best_library_tags = tags;
|
||||
}
|
||||
}
|
||||
|
||||
if (!best_library_path.is_empty()) {
|
||||
if (best_library_path.is_relative_path()) {
|
||||
best_library_path = p_path.get_base_dir().path_join(best_library_path);
|
||||
}
|
||||
if (r_tags != nullptr) {
|
||||
r_tags->append_array(best_library_tags);
|
||||
}
|
||||
return best_library_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Second, try to autodetect.
|
||||
String autodetect_library_prefix;
|
||||
if (p_config->has_section_key("configuration", "autodetect_library_prefix")) {
|
||||
autodetect_library_prefix = p_config->get_value("configuration", "autodetect_library_prefix");
|
||||
}
|
||||
if (!autodetect_library_prefix.is_empty()) {
|
||||
String autodetect_path = autodetect_library_prefix;
|
||||
if (autodetect_path.is_relative_path()) {
|
||||
autodetect_path = p_path.get_base_dir().path_join(autodetect_path);
|
||||
}
|
||||
|
||||
// Find the folder and file parts of the prefix.
|
||||
String folder;
|
||||
String file_prefix;
|
||||
if (DirAccess::dir_exists_absolute(autodetect_path)) {
|
||||
folder = autodetect_path;
|
||||
} else if (DirAccess::dir_exists_absolute(autodetect_path.get_base_dir())) {
|
||||
folder = autodetect_path.get_base_dir();
|
||||
file_prefix = autodetect_path.get_file();
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
// Open the folder.
|
||||
Ref<DirAccess> dir = DirAccess::open(folder);
|
||||
ERR_FAIL_COND_V_MSG(dir.is_null(), 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));
|
||||
|
||||
// Iterate the files and check the prefixes, finding the best matching file.
|
||||
String best_file;
|
||||
Vector<String> best_file_tags;
|
||||
dir->list_dir_begin();
|
||||
String file_name = dir->_get_next();
|
||||
while (file_name != "") {
|
||||
if (!dir->current_is_dir() && file_name.begins_with(file_prefix)) {
|
||||
// Check if the files matches all requested feature tags.
|
||||
String tags_str = file_name.trim_prefix(file_prefix);
|
||||
tags_str = tags_str.trim_suffix(tags_str.get_extension());
|
||||
|
||||
Vector<String> tags = tags_str.split(".", false);
|
||||
bool all_tags_met = true;
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
String tag = tags[i].strip_edges();
|
||||
if (!p_has_feature(tag)) {
|
||||
all_tags_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If all tags are found in the feature list, and we found more tags than before, use this file.
|
||||
if (all_tags_met && tags.size() > best_file_tags.size()) {
|
||||
best_file_tags = tags;
|
||||
best_file = file_name;
|
||||
}
|
||||
}
|
||||
file_name = dir->_get_next();
|
||||
}
|
||||
|
||||
if (!best_file.is_empty()) {
|
||||
String library_path = folder.path_join(best_file);
|
||||
if (r_tags != nullptr) {
|
||||
r_tags->append_array(best_file_tags);
|
||||
}
|
||||
return library_path;
|
||||
}
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
Error GDExtensionLibraryLoader::open_library(const String &p_path) {
|
||||
Error err = parse_gdextension_file(p_path);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
String abs_path = ProjectSettings::get_singleton()->globalize_path(library_path);
|
||||
|
||||
Vector<String> abs_dependencies_paths;
|
||||
if (!library_dependencies.is_empty()) {
|
||||
for (const SharedObject &dependency : library_dependencies) {
|
||||
abs_dependencies_paths.push_back(ProjectSettings::get_singleton()->globalize_path(dependency.path));
|
||||
}
|
||||
}
|
||||
|
||||
OS::GDExtensionData data = {
|
||||
true, // also_set_library_path
|
||||
&library_path, // r_resolved_path
|
||||
Engine::get_singleton()->is_editor_hint(), // generate_temp_files
|
||||
&abs_dependencies_paths, // library_dependencies
|
||||
};
|
||||
|
||||
err = OS::get_singleton()->open_dynamic_library(is_static_library ? String() : abs_path, library, &data);
|
||||
if (err != OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error GDExtensionLibraryLoader::initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
p_extension->set_reloadable(is_reloadable && Engine::get_singleton()->is_extension_reloading_enabled());
|
||||
#endif
|
||||
|
||||
for (const KeyValue<String, String> &icon : class_icon_paths) {
|
||||
p_extension->class_icon_paths[icon.key] = icon.value;
|
||||
}
|
||||
|
||||
void *entry_funcptr = nullptr;
|
||||
|
||||
Error err = OS::get_singleton()->get_dynamic_library_symbol_handle(library, entry_symbol, entry_funcptr, false);
|
||||
|
||||
if (err != OK) {
|
||||
ERR_PRINT(vformat("GDExtension entry point '%s' not found in library %s.", entry_symbol, library_path));
|
||||
return err;
|
||||
}
|
||||
|
||||
GDExtensionInitializationFunction initialization_function = (GDExtensionInitializationFunction)entry_funcptr;
|
||||
|
||||
GDExtensionBool ret = initialization_function(p_get_proc_address, p_extension.ptr(), r_initialization);
|
||||
|
||||
if (ret) {
|
||||
return OK;
|
||||
} else {
|
||||
ERR_PRINT(vformat("GDExtension initialization function '%s' returned an error.", entry_symbol));
|
||||
return FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionLibraryLoader::close_library() {
|
||||
OS::get_singleton()->close_dynamic_library(library);
|
||||
library = nullptr;
|
||||
}
|
||||
|
||||
bool GDExtensionLibraryLoader::is_library_open() const {
|
||||
return library != nullptr;
|
||||
}
|
||||
|
||||
bool GDExtensionLibraryLoader::has_library_changed() const {
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Check only that the last modified time is different (rather than checking
|
||||
// that it's newer) since some OS's (namely Windows) will preserve the modified
|
||||
// time by default when copying files.
|
||||
if (FileAccess::get_modified_time(resource_path) != resource_last_modified_time) {
|
||||
return true;
|
||||
}
|
||||
if (FileAccess::get_modified_time(library_path) != library_last_modified_time) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GDExtensionLibraryLoader::library_exists() const {
|
||||
return FileAccess::exists(resource_path);
|
||||
}
|
||||
|
||||
Error GDExtensionLibraryLoader::parse_gdextension_file(const String &p_path) {
|
||||
resource_path = p_path;
|
||||
|
||||
Ref<ConfigFile> config;
|
||||
config.instantiate();
|
||||
|
||||
Error err = config->load(p_path);
|
||||
|
||||
if (err != OK) {
|
||||
ERR_PRINT(vformat("Error loading GDExtension configuration file: '%s'.", p_path));
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!config->has_section_key("configuration", "entry_symbol")) {
|
||||
ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/entry_symbol\" key: '%s'.", p_path));
|
||||
return ERR_INVALID_DATA;
|
||||
}
|
||||
|
||||
entry_symbol = config->get_value("configuration", "entry_symbol");
|
||||
|
||||
uint32_t compatibility_minimum[3] = { 0, 0, 0 };
|
||||
if (config->has_section_key("configuration", "compatibility_minimum")) {
|
||||
String compat_string = config->get_value("configuration", "compatibility_minimum");
|
||||
Vector<int> parts = compat_string.split_ints(".");
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
if (i >= 3) {
|
||||
break;
|
||||
}
|
||||
if (parts[i] >= 0) {
|
||||
compatibility_minimum[i] = parts[i];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ERR_PRINT(vformat("GDExtension configuration file must contain a \"configuration/compatibility_minimum\" key: '%s'.", p_path));
|
||||
return ERR_INVALID_DATA;
|
||||
}
|
||||
|
||||
if (compatibility_minimum[0] < 4 || (compatibility_minimum[0] == 4 && compatibility_minimum[1] == 0)) {
|
||||
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));
|
||||
return ERR_INVALID_DATA;
|
||||
}
|
||||
|
||||
bool compatible = true;
|
||||
// Check version lexicographically.
|
||||
if (GODOT_VERSION_MAJOR != compatibility_minimum[0]) {
|
||||
compatible = GODOT_VERSION_MAJOR > compatibility_minimum[0];
|
||||
} else if (GODOT_VERSION_MINOR != compatibility_minimum[1]) {
|
||||
compatible = GODOT_VERSION_MINOR > compatibility_minimum[1];
|
||||
} else {
|
||||
compatible = GODOT_VERSION_PATCH >= compatibility_minimum[2];
|
||||
}
|
||||
if (!compatible) {
|
||||
ERR_PRINT(vformat("GDExtension only compatible with Godot version %d.%d.%d or later: %s, but your Godot version is %d.%d.%d",
|
||||
compatibility_minimum[0], compatibility_minimum[1], compatibility_minimum[2], p_path,
|
||||
GODOT_VERSION_MAJOR, GODOT_VERSION_MINOR, GODOT_VERSION_PATCH));
|
||||
return ERR_INVALID_DATA;
|
||||
}
|
||||
|
||||
// Optionally check maximum compatibility.
|
||||
if (config->has_section_key("configuration", "compatibility_maximum")) {
|
||||
uint32_t compatibility_maximum[3] = { 0, 0, 0 };
|
||||
String compat_string = config->get_value("configuration", "compatibility_maximum");
|
||||
Vector<int> parts = compat_string.split_ints(".");
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (i < parts.size() && parts[i] >= 0) {
|
||||
compatibility_maximum[i] = parts[i];
|
||||
} else {
|
||||
// If a version part is missing, set the maximum to an arbitrary high value.
|
||||
compatibility_maximum[i] = 9999;
|
||||
}
|
||||
}
|
||||
|
||||
compatible = true;
|
||||
if (GODOT_VERSION_MAJOR != compatibility_maximum[0]) {
|
||||
compatible = GODOT_VERSION_MAJOR < compatibility_maximum[0];
|
||||
} else if (GODOT_VERSION_MINOR != compatibility_maximum[1]) {
|
||||
compatible = GODOT_VERSION_MINOR < compatibility_maximum[1];
|
||||
}
|
||||
#if GODOT_VERSION_PATCH
|
||||
// #if check to avoid -Wtype-limits warning when 0.
|
||||
else {
|
||||
compatible = GODOT_VERSION_PATCH <= compatibility_maximum[2];
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!compatible) {
|
||||
ERR_PRINT(vformat("GDExtension only compatible with Godot version %s or earlier: %s, but your Godot version is %d.%d.%d",
|
||||
compat_string, p_path, GODOT_VERSION_MAJOR, GODOT_VERSION_MINOR, GODOT_VERSION_PATCH));
|
||||
return ERR_INVALID_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
library_path = find_extension_library(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
|
||||
|
||||
if (library_path.is_empty()) {
|
||||
const String os_arch = OS::get_singleton()->get_name().to_lower() + "." + Engine::get_singleton()->get_architecture_name();
|
||||
ERR_PRINT(vformat("No GDExtension library found for current OS and architecture (%s) in configuration file: %s", os_arch, p_path));
|
||||
return ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
is_static_library = library_path.ends_with(".a") || library_path.ends_with(".xcframework");
|
||||
|
||||
if (!library_path.is_resource_file() && !library_path.is_absolute_path()) {
|
||||
library_path = p_path.get_base_dir().path_join(library_path);
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
is_reloadable = config->get_value("configuration", "reloadable", false);
|
||||
|
||||
update_last_modified_time(
|
||||
FileAccess::get_modified_time(resource_path),
|
||||
FileAccess::get_modified_time(library_path));
|
||||
#endif
|
||||
|
||||
library_dependencies = find_extension_dependencies(p_path, config, [](const String &p_feature) { return OS::get_singleton()->has_feature(p_feature); });
|
||||
|
||||
// Handle icons if any are specified.
|
||||
if (config->has_section("icons")) {
|
||||
Vector<String> keys = config->get_section_keys("icons");
|
||||
for (const String &key : keys) {
|
||||
String icon_path = config->get_value("icons", key);
|
||||
if (icon_path.is_relative_path()) {
|
||||
icon_path = p_path.get_base_dir().path_join(icon_path);
|
||||
}
|
||||
|
||||
class_icon_paths[key] = icon_path;
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
84
core/extension/gdextension_library_loader.h
Normal file
84
core/extension/gdextension_library_loader.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_library_loader.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "core/extension/gdextension_loader.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/os/shared_object.h"
|
||||
|
||||
class GDExtensionLibraryLoader : public GDExtensionLoader {
|
||||
GDSOFTCLASS(GDExtensionLibraryLoader, GDExtensionLoader);
|
||||
|
||||
friend class GDExtensionManager;
|
||||
friend class GDExtension;
|
||||
|
||||
private:
|
||||
String resource_path;
|
||||
|
||||
void *library = nullptr; // pointer if valid.
|
||||
String library_path;
|
||||
String entry_symbol;
|
||||
|
||||
bool is_static_library = false;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
bool is_reloadable = false;
|
||||
#endif
|
||||
|
||||
Vector<SharedObject> library_dependencies;
|
||||
|
||||
HashMap<String, String> class_icon_paths;
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
uint64_t resource_last_modified_time = 0;
|
||||
uint64_t library_last_modified_time = 0;
|
||||
|
||||
void update_last_modified_time(uint64_t p_resource_last_modified_time, uint64_t p_library_last_modified_time) {
|
||||
resource_last_modified_time = p_resource_last_modified_time;
|
||||
library_last_modified_time = p_library_last_modified_time;
|
||||
}
|
||||
#endif
|
||||
|
||||
public:
|
||||
static String find_extension_library(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature, PackedStringArray *r_tags = nullptr);
|
||||
static Vector<SharedObject> find_extension_dependencies(const String &p_path, Ref<ConfigFile> p_config, std::function<bool(String)> p_has_feature);
|
||||
|
||||
virtual Error open_library(const String &p_path) override;
|
||||
virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) override;
|
||||
virtual void close_library() override;
|
||||
virtual bool is_library_open() const override;
|
||||
virtual bool has_library_changed() const override;
|
||||
virtual bool library_exists() const override;
|
||||
|
||||
Error parse_gdextension_file(const String &p_path);
|
||||
};
|
47
core/extension/gdextension_loader.h
Normal file
47
core/extension/gdextension_loader.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_loader.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/object/ref_counted.h"
|
||||
|
||||
class GDExtension;
|
||||
|
||||
class GDExtensionLoader : public RefCounted {
|
||||
GDSOFTCLASS(GDExtensionLoader, RefCounted);
|
||||
|
||||
public:
|
||||
virtual Error open_library(const String &p_path) = 0;
|
||||
virtual Error initialize(GDExtensionInterfaceGetProcAddress p_get_proc_address, const Ref<GDExtension> &p_extension, GDExtensionInitialization *r_initialization) = 0;
|
||||
virtual void close_library() = 0;
|
||||
virtual bool is_library_open() const = 0;
|
||||
virtual bool has_library_changed() const = 0;
|
||||
virtual bool library_exists() const = 0;
|
||||
};
|
491
core/extension/gdextension_manager.cpp
Normal file
491
core/extension/gdextension_manager.cpp
Normal file
@@ -0,0 +1,491 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_manager.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 "gdextension_manager.h"
|
||||
|
||||
#include "core/extension/gdextension_library_loader.h"
|
||||
#include "core/extension/gdextension_special_compat_hashes.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/object/script_language.h"
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) {
|
||||
if (level >= 0) { // Already initialized up to some level.
|
||||
int32_t minimum_level = 0;
|
||||
if (!p_first_load) {
|
||||
minimum_level = p_extension->get_minimum_library_initialization_level();
|
||||
if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {
|
||||
return LOAD_STATUS_NEEDS_RESTART;
|
||||
}
|
||||
}
|
||||
// Initialize up to current level.
|
||||
for (int32_t i = minimum_level; i <= level; i++) {
|
||||
p_extension->initialize_library(GDExtension::InitializationLevel(i));
|
||||
}
|
||||
}
|
||||
|
||||
for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {
|
||||
gdextension_class_icon_paths[kv.key] = kv.value;
|
||||
}
|
||||
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
void GDExtensionManager::_finish_load_extension(const Ref<GDExtension> &p_extension) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Signals that a new extension is loaded so GDScript can register new class names.
|
||||
emit_signal("extension_loaded", p_extension);
|
||||
#endif
|
||||
|
||||
if (startup_callback_called) {
|
||||
// Extension is loading after the startup callback has already been called,
|
||||
// so we call it now for this extension to make sure it doesn't miss it.
|
||||
if (p_extension->startup_callback) {
|
||||
p_extension->startup_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) {
|
||||
#ifdef TOOLS_ENABLED
|
||||
// Signals that a new extension is unloading so GDScript can unregister class names.
|
||||
emit_signal("extension_unloading", p_extension);
|
||||
#endif
|
||||
|
||||
if (!shutdown_callback_called) {
|
||||
// Extension is unloading before the shutdown callback has been called,
|
||||
// which means the engine hasn't shutdown yet but we want to make sure
|
||||
// to call the shutdown callback so it doesn't miss it.
|
||||
if (p_extension->shutdown_callback) {
|
||||
p_extension->shutdown_callback();
|
||||
}
|
||||
}
|
||||
|
||||
if (level >= 0) { // Already initialized up to some level.
|
||||
// Deinitialize down from current level.
|
||||
for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) {
|
||||
p_extension->deinitialize_library(GDExtension::InitializationLevel(i));
|
||||
}
|
||||
}
|
||||
|
||||
for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {
|
||||
gdextension_class_icon_paths.erase(kv.key);
|
||||
}
|
||||
|
||||
// Clear main loop callbacks.
|
||||
p_extension->startup_callback = nullptr;
|
||||
p_extension->shutdown_callback = nullptr;
|
||||
p_extension->frame_callback = nullptr;
|
||||
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return LOAD_STATUS_FAILED;
|
||||
}
|
||||
|
||||
Ref<GDExtensionLibraryLoader> loader;
|
||||
loader.instantiate();
|
||||
return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader);
|
||||
}
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader) {
|
||||
DEV_ASSERT(p_loader.is_valid());
|
||||
|
||||
if (gdextension_map.has(p_path)) {
|
||||
return LOAD_STATUS_ALREADY_LOADED;
|
||||
}
|
||||
|
||||
Ref<GDExtension> extension;
|
||||
extension.instantiate();
|
||||
Error err = extension->open_library(p_path, p_loader);
|
||||
if (err != OK) {
|
||||
return LOAD_STATUS_FAILED;
|
||||
}
|
||||
|
||||
LoadStatus status = _load_extension_internal(extension, true);
|
||||
if (status != LOAD_STATUS_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
_finish_load_extension(extension);
|
||||
|
||||
extension->set_path(p_path);
|
||||
gdextension_map[p_path] = extension;
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String &p_path) {
|
||||
#ifndef TOOLS_ENABLED
|
||||
ERR_FAIL_V_MSG(LOAD_STATUS_FAILED, "GDExtensions can only be reloaded in an editor build.");
|
||||
#else
|
||||
ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled.");
|
||||
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return LOAD_STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!gdextension_map.has(p_path)) {
|
||||
return LOAD_STATUS_NOT_LOADED;
|
||||
}
|
||||
|
||||
Ref<GDExtension> extension = gdextension_map[p_path];
|
||||
ERR_FAIL_COND_V_MSG(!extension->is_reloadable(), LOAD_STATUS_FAILED, vformat("This GDExtension is not marked as 'reloadable' or doesn't support reloading: %s.", p_path));
|
||||
|
||||
LoadStatus status;
|
||||
|
||||
extension->prepare_reload();
|
||||
|
||||
// Unload library if it's open. It may not be open if the developer made a
|
||||
// change that broke loading in a previous hot-reload attempt.
|
||||
if (extension->is_library_open()) {
|
||||
status = _unload_extension_internal(extension);
|
||||
if (status != LOAD_STATUS_OK) {
|
||||
// We need to clear these no matter what.
|
||||
extension->clear_instance_bindings();
|
||||
return status;
|
||||
}
|
||||
|
||||
extension->clear_instance_bindings();
|
||||
extension->close_library();
|
||||
}
|
||||
|
||||
Error err = extension->open_library(p_path, extension->loader);
|
||||
if (err != OK) {
|
||||
return LOAD_STATUS_FAILED;
|
||||
}
|
||||
|
||||
status = _load_extension_internal(extension, false);
|
||||
if (status != LOAD_STATUS_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
extension->finish_reload();
|
||||
|
||||
// Needs to come after reload is fully finished, so all objects using
|
||||
// extension classes are in a consistent state.
|
||||
_finish_load_extension(extension);
|
||||
|
||||
return LOAD_STATUS_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return LOAD_STATUS_FAILED;
|
||||
}
|
||||
|
||||
if (!gdextension_map.has(p_path)) {
|
||||
return LOAD_STATUS_NOT_LOADED;
|
||||
}
|
||||
|
||||
Ref<GDExtension> extension = gdextension_map[p_path];
|
||||
|
||||
LoadStatus status = _unload_extension_internal(extension);
|
||||
if (status != LOAD_STATUS_OK) {
|
||||
return status;
|
||||
}
|
||||
|
||||
gdextension_map.erase(p_path);
|
||||
return LOAD_STATUS_OK;
|
||||
}
|
||||
|
||||
bool GDExtensionManager::is_extension_loaded(const String &p_path) const {
|
||||
return gdextension_map.has(p_path);
|
||||
}
|
||||
|
||||
Vector<String> GDExtensionManager::get_loaded_extensions() const {
|
||||
Vector<String> ret;
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
ret.push_back(E.key);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) {
|
||||
HashMap<String, Ref<GDExtension>>::Iterator E = gdextension_map.find(p_path);
|
||||
ERR_FAIL_COND_V(!E, Ref<GDExtension>());
|
||||
return E->value;
|
||||
}
|
||||
|
||||
bool GDExtensionManager::class_has_icon_path(const String &p_class) const {
|
||||
// TODO: Check that the icon belongs to a registered class somehow.
|
||||
return gdextension_class_icon_paths.has(p_class);
|
||||
}
|
||||
|
||||
String GDExtensionManager::class_get_icon_path(const String &p_class) const {
|
||||
// TODO: Check that the icon belongs to a registered class somehow.
|
||||
if (gdextension_class_icon_paths.has(p_class)) {
|
||||
return gdextension_class_icon_paths[p_class];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(int32_t(p_level) - 1 != level);
|
||||
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
E.value->initialize_library(p_level);
|
||||
|
||||
if (p_level == GDExtension::INITIALIZATION_LEVEL_EDITOR) {
|
||||
for (const KeyValue<String, String> &kv : E.value->class_icon_paths) {
|
||||
gdextension_class_icon_paths[kv.key] = kv.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
level = p_level;
|
||||
}
|
||||
|
||||
void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND(int32_t(p_level) != level);
|
||||
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
E.value->deinitialize_library(p_level);
|
||||
}
|
||||
level = int32_t(p_level) - 1;
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void GDExtensionManager::track_instance_binding(void *p_token, Object *p_object) {
|
||||
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
if (E.value.ptr() == p_token) {
|
||||
if (E.value->is_reloadable()) {
|
||||
E.value->track_instance_binding(p_object);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::untrack_instance_binding(void *p_token, Object *p_object) {
|
||||
for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
if (E.value.ptr() == p_token) {
|
||||
if (E.value->is_reloadable()) {
|
||||
E.value->untrack_instance_binding(p_object);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::_reload_all_scripts() {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptServer::get_language(i)->reload_all_scripts();
|
||||
}
|
||||
}
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
void GDExtensionManager::load_extensions() {
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Ref<FileAccess> f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ);
|
||||
while (f.is_valid() && !f->eof_reached()) {
|
||||
String s = f->get_line().strip_edges();
|
||||
if (!s.is_empty()) {
|
||||
LoadStatus err = load_extension(s);
|
||||
ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, vformat("Error loading extension: '%s'.", s));
|
||||
}
|
||||
}
|
||||
|
||||
OS::get_singleton()->load_platform_gdextensions();
|
||||
}
|
||||
|
||||
void GDExtensionManager::reload_extensions() {
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (Engine::get_singleton()->is_recovery_mode_hint()) {
|
||||
return;
|
||||
}
|
||||
bool reloaded = false;
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
if (!E.value->is_reloadable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (E.value->has_library_changed()) {
|
||||
reloaded = true;
|
||||
reload_extension(E.value->get_path());
|
||||
}
|
||||
}
|
||||
|
||||
if (reloaded) {
|
||||
emit_signal("extensions_reloaded");
|
||||
|
||||
// Reload all scripts to clear out old references.
|
||||
callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) {
|
||||
Vector<String> extensions_added;
|
||||
Vector<String> extensions_removed;
|
||||
|
||||
for (const String &E : p_extensions) {
|
||||
if (!is_extension_loaded(E)) {
|
||||
extensions_added.push_back(E);
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> loaded_extensions = get_loaded_extensions();
|
||||
for (const String &loaded_extension : loaded_extensions) {
|
||||
if (!p_extensions.has(loaded_extension)) {
|
||||
// The extension may not have a .gdextension file.
|
||||
const Ref<GDExtension> extension = GDExtensionManager::get_singleton()->get_extension(loaded_extension);
|
||||
if (!extension->get_loader()->library_exists()) {
|
||||
extensions_removed.push_back(loaded_extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String extension_list_config_file = GDExtension::get_extension_list_config_file();
|
||||
if (p_extensions.size()) {
|
||||
if (extensions_added.size() || extensions_removed.size()) {
|
||||
// Extensions were added or removed.
|
||||
Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);
|
||||
for (const String &E : p_extensions) {
|
||||
f->store_line(E);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) {
|
||||
// Extensions were removed.
|
||||
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);
|
||||
da->remove(extension_list_config_file);
|
||||
}
|
||||
}
|
||||
|
||||
bool needs_restart = false;
|
||||
for (const String &extension : extensions_added) {
|
||||
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension);
|
||||
if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
|
||||
needs_restart = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (const String &extension : extensions_removed) {
|
||||
GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension);
|
||||
if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {
|
||||
needs_restart = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
if (extensions_added.size() || extensions_removed.size()) {
|
||||
// Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation.
|
||||
emit_signal("extensions_reloaded");
|
||||
|
||||
// Reload all scripts to clear out old references.
|
||||
callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();
|
||||
}
|
||||
#endif
|
||||
|
||||
return needs_restart;
|
||||
}
|
||||
|
||||
void GDExtensionManager::startup() {
|
||||
startup_callback_called = true;
|
||||
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
const Ref<GDExtension> &extension = E.value;
|
||||
if (extension->startup_callback) {
|
||||
extension->startup_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::shutdown() {
|
||||
shutdown_callback_called = true;
|
||||
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
const Ref<GDExtension> &extension = E.value;
|
||||
if (extension->shutdown_callback) {
|
||||
extension->shutdown_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GDExtensionManager::frame() {
|
||||
for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {
|
||||
const Ref<GDExtension> &extension = E.value;
|
||||
if (extension->frame_callback) {
|
||||
extension->frame_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GDExtensionManager *GDExtensionManager::get_singleton() {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
void GDExtensionManager::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("load_extension", "path"), &GDExtensionManager::load_extension);
|
||||
ClassDB::bind_method(D_METHOD("reload_extension", "path"), &GDExtensionManager::reload_extension);
|
||||
ClassDB::bind_method(D_METHOD("unload_extension", "path"), &GDExtensionManager::unload_extension);
|
||||
ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &GDExtensionManager::is_extension_loaded);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &GDExtensionManager::get_loaded_extensions);
|
||||
ClassDB::bind_method(D_METHOD("get_extension", "path"), &GDExtensionManager::get_extension);
|
||||
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_OK);
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_FAILED);
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_ALREADY_LOADED);
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_NOT_LOADED);
|
||||
BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);
|
||||
|
||||
ADD_SIGNAL(MethodInfo("extensions_reloaded"));
|
||||
ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
|
||||
ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));
|
||||
}
|
||||
|
||||
GDExtensionManager::GDExtensionManager() {
|
||||
ERR_FAIL_COND(singleton != nullptr);
|
||||
singleton = this;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
GDExtensionSpecialCompatHashes::initialize();
|
||||
#endif
|
||||
}
|
||||
|
||||
GDExtensionManager::~GDExtensionManager() {
|
||||
if (singleton == this) {
|
||||
singleton = nullptr;
|
||||
}
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
GDExtensionSpecialCompatHashes::finalize();
|
||||
#endif
|
||||
}
|
101
core/extension/gdextension_manager.h
Normal file
101
core/extension/gdextension_manager.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_manager.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/extension/gdextension.h"
|
||||
|
||||
class GDExtensionManager : public Object {
|
||||
GDCLASS(GDExtensionManager, Object);
|
||||
|
||||
int32_t level = -1;
|
||||
HashMap<String, Ref<GDExtension>> gdextension_map;
|
||||
HashMap<String, String> gdextension_class_icon_paths;
|
||||
|
||||
bool startup_callback_called = false;
|
||||
bool shutdown_callback_called = false;
|
||||
|
||||
static void _bind_methods();
|
||||
|
||||
static inline GDExtensionManager *singleton = nullptr;
|
||||
|
||||
public:
|
||||
enum LoadStatus {
|
||||
LOAD_STATUS_OK,
|
||||
LOAD_STATUS_FAILED,
|
||||
LOAD_STATUS_ALREADY_LOADED,
|
||||
LOAD_STATUS_NOT_LOADED,
|
||||
LOAD_STATUS_NEEDS_RESTART,
|
||||
};
|
||||
|
||||
private:
|
||||
LoadStatus _load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load);
|
||||
void _finish_load_extension(const Ref<GDExtension> &p_extension);
|
||||
LoadStatus _unload_extension_internal(const Ref<GDExtension> &p_extension);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
static void _reload_all_scripts();
|
||||
#endif
|
||||
|
||||
public:
|
||||
LoadStatus load_extension(const String &p_path);
|
||||
LoadStatus load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader);
|
||||
LoadStatus reload_extension(const String &p_path);
|
||||
LoadStatus unload_extension(const String &p_path);
|
||||
bool is_extension_loaded(const String &p_path) const;
|
||||
Vector<String> get_loaded_extensions() const;
|
||||
Ref<GDExtension> get_extension(const String &p_path);
|
||||
|
||||
bool class_has_icon_path(const String &p_class) const;
|
||||
String class_get_icon_path(const String &p_class) const;
|
||||
|
||||
void initialize_extensions(GDExtension::InitializationLevel p_level);
|
||||
void deinitialize_extensions(GDExtension::InitializationLevel p_level);
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
void track_instance_binding(void *p_token, Object *p_object);
|
||||
void untrack_instance_binding(void *p_token, Object *p_object);
|
||||
#endif
|
||||
|
||||
static GDExtensionManager *get_singleton();
|
||||
|
||||
void load_extensions();
|
||||
void reload_extensions();
|
||||
bool ensure_extensions_loaded(const HashSet<String> &p_extensions);
|
||||
|
||||
void startup();
|
||||
void shutdown();
|
||||
void frame();
|
||||
|
||||
GDExtensionManager();
|
||||
~GDExtensionManager();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(GDExtensionManager::LoadStatus)
|
1026
core/extension/gdextension_special_compat_hashes.cpp
Normal file
1026
core/extension/gdextension_special_compat_hashes.cpp
Normal file
File diff suppressed because it is too large
Load Diff
59
core/extension/gdextension_special_compat_hashes.h
Normal file
59
core/extension/gdextension_special_compat_hashes.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/**************************************************************************/
|
||||
/* gdextension_special_compat_hashes.h */
|
||||
/**************************************************************************/
|
||||
/* 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. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
// Note: In most situations, compatibility methods should be registered via ClassDB::bind_compatibility_method().
|
||||
// This class is only meant to be used in exceptional circumstances, for example, when Godot's hashing
|
||||
// algorithm changes and registering compatibility methods for all affect methods would be onerous.
|
||||
|
||||
class GDExtensionSpecialCompatHashes {
|
||||
struct Mapping {
|
||||
StringName method;
|
||||
uint32_t legacy_hash;
|
||||
uint32_t current_hash;
|
||||
};
|
||||
|
||||
static inline HashMap<StringName, LocalVector<Mapping>> mappings;
|
||||
|
||||
public:
|
||||
static void initialize();
|
||||
static void finalize();
|
||||
static bool lookup_current_hash(const StringName &p_class, const StringName &p_method, uint32_t p_legacy_hash, uint32_t *r_current_hash);
|
||||
static bool get_legacy_hashes(const StringName &p_class, const StringName &p_method, Array &r_hashes, bool p_check_valid = true);
|
||||
};
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
37
core/extension/make_interface_dumper.py
Normal file
37
core/extension/make_interface_dumper.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import methods
|
||||
|
||||
|
||||
def run(target, source, env):
|
||||
buffer = methods.get_buffer(str(source[0]))
|
||||
decomp_size = len(buffer)
|
||||
buffer = methods.compress_buffer(buffer)
|
||||
|
||||
with methods.generated_wrapper(str(target[0])) as file:
|
||||
file.write(f"""\
|
||||
#ifdef TOOLS_ENABLED
|
||||
|
||||
#include "core/io/compression.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
inline constexpr int _gdextension_interface_data_compressed_size = {len(buffer)};
|
||||
inline constexpr int _gdextension_interface_data_uncompressed_size = {decomp_size};
|
||||
inline constexpr unsigned char _gdextension_interface_data_compressed[] = {{
|
||||
{methods.format_buffer(buffer, 1)}
|
||||
}};
|
||||
|
||||
class GDExtensionInterfaceDump {{
|
||||
public:
|
||||
static void generate_gdextension_interface_file(const String &p_path) {{
|
||||
Ref<FileAccess> fa = FileAccess::open(p_path, FileAccess::WRITE);
|
||||
ERR_FAIL_COND_MSG(fa.is_null(), vformat("Cannot open file '%s' for writing.", p_path));
|
||||
Vector<uint8_t> data;
|
||||
data.resize(_gdextension_interface_data_uncompressed_size);
|
||||
int ret = Compression::decompress(data.ptrw(), _gdextension_interface_data_uncompressed_size, _gdextension_interface_data_compressed, _gdextension_interface_data_compressed_size, Compression::MODE_DEFLATE);
|
||||
ERR_FAIL_COND_MSG(ret == -1, "Compressed file is corrupt.");
|
||||
fa->store_buffer(data.ptr(), data.size());
|
||||
}};
|
||||
}};
|
||||
|
||||
#endif // TOOLS_ENABLED
|
||||
""")
|
139
core/extension/make_wrappers.py
Normal file
139
core/extension/make_wrappers.py
Normal file
@@ -0,0 +1,139 @@
|
||||
proto_mod = """
|
||||
#define MODBIND$VER($RETTYPE m_name$ARG) \\
|
||||
virtual $RETVAL _##m_name($FUNCARGS) $CONST; \\
|
||||
_FORCE_INLINE_ virtual $RETVAL m_name($FUNCARGS) $CONST override { \\
|
||||
$RETX _##m_name($CALLARGS);\\
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def generate_mod_version(argcount, const=False, returns=False):
|
||||
s = proto_mod
|
||||
sproto = str(argcount)
|
||||
if returns:
|
||||
sproto += "R"
|
||||
s = s.replace("$RETTYPE", "m_ret, ")
|
||||
s = s.replace("$RETVAL", "m_ret")
|
||||
s = s.replace("$RETX", "return")
|
||||
|
||||
else:
|
||||
s = s.replace("$RETTYPE", "")
|
||||
s = s.replace("$RETVAL", "void")
|
||||
s = s.replace("$RETX", "")
|
||||
|
||||
if const:
|
||||
sproto += "C"
|
||||
s = s.replace("$CONST", "const")
|
||||
else:
|
||||
s = s.replace("$CONST", "")
|
||||
|
||||
s = s.replace("$VER", sproto)
|
||||
argtext = ""
|
||||
funcargs = ""
|
||||
callargs = ""
|
||||
|
||||
for i in range(argcount):
|
||||
if i > 0:
|
||||
funcargs += ", "
|
||||
callargs += ", "
|
||||
|
||||
argtext += ", m_type" + str(i + 1)
|
||||
funcargs += "m_type" + str(i + 1) + " arg" + str(i + 1)
|
||||
callargs += "arg" + str(i + 1)
|
||||
|
||||
if argcount:
|
||||
s = s.replace("$ARG", argtext)
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
else:
|
||||
s = s.replace("$ARG", "")
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
|
||||
return s
|
||||
|
||||
|
||||
proto_ex = """
|
||||
#define EXBIND$VER($RETTYPE m_name$ARG) \\
|
||||
GDVIRTUAL$VER_REQUIRED($RETTYPE_##m_name$ARG)\\
|
||||
virtual $RETVAL m_name($FUNCARGS) $CONST override { \\
|
||||
$RETPRE\\
|
||||
GDVIRTUAL_CALL(_##m_name$CALLARGS$RETREF);\\
|
||||
$RETPOST\\
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def generate_ex_version(argcount, const=False, returns=False):
|
||||
s = proto_ex
|
||||
sproto = str(argcount)
|
||||
if returns:
|
||||
sproto += "R"
|
||||
s = s.replace("$RETTYPE", "m_ret, ")
|
||||
s = s.replace("$RETVAL", "m_ret")
|
||||
s = s.replace("$RETPRE", "m_ret ret; ZeroInitializer<m_ret>::initialize(ret);\\\n")
|
||||
s = s.replace("$RETPOST", "return ret;\\\n")
|
||||
|
||||
else:
|
||||
s = s.replace("$RETTYPE", "")
|
||||
s = s.replace("$RETVAL", "void")
|
||||
s = s.replace("$RETPRE", "")
|
||||
s = s.replace("$RETPOST", "return;")
|
||||
|
||||
if const:
|
||||
sproto += "C"
|
||||
s = s.replace("$CONST", "const")
|
||||
else:
|
||||
s = s.replace("$CONST", "")
|
||||
|
||||
s = s.replace("$VER", sproto)
|
||||
argtext = ""
|
||||
funcargs = ""
|
||||
callargs = ""
|
||||
|
||||
for i in range(argcount):
|
||||
if i > 0:
|
||||
funcargs += ", "
|
||||
|
||||
argtext += ", m_type" + str(i + 1)
|
||||
funcargs += "m_type" + str(i + 1) + " arg" + str(i + 1)
|
||||
callargs += ", arg" + str(i + 1)
|
||||
|
||||
if argcount:
|
||||
s = s.replace("$ARG", argtext)
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
else:
|
||||
s = s.replace("$ARG", "")
|
||||
s = s.replace("$FUNCARGS", funcargs)
|
||||
s = s.replace("$CALLARGS", callargs)
|
||||
|
||||
if returns:
|
||||
s = s.replace("$RETREF", ", ret")
|
||||
else:
|
||||
s = s.replace("$RETREF", "")
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def run(target, source, env):
|
||||
max_versions = 12
|
||||
|
||||
txt = "#pragma once"
|
||||
|
||||
for i in range(max_versions + 1):
|
||||
txt += "\n/* Extension Wrapper " + str(i) + " Arguments */\n"
|
||||
txt += generate_ex_version(i, False, False)
|
||||
txt += generate_ex_version(i, False, True)
|
||||
txt += generate_ex_version(i, True, False)
|
||||
txt += generate_ex_version(i, True, True)
|
||||
|
||||
for i in range(max_versions + 1):
|
||||
txt += "\n/* Module Wrapper " + str(i) + " Arguments */\n"
|
||||
txt += generate_mod_version(i, False, False)
|
||||
txt += generate_mod_version(i, False, True)
|
||||
txt += generate_mod_version(i, True, False)
|
||||
txt += generate_mod_version(i, True, True)
|
||||
|
||||
with open(str(target[0]), "w", encoding="utf-8", newline="\n") as f:
|
||||
f.write(txt)
|
Reference in New Issue
Block a user