2023-01-05 13:25:55 +01:00
/**************************************************************************/
/* 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. */
/**************************************************************************/
2021-06-19 17:58:49 +02:00
2022-12-07 12:11:28 +01:00
# include "gdextension_manager.h"
2023-09-10 19:36:44 +02:00
# include "core/extension/gdextension_compat_hashes.h"
2024-04-24 01:32:25 +02:00
# include "core/extension/gdextension_library_loader.h"
2024-07-05 16:16:36 +02:00
# include "core/io/dir_access.h"
2021-08-20 20:32:56 +02:00
# include "core/io/file_access.h"
2023-08-05 03:34:14 +02:00
# include "core/object/script_language.h"
2021-06-19 17:58:49 +02:00
2024-07-05 16:16:36 +02:00
GDExtensionManager : : LoadStatus GDExtensionManager : : _load_extension_internal ( const Ref < GDExtension > & p_extension , bool p_first_load ) {
2022-02-20 14:56:58 +01:00
if ( level > = 0 ) { // Already initialized up to some level.
2024-07-05 16:16:36 +02:00
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 ;
}
2021-06-19 17:58:49 +02:00
}
2022-02-20 14:56:58 +01:00
// Initialize up to current level.
2022-02-20 11:41:39 +01:00
for ( int32_t i = minimum_level ; i < = level ; i + + ) {
2023-08-05 03:34:14 +02:00
p_extension - > initialize_library ( GDExtension : : InitializationLevel ( i ) ) ;
2021-06-19 17:58:49 +02:00
}
}
2023-03-31 21:17:59 +02:00
2023-08-05 03:34:14 +02:00
for ( const KeyValue < String , String > & kv : p_extension - > class_icon_paths ) {
2023-03-31 21:17:59 +02:00
gdextension_class_icon_paths [ kv . key ] = kv . value ;
}
2024-07-05 16:16:36 +02:00
# ifdef TOOLS_ENABLED
// Signals that a new extension is loaded so GDScript can register new class names.
emit_signal ( " extension_loaded " , p_extension ) ;
# endif
2023-08-05 03:34:14 +02:00
return LOAD_STATUS_OK ;
}
GDExtensionManager : : LoadStatus GDExtensionManager : : _unload_extension_internal ( const Ref < GDExtension > & p_extension ) {
2024-07-05 16:16:36 +02:00
# ifdef TOOLS_ENABLED
// Signals that a new extension is unloading so GDScript can unregister class names.
emit_signal ( " extension_unloading " , p_extension ) ;
# endif
2023-08-05 03:34:14 +02:00
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 ) ;
}
return LOAD_STATUS_OK ;
}
GDExtensionManager : : LoadStatus GDExtensionManager : : load_extension ( const String & p_path ) {
2024-04-24 01:32:25 +02:00
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 ( ) ) ;
2023-08-05 03:34:14 +02:00
if ( gdextension_map . has ( p_path ) ) {
return LOAD_STATUS_ALREADY_LOADED ;
}
2024-04-24 01:32:25 +02:00
Ref < GDExtension > extension ;
extension . instantiate ( ) ;
Error err = extension - > open_library ( p_path , p_loader ) ;
if ( err ! = OK ) {
2023-08-05 03:34:14 +02:00
return LOAD_STATUS_FAILED ;
}
2024-07-05 16:16:36 +02:00
LoadStatus status = _load_extension_internal ( extension , true ) ;
2023-08-05 03:34:14 +02:00
if ( status ! = LOAD_STATUS_OK ) {
return status ;
}
2024-04-24 01:32:25 +02:00
extension - > set_path ( p_path ) ;
2022-12-07 12:11:28 +01:00
gdextension_map [ p_path ] = extension ;
2021-06-19 17:58:49 +02:00
return LOAD_STATUS_OK ;
}
2022-12-07 12:11:28 +01:00
GDExtensionManager : : LoadStatus GDExtensionManager : : reload_extension ( const String & p_path ) {
2023-08-05 03:34:14 +02:00
# 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. " ) ;
2022-12-07 12:11:28 +01:00
if ( ! gdextension_map . has ( p_path ) ) {
2021-06-19 17:58:49 +02:00
return LOAD_STATUS_NOT_LOADED ;
}
2022-12-07 12:11:28 +01:00
Ref < GDExtension > extension = gdextension_map [ p_path ] ;
2023-08-05 03:34:14 +02:00
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 ) ) ;
2021-06-19 17:58:49 +02:00
2023-08-05 03:34:14 +02:00
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 ;
2021-06-19 17:58:49 +02:00
}
2023-08-05 03:34:14 +02:00
extension - > clear_instance_bindings ( ) ;
extension - > close_library ( ) ;
2021-06-19 17:58:49 +02:00
}
2023-03-31 21:17:59 +02:00
2024-04-24 01:32:25 +02:00
Error err = extension - > open_library ( p_path , extension - > loader ) ;
2023-08-05 03:34:14 +02:00
if ( err ! = OK ) {
return LOAD_STATUS_FAILED ;
}
2024-07-05 16:16:36 +02:00
status = _load_extension_internal ( extension , false ) ;
2023-08-05 03:34:14 +02:00
if ( status ! = LOAD_STATUS_OK ) {
return status ;
}
extension - > finish_reload ( ) ;
return LOAD_STATUS_OK ;
# endif
}
GDExtensionManager : : LoadStatus GDExtensionManager : : unload_extension ( const String & p_path ) {
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 ;
2023-03-31 21:17:59 +02:00
}
2022-12-07 12:11:28 +01:00
gdextension_map . erase ( p_path ) ;
2021-06-19 17:58:49 +02:00
return LOAD_STATUS_OK ;
}
2021-08-20 20:32:56 +02:00
2022-12-07 12:11:28 +01:00
bool GDExtensionManager : : is_extension_loaded ( const String & p_path ) const {
return gdextension_map . has ( p_path ) ;
2021-08-20 20:32:56 +02:00
}
2022-12-07 12:11:28 +01:00
Vector < String > GDExtensionManager : : get_loaded_extensions ( ) const {
2021-06-19 17:58:49 +02:00
Vector < String > ret ;
2022-12-07 12:11:28 +01:00
for ( const KeyValue < String , Ref < GDExtension > > & E : gdextension_map ) {
2021-08-09 22:13:42 +02:00
ret . push_back ( E . key ) ;
2021-06-19 17:58:49 +02:00
}
return ret ;
}
2022-12-07 12:11:28 +01:00
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 > ( ) ) ;
2022-05-13 15:04:37 +02:00
return E - > value ;
2021-06-19 17:58:49 +02:00
}
2023-03-31 21:17:59 +02:00
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 " " ;
}
2021-06-19 17:58:49 +02:00
2022-12-07 12:11:28 +01:00
void GDExtensionManager : : initialize_extensions ( GDExtension : : InitializationLevel p_level ) {
2021-06-19 17:58:49 +02:00
ERR_FAIL_COND ( int32_t ( p_level ) - 1 ! = level ) ;
2022-12-07 12:11:28 +01:00
for ( KeyValue < String , Ref < GDExtension > > & E : gdextension_map ) {
2021-08-09 22:13:42 +02:00
E . value - > initialize_library ( p_level ) ;
2021-06-19 17:58:49 +02:00
}
level = p_level ;
}
2022-12-07 12:11:28 +01:00
void GDExtensionManager : : deinitialize_extensions ( GDExtension : : InitializationLevel p_level ) {
2021-06-19 17:58:49 +02:00
ERR_FAIL_COND ( int32_t ( p_level ) ! = level ) ;
2022-12-07 12:11:28 +01:00
for ( KeyValue < String , Ref < GDExtension > > & E : gdextension_map ) {
2021-08-09 22:13:42 +02:00
E . value - > deinitialize_library ( p_level ) ;
2021-06-19 17:58:49 +02:00
}
level = int32_t ( p_level ) - 1 ;
}
2023-08-05 03:34:14 +02:00
# 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
2022-12-07 12:11:28 +01:00
void GDExtensionManager : : load_extensions ( ) {
Ref < FileAccess > f = FileAccess : : open ( GDExtension : : get_extension_list_config_file ( ) , FileAccess : : READ ) ;
2022-03-23 10:08:58 +01:00
while ( f . is_valid ( ) & & ! f - > eof_reached ( ) ) {
2021-08-20 20:32:56 +02:00
String s = f - > get_line ( ) . strip_edges ( ) ;
2021-12-09 10:42:46 +01:00
if ( ! s . is_empty ( ) ) {
2021-08-20 20:32:56 +02:00
LoadStatus err = load_extension ( s ) ;
ERR_CONTINUE_MSG ( err = = LOAD_STATUS_FAILED , " Error loading extension: " + s ) ;
}
}
2023-07-17 21:11:37 +02:00
OS : : get_singleton ( ) - > load_platform_gdextensions ( ) ;
2021-08-20 20:32:56 +02:00
}
2023-08-05 03:34:14 +02:00
void GDExtensionManager : : reload_extensions ( ) {
# ifdef TOOLS_ENABLED
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
}
2024-07-05 16:16:36 +02:00
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.
2024-07-14 09:21:31 +02:00
const Ref < GDExtension > extension = GDExtensionManager : : get_singleton ( ) - > get_extension ( loaded_extension ) ;
if ( ! extension - > get_loader ( ) - > library_exists ( ) ) {
2024-07-05 16:16:36 +02:00
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 ;
}
2022-12-07 12:11:28 +01:00
GDExtensionManager * GDExtensionManager : : get_singleton ( ) {
2021-06-19 17:58:49 +02:00
return singleton ;
}
2023-08-05 03:34:14 +02:00
2022-12-07 12:11:28 +01:00
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 ) ;
2021-08-20 20:32:56 +02:00
2022-12-07 12:11:28 +01:00
ClassDB : : bind_method ( D_METHOD ( " get_loaded_extensions " ) , & GDExtensionManager : : get_loaded_extensions ) ;
ClassDB : : bind_method ( D_METHOD ( " get_extension " , " path " ) , & GDExtensionManager : : get_extension ) ;
2021-06-19 17:58:49 +02:00
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 ) ;
2023-08-05 03:34:14 +02:00
ADD_SIGNAL ( MethodInfo ( " extensions_reloaded " ) ) ;
2024-07-05 16:16:36 +02:00
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 " ) ) ) ;
2021-06-19 17:58:49 +02:00
}
2022-12-07 12:11:28 +01:00
GDExtensionManager * GDExtensionManager : : singleton = nullptr ;
2021-06-19 17:58:49 +02:00
2022-12-07 12:11:28 +01:00
GDExtensionManager : : GDExtensionManager ( ) {
2021-06-19 17:58:49 +02:00
ERR_FAIL_COND ( singleton ! = nullptr ) ;
singleton = this ;
2023-09-10 19:36:44 +02:00
# ifndef DISABLE_DEPRECATED
GDExtensionCompatHashes : : initialize ( ) ;
# endif
2021-06-19 17:58:49 +02:00
}
2023-10-18 17:36:20 +02:00
GDExtensionManager : : ~ GDExtensionManager ( ) {
2024-05-10 19:07:21 +02:00
if ( singleton = = this ) {
singleton = nullptr ;
}
2023-10-18 17:36:20 +02:00
# ifndef DISABLE_DEPRECATED
GDExtensionCompatHashes : : finalize ( ) ;
# endif
}