@@ -12,7 +12,10 @@ use std::path::{Path, PathBuf};
1212
1313use crate :: api_parser:: * ;
1414use crate :: central_generator:: { collect_builtin_types, BuiltinTypeInfo } ;
15- use crate :: util:: { ident, safe_ident, to_pascal_case, to_rust_type} ;
15+ use crate :: util:: {
16+ function_uses_pointers, ident, parse_native_structures_format, safe_ident, to_pascal_case,
17+ to_rust_type, to_rust_type_abi, to_snake_case, NativeStructuresField ,
18+ } ;
1619use crate :: {
1720 special_cases, util, Context , GeneratedBuiltin , GeneratedBuiltinModule , GeneratedClass ,
1821 GeneratedClassModule , ModName , RustTy , TyName ,
@@ -112,6 +115,40 @@ pub(crate) fn generate_builtin_class_files(
112115 out_files. push ( out_path) ;
113116}
114117
118+ pub ( crate ) fn generate_native_structures_files (
119+ api : & ExtensionApi ,
120+ ctx : & mut Context ,
121+ _build_config : & str ,
122+ gen_path : & Path ,
123+ out_files : & mut Vec < PathBuf > ,
124+ ) {
125+ let _ = std:: fs:: remove_dir_all ( gen_path) ;
126+ std:: fs:: create_dir_all ( gen_path) . expect ( "create native directory" ) ;
127+
128+ let mut modules = vec ! [ ] ;
129+ for native_structure in api. native_structures . iter ( ) {
130+ let module_name = ModName :: from_godot ( & native_structure. name ) ;
131+ let class_name = TyName :: from_godot ( & native_structure. name ) ;
132+
133+ let generated_class = make_native_structure ( native_structure, & class_name, ctx) ;
134+ let file_contents = generated_class. code . to_string ( ) ;
135+
136+ let out_path = gen_path. join ( format ! ( "{}.rs" , module_name. rust_mod) ) ;
137+ std:: fs:: write ( & out_path, file_contents) . expect ( "failed to write native structures file" ) ;
138+ out_files. push ( out_path) ;
139+
140+ modules. push ( GeneratedBuiltinModule {
141+ class_name,
142+ module_name,
143+ } ) ;
144+ }
145+
146+ let out_path = gen_path. join ( "mod.rs" ) ;
147+ let mod_contents = make_builtin_module_file ( modules) . to_string ( ) ;
148+ std:: fs:: write ( & out_path, mod_contents) . expect ( "failed to write mod.rs file" ) ;
149+ out_files. push ( out_path) ;
150+ }
151+
115152fn make_class_doc (
116153 class_name : & TyName ,
117154 base_ident_opt : Option < Ident > ,
@@ -296,8 +333,10 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
296333 use godot_ffi as sys;
297334 use crate :: engine:: notify:: * ;
298335 use crate :: builtin:: * ;
336+ use crate :: native_structure:: * ;
299337 use crate :: obj:: { AsArg , Gd } ;
300338 use sys:: GodotFfi as _;
339+ use std:: ffi:: c_void;
301340
302341 pub ( super ) mod re_export {
303342 use super :: * ;
@@ -525,6 +564,7 @@ fn make_builtin_class(
525564 let tokens = quote ! {
526565 use godot_ffi as sys;
527566 use crate :: builtin:: * ;
567+ use crate :: native_structure:: * ;
528568 use crate :: obj:: { AsArg , Gd } ;
529569 use crate :: sys:: GodotFfi as _;
530570 use crate :: engine:: Object ;
@@ -552,6 +592,69 @@ fn make_builtin_class(
552592 GeneratedBuiltin { code : tokens }
553593}
554594
595+ fn make_native_structure (
596+ structure : & NativeStructure ,
597+ class_name : & TyName ,
598+ ctx : & mut Context ,
599+ ) -> GeneratedBuiltin {
600+ let class_name = & class_name. rust_ty ;
601+
602+ let fields = make_native_structure_fields ( & structure. format , ctx) ;
603+
604+ // mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub
605+ let tokens = quote ! {
606+ use godot_ffi as sys;
607+ use crate :: builtin:: * ;
608+ use crate :: native_structure:: * ;
609+ use crate :: obj:: { AsArg , Gd } ;
610+ use crate :: sys:: GodotFfi as _;
611+ use crate :: engine:: Object ;
612+
613+ #[ repr( C ) ]
614+ pub struct #class_name {
615+ #fields
616+ }
617+ } ;
618+ // note: TypePtr -> ObjectPtr conversion OK?
619+
620+ GeneratedBuiltin { code : tokens }
621+ }
622+
623+ fn make_native_structure_fields ( format_str : & str , ctx : & mut Context ) -> TokenStream {
624+ let fields = parse_native_structures_format ( format_str)
625+ . expect ( "Could not parse native_structures format field" ) ;
626+ let field_definitions = fields
627+ . into_iter ( )
628+ . map ( |field| make_native_structure_field_definition ( field, ctx) ) ;
629+ quote ! {
630+ #( #field_definitions ) *
631+ }
632+ }
633+
634+ fn make_native_structure_field_definition (
635+ field : NativeStructuresField ,
636+ ctx : & mut Context ,
637+ ) -> TokenStream {
638+ let field_type = normalize_native_structure_field_type ( & field. field_type ) ;
639+ let field_type = to_rust_type_abi ( & field_type, ctx) ;
640+ let field_name = ident ( & to_snake_case ( & field. field_name ) ) ;
641+ quote ! {
642+ pub #field_name: #field_type,
643+ }
644+ }
645+
646+ fn normalize_native_structure_field_type ( field_type : & str ) -> String {
647+ // native_structures uses a different format for enums than the
648+ // rest of the JSON file. If we detect a scoped field, convert it
649+ // to the enum format expected by to_rust_type.
650+ if field_type. contains ( "::" ) {
651+ let with_dot = field_type. replace ( "::" , "." ) ;
652+ format ! ( "enum::{}" , with_dot)
653+ } else {
654+ field_type. to_string ( )
655+ }
656+ }
657+
555658fn make_module_file ( classes_and_modules : Vec < GeneratedClassModule > ) -> TokenStream {
556659 let mut class_decls = Vec :: new ( ) ;
557660 let mut notify_decls = Vec :: new ( ) ;
@@ -718,20 +821,26 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr
718821
719822#[ cfg( not( feature = "codegen-full" ) ) ]
720823fn is_type_excluded ( ty : & str , ctx : & mut Context ) -> bool {
721- let is_class_excluded = |class : & str | !crate :: SELECTED_CLASSES . contains ( & class) ;
722-
723- match to_rust_type ( ty, ctx) {
724- RustTy :: BuiltinIdent ( _) => false ,
725- RustTy :: BuiltinArray ( _) => false ,
726- RustTy :: EngineArray { elem_class, .. } => is_class_excluded ( elem_class. as_str ( ) ) ,
727- RustTy :: EngineEnum {
728- surrounding_class, ..
729- } => match surrounding_class. as_ref ( ) {
730- None => false ,
731- Some ( class) => is_class_excluded ( class. as_str ( ) ) ,
732- } ,
733- RustTy :: EngineClass { .. } => is_class_excluded ( ty) ,
824+ fn is_class_excluded ( class : & str ) -> bool {
825+ !crate :: SELECTED_CLASSES . contains ( & class)
826+ }
827+
828+ fn is_rust_type_excluded ( ty : & RustTy ) -> bool {
829+ match ty {
830+ RustTy :: BuiltinIdent ( _) => false ,
831+ RustTy :: BuiltinArray ( _) => false ,
832+ RustTy :: RawPointer { inner, .. } => is_rust_type_excluded ( & inner) ,
833+ RustTy :: EngineArray { elem_class, .. } => is_class_excluded ( elem_class. as_str ( ) ) ,
834+ RustTy :: EngineEnum {
835+ surrounding_class, ..
836+ } => match surrounding_class. as_ref ( ) {
837+ None => false ,
838+ Some ( class) => is_class_excluded ( class. as_str ( ) ) ,
839+ } ,
840+ RustTy :: EngineClass { class, .. } => is_class_excluded ( & class) ,
841+ }
734842 }
843+ is_rust_type_excluded ( & to_rust_type ( ty, ctx) )
735844}
736845
737846fn is_method_excluded (
@@ -743,11 +852,6 @@ fn is_method_excluded(
743852 //
744853 // * Private virtual methods are only included in a virtual
745854 // implementation.
746- //
747- // * Methods accepting pointers are often supplementary
748- // E.g.: TextServer::font_set_data_ptr() -- in addition to TextServer::font_set_data().
749- // These are anyway not accessible in GDScript since that language has no pointers.
750- // As such support could be added later (if at all), with possibly safe interfaces (e.g. Vec for void*+size pairs)
751855
752856 // -- FIXME remove when impl complete
753857 #[ cfg( not( feature = "codegen-full" ) ) ]
@@ -768,14 +872,7 @@ fn is_method_excluded(
768872 return true ;
769873 }
770874
771- method
772- . return_value
773- . as_ref ( )
774- . map_or ( false , |ret| ret. type_ . contains ( '*' ) )
775- || method
776- . arguments
777- . as_ref ( )
778- . map_or ( false , |args| args. iter ( ) . any ( |arg| arg. type_ . contains ( '*' ) ) )
875+ false
779876}
780877
781878#[ cfg( feature = "codegen-full" ) ]
@@ -996,6 +1093,18 @@ fn make_function_definition(
9961093 } else {
9971094 quote ! { pub }
9981095 } ;
1096+ let ( safety, doc) = if function_uses_pointers ( method_args, & return_value) {
1097+ (
1098+ quote ! { unsafe } ,
1099+ quote ! {
1100+ #[ doc = "# Safety" ]
1101+ #[ doc = "" ]
1102+ #[ doc = "Godot currently does not document safety requirements on this method. Make sure you understand the underlying semantics." ]
1103+ } ,
1104+ )
1105+ } else {
1106+ ( quote ! { } , quote ! { } )
1107+ } ;
9991108
10001109 let is_varcall = variant_ffi. is_some ( ) ;
10011110 let fn_name = safe_ident ( function_name) ;
@@ -1042,15 +1151,17 @@ fn make_function_definition(
10421151
10431152 if is_virtual {
10441153 quote ! {
1045- fn #fn_name( #receiver #( #params, ) * ) #return_decl {
1154+ #doc
1155+ #safety fn #fn_name( #receiver #( #params, ) * ) #return_decl {
10461156 #call_code
10471157 }
10481158 }
10491159 } else if let Some ( variant_ffi) = variant_ffi. as_ref ( ) {
10501160 // varcall (using varargs)
10511161 let sys_method = & variant_ffi. sys_method ;
10521162 quote ! {
1053- #vis fn #fn_name( #receiver #( #params, ) * varargs: & [ Variant ] ) #return_decl {
1163+ #doc
1164+ #vis #safety fn #fn_name( #receiver #( #params, ) * varargs: & [ Variant ] ) #return_decl {
10541165 unsafe {
10551166 #init_code
10561167
@@ -1071,7 +1182,8 @@ fn make_function_definition(
10711182 } else {
10721183 // ptrcall
10731184 quote ! {
1074- #vis fn #fn_name( #receiver #( #params, ) * ) #return_decl {
1185+ #doc
1186+ #vis #safety fn #fn_name( #receiver #( #params, ) * ) #return_decl {
10751187 unsafe {
10761188 #init_code
10771189
0 commit comments