Skip to content

Commit 3b30209

Browse files
Merge #272
272: Implement virtual methods that accept or return pointers (per #191) r=Bromeon a=Mercerenies This MR adds all missing virtual functions which were previously omitted due to an argument or return value being a pointer. All pointers are translated into Rust-side raw pointers (`*const T` or `*mut T`, as appropriate). All virtual trait functions which either take or return a pointer are marked as `unsafe`. All `native_structures` structs in the JSON file have been translated into Rust-side structures (with `#[repr(C)]` to ensure binary compatibility) and placed in the `godot::native` directory. These are all pure data structs (all fields are public and they have no methods). Many of the pointer functions take or return pointers to these native structures, so being able to construct and access them Rust-side is essential for this functionality. There is one double pointer in the JSON API. `const uint8_t**` appears in several of the networking functions. I believe the correct translation of this type (preserving `const`) is `*mut *const u8`, which is what the code in this MR uses. Co-authored-by: Silvio Mayolo <[email protected]>
2 parents 81f81c6 + 07f8d2e commit 3b30209

File tree

15 files changed

+614
-59
lines changed

15 files changed

+614
-59
lines changed

godot-codegen/src/api_parser.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub struct ExtensionApi {
2121
pub classes: Vec<Class>,
2222
pub global_enums: Vec<Enum>,
2323
pub utility_functions: Vec<UtilityFunction>,
24+
pub native_structures: Vec<NativeStructure>,
2425
pub singletons: Vec<Singleton>,
2526
}
2627

@@ -74,6 +75,12 @@ pub struct Class {
7475
// pub signals: Option<Vec<Signal>>,
7576
}
7677

78+
#[derive(DeJson)]
79+
pub struct NativeStructure {
80+
pub name: String,
81+
pub format: String,
82+
}
83+
7784
#[derive(DeJson)]
7885
pub struct Singleton {
7986
pub name: String,

godot-codegen/src/central_generator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub(crate) fn generate_core_mod_file(core_gen_path: &Path, out_files: &mut Vec<P
8282
pub mod classes;
8383
pub mod builtin_classes;
8484
pub mod utilities;
85+
pub mod native;
8586
};
8687

8788
write_file(core_gen_path, "mod.rs", code.to_string(), out_files);

godot-codegen/src/class_generator.rs

Lines changed: 142 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ use std::path::{Path, PathBuf};
1212

1313
use crate::api_parser::*;
1414
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+
};
1619
use 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+
115152
fn 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+
555658
fn 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"))]
720823
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+
}
734842
}
843+
is_rust_type_excluded(&to_rust_type(ty, ctx))
735844
}
736845

737846
fn 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

godot-codegen/src/context.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use std::collections::{HashMap, HashSet};
1414
pub(crate) struct Context<'a> {
1515
engine_classes: HashMap<TyName, &'a Class>,
1616
builtin_types: HashSet<&'a str>,
17+
native_structures_types: HashSet<&'a str>,
1718
singletons: HashSet<&'a str>,
1819
inheritance_tree: InheritanceTree,
1920
cached_rust_types: HashMap<String, RustTy>,
@@ -35,6 +36,11 @@ impl<'a> Context<'a> {
3536
ctx.builtin_types.insert(ty_name);
3637
}
3738

39+
for structure in api.native_structures.iter() {
40+
let ty_name = structure.name.as_str();
41+
ctx.native_structures_types.insert(ty_name);
42+
}
43+
3844
for class in api.classes.iter() {
3945
let class_name = TyName::from_godot(&class.name);
4046

@@ -133,6 +139,10 @@ impl<'a> Context<'a> {
133139
self.builtin_types.contains(ty_name)
134140
}
135141

142+
pub fn is_native_structure(&self, ty_name: &str) -> bool {
143+
self.native_structures_types.contains(ty_name)
144+
}
145+
136146
pub fn is_singleton(&self, class_name: &str) -> bool {
137147
self.singletons.contains(class_name)
138148
}

godot-codegen/src/lib.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ use central_generator::{
2121
generate_core_central_file, generate_core_mod_file, generate_sys_central_file,
2222
generate_sys_mod_file,
2323
};
24-
use class_generator::{generate_builtin_class_files, generate_class_files};
24+
use class_generator::{
25+
generate_builtin_class_files, generate_class_files, generate_native_structures_files,
26+
};
2527
use context::Context;
2628
use interface_generator::generate_sys_interface_file;
2729
use util::{ident, to_pascal_case, to_snake_case};
@@ -91,6 +93,15 @@ pub fn generate_core_files(core_gen_path: &Path) {
9193
);
9294
watch.record("generate_builtin_class_files");
9395

96+
generate_native_structures_files(
97+
&api,
98+
&mut ctx,
99+
build_config,
100+
&core_gen_path.join("native"),
101+
&mut out_files,
102+
);
103+
watch.record("generate_native_structures_files");
104+
94105
rustfmt_if_needed(out_files);
95106
watch.record("rustfmt");
96107
watch.write_stats_to(&core_gen_path.join("codegen-stats.txt"));
@@ -131,6 +142,9 @@ enum RustTy {
131142
/// `TypedArray<i32>`
132143
BuiltinArray(TokenStream),
133144

145+
/// C-style raw pointer to a `RustTy`.
146+
RawPointer { inner: Box<RustTy>, is_const: bool },
147+
134148
/// `TypedArray<Gd<PhysicsBody3D>>`
135149
EngineArray {
136150
tokens: TokenStream,
@@ -168,6 +182,14 @@ impl ToTokens for RustTy {
168182
match self {
169183
RustTy::BuiltinIdent(ident) => ident.to_tokens(tokens),
170184
RustTy::BuiltinArray(path) => path.to_tokens(tokens),
185+
RustTy::RawPointer {
186+
inner,
187+
is_const: true,
188+
} => quote! { *const #inner }.to_tokens(tokens),
189+
RustTy::RawPointer {
190+
inner,
191+
is_const: false,
192+
} => quote! { *mut #inner }.to_tokens(tokens),
171193
RustTy::EngineArray { tokens: path, .. } => path.to_tokens(tokens),
172194
RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens),
173195
RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens),
@@ -311,6 +333,8 @@ const SELECTED_CLASSES: &[&str] = &[
311333
"SceneTree",
312334
"Sprite2D",
313335
"SpriteFrames",
336+
"TextServer",
337+
"TextServerExtension",
314338
"Texture",
315339
"Texture2DArray",
316340
"TextureLayered",

0 commit comments

Comments
 (0)