Path: blob/master/core/extension/gdextension_manager.cpp
9903 views
/**************************************************************************/1/* gdextension_manager.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "gdextension_manager.h"3132#include "core/extension/gdextension_library_loader.h"33#include "core/extension/gdextension_special_compat_hashes.h"34#include "core/io/dir_access.h"35#include "core/io/file_access.h"36#include "core/object/script_language.h"3738GDExtensionManager::LoadStatus GDExtensionManager::_load_extension_internal(const Ref<GDExtension> &p_extension, bool p_first_load) {39if (level >= 0) { // Already initialized up to some level.40int32_t minimum_level = 0;41if (!p_first_load) {42minimum_level = p_extension->get_minimum_library_initialization_level();43if (minimum_level < MIN(level, GDExtension::INITIALIZATION_LEVEL_SCENE)) {44return LOAD_STATUS_NEEDS_RESTART;45}46}47// Initialize up to current level.48for (int32_t i = minimum_level; i <= level; i++) {49p_extension->initialize_library(GDExtension::InitializationLevel(i));50}51}5253for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {54gdextension_class_icon_paths[kv.key] = kv.value;55}5657return LOAD_STATUS_OK;58}5960void GDExtensionManager::_finish_load_extension(const Ref<GDExtension> &p_extension) {61#ifdef TOOLS_ENABLED62// Signals that a new extension is loaded so GDScript can register new class names.63emit_signal("extension_loaded", p_extension);64#endif6566if (startup_callback_called) {67// Extension is loading after the startup callback has already been called,68// so we call it now for this extension to make sure it doesn't miss it.69if (p_extension->startup_callback) {70p_extension->startup_callback();71}72}73}7475GDExtensionManager::LoadStatus GDExtensionManager::_unload_extension_internal(const Ref<GDExtension> &p_extension) {76#ifdef TOOLS_ENABLED77// Signals that a new extension is unloading so GDScript can unregister class names.78emit_signal("extension_unloading", p_extension);79#endif8081if (!shutdown_callback_called) {82// Extension is unloading before the shutdown callback has been called,83// which means the engine hasn't shutdown yet but we want to make sure84// to call the shutdown callback so it doesn't miss it.85if (p_extension->shutdown_callback) {86p_extension->shutdown_callback();87}88}8990if (level >= 0) { // Already initialized up to some level.91// Deinitialize down from current level.92for (int32_t i = level; i >= GDExtension::INITIALIZATION_LEVEL_CORE; i--) {93p_extension->deinitialize_library(GDExtension::InitializationLevel(i));94}95}9697for (const KeyValue<String, String> &kv : p_extension->class_icon_paths) {98gdextension_class_icon_paths.erase(kv.key);99}100101// Clear main loop callbacks.102p_extension->startup_callback = nullptr;103p_extension->shutdown_callback = nullptr;104p_extension->frame_callback = nullptr;105106return LOAD_STATUS_OK;107}108109GDExtensionManager::LoadStatus GDExtensionManager::load_extension(const String &p_path) {110if (Engine::get_singleton()->is_recovery_mode_hint()) {111return LOAD_STATUS_FAILED;112}113114Ref<GDExtensionLibraryLoader> loader;115loader.instantiate();116return GDExtensionManager::get_singleton()->load_extension_with_loader(p_path, loader);117}118119GDExtensionManager::LoadStatus GDExtensionManager::load_extension_with_loader(const String &p_path, const Ref<GDExtensionLoader> &p_loader) {120DEV_ASSERT(p_loader.is_valid());121122if (gdextension_map.has(p_path)) {123return LOAD_STATUS_ALREADY_LOADED;124}125126Ref<GDExtension> extension;127extension.instantiate();128Error err = extension->open_library(p_path, p_loader);129if (err != OK) {130return LOAD_STATUS_FAILED;131}132133LoadStatus status = _load_extension_internal(extension, true);134if (status != LOAD_STATUS_OK) {135return status;136}137138_finish_load_extension(extension);139140extension->set_path(p_path);141gdextension_map[p_path] = extension;142return LOAD_STATUS_OK;143}144145GDExtensionManager::LoadStatus GDExtensionManager::reload_extension(const String &p_path) {146#ifndef TOOLS_ENABLED147ERR_FAIL_V_MSG(LOAD_STATUS_FAILED, "GDExtensions can only be reloaded in an editor build.");148#else149ERR_FAIL_COND_V_MSG(!Engine::get_singleton()->is_extension_reloading_enabled(), LOAD_STATUS_FAILED, "GDExtension reloading is disabled.");150151if (Engine::get_singleton()->is_recovery_mode_hint()) {152return LOAD_STATUS_FAILED;153}154155if (!gdextension_map.has(p_path)) {156return LOAD_STATUS_NOT_LOADED;157}158159Ref<GDExtension> extension = gdextension_map[p_path];160ERR_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));161162LoadStatus status;163164extension->prepare_reload();165166// Unload library if it's open. It may not be open if the developer made a167// change that broke loading in a previous hot-reload attempt.168if (extension->is_library_open()) {169status = _unload_extension_internal(extension);170if (status != LOAD_STATUS_OK) {171// We need to clear these no matter what.172extension->clear_instance_bindings();173return status;174}175176extension->clear_instance_bindings();177extension->close_library();178}179180Error err = extension->open_library(p_path, extension->loader);181if (err != OK) {182return LOAD_STATUS_FAILED;183}184185status = _load_extension_internal(extension, false);186if (status != LOAD_STATUS_OK) {187return status;188}189190extension->finish_reload();191192// Needs to come after reload is fully finished, so all objects using193// extension classes are in a consistent state.194_finish_load_extension(extension);195196return LOAD_STATUS_OK;197#endif198}199200GDExtensionManager::LoadStatus GDExtensionManager::unload_extension(const String &p_path) {201if (Engine::get_singleton()->is_recovery_mode_hint()) {202return LOAD_STATUS_FAILED;203}204205if (!gdextension_map.has(p_path)) {206return LOAD_STATUS_NOT_LOADED;207}208209Ref<GDExtension> extension = gdextension_map[p_path];210211LoadStatus status = _unload_extension_internal(extension);212if (status != LOAD_STATUS_OK) {213return status;214}215216gdextension_map.erase(p_path);217return LOAD_STATUS_OK;218}219220bool GDExtensionManager::is_extension_loaded(const String &p_path) const {221return gdextension_map.has(p_path);222}223224Vector<String> GDExtensionManager::get_loaded_extensions() const {225Vector<String> ret;226for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {227ret.push_back(E.key);228}229return ret;230}231Ref<GDExtension> GDExtensionManager::get_extension(const String &p_path) {232HashMap<String, Ref<GDExtension>>::Iterator E = gdextension_map.find(p_path);233ERR_FAIL_COND_V(!E, Ref<GDExtension>());234return E->value;235}236237bool GDExtensionManager::class_has_icon_path(const String &p_class) const {238// TODO: Check that the icon belongs to a registered class somehow.239return gdextension_class_icon_paths.has(p_class);240}241242String GDExtensionManager::class_get_icon_path(const String &p_class) const {243// TODO: Check that the icon belongs to a registered class somehow.244if (gdextension_class_icon_paths.has(p_class)) {245return gdextension_class_icon_paths[p_class];246}247return "";248}249250void GDExtensionManager::initialize_extensions(GDExtension::InitializationLevel p_level) {251if (Engine::get_singleton()->is_recovery_mode_hint()) {252return;253}254255ERR_FAIL_COND(int32_t(p_level) - 1 != level);256for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {257E.value->initialize_library(p_level);258259if (p_level == GDExtension::INITIALIZATION_LEVEL_EDITOR) {260for (const KeyValue<String, String> &kv : E.value->class_icon_paths) {261gdextension_class_icon_paths[kv.key] = kv.value;262}263}264}265level = p_level;266}267268void GDExtensionManager::deinitialize_extensions(GDExtension::InitializationLevel p_level) {269if (Engine::get_singleton()->is_recovery_mode_hint()) {270return;271}272273ERR_FAIL_COND(int32_t(p_level) != level);274for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {275E.value->deinitialize_library(p_level);276}277level = int32_t(p_level) - 1;278}279280#ifdef TOOLS_ENABLED281void GDExtensionManager::track_instance_binding(void *p_token, Object *p_object) {282for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {283if (E.value.ptr() == p_token) {284if (E.value->is_reloadable()) {285E.value->track_instance_binding(p_object);286return;287}288}289}290}291292void GDExtensionManager::untrack_instance_binding(void *p_token, Object *p_object) {293for (KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {294if (E.value.ptr() == p_token) {295if (E.value->is_reloadable()) {296E.value->untrack_instance_binding(p_object);297return;298}299}300}301}302303void GDExtensionManager::_reload_all_scripts() {304for (int i = 0; i < ScriptServer::get_language_count(); i++) {305ScriptServer::get_language(i)->reload_all_scripts();306}307}308#endif // TOOLS_ENABLED309310void GDExtensionManager::load_extensions() {311if (Engine::get_singleton()->is_recovery_mode_hint()) {312return;313}314315Ref<FileAccess> f = FileAccess::open(GDExtension::get_extension_list_config_file(), FileAccess::READ);316while (f.is_valid() && !f->eof_reached()) {317String s = f->get_line().strip_edges();318if (!s.is_empty()) {319LoadStatus err = load_extension(s);320ERR_CONTINUE_MSG(err == LOAD_STATUS_FAILED, vformat("Error loading extension: '%s'.", s));321}322}323324OS::get_singleton()->load_platform_gdextensions();325}326327void GDExtensionManager::reload_extensions() {328#ifdef TOOLS_ENABLED329if (Engine::get_singleton()->is_recovery_mode_hint()) {330return;331}332bool reloaded = false;333for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {334if (!E.value->is_reloadable()) {335continue;336}337338if (E.value->has_library_changed()) {339reloaded = true;340reload_extension(E.value->get_path());341}342}343344if (reloaded) {345emit_signal("extensions_reloaded");346347// Reload all scripts to clear out old references.348callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();349}350#endif351}352353bool GDExtensionManager::ensure_extensions_loaded(const HashSet<String> &p_extensions) {354Vector<String> extensions_added;355Vector<String> extensions_removed;356357for (const String &E : p_extensions) {358if (!is_extension_loaded(E)) {359extensions_added.push_back(E);360}361}362363Vector<String> loaded_extensions = get_loaded_extensions();364for (const String &loaded_extension : loaded_extensions) {365if (!p_extensions.has(loaded_extension)) {366// The extension may not have a .gdextension file.367const Ref<GDExtension> extension = GDExtensionManager::get_singleton()->get_extension(loaded_extension);368if (!extension->get_loader()->library_exists()) {369extensions_removed.push_back(loaded_extension);370}371}372}373374String extension_list_config_file = GDExtension::get_extension_list_config_file();375if (p_extensions.size()) {376if (extensions_added.size() || extensions_removed.size()) {377// Extensions were added or removed.378Ref<FileAccess> f = FileAccess::open(extension_list_config_file, FileAccess::WRITE);379for (const String &E : p_extensions) {380f->store_line(E);381}382}383} else {384if (loaded_extensions.size() || FileAccess::exists(extension_list_config_file)) {385// Extensions were removed.386Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);387da->remove(extension_list_config_file);388}389}390391bool needs_restart = false;392for (const String &extension : extensions_added) {393GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->load_extension(extension);394if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {395needs_restart = true;396}397}398399for (const String &extension : extensions_removed) {400GDExtensionManager::LoadStatus st = GDExtensionManager::get_singleton()->unload_extension(extension);401if (st == GDExtensionManager::LOAD_STATUS_NEEDS_RESTART) {402needs_restart = true;403}404}405406#ifdef TOOLS_ENABLED407if (extensions_added.size() || extensions_removed.size()) {408// Emitting extensions_reloaded so EditorNode can reload Inspector and regenerate documentation.409emit_signal("extensions_reloaded");410411// Reload all scripts to clear out old references.412callable_mp_static(&GDExtensionManager::_reload_all_scripts).call_deferred();413}414#endif415416return needs_restart;417}418419void GDExtensionManager::startup() {420startup_callback_called = true;421422for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {423const Ref<GDExtension> &extension = E.value;424if (extension->startup_callback) {425extension->startup_callback();426}427}428}429430void GDExtensionManager::shutdown() {431shutdown_callback_called = true;432433for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {434const Ref<GDExtension> &extension = E.value;435if (extension->shutdown_callback) {436extension->shutdown_callback();437}438}439}440441void GDExtensionManager::frame() {442for (const KeyValue<String, Ref<GDExtension>> &E : gdextension_map) {443const Ref<GDExtension> &extension = E.value;444if (extension->frame_callback) {445extension->frame_callback();446}447}448}449450GDExtensionManager *GDExtensionManager::get_singleton() {451return singleton;452}453454void GDExtensionManager::_bind_methods() {455ClassDB::bind_method(D_METHOD("load_extension", "path"), &GDExtensionManager::load_extension);456ClassDB::bind_method(D_METHOD("reload_extension", "path"), &GDExtensionManager::reload_extension);457ClassDB::bind_method(D_METHOD("unload_extension", "path"), &GDExtensionManager::unload_extension);458ClassDB::bind_method(D_METHOD("is_extension_loaded", "path"), &GDExtensionManager::is_extension_loaded);459460ClassDB::bind_method(D_METHOD("get_loaded_extensions"), &GDExtensionManager::get_loaded_extensions);461ClassDB::bind_method(D_METHOD("get_extension", "path"), &GDExtensionManager::get_extension);462463BIND_ENUM_CONSTANT(LOAD_STATUS_OK);464BIND_ENUM_CONSTANT(LOAD_STATUS_FAILED);465BIND_ENUM_CONSTANT(LOAD_STATUS_ALREADY_LOADED);466BIND_ENUM_CONSTANT(LOAD_STATUS_NOT_LOADED);467BIND_ENUM_CONSTANT(LOAD_STATUS_NEEDS_RESTART);468469ADD_SIGNAL(MethodInfo("extensions_reloaded"));470ADD_SIGNAL(MethodInfo("extension_loaded", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));471ADD_SIGNAL(MethodInfo("extension_unloading", PropertyInfo(Variant::OBJECT, "extension", PROPERTY_HINT_RESOURCE_TYPE, "GDExtension")));472}473474GDExtensionManager::GDExtensionManager() {475ERR_FAIL_COND(singleton != nullptr);476singleton = this;477478#ifndef DISABLE_DEPRECATED479GDExtensionSpecialCompatHashes::initialize();480#endif481}482483GDExtensionManager::~GDExtensionManager() {484if (singleton == this) {485singleton = nullptr;486}487#ifndef DISABLE_DEPRECATED488GDExtensionSpecialCompatHashes::finalize();489#endif490}491492493