2021-06-19 15:58:49 +00:00
/**************************************************************************/
2022-12-07 11:11:28 +00:00
/* gdextension.cpp */
2021-06-19 15:58:49 +00:00
/**************************************************************************/
/* 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. */
/**************************************************************************/
2022-12-07 11:11:28 +00:00
# include "gdextension.h"
2021-07-29 13:53:05 +00:00
# include "core/config/project_settings.h"
2022-10-26 16:23:09 +00:00
# include "core/io/dir_access.h"
2021-06-19 15:58:49 +00:00
# include "core/object/class_db.h"
# include "core/object/method_bind.h"
# include "core/os/os.h"
2023-04-24 15:15:27 +00:00
# include "core/version.h"
2023-10-13 17:54:21 +00:00
# include "gdextension_manager.h"
2023-04-24 15:15:27 +00:00
extern void gdextension_setup_interface ( ) ;
extern GDExtensionInterfaceFunctionPtr gdextension_get_proc_address ( const char * p_name ) ;
typedef GDExtensionBool ( * GDExtensionLegacyInitializationFunction ) ( void * p_interface , GDExtensionClassLibraryPtr p_library , GDExtensionInitialization * r_initialization ) ;
2021-06-19 15:58:49 +00:00
2022-12-07 11:11:28 +00:00
String GDExtension : : get_extension_list_config_file ( ) {
2022-08-30 00:34:01 +00:00
return ProjectSettings : : get_singleton ( ) - > get_project_data_path ( ) . path_join ( " extension_list.cfg " ) ;
2021-09-10 15:32:29 +00:00
}
2021-08-20 18:32:56 +00:00
2022-12-07 11:11:28 +00:00
String GDExtension : : find_extension_library ( const String & p_path , Ref < ConfigFile > p_config , std : : function < bool ( String ) > p_has_feature , PackedStringArray * r_tags ) {
2022-10-26 16:23:09 +00:00
// First, check the explicit libraries.
if ( p_config - > has_section ( " libraries " ) ) {
List < String > libraries ;
p_config - > get_section_keys ( " libraries " , & 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_valid ( ) , String ( ) , vformat ( " Error in extension: %s. Could not open folder for automatic detection of libraries files. autodetect_library_prefix= \" %s \" " , p_path , autodetect_library_prefix ) ) ;
// 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 ( ) ;
}
2022-12-07 11:11:28 +00:00
class GDExtensionMethodBind : public MethodBind {
GDExtensionClassMethodCall call_func ;
2023-04-24 22:21:32 +00:00
GDExtensionClassMethodValidatedCall validated_call_func ;
2022-12-07 11:11:28 +00:00
GDExtensionClassMethodPtrCall ptrcall_func ;
2021-06-19 15:58:49 +00:00
void * method_userdata ;
bool vararg ;
2023-04-24 22:21:32 +00:00
uint32_t argument_count ;
2022-10-23 08:43:09 +00:00
PropertyInfo return_value_info ;
GodotTypeInfo : : Metadata return_value_metadata ;
List < PropertyInfo > arguments_info ;
List < GodotTypeInfo : : Metadata > arguments_metadata ;
2021-06-19 15:58:49 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
friend class GDExtension ;
StringName name ;
bool is_reloading = false ;
bool valid = true ;
# endif
2021-06-19 15:58:49 +00:00
protected :
2022-04-07 10:23:40 +00:00
virtual Variant : : Type _gen_argument_type ( int p_arg ) const override {
2022-10-23 08:43:09 +00:00
if ( p_arg < 0 ) {
return return_value_info . type ;
} else {
return arguments_info [ p_arg ] . type ;
}
2021-06-19 15:58:49 +00:00
}
2022-04-07 10:23:40 +00:00
virtual PropertyInfo _gen_argument_type_info ( int p_arg ) const override {
2022-10-23 08:43:09 +00:00
if ( p_arg < 0 ) {
return return_value_info ;
} else {
return arguments_info [ p_arg ] ;
}
2021-06-19 15:58:49 +00:00
}
public :
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
virtual bool is_valid ( ) const override { return valid ; }
# endif
2022-04-07 10:23:40 +00:00
# ifdef DEBUG_METHODS_ENABLED
virtual GodotTypeInfo : : Metadata get_argument_meta ( int p_arg ) const override {
2022-10-23 08:43:09 +00:00
if ( p_arg < 0 ) {
return return_value_metadata ;
} else {
return arguments_metadata [ p_arg ] ;
}
2021-06-19 15:58:49 +00:00
}
2022-04-07 10:23:40 +00:00
# endif
2021-06-19 15:58:49 +00:00
2022-10-22 13:18:33 +00:00
virtual Variant call ( Object * p_object , const Variant * * p_args , int p_arg_count , Callable : : CallError & r_error ) const override {
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
ERR_FAIL_COND_V_MSG ( ! valid , Variant ( ) , vformat ( " Cannot call invalid GDExtension method bind '%s'. It's probably cached - you may need to restart Godot. " , name ) ) ;
# endif
2021-06-19 15:58:49 +00:00
Variant ret ;
2022-05-06 06:44:13 +00:00
GDExtensionClassInstancePtr extension_instance = is_static ( ) ? nullptr : p_object - > _get_extension_instance ( ) ;
2022-12-07 11:11:28 +00:00
GDExtensionCallError ce { GDEXTENSION_CALL_OK , 0 , 0 } ;
call_func ( method_userdata , extension_instance , reinterpret_cast < GDExtensionConstVariantPtr * > ( p_args ) , p_arg_count , ( GDExtensionVariantPtr ) & ret , & ce ) ;
2021-06-19 15:58:49 +00:00
r_error . error = Callable : : CallError : : Error ( ce . error ) ;
r_error . argument = ce . argument ;
r_error . expected = ce . expected ;
return ret ;
}
2023-04-24 22:21:32 +00:00
virtual void validated_call ( Object * p_object , const Variant * * p_args , Variant * r_ret ) const override {
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
ERR_FAIL_COND_MSG ( ! valid , vformat ( " Cannot call invalid GDExtension method bind '%s'. It's probably cached - you may need to restart Godot. " , name ) ) ;
# endif
2023-10-10 13:39:35 +00:00
ERR_FAIL_COND_MSG ( vararg , " Vararg methods don't have validated call support. This is most likely an engine bug. " ) ;
2023-04-24 22:21:32 +00:00
GDExtensionClassInstancePtr extension_instance = is_static ( ) ? nullptr : p_object - > _get_extension_instance ( ) ;
if ( validated_call_func ) {
// This is added here, but it's unlikely to be provided by most extensions.
validated_call_func ( method_userdata , extension_instance , reinterpret_cast < GDExtensionConstVariantPtr * > ( p_args ) , ( GDExtensionVariantPtr ) r_ret ) ;
} else {
// If not provided, go via ptrcall, which is faster than resorting to regular call.
const void * * argptrs = ( const void * * ) alloca ( argument_count * sizeof ( void * ) ) ;
for ( uint32_t i = 0 ; i < argument_count ; i + + ) {
argptrs [ i ] = VariantInternal : : get_opaque_pointer ( p_args [ i ] ) ;
}
2023-10-04 15:00:52 +00:00
void * ret_opaque = nullptr ;
if ( r_ret ) {
VariantInternal : : initialize ( r_ret , return_value_info . type ) ;
2023-10-09 15:59:45 +00:00
ret_opaque = r_ret - > get_type ( ) = = Variant : : NIL ? r_ret : VariantInternal : : get_opaque_pointer ( r_ret ) ;
2023-04-24 22:21:32 +00:00
}
ptrcall ( p_object , argptrs , ret_opaque ) ;
2023-10-04 15:00:52 +00:00
if ( r_ret & & r_ret - > get_type ( ) = = Variant : : OBJECT ) {
VariantInternal : : update_object_id ( r_ret ) ;
}
2023-04-24 22:21:32 +00:00
}
}
2022-10-22 13:18:33 +00:00
virtual void ptrcall ( Object * p_object , const void * * p_args , void * r_ret ) const override {
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
ERR_FAIL_COND_MSG ( ! valid , vformat ( " Cannot call invalid GDExtension method bind '%s'. It's probably cached - you may need to restart Godot. " , name ) ) ;
# endif
2021-06-19 15:58:49 +00:00
ERR_FAIL_COND_MSG ( vararg , " Vararg methods don't have ptrcall support. This is most likely an engine bug. " ) ;
GDExtensionClassInstancePtr extension_instance = p_object - > _get_extension_instance ( ) ;
2022-12-07 11:11:28 +00:00
ptrcall_func ( method_userdata , extension_instance , reinterpret_cast < GDExtensionConstTypePtr * > ( p_args ) , ( GDExtensionTypePtr ) r_ret ) ;
2021-06-19 15:58:49 +00:00
}
2022-04-07 10:23:40 +00:00
virtual bool is_vararg ( ) const override {
2021-06-19 15:58:49 +00:00
return false ;
}
2022-05-06 06:44:13 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
bool try_update ( const GDExtensionClassMethodInfo * p_method_info ) {
if ( is_static ( ) ! = ( bool ) ( p_method_info - > method_flags & GDEXTENSION_METHOD_FLAG_STATIC ) ) {
return false ;
}
if ( vararg ! = ( bool ) ( p_method_info - > method_flags & GDEXTENSION_METHOD_FLAG_VARARG ) ) {
return false ;
}
if ( has_return ( ) ! = ( bool ) p_method_info - > has_return_value ) {
return false ;
}
if ( has_return ( ) & & return_value_info . type ! = ( Variant : : Type ) p_method_info - > return_value_info - > type ) {
return false ;
}
if ( argument_count ! = p_method_info - > argument_count ) {
return false ;
}
for ( uint32_t i = 0 ; i < p_method_info - > argument_count ; i + + ) {
if ( arguments_info [ i ] . type ! = ( Variant : : Type ) p_method_info - > arguments_info [ i ] . type ) {
return false ;
}
}
update ( p_method_info ) ;
return true ;
}
# endif
void update ( const GDExtensionClassMethodInfo * p_method_info ) {
# ifdef TOOLS_ENABLED
name = * reinterpret_cast < StringName * > ( p_method_info - > name ) ;
# endif
2021-06-19 15:58:49 +00:00
method_userdata = p_method_info - > method_userdata ;
call_func = p_method_info - > call_func ;
2023-04-24 22:21:32 +00:00
validated_call_func = nullptr ;
2021-06-19 15:58:49 +00:00
ptrcall_func = p_method_info - > ptrcall_func ;
2022-10-23 08:43:09 +00:00
set_name ( * reinterpret_cast < StringName * > ( p_method_info - > name ) ) ;
if ( p_method_info - > has_return_value ) {
return_value_info = PropertyInfo ( * p_method_info - > return_value_info ) ;
return_value_metadata = GodotTypeInfo : : Metadata ( p_method_info - > return_value_metadata ) ;
}
2023-08-05 01:34:14 +00:00
arguments_info . clear ( ) ;
arguments_metadata . clear ( ) ;
2022-10-23 08:43:09 +00:00
for ( uint32_t i = 0 ; i < p_method_info - > argument_count ; i + + ) {
2022-11-15 23:51:48 +00:00
arguments_info . push_back ( PropertyInfo ( p_method_info - > arguments_info [ i ] ) ) ;
arguments_metadata . push_back ( GodotTypeInfo : : Metadata ( p_method_info - > arguments_metadata [ i ] ) ) ;
2022-10-23 08:43:09 +00:00
}
2021-06-19 15:58:49 +00:00
2022-05-06 06:44:13 +00:00
set_hint_flags ( p_method_info - > method_flags ) ;
2023-04-24 22:21:32 +00:00
argument_count = p_method_info - > argument_count ;
2022-12-07 11:11:28 +00:00
vararg = p_method_info - > method_flags & GDEXTENSION_METHOD_FLAG_VARARG ;
2021-06-19 15:58:49 +00:00
_set_returns ( p_method_info - > has_return_value ) ;
2022-12-07 11:11:28 +00:00
_set_const ( p_method_info - > method_flags & GDEXTENSION_METHOD_FLAG_CONST ) ;
_set_static ( p_method_info - > method_flags & GDEXTENSION_METHOD_FLAG_STATIC ) ;
2021-06-19 15:58:49 +00:00
# ifdef DEBUG_METHODS_ENABLED
_generate_argument_types ( p_method_info - > argument_count ) ;
# endif
set_argument_count ( p_method_info - > argument_count ) ;
2022-05-06 06:44:13 +00:00
Vector < Variant > defargs ;
defargs . resize ( p_method_info - > default_argument_count ) ;
for ( uint32_t i = 0 ; i < p_method_info - > default_argument_count ; i + + ) {
defargs . write [ i ] = * static_cast < Variant * > ( p_method_info - > default_arguments [ i ] ) ;
}
set_default_arguments ( defargs ) ;
2021-06-19 15:58:49 +00:00
}
2023-08-05 01:34:14 +00:00
explicit GDExtensionMethodBind ( const GDExtensionClassMethodInfo * p_method_info ) {
update ( p_method_info ) ;
}
2021-06-19 15:58:49 +00:00
} ;
2023-06-24 01:07:22 +00:00
# ifndef DISABLE_DEPRECATED
2022-12-07 11:11:28 +00:00
void GDExtension : : _register_extension_class ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , GDExtensionConstStringNamePtr p_parent_class_name , const GDExtensionClassCreationInfo * p_extension_funcs ) {
2023-06-24 01:07:22 +00:00
const GDExtensionClassCreationInfo2 class_info2 = {
p_extension_funcs - > is_virtual , // GDExtensionBool is_virtual;
p_extension_funcs - > is_abstract , // GDExtensionBool is_abstract;
2022-12-20 03:36:25 +00:00
true , // GDExtensionBool is_exposed;
2023-06-24 01:07:22 +00:00
p_extension_funcs - > set_func , // GDExtensionClassSet set_func;
p_extension_funcs - > get_func , // GDExtensionClassGet get_func;
p_extension_funcs - > get_property_list_func , // GDExtensionClassGetPropertyList get_property_list_func;
p_extension_funcs - > free_property_list_func , // GDExtensionClassFreePropertyList free_property_list_func;
p_extension_funcs - > property_can_revert_func , // GDExtensionClassPropertyCanRevert property_can_revert_func;
p_extension_funcs - > property_get_revert_func , // GDExtensionClassPropertyGetRevert property_get_revert_func;
2023-09-09 18:05:16 +00:00
nullptr , // GDExtensionClassValidateProperty validate_property_func;
2023-06-24 01:07:22 +00:00
nullptr , // GDExtensionClassNotification2 notification_func;
p_extension_funcs - > to_string_func , // GDExtensionClassToString to_string_func;
p_extension_funcs - > reference_func , // GDExtensionClassReference reference_func;
p_extension_funcs - > unreference_func , // GDExtensionClassUnreference unreference_func;
p_extension_funcs - > create_instance_func , // GDExtensionClassCreateInstance create_instance_func; /* this one is mandatory */
p_extension_funcs - > free_instance_func , // GDExtensionClassFreeInstance free_instance_func; /* this one is mandatory */
2023-08-05 01:34:14 +00:00
nullptr , // GDExtensionClassRecreateInstance recreate_instance_func;
2023-06-24 01:07:22 +00:00
p_extension_funcs - > get_virtual_func , // GDExtensionClassGetVirtual get_virtual_func;
2023-08-16 00:03:49 +00:00
nullptr , // GDExtensionClassGetVirtualCallData get_virtual_call_data_func;
nullptr , // GDExtensionClassCallVirtualWithData call_virtual_func;
2023-06-24 01:07:22 +00:00
p_extension_funcs - > get_rid_func , // GDExtensionClassGetRID get_rid;
p_extension_funcs - > class_userdata , // void *class_userdata;
} ;
const ClassCreationDeprecatedInfo legacy = {
p_extension_funcs - > notification_func ,
} ;
_register_extension_class_internal ( p_library , p_class_name , p_parent_class_name , & class_info2 , & legacy ) ;
}
# endif // DISABLE_DEPRECATED
void GDExtension : : _register_extension_class2 ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , GDExtensionConstStringNamePtr p_parent_class_name , const GDExtensionClassCreationInfo2 * p_extension_funcs ) {
_register_extension_class_internal ( p_library , p_class_name , p_parent_class_name , p_extension_funcs ) ;
}
void GDExtension : : _register_extension_class_internal ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , GDExtensionConstStringNamePtr p_parent_class_name , const GDExtensionClassCreationInfo2 * p_extension_funcs , const ClassCreationDeprecatedInfo * p_deprecated_funcs ) {
2022-12-07 11:11:28 +00:00
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-06-19 15:58:49 +00:00
2022-10-22 13:18:33 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
StringName parent_class_name = * reinterpret_cast < const StringName * > ( p_parent_class_name ) ;
2021-07-29 13:53:05 +00:00
ERR_FAIL_COND_MSG ( ! String ( class_name ) . is_valid_identifier ( ) , " Attempt to register extension class ' " + class_name + " ', which is not a valid class identifier. " ) ;
2021-06-19 15:58:49 +00:00
ERR_FAIL_COND_MSG ( ClassDB : : class_exists ( class_name ) , " Attempt to register extension class ' " + class_name + " ', which appears to be already registered. " ) ;
Extension * parent_extension = nullptr ;
if ( self - > extension_classes . has ( parent_class_name ) ) {
parent_extension = & self - > extension_classes [ parent_class_name ] ;
} else if ( ClassDB : : class_exists ( parent_class_name ) ) {
if ( ClassDB : : get_api_type ( parent_class_name ) = = ClassDB : : API_EXTENSION | | ClassDB : : get_api_type ( parent_class_name ) = = ClassDB : : API_EDITOR_EXTENSION ) {
ERR_PRINT ( " Unimplemented yet " ) ;
//inheriting from another extension
} else {
//inheriting from engine class
}
} else {
ERR_FAIL_MSG ( " Attempt to register an extension class ' " + String ( class_name ) + " ' using non-existing parent class ' " + String ( parent_class_name ) + " ' " ) ;
}
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
Extension * extension = nullptr ;
if ( self - > is_reloading & & self - > extension_classes . has ( class_name ) ) {
extension = & self - > extension_classes [ class_name ] ;
if ( ! parent_extension & & parent_class_name ! = extension - > gdextension . parent_class_name ) {
ERR_FAIL_MSG ( vformat ( " GDExtension class '%s' attempt to change parent type from '%s' to '%s' on hot reload. Restart Godot for this change to take effect. " , class_name , extension - > gdextension . parent_class_name , parent_class_name ) ) ;
}
extension - > is_reloading = false ;
} else {
self - > extension_classes [ class_name ] = Extension ( ) ;
extension = & self - > extension_classes [ class_name ] ;
}
# else
2021-06-19 15:58:49 +00:00
self - > extension_classes [ class_name ] = Extension ( ) ;
Extension * extension = & self - > extension_classes [ class_name ] ;
2023-08-05 01:34:14 +00:00
# endif
2021-06-19 15:58:49 +00:00
if ( parent_extension ) {
2022-12-07 11:11:28 +00:00
extension - > gdextension . parent = & parent_extension - > gdextension ;
parent_extension - > gdextension . children . push_back ( & extension - > gdextension ) ;
2021-06-19 15:58:49 +00:00
}
2023-08-05 01:34:14 +00:00
if ( self - > reloadable & & p_extension_funcs - > recreate_instance_func = = nullptr ) {
ERR_PRINT ( vformat ( " Extension marked as reloadable, but attempted to register class '%s' which doesn't support reloading. Perhaps your language binding don't support it? Reloading disabled for this extension. " , class_name ) ) ;
self - > reloadable = false ;
}
2023-02-16 15:38:57 +00:00
extension - > gdextension . library = self ;
2022-12-07 11:11:28 +00:00
extension - > gdextension . parent_class_name = parent_class_name ;
extension - > gdextension . class_name = class_name ;
extension - > gdextension . editor_class = self - > level_initialized = = INITIALIZATION_LEVEL_EDITOR ;
extension - > gdextension . is_virtual = p_extension_funcs - > is_virtual ;
extension - > gdextension . is_abstract = p_extension_funcs - > is_abstract ;
2022-12-20 03:36:25 +00:00
extension - > gdextension . is_exposed = p_extension_funcs - > is_exposed ;
2022-12-07 11:11:28 +00:00
extension - > gdextension . set = p_extension_funcs - > set_func ;
extension - > gdextension . get = p_extension_funcs - > get_func ;
extension - > gdextension . get_property_list = p_extension_funcs - > get_property_list_func ;
extension - > gdextension . free_property_list = p_extension_funcs - > free_property_list_func ;
extension - > gdextension . property_can_revert = p_extension_funcs - > property_can_revert_func ;
extension - > gdextension . property_get_revert = p_extension_funcs - > property_get_revert_func ;
2023-09-09 18:05:16 +00:00
extension - > gdextension . validate_property = p_extension_funcs - > validate_property_func ;
2023-06-24 01:07:22 +00:00
# ifndef DISABLE_DEPRECATED
if ( p_deprecated_funcs ) {
extension - > gdextension . notification = p_deprecated_funcs - > notification_func ;
}
# endif // DISABLE_DEPRECATED
extension - > gdextension . notification2 = p_extension_funcs - > notification_func ;
2022-12-07 11:11:28 +00:00
extension - > gdextension . to_string = p_extension_funcs - > to_string_func ;
extension - > gdextension . reference = p_extension_funcs - > reference_func ;
extension - > gdextension . unreference = p_extension_funcs - > unreference_func ;
extension - > gdextension . class_userdata = p_extension_funcs - > class_userdata ;
extension - > gdextension . create_instance = p_extension_funcs - > create_instance_func ;
extension - > gdextension . free_instance = p_extension_funcs - > free_instance_func ;
2023-08-05 01:34:14 +00:00
extension - > gdextension . recreate_instance = p_extension_funcs - > recreate_instance_func ;
2022-12-07 11:11:28 +00:00
extension - > gdextension . get_virtual = p_extension_funcs - > get_virtual_func ;
2023-08-16 00:03:49 +00:00
extension - > gdextension . get_virtual_call_data = p_extension_funcs - > get_virtual_call_data_func ;
extension - > gdextension . call_virtual_with_data = p_extension_funcs - > call_virtual_with_data_func ;
2022-12-07 11:11:28 +00:00
extension - > gdextension . get_rid = p_extension_funcs - > get_rid_func ;
2023-08-05 01:34:14 +00:00
extension - > gdextension . reloadable = self - > reloadable ;
# ifdef TOOLS_ENABLED
if ( extension - > gdextension . reloadable ) {
extension - > gdextension . tracking_userdata = extension ;
extension - > gdextension . track_instance = & GDExtension : : _track_instance ;
extension - > gdextension . untrack_instance = & GDExtension : : _untrack_instance ;
} else {
extension - > gdextension . tracking_userdata = nullptr ;
extension - > gdextension . track_instance = nullptr ;
extension - > gdextension . untrack_instance = nullptr ;
}
# endif
2022-12-07 11:11:28 +00:00
ClassDB : : register_extension_class ( & extension - > gdextension ) ;
2021-06-19 15:58:49 +00:00
}
2023-06-24 01:07:22 +00:00
2022-12-07 11:11:28 +00:00
void GDExtension : : _register_extension_class_method ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , const GDExtensionClassMethodInfo * p_method_info ) {
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-06-19 15:58:49 +00:00
2022-10-23 08:43:09 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
StringName method_name = * reinterpret_cast < const StringName * > ( p_method_info - > name ) ;
2021-07-29 13:53:05 +00:00
ERR_FAIL_COND_MSG ( ! self - > extension_classes . has ( class_name ) , " Attempt to register extension method ' " + String ( method_name ) + " ' for unexisting class ' " + class_name + " '. " ) ;
2021-06-19 15:58:49 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
Extension * extension = & self - > extension_classes [ class_name ] ;
GDExtensionMethodBind * method = nullptr ;
2021-06-19 15:58:49 +00:00
2023-08-05 01:34:14 +00:00
// If the extension is still marked as reloading, that means it failed to register again.
if ( extension - > is_reloading ) {
return ;
}
if ( self - > is_reloading & & extension - > methods . has ( method_name ) ) {
method = extension - > methods [ method_name ] ;
// Try to update the method bind. If it doesn't work (because it's incompatible) then
// mark as invalid and create a new one.
if ( ! method - > is_reloading | | ! method - > try_update ( p_method_info ) ) {
method - > valid = false ;
self - > invalid_methods . push_back ( method ) ;
method = nullptr ;
}
}
if ( method = = nullptr ) {
method = memnew ( GDExtensionMethodBind ( p_method_info ) ) ;
method - > set_instance_class ( class_name ) ;
extension - > methods [ method_name ] = method ;
} else {
method - > is_reloading = false ;
}
# else
2022-12-07 11:11:28 +00:00
GDExtensionMethodBind * method = memnew ( GDExtensionMethodBind ( p_method_info ) ) ;
2021-07-29 13:53:05 +00:00
method - > set_instance_class ( class_name ) ;
2023-08-05 01:34:14 +00:00
# endif
2021-06-19 15:58:49 +00:00
ClassDB : : bind_method_custom ( class_name , method ) ;
}
2024-01-21 01:40:43 +00:00
void GDExtension : : _register_extension_class_virtual_method ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , const GDExtensionClassVirtualMethodInfo * p_method_info ) {
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
ClassDB : : add_extension_class_virtual_method ( class_name , p_method_info ) ;
}
2022-12-07 11:11:28 +00:00
void GDExtension : : _register_extension_class_integer_constant ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , GDExtensionConstStringNamePtr p_enum_name , GDExtensionConstStringNamePtr p_constant_name , GDExtensionInt p_constant_value , GDExtensionBool p_is_bitfield ) {
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-06-19 15:58:49 +00:00
2022-10-23 08:43:09 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
StringName enum_name = * reinterpret_cast < const StringName * > ( p_enum_name ) ;
StringName constant_name = * reinterpret_cast < const StringName * > ( p_constant_name ) ;
ERR_FAIL_COND_MSG ( ! self - > extension_classes . has ( class_name ) , " Attempt to register extension constant ' " + constant_name + " ' for unexisting class ' " + class_name + " '. " ) ;
2021-06-19 15:58:49 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
Extension * extension = & self - > extension_classes [ class_name ] ;
if ( extension - > is_reloading ) {
return ;
}
# endif
2022-10-23 08:43:09 +00:00
ClassDB : : bind_integer_constant ( class_name , enum_name , constant_name , p_constant_value , p_is_bitfield ) ;
2021-06-19 15:58:49 +00:00
}
2021-09-16 02:37:43 +00:00
2022-12-07 11:11:28 +00:00
void GDExtension : : _register_extension_class_property ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , const GDExtensionPropertyInfo * p_info , GDExtensionConstStringNamePtr p_setter , GDExtensionConstStringNamePtr p_getter ) {
2023-07-21 20:59:24 +00:00
_register_extension_class_property_indexed ( p_library , p_class_name , p_info , p_setter , p_getter , - 1 ) ;
}
void GDExtension : : _register_extension_class_property_indexed ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , const GDExtensionPropertyInfo * p_info , GDExtensionConstStringNamePtr p_setter , GDExtensionConstStringNamePtr p_getter , GDExtensionInt p_index ) {
2022-12-07 11:11:28 +00:00
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-06-19 15:58:49 +00:00
2022-10-23 08:43:09 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
StringName setter = * reinterpret_cast < const StringName * > ( p_setter ) ;
StringName getter = * reinterpret_cast < const StringName * > ( p_getter ) ;
String property_name = * reinterpret_cast < const StringName * > ( p_info - > name ) ;
2022-06-06 20:24:13 +00:00
ERR_FAIL_COND_MSG ( ! self - > extension_classes . has ( class_name ) , " Attempt to register extension class property ' " + property_name + " ' for unexisting class ' " + class_name + " '. " ) ;
2021-06-19 15:58:49 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
Extension * extension = & self - > extension_classes [ class_name ] ;
if ( extension - > is_reloading ) {
return ;
}
# endif
2022-06-06 20:24:13 +00:00
PropertyInfo pinfo ( * p_info ) ;
2021-06-19 15:58:49 +00:00
2023-07-21 20:59:24 +00:00
ClassDB : : add_property ( class_name , pinfo , setter , getter , p_index ) ;
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
void GDExtension : : _register_extension_class_property_group ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , GDExtensionConstStringPtr p_group_name , GDExtensionConstStringPtr p_prefix ) {
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-09-16 02:37:43 +00:00
2022-10-23 08:43:09 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
String group_name = * reinterpret_cast < const String * > ( p_group_name ) ;
String prefix = * reinterpret_cast < const String * > ( p_prefix ) ;
ERR_FAIL_COND_MSG ( ! self - > extension_classes . has ( class_name ) , " Attempt to register extension class property group ' " + group_name + " ' for unexisting class ' " + class_name + " '. " ) ;
2021-09-16 02:37:43 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
Extension * extension = & self - > extension_classes [ class_name ] ;
if ( extension - > is_reloading ) {
return ;
}
# endif
2022-10-23 08:43:09 +00:00
ClassDB : : add_property_group ( class_name , group_name , prefix ) ;
2021-09-16 02:37:43 +00:00
}
2022-12-07 11:11:28 +00:00
void GDExtension : : _register_extension_class_property_subgroup ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , GDExtensionConstStringPtr p_subgroup_name , GDExtensionConstStringPtr p_prefix ) {
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-09-16 02:37:43 +00:00
2022-10-23 08:43:09 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
String subgroup_name = * reinterpret_cast < const String * > ( p_subgroup_name ) ;
String prefix = * reinterpret_cast < const String * > ( p_prefix ) ;
ERR_FAIL_COND_MSG ( ! self - > extension_classes . has ( class_name ) , " Attempt to register extension class property subgroup ' " + subgroup_name + " ' for unexisting class ' " + class_name + " '. " ) ;
2021-09-16 02:37:43 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
Extension * extension = & self - > extension_classes [ class_name ] ;
if ( extension - > is_reloading ) {
return ;
}
# endif
2022-10-23 08:43:09 +00:00
ClassDB : : add_property_subgroup ( class_name , subgroup_name , prefix ) ;
2021-09-16 02:37:43 +00:00
}
2022-12-07 11:11:28 +00:00
void GDExtension : : _register_extension_class_signal ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name , GDExtensionConstStringNamePtr p_signal_name , const GDExtensionPropertyInfo * p_argument_info , GDExtensionInt p_argument_count ) {
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-06-19 15:58:49 +00:00
2022-10-23 08:43:09 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
StringName signal_name = * reinterpret_cast < const StringName * > ( p_signal_name ) ;
ERR_FAIL_COND_MSG ( ! self - > extension_classes . has ( class_name ) , " Attempt to register extension class signal ' " + signal_name + " ' for unexisting class ' " + class_name + " '. " ) ;
2021-06-19 15:58:49 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
// If the extension is still marked as reloading, that means it failed to register again.
Extension * extension = & self - > extension_classes [ class_name ] ;
if ( extension - > is_reloading ) {
return ;
}
# endif
2021-06-19 15:58:49 +00:00
MethodInfo s ;
2022-10-23 08:43:09 +00:00
s . name = signal_name ;
2021-06-19 15:58:49 +00:00
for ( int i = 0 ; i < p_argument_count ; i + + ) {
2022-06-06 20:24:13 +00:00
PropertyInfo arg ( p_argument_info [ i ] ) ;
2021-06-19 15:58:49 +00:00
s . arguments . push_back ( arg ) ;
}
ClassDB : : add_signal ( class_name , s ) ;
}
2022-12-07 11:11:28 +00:00
void GDExtension : : _unregister_extension_class ( GDExtensionClassLibraryPtr p_library , GDExtensionConstStringNamePtr p_class_name ) {
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2021-06-19 15:58:49 +00:00
2022-10-23 08:43:09 +00:00
StringName class_name = * reinterpret_cast < const StringName * > ( p_class_name ) ;
2021-07-29 13:53:05 +00:00
ERR_FAIL_COND_MSG ( ! self - > extension_classes . has ( class_name ) , " Attempt to unregister unexisting extension class ' " + class_name + " '. " ) ;
2023-08-05 01:34:14 +00:00
2021-06-19 15:58:49 +00:00
Extension * ext = & self - > extension_classes [ class_name ] ;
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
if ( ext - > is_reloading ) {
self - > _clear_extension ( ext ) ;
}
# endif
2022-12-07 11:11:28 +00:00
ERR_FAIL_COND_MSG ( ext - > gdextension . children . size ( ) , " Attempt to unregister class ' " + class_name + " ' while other extension classes inherit from it. " ) ;
2021-06-19 15:58:49 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
ClassDB : : unregister_extension_class ( class_name , ! ext - > is_reloading ) ;
# else
2021-06-19 15:58:49 +00:00
ClassDB : : unregister_extension_class ( class_name ) ;
2023-08-05 01:34:14 +00:00
# endif
2022-12-07 11:11:28 +00:00
if ( ext - > gdextension . parent ! = nullptr ) {
ext - > gdextension . parent - > children . erase ( & ext - > gdextension ) ;
2021-06-19 15:58:49 +00:00
}
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
if ( ! ext - > is_reloading ) {
self - > extension_classes . erase ( class_name ) ;
}
2023-10-18 22:50:30 +00:00
GDExtensionEditorHelp : : remove_class ( class_name ) ;
2023-08-05 01:34:14 +00:00
# else
2021-06-19 15:58:49 +00:00
self - > extension_classes . erase ( class_name ) ;
2023-08-05 01:34:14 +00:00
# endif
2021-06-19 15:58:49 +00:00
}
2023-05-13 17:05:54 +00:00
void GDExtension : : _get_library_path ( GDExtensionClassLibraryPtr p_library , GDExtensionUninitializedStringPtr r_path ) {
2022-12-07 11:11:28 +00:00
GDExtension * self = reinterpret_cast < GDExtension * > ( p_library ) ;
2022-04-28 22:51:04 +00:00
2023-05-13 17:05:54 +00:00
memnew_placement ( r_path , String ( self - > library_path ) ) ;
2022-04-28 22:51:04 +00:00
}
2023-10-18 15:36:20 +00:00
HashMap < StringName , GDExtensionInterfaceFunctionPtr > GDExtension : : gdextension_interface_functions ;
2023-04-24 15:15:27 +00:00
2021-08-01 19:47:20 +00:00
void GDExtension : : register_interface_function ( const StringName & p_function_name , GDExtensionInterfaceFunctionPtr p_function_pointer ) {
2023-04-24 15:15:27 +00:00
ERR_FAIL_COND_MSG ( gdextension_interface_functions . has ( p_function_name ) , " Attempt to register interface function ' " + p_function_name + " ', which appears to be already registered. " ) ;
gdextension_interface_functions . insert ( p_function_name , p_function_pointer ) ;
}
2021-08-01 19:47:20 +00:00
GDExtensionInterfaceFunctionPtr GDExtension : : get_interface_function ( const StringName & p_function_name ) {
2023-04-24 15:15:27 +00:00
GDExtensionInterfaceFunctionPtr * function = gdextension_interface_functions . getptr ( p_function_name ) ;
2023-09-09 14:11:33 +00:00
ERR_FAIL_NULL_V_MSG ( function , nullptr , " Attempt to get non-existent interface function: " + String ( p_function_name ) + " . " ) ;
2023-04-24 15:15:27 +00:00
return * function ;
}
2023-06-07 14:19:47 +00:00
Error GDExtension : : open_library ( const String & p_path , const String & p_entry_symbol ) {
2023-10-01 01:20:12 +00:00
String abs_path = ProjectSettings : : get_singleton ( ) - > globalize_path ( p_path ) ;
# if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
// If running on the editor on Windows, we copy the library and open the copy.
// This is so the original file isn't locked and can be updated by a compiler.
2023-11-08 15:02:20 +00:00
bool library_copied = false ;
2023-10-01 01:20:12 +00:00
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
if ( ! FileAccess : : exists ( abs_path ) ) {
ERR_PRINT ( " GDExtension library not found: " + library_path ) ;
return ERR_FILE_NOT_FOUND ;
}
// Copy the file to the same directory as the original with a prefix in the name.
// This is so relative path to dependencies are satisfied.
String copy_path = abs_path . get_base_dir ( ) . path_join ( " ~ " + abs_path . get_file ( ) ) ;
// If there's a left-over copy (possibly from a crash) then delete it first.
if ( FileAccess : : exists ( copy_path ) ) {
DirAccess : : remove_absolute ( copy_path ) ;
}
Error copy_err = DirAccess : : copy_absolute ( abs_path , copy_path ) ;
if ( copy_err ) {
ERR_PRINT ( " Error copying GDExtension library: " + library_path ) ;
return ERR_CANT_CREATE ;
}
FileAccess : : set_hidden_attribute ( copy_path , true ) ;
2023-11-08 15:02:20 +00:00
library_copied = true ;
2023-10-01 01:20:12 +00:00
// Save the copied path so it can be deleted later.
temp_lib_path = copy_path ;
// Use the copy to open the library.
abs_path = copy_path ;
}
# endif
2023-11-08 15:02:20 +00:00
Error err = OS : : get_singleton ( ) - > open_dynamic_library ( abs_path , library , true , & library_path ) ;
2024-01-01 11:48:28 +00:00
ERR_FAIL_COND_V_MSG ( err = = ERR_FILE_NOT_FOUND , err , " GDExtension dynamic library not found: " + abs_path ) ;
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Can't open GDExtension dynamic library: " + abs_path ) ;
2021-06-19 15:58:49 +00:00
2023-11-08 15:02:20 +00:00
# if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
// If we copied the file, let's change the library path to point at the original,
// because that's what we want to check to see if it's changed.
if ( library_copied ) {
library_path = library_path . get_base_dir ( ) + " \\ " + p_path . get_file ( ) ;
}
# endif
2021-06-19 15:58:49 +00:00
void * entry_funcptr = nullptr ;
err = OS : : get_singleton ( ) - > get_dynamic_library_symbol_handle ( library , p_entry_symbol , entry_funcptr , false ) ;
if ( err ! = OK ) {
2023-10-01 01:20:12 +00:00
ERR_PRINT ( " GDExtension entry point ' " + p_entry_symbol + " ' not found in library " + abs_path ) ;
2021-06-19 15:58:49 +00:00
OS : : get_singleton ( ) - > close_dynamic_library ( library ) ;
return err ;
}
2023-06-07 14:19:47 +00:00
GDExtensionInitializationFunction initialization_function = ( GDExtensionInitializationFunction ) entry_funcptr ;
GDExtensionBool ret = initialization_function ( & gdextension_get_proc_address , this , & initialization ) ;
2021-06-19 15:58:49 +00:00
2023-04-24 15:15:27 +00:00
if ( ret ) {
2022-06-11 22:18:08 +00:00
level_initialized = - 1 ;
return OK ;
} else {
2022-06-19 08:14:41 +00:00
ERR_PRINT ( " GDExtension initialization function ' " + p_entry_symbol + " ' returned an error. " ) ;
2023-10-05 14:31:11 +00:00
OS : : get_singleton ( ) - > close_dynamic_library ( library ) ;
2022-06-11 22:18:08 +00:00
return FAILED ;
}
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
void GDExtension : : close_library ( ) {
2023-09-09 14:11:33 +00:00
ERR_FAIL_NULL ( library ) ;
2021-06-19 15:58:49 +00:00
OS : : get_singleton ( ) - > close_dynamic_library ( library ) ;
2023-08-02 20:14:03 +00:00
# if defined(TOOLS_ENABLED) && defined(WINDOWS_ENABLED)
// Delete temporary copy of library if it exists.
if ( ! temp_lib_path . is_empty ( ) & & Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
DirAccess : : remove_absolute ( temp_lib_path ) ;
}
# endif
2021-06-19 15:58:49 +00:00
library = nullptr ;
2023-08-05 01:34:14 +00:00
class_icon_paths . clear ( ) ;
# ifdef TOOLS_ENABLED
instance_bindings . clear ( ) ;
# endif
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
bool GDExtension : : is_library_open ( ) const {
2021-06-19 15:58:49 +00:00
return library ! = nullptr ;
}
2022-12-07 11:11:28 +00:00
GDExtension : : InitializationLevel GDExtension : : get_minimum_library_initialization_level ( ) const {
2023-09-09 14:11:33 +00:00
ERR_FAIL_NULL_V ( library , INITIALIZATION_LEVEL_CORE ) ;
2021-06-19 15:58:49 +00:00
return InitializationLevel ( initialization . minimum_initialization_level ) ;
}
2022-02-16 17:02:38 +00:00
2022-12-07 11:11:28 +00:00
void GDExtension : : initialize_library ( InitializationLevel p_level ) {
2023-09-09 14:11:33 +00:00
ERR_FAIL_NULL ( library ) ;
2022-02-16 17:02:38 +00:00
ERR_FAIL_COND_MSG ( p_level < = int32_t ( level_initialized ) , vformat ( " Level '%d' must be higher than the current level '%d' " , p_level , level_initialized ) ) ;
2021-06-19 15:58:49 +00:00
level_initialized = int32_t ( p_level ) ;
2023-09-28 09:40:18 +00:00
ERR_FAIL_NULL ( initialization . initialize ) ;
2021-06-19 15:58:49 +00:00
2022-12-07 11:11:28 +00:00
initialization . initialize ( initialization . userdata , GDExtensionInitializationLevel ( p_level ) ) ;
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
void GDExtension : : deinitialize_library ( InitializationLevel p_level ) {
2023-09-09 14:11:33 +00:00
ERR_FAIL_NULL ( library ) ;
2021-06-19 15:58:49 +00:00
ERR_FAIL_COND ( p_level > int32_t ( level_initialized ) ) ;
level_initialized = int32_t ( p_level ) - 1 ;
2024-02-04 11:41:23 +00:00
ERR_FAIL_NULL ( initialization . deinitialize ) ;
2022-12-07 11:11:28 +00:00
initialization . deinitialize ( initialization . userdata , GDExtensionInitializationLevel ( p_level ) ) ;
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
void GDExtension : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " is_library_open " ) , & GDExtension : : is_library_open ) ;
ClassDB : : bind_method ( D_METHOD ( " get_minimum_library_initialization_level " ) , & GDExtension : : get_minimum_library_initialization_level ) ;
2021-06-19 15:58:49 +00:00
BIND_ENUM_CONSTANT ( INITIALIZATION_LEVEL_CORE ) ;
BIND_ENUM_CONSTANT ( INITIALIZATION_LEVEL_SERVERS ) ;
BIND_ENUM_CONSTANT ( INITIALIZATION_LEVEL_SCENE ) ;
BIND_ENUM_CONSTANT ( INITIALIZATION_LEVEL_EDITOR ) ;
}
2022-12-07 11:11:28 +00:00
GDExtension : : GDExtension ( ) {
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
GDExtension : : ~ GDExtension ( ) {
2021-06-19 15:58:49 +00:00
if ( library ! = nullptr ) {
close_library ( ) ;
}
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
// If we have any invalid method binds still laying around, we can finally free them!
for ( GDExtensionMethodBind * E : invalid_methods ) {
memdelete ( E ) ;
}
# endif
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
void GDExtension : : initialize_gdextensions ( ) {
2023-04-24 15:15:27 +00:00
gdextension_setup_interface ( ) ;
2023-06-24 01:07:22 +00:00
# ifndef DISABLE_DEPRECATED
2023-04-24 15:15:27 +00:00
register_interface_function ( " classdb_register_extension_class " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class ) ;
2023-06-24 01:07:22 +00:00
# endif // DISABLE_DEPRECATED
register_interface_function ( " classdb_register_extension_class2 " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class2 ) ;
2023-04-24 15:15:27 +00:00
register_interface_function ( " classdb_register_extension_class_method " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_method ) ;
2024-01-21 01:40:43 +00:00
register_interface_function ( " classdb_register_extension_class_virtual_method " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_virtual_method ) ;
2023-04-24 15:15:27 +00:00
register_interface_function ( " classdb_register_extension_class_integer_constant " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_integer_constant ) ;
register_interface_function ( " classdb_register_extension_class_property " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_property ) ;
2023-07-21 20:59:24 +00:00
register_interface_function ( " classdb_register_extension_class_property_indexed " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_property_indexed ) ;
2023-04-24 15:15:27 +00:00
register_interface_function ( " classdb_register_extension_class_property_group " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_property_group ) ;
register_interface_function ( " classdb_register_extension_class_property_subgroup " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_property_subgroup ) ;
register_interface_function ( " classdb_register_extension_class_signal " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _register_extension_class_signal ) ;
register_interface_function ( " classdb_unregister_extension_class " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _unregister_extension_class ) ;
register_interface_function ( " get_library_path " , ( GDExtensionInterfaceFunctionPtr ) & GDExtension : : _get_library_path ) ;
2021-06-19 15:58:49 +00:00
}
2023-10-18 15:36:20 +00:00
void GDExtension : : finalize_gdextensions ( ) {
gdextension_interface_functions . clear ( ) ;
}
2023-08-05 01:34:14 +00:00
Error GDExtensionResourceLoader : : load_gdextension_resource ( const String & p_path , Ref < GDExtension > & p_extension ) {
ERR_FAIL_COND_V_MSG ( p_extension . is_valid ( ) & & p_extension - > is_library_open ( ) , ERR_ALREADY_IN_USE , " Cannot load GDExtension resource into already opened library. " ) ;
2021-06-19 15:58:49 +00:00
Ref < ConfigFile > config ;
config . instantiate ( ) ;
Error err = config - > load ( p_path ) ;
if ( err ! = OK ) {
2022-07-20 17:05:49 +00:00
ERR_PRINT ( " Error loading GDExtension configuration file: " + p_path ) ;
2023-08-05 01:34:14 +00:00
return err ;
2021-06-19 15:58:49 +00:00
}
if ( ! config - > has_section_key ( " configuration " , " entry_symbol " ) ) {
2022-07-20 17:05:49 +00:00
ERR_PRINT ( " GDExtension configuration file must contain a \" configuration/entry_symbol \" key: " + p_path ) ;
2023-08-05 01:34:14 +00:00
return ERR_INVALID_DATA ;
2021-06-19 15:58:49 +00:00
}
String entry_symbol = config - > get_value ( " configuration " , " entry_symbol " ) ;
2023-04-24 15:15:27 +00:00
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 ] ;
}
}
2023-06-07 14:19:47 +00:00
} else {
ERR_PRINT ( " GDExtension configuration file must contain a \" configuration/compatibility_minimum \" key: " + p_path ) ;
2023-08-05 01:34:14 +00:00
return ERR_INVALID_DATA ;
2023-04-24 15:15:27 +00:00
}
2023-06-07 14:19:47 +00:00
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 ) ) ;
2023-08-05 01:34:14 +00:00
return ERR_INVALID_DATA ;
2023-04-24 15:15:27 +00:00
}
bool compatible = true ;
2023-08-13 14:13:57 +00:00
// Check version lexicographically.
if ( VERSION_MAJOR ! = compatibility_minimum [ 0 ] ) {
compatible = VERSION_MAJOR > compatibility_minimum [ 0 ] ;
} else if ( VERSION_MINOR ! = compatibility_minimum [ 1 ] ) {
compatible = VERSION_MINOR > compatibility_minimum [ 1 ] ;
} else {
compatible = VERSION_PATCH > = compatibility_minimum [ 2 ] ;
2023-04-24 15:15:27 +00:00
}
if ( ! compatible ) {
ERR_PRINT ( vformat ( " GDExtension only compatible with Godot version %d.%d.%d or later: %s " , compatibility_minimum [ 0 ] , compatibility_minimum [ 1 ] , compatibility_minimum [ 2 ] , p_path ) ) ;
2023-08-05 01:34:14 +00:00
return ERR_INVALID_DATA ;
2023-04-24 15:15:27 +00:00
}
2024-02-16 20:40:04 +00:00
// 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 ( VERSION_MAJOR ! = compatibility_maximum [ 0 ] ) {
compatible = VERSION_MAJOR < compatibility_maximum [ 0 ] ;
} else if ( VERSION_MINOR ! = compatibility_maximum [ 1 ] ) {
compatible = VERSION_MINOR < compatibility_maximum [ 1 ] ;
} else {
compatible = VERSION_PATCH < = compatibility_maximum [ 2 ] ;
}
if ( ! compatible ) {
ERR_PRINT ( vformat ( " GDExtension only compatible with Godot version %s or earlier: %s " , compat_string , p_path ) ) ;
return ERR_INVALID_DATA ;
}
}
2024-01-09 01:36:19 +00:00
String library_path = GDExtension : : find_extension_library ( p_path , config , [ ] ( const String & p_feature ) { return OS : : get_singleton ( ) - > has_feature ( p_feature ) ; } ) ;
2021-06-19 15:58:49 +00:00
2021-12-09 09:42:46 +00:00
if ( library_path . is_empty ( ) ) {
2022-07-20 17:05:49 +00:00
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 ) ) ;
2023-08-05 01:34:14 +00:00
return ERR_FILE_NOT_FOUND ;
2021-06-19 15:58:49 +00:00
}
2023-11-05 20:59:36 +00:00
bool is_static_library = library_path . ends_with ( " .a " ) | | library_path . ends_with ( " .xcframework " ) ;
2021-12-12 21:11:28 +00:00
if ( ! library_path . is_resource_file ( ) & & ! library_path . is_absolute_path ( ) ) {
2022-08-30 00:34:01 +00:00
library_path = p_path . get_base_dir ( ) . path_join ( library_path ) ;
2021-06-19 15:58:49 +00:00
}
2023-08-05 01:34:14 +00:00
if ( p_extension . is_null ( ) ) {
p_extension . instantiate ( ) ;
}
2023-08-02 20:14:03 +00:00
2023-08-05 01:34:14 +00:00
# ifdef TOOLS_ENABLED
p_extension - > set_reloadable ( config - > get_value ( " configuration " , " reloadable " , false ) & & Engine : : get_singleton ( ) - > is_extension_reloading_enabled ( ) ) ;
2023-10-31 22:10:18 +00:00
p_extension - > update_last_modified_time (
FileAccess : : get_modified_time ( p_path ) ,
FileAccess : : get_modified_time ( library_path ) ) ;
2023-08-05 01:34:14 +00:00
# endif
2023-11-05 20:59:36 +00:00
err = p_extension - > open_library ( is_static_library ? String ( ) : library_path , entry_symbol ) ;
2021-06-19 15:58:49 +00:00
if ( err ! = OK ) {
2023-08-16 22:49:59 +00:00
# if defined(WINDOWS_ENABLED) && defined(TOOLS_ENABLED)
// If the DLL fails to load, make sure that temporary DLL copies are cleaned up.
if ( Engine : : get_singleton ( ) - > is_editor_hint ( ) ) {
2023-08-05 01:34:14 +00:00
DirAccess : : remove_absolute ( p_extension - > get_temp_library_path ( ) ) ;
2023-08-16 22:49:59 +00:00
}
# endif
2023-10-05 14:31:11 +00:00
// Unreference the extension so that this loading can be considered a failure.
p_extension . unref ( ) ;
2022-06-19 08:14:41 +00:00
// Errors already logged in open_library()
2023-08-05 01:34:14 +00:00
return err ;
2021-06-19 15:58:49 +00:00
}
2023-03-31 19:17:59 +00:00
// Handle icons if any are specified.
if ( config - > has_section ( " icons " ) ) {
List < String > keys ;
config - > get_section_keys ( " icons " , & keys ) ;
for ( const String & key : keys ) {
2023-10-05 10:49:31 +00:00
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 ) ;
}
p_extension - > class_icon_paths [ key ] = icon_path ;
2023-03-31 19:17:59 +00:00
}
}
2023-08-05 01:34:14 +00:00
return OK ;
}
Ref < Resource > GDExtensionResourceLoader : : load ( const String & p_path , const String & p_original_path , Error * r_error , bool p_use_sub_threads , float * r_progress , CacheMode p_cache_mode ) {
2023-10-13 17:54:21 +00:00
// We can't have two GDExtension resource object representing the same library, because
// loading (or unloading) a GDExtension affects global data. So, we need reuse the same
// object if one has already been loaded (even if caching is disabled at the resource
// loader level).
GDExtensionManager * manager = GDExtensionManager : : get_singleton ( ) ;
2023-10-21 15:00:18 +00:00
if ( manager - > is_extension_loaded ( p_path ) ) {
return manager - > get_extension ( p_path ) ;
}
Ref < GDExtension > lib ;
Error err = load_gdextension_resource ( p_path , lib ) ;
if ( err ! = OK & & r_error ) {
// Errors already logged in load_gdextension_resource().
* r_error = err ;
2023-08-05 01:34:14 +00:00
}
2021-06-19 15:58:49 +00:00
return lib ;
}
2022-12-07 11:11:28 +00:00
void GDExtensionResourceLoader : : get_recognized_extensions ( List < String > * p_extensions ) const {
2021-06-19 15:58:49 +00:00
p_extensions - > push_back ( " gdextension " ) ;
}
2022-12-07 11:11:28 +00:00
bool GDExtensionResourceLoader : : handles_type ( const String & p_type ) const {
return p_type = = " GDExtension " ;
2021-06-19 15:58:49 +00:00
}
2022-12-07 11:11:28 +00:00
String GDExtensionResourceLoader : : get_resource_type ( const String & p_path ) const {
2021-06-19 15:58:49 +00:00
String el = p_path . get_extension ( ) . to_lower ( ) ;
if ( el = = " gdextension " ) {
2022-12-07 11:11:28 +00:00
return " GDExtension " ;
2021-06-19 15:58:49 +00:00
}
return " " ;
}
2023-05-11 21:46:53 +00:00
# ifdef TOOLS_ENABLED
2023-08-05 01:34:14 +00:00
bool GDExtension : : has_library_changed ( ) const {
2023-10-31 22:10:18 +00:00
// 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 ( get_path ( ) ) ! = resource_last_modified_time ) {
2023-08-05 01:34:14 +00:00
return true ;
}
2023-10-31 22:10:18 +00:00
if ( FileAccess : : get_modified_time ( library_path ) ! = library_last_modified_time ) {
2023-08-05 01:34:14 +00:00
return true ;
}
return false ;
}
void GDExtension : : prepare_reload ( ) {
is_reloading = true ;
for ( KeyValue < StringName , Extension > & E : extension_classes ) {
E . value . is_reloading = true ;
for ( KeyValue < StringName , GDExtensionMethodBind * > & M : E . value . methods ) {
M . value - > is_reloading = true ;
}
for ( const ObjectID & obj_id : E . value . instances ) {
Object * obj = ObjectDB : : get_instance ( obj_id ) ;
if ( ! obj ) {
continue ;
}
// Store instance state so it can be restored after reload.
List < Pair < String , Variant > > state ;
List < PropertyInfo > prop_list ;
obj - > get_property_list ( & prop_list ) ;
for ( const PropertyInfo & P : prop_list ) {
if ( ! ( P . usage & PROPERTY_USAGE_STORAGE ) ) {
continue ;
}
Variant value = obj - > get ( P . name ) ;
Variant default_value = ClassDB : : class_get_default_property_value ( obj - > get_class_name ( ) , P . name ) ;
if ( default_value . get_type ( ) ! = Variant : : NIL & & bool ( Variant : : evaluate ( Variant : : OP_EQUAL , value , default_value ) ) ) {
continue ;
}
if ( P . type = = Variant : : OBJECT & & value . is_zero ( ) & & ! ( P . usage & PROPERTY_USAGE_STORE_IF_NULL ) ) {
continue ;
}
state . push_back ( Pair < String , Variant > ( P . name , value ) ) ;
}
E . value . instance_state [ obj_id ] = state ;
}
}
}
void GDExtension : : _clear_extension ( Extension * p_extension ) {
// Clear out hierarchy information because it may change.
p_extension - > gdextension . parent = nullptr ;
p_extension - > gdextension . children . clear ( ) ;
// Clear all objects of any GDExtension data. It will become its native parent class
// until the reload can reset the object with the new GDExtension data.
for ( const ObjectID & obj_id : p_extension - > instances ) {
Object * obj = ObjectDB : : get_instance ( obj_id ) ;
if ( ! obj ) {
continue ;
}
obj - > clear_internal_extension ( ) ;
}
}
void GDExtension : : track_instance_binding ( Object * p_object ) {
instance_bindings . push_back ( p_object - > get_instance_id ( ) ) ;
}
void GDExtension : : untrack_instance_binding ( Object * p_object ) {
instance_bindings . erase ( p_object - > get_instance_id ( ) ) ;
}
void GDExtension : : clear_instance_bindings ( ) {
for ( ObjectID obj_id : instance_bindings ) {
Object * obj = ObjectDB : : get_instance ( obj_id ) ;
if ( ! obj ) {
continue ;
}
obj - > free_instance_binding ( this ) ;
}
instance_bindings . clear ( ) ;
}
void GDExtension : : finish_reload ( ) {
is_reloading = false ;
// Clean up any classes or methods that didn't get re-added.
Vector < StringName > classes_to_remove ;
for ( KeyValue < StringName , Extension > & E : extension_classes ) {
if ( E . value . is_reloading ) {
E . value . is_reloading = false ;
classes_to_remove . push_back ( E . key ) ;
}
Vector < StringName > methods_to_remove ;
for ( KeyValue < StringName , GDExtensionMethodBind * > & M : E . value . methods ) {
if ( M . value - > is_reloading ) {
M . value - > valid = false ;
invalid_methods . push_back ( M . value ) ;
M . value - > is_reloading = false ;
methods_to_remove . push_back ( M . key ) ;
}
}
for ( const StringName & method_name : methods_to_remove ) {
E . value . methods . erase ( method_name ) ;
}
}
for ( const StringName & class_name : classes_to_remove ) {
extension_classes . erase ( class_name ) ;
}
// Reset any the extension on instances made from the classes that remain.
for ( KeyValue < StringName , Extension > & E : extension_classes ) {
// Loop over 'instance_state' rather than 'instance' because new instances
// may have been created when re-initializing the extension.
for ( const KeyValue < ObjectID , List < Pair < String , Variant > > > & S : E . value . instance_state ) {
Object * obj = ObjectDB : : get_instance ( S . key ) ;
if ( ! obj ) {
continue ;
}
obj - > reset_internal_extension ( & E . value . gdextension ) ;
}
}
// Now that all the classes are back, restore the state.
for ( KeyValue < StringName , Extension > & E : extension_classes ) {
for ( const KeyValue < ObjectID , List < Pair < String , Variant > > > & S : E . value . instance_state ) {
Object * obj = ObjectDB : : get_instance ( S . key ) ;
if ( ! obj ) {
continue ;
}
for ( const Pair < String , Variant > & state : S . value ) {
obj - > set ( state . first , state . second ) ;
}
}
}
// Finally, let the objects know that we are done reloading them.
for ( KeyValue < StringName , Extension > & E : extension_classes ) {
for ( const KeyValue < ObjectID , List < Pair < String , Variant > > > & S : E . value . instance_state ) {
Object * obj = ObjectDB : : get_instance ( S . key ) ;
if ( ! obj ) {
continue ;
}
obj - > notification ( NOTIFICATION_EXTENSION_RELOADED ) ;
}
// Clear the instance state, we're done looping.
E . value . instance_state . clear ( ) ;
}
}
void GDExtension : : _track_instance ( void * p_user_data , void * p_instance ) {
Extension * extension = reinterpret_cast < Extension * > ( p_user_data ) ;
Object * obj = reinterpret_cast < Object * > ( p_instance ) ;
extension - > instances . insert ( obj - > get_instance_id ( ) ) ;
}
void GDExtension : : _untrack_instance ( void * p_user_data , void * p_instance ) {
Extension * extension = reinterpret_cast < Extension * > ( p_user_data ) ;
Object * obj = reinterpret_cast < Object * > ( p_instance ) ;
extension - > instances . erase ( obj - > get_instance_id ( ) ) ;
}
2023-05-11 21:46:53 +00:00
Vector < StringName > GDExtensionEditorPlugins : : extension_classes ;
GDExtensionEditorPlugins : : EditorPluginRegisterFunc GDExtensionEditorPlugins : : editor_node_add_plugin = nullptr ;
GDExtensionEditorPlugins : : EditorPluginRegisterFunc GDExtensionEditorPlugins : : editor_node_remove_plugin = nullptr ;
void GDExtensionEditorPlugins : : add_extension_class ( const StringName & p_class_name ) {
if ( editor_node_add_plugin ) {
editor_node_add_plugin ( p_class_name ) ;
} else {
extension_classes . push_back ( p_class_name ) ;
}
}
void GDExtensionEditorPlugins : : remove_extension_class ( const StringName & p_class_name ) {
if ( editor_node_remove_plugin ) {
editor_node_remove_plugin ( p_class_name ) ;
} else {
extension_classes . erase ( p_class_name ) ;
}
}
2023-10-18 22:50:30 +00:00
GDExtensionEditorHelp : : EditorHelpLoadXmlBufferFunc GDExtensionEditorHelp : : editor_help_load_xml_buffer = nullptr ;
GDExtensionEditorHelp : : EditorHelpRemoveClassFunc GDExtensionEditorHelp : : editor_help_remove_class = nullptr ;
void GDExtensionEditorHelp : : load_xml_buffer ( const uint8_t * p_buffer , int p_size ) {
ERR_FAIL_NULL ( editor_help_load_xml_buffer ) ;
editor_help_load_xml_buffer ( p_buffer , p_size ) ;
}
void GDExtensionEditorHelp : : remove_class ( const String & p_class ) {
ERR_FAIL_NULL ( editor_help_remove_class ) ;
editor_help_remove_class ( p_class ) ;
}
2023-05-11 21:46:53 +00:00
# endif // TOOLS_ENABLED