@@ -12,7 +12,10 @@ use std::path::{Path, PathBuf};
12
12
13
13
use crate :: api_parser:: * ;
14
14
use 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
+ } ;
16
19
use crate :: {
17
20
special_cases, util, Context , GeneratedBuiltin , GeneratedBuiltinModule , GeneratedClass ,
18
21
GeneratedClassModule , ModName , RustTy , TyName ,
@@ -112,6 +115,40 @@ pub(crate) fn generate_builtin_class_files(
112
115
out_files. push ( out_path) ;
113
116
}
114
117
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
+
115
152
fn make_class_doc (
116
153
class_name : & TyName ,
117
154
base_ident_opt : Option < Ident > ,
@@ -296,8 +333,10 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
296
333
use godot_ffi as sys;
297
334
use crate :: engine:: notify:: * ;
298
335
use crate :: builtin:: * ;
336
+ use crate :: native_structure:: * ;
299
337
use crate :: obj:: { AsArg , Gd } ;
300
338
use sys:: GodotFfi as _;
339
+ use std:: ffi:: c_void;
301
340
302
341
pub ( super ) mod re_export {
303
342
use super :: * ;
@@ -525,6 +564,7 @@ fn make_builtin_class(
525
564
let tokens = quote ! {
526
565
use godot_ffi as sys;
527
566
use crate :: builtin:: * ;
567
+ use crate :: native_structure:: * ;
528
568
use crate :: obj:: { AsArg , Gd } ;
529
569
use crate :: sys:: GodotFfi as _;
530
570
use crate :: engine:: Object ;
@@ -552,6 +592,69 @@ fn make_builtin_class(
552
592
GeneratedBuiltin { code : tokens }
553
593
}
554
594
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
+
555
658
fn make_module_file ( classes_and_modules : Vec < GeneratedClassModule > ) -> TokenStream {
556
659
let mut class_decls = Vec :: new ( ) ;
557
660
let mut notify_decls = Vec :: new ( ) ;
@@ -718,20 +821,26 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr
718
821
719
822
#[ cfg( not( feature = "codegen-full" ) ) ]
720
823
fn 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
+ }
734
842
}
843
+ is_rust_type_excluded ( & to_rust_type ( ty, ctx) )
735
844
}
736
845
737
846
fn is_method_excluded (
@@ -743,11 +852,6 @@ fn is_method_excluded(
743
852
//
744
853
// * Private virtual methods are only included in a virtual
745
854
// 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)
751
855
752
856
// -- FIXME remove when impl complete
753
857
#[ cfg( not( feature = "codegen-full" ) ) ]
@@ -768,14 +872,7 @@ fn is_method_excluded(
768
872
return true ;
769
873
}
770
874
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
779
876
}
780
877
781
878
#[ cfg( feature = "codegen-full" ) ]
@@ -996,6 +1093,18 @@ fn make_function_definition(
996
1093
} else {
997
1094
quote ! { pub }
998
1095
} ;
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
+ } ;
999
1108
1000
1109
let is_varcall = variant_ffi. is_some ( ) ;
1001
1110
let fn_name = safe_ident ( function_name) ;
@@ -1042,15 +1151,17 @@ fn make_function_definition(
1042
1151
1043
1152
if is_virtual {
1044
1153
quote ! {
1045
- fn #fn_name( #receiver #( #params, ) * ) #return_decl {
1154
+ #doc
1155
+ #safety fn #fn_name( #receiver #( #params, ) * ) #return_decl {
1046
1156
#call_code
1047
1157
}
1048
1158
}
1049
1159
} else if let Some ( variant_ffi) = variant_ffi. as_ref ( ) {
1050
1160
// varcall (using varargs)
1051
1161
let sys_method = & variant_ffi. sys_method ;
1052
1162
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 {
1054
1165
unsafe {
1055
1166
#init_code
1056
1167
@@ -1071,7 +1182,8 @@ fn make_function_definition(
1071
1182
} else {
1072
1183
// ptrcall
1073
1184
quote ! {
1074
- #vis fn #fn_name( #receiver #( #params, ) * ) #return_decl {
1185
+ #doc
1186
+ #vis #safety fn #fn_name( #receiver #( #params, ) * ) #return_decl {
1075
1187
unsafe {
1076
1188
#init_code
1077
1189
0 commit comments