Skip to content

Commit 4107ee6

Browse files
committed
Implement virtual methods that accept or return pointers (per godot-rust#191)
1 parent 8990464 commit 4107ee6

File tree

15 files changed

+628
-36
lines changed

15 files changed

+628
-36
lines changed

godot-codegen/src/api_parser.rs

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

@@ -63,6 +64,12 @@ pub struct Class {
6364
// pub signals: Option<Vec<Signal>>,
6465
}
6566

67+
#[derive(DeJson)]
68+
pub struct NativeStructure {
69+
pub name: String,
70+
pub format: String,
71+
}
72+
6673
#[derive(DeJson)]
6774
pub struct Singleton {
6875
pub name: String,

godot-codegen/src/central_generator.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ pub(crate) fn generate_core_mod_file(core_gen_path: &Path, out_files: &mut Vec<P
8080
pub mod classes;
8181
pub mod builtin_classes;
8282
pub mod utilities;
83+
pub mod native;
8384
};
8485

8586
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
@@ -20,7 +20,9 @@ use central_generator::{
2020
generate_core_central_file, generate_core_mod_file, generate_sys_central_file,
2121
generate_sys_mod_file,
2222
};
23-
use class_generator::{generate_builtin_class_files, generate_class_files};
23+
use class_generator::{
24+
generate_builtin_class_files, generate_class_files, generate_native_structures_files,
25+
};
2426
use context::Context;
2527
use util::{ident, to_pascal_case, to_snake_case};
2628
use utilities_generator::generate_utilities_file;
@@ -81,6 +83,15 @@ pub fn generate_core_files(core_gen_path: &Path) {
8183
);
8284
watch.record("generate_builtin_class_files");
8385

86+
generate_native_structures_files(
87+
&api,
88+
&mut ctx,
89+
build_config,
90+
&core_gen_path.join("native"),
91+
&mut out_files,
92+
);
93+
watch.record("generate_native_structures_files");
94+
8495
rustfmt_if_needed(out_files);
8596
watch.record("rustfmt");
8697
watch.write_stats_to(&core_gen_path.join("codegen-stats.txt"));
@@ -121,6 +132,9 @@ enum RustTy {
121132
/// `TypedArray<i32>`
122133
BuiltinArray(TokenStream),
123134

135+
/// C-style raw pointer to a `RustTy`.
136+
RawPointer { inner: Box<RustTy>, is_const: bool },
137+
124138
/// `TypedArray<Gd<PhysicsBody3D>>`
125139
EngineArray {
126140
tokens: TokenStream,
@@ -158,6 +172,14 @@ impl ToTokens for RustTy {
158172
match self {
159173
RustTy::BuiltinIdent(ident) => ident.to_tokens(tokens),
160174
RustTy::BuiltinArray(path) => path.to_tokens(tokens),
175+
RustTy::RawPointer {
176+
inner,
177+
is_const: true,
178+
} => quote! { *const #inner }.to_tokens(tokens),
179+
RustTy::RawPointer {
180+
inner,
181+
is_const: false,
182+
} => quote! { *mut #inner }.to_tokens(tokens),
161183
RustTy::EngineArray { tokens: path, .. } => path.to_tokens(tokens),
162184
RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens),
163185
RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens),
@@ -301,6 +323,8 @@ const SELECTED_CLASSES: &[&str] = &[
301323
"SceneTree",
302324
"Sprite2D",
303325
"SpriteFrames",
326+
"TextServer",
327+
"TextServerExtension",
304328
"Texture",
305329
"Texture2DArray",
306330
"TextureLayered",

0 commit comments

Comments
 (0)