Skip to content

Commit 0bf0ad0

Browse files
committed
Fix native structures exposing *mut Gd<Object>, provide accessor
1 parent 7eec09c commit 0bf0ad0

File tree

3 files changed

+107
-26
lines changed

3 files changed

+107
-26
lines changed

godot-codegen/src/conv/type_conversions.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,24 @@ fn to_hardcoded_rust_enum(ty: &str) -> Option<&str> {
8484
/// Maps an input type to a Godot type with the same C representation. This is subtly different than [`to_rust_type`],
8585
/// which maps to an appropriate corresponding Rust type. This function should be used in situations where the C ABI for
8686
/// a type must match the Godot equivalent exactly, such as when dealing with pointers.
87-
pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context) -> RustTy {
88-
match ty {
87+
pub(crate) fn to_rust_type_abi(ty: &str, ctx: &mut Context) -> (RustTy, bool) {
88+
let mut is_obj = false;
89+
let ty = match ty {
90+
// In native structures, object pointers are mapped to opaque entities. Instead, an accessor function is provided.
91+
"Object*" => {
92+
is_obj = true;
93+
RustTy::RawPointer {
94+
inner: Box::new(RustTy::BuiltinIdent(ident("c_void"))),
95+
is_const: false,
96+
}
97+
}
8998
"int" => RustTy::BuiltinIdent(ident("i32")),
9099
"float" => RustTy::BuiltinIdent(ident("f32")),
91100
"double" => RustTy::BuiltinIdent(ident("f64")),
92101
_ => to_rust_type(ty, None, ctx),
93-
}
102+
};
103+
104+
(ty, is_obj)
94105
}
95106

96107
/// Maps an _input_ type from the Godot JSON to the corresponding Rust type (wrapping some sort of a token stream).

godot-codegen/src/generator/native_structures.rs

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::models::domain::{ExtensionApi, ModName, NativeStructure, TyName};
1111
use crate::util::ident;
1212
use crate::{conv, util, SubmitFn};
1313
use proc_macro2::TokenStream;
14-
use quote::quote;
14+
use quote::{format_ident, quote};
1515
use std::path::Path;
1616

1717
pub fn generate_native_structures_files(
@@ -52,8 +52,15 @@ pub fn generate_native_structures_files(
5252

5353
#[derive(Clone, Eq, PartialEq, Debug)]
5454
pub(crate) struct NativeStructuresField {
55+
/// Identifier for the field name, e.g. `collider`.
5556
pub field_name: String,
57+
58+
/// Type which has a raw format that is latter mapped to `RustTy`.
59+
///
60+
/// Corresponds to other Godot type names, e.g. `Object*` or `enum::TextServer.Direction`.
5661
pub field_type: String,
62+
63+
/// If the field is an array, this contains the number of elements.
5764
pub array_size: Option<usize>,
5865
}
5966

@@ -65,12 +72,13 @@ fn make_native_structure(
6572
let class_name = &class_name.rust_ty;
6673

6774
let imports = util::make_imports();
68-
let fields = make_native_structure_fields(&structure.format, ctx);
75+
let (fields, methods) = make_native_structure_fields_and_methods(&structure.format, ctx);
6976
let doc = format!("[`ToGodot`] and [`FromGodot`] are implemented for `*mut {class_name}` and `*const {class_name}`.");
7077

7178
// mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub
7279
let tokens = quote! {
7380
#imports
81+
use std::ffi::c_void; // for opaque object pointers
7482
use crate::meta::{GodotConvert, FromGodot, ToGodot};
7583

7684
/// Native structure; can be passed via pointer in APIs that are not exposed to GDScript.
@@ -82,6 +90,10 @@ fn make_native_structure(
8290
#fields
8391
}
8492

93+
impl #class_name {
94+
#methods
95+
}
96+
8597
impl GodotConvert for *mut #class_name {
8698
type Via = i64;
8799
}
@@ -119,25 +131,35 @@ fn make_native_structure(
119131
builtins::GeneratedBuiltin { code: tokens }
120132
}
121133

122-
fn make_native_structure_fields(format_str: &str, ctx: &mut Context) -> TokenStream {
134+
fn make_native_structure_fields_and_methods(
135+
format_str: &str,
136+
ctx: &mut Context,
137+
) -> (TokenStream, TokenStream) {
123138
let fields = parse_native_structures_format(format_str)
124139
.expect("Could not parse native_structures format field");
125140

126-
let field_definitions = fields
127-
.into_iter()
128-
.map(|field| make_native_structure_field_definition(field, ctx));
141+
let mut field_definitions = vec![];
142+
let mut accessors = vec![];
129143

130-
quote! {
131-
#( #field_definitions )*
144+
for field in fields {
145+
let (field_def, accessor) = make_native_structure_field_and_accessor(field, ctx);
146+
field_definitions.push(field_def);
147+
if let Some(accessor) = accessor {
148+
accessors.push(accessor);
149+
}
132150
}
151+
152+
let fields = quote! { #( #field_definitions )* };
153+
let methods = quote! { #( #accessors )* };
154+
(fields, methods)
133155
}
134156

135-
fn make_native_structure_field_definition(
157+
fn make_native_structure_field_and_accessor(
136158
field: NativeStructuresField,
137159
ctx: &mut Context,
138-
) -> TokenStream {
160+
) -> (TokenStream, Option<TokenStream>) {
139161
let field_type = normalize_native_structure_field_type(&field.field_type);
140-
let field_type = conv::to_rust_type_abi(&field_type, ctx);
162+
let (field_type, is_object_ptr) = conv::to_rust_type_abi(&field_type, ctx);
141163

142164
// Make array if needed.
143165
let field_type = if let Some(size) = field.array_size {
@@ -146,11 +168,36 @@ fn make_native_structure_field_definition(
146168
quote! { #field_type }
147169
};
148170

149-
let field_name = ident(&conv::to_snake_case(&field.field_name));
171+
let snake_field_name = ident(&conv::to_snake_case(&field.field_name));
172+
173+
let (field_name, accessor);
174+
if is_object_ptr {
175+
// Highlight that the pointer field is internal/opaque.
176+
field_name = format_ident!("__unused_{}_ptr", snake_field_name);
177+
178+
// Generate method that converts from instance ID.
179+
let id_field_name = format_ident!("{}_id", snake_field_name);
180+
accessor = Some(quote! {
181+
/// Returns the object as a `Gd<T>`, or `None` if it no longer exists.
182+
pub fn #snake_field_name(&self) -> Option<Gd<Object>> {
183+
crate::obj::InstanceId::try_from_u64(self.#id_field_name.id)
184+
.and_then(|id| Gd::try_from_instance_id(id).ok())
150185

151-
quote! {
186+
// Sanity check for consistency (if Some(...)):
187+
// let ptr = self.#field_name as sys::GDExtensionObjectPtr;
188+
// unsafe { Gd::from_obj_sys(ptr) }
189+
}
190+
});
191+
} else {
192+
field_name = snake_field_name;
193+
accessor = None;
194+
};
195+
196+
let field_def = quote! {
152197
pub #field_name: #field_type,
153-
}
198+
};
199+
200+
(field_def, accessor)
154201
}
155202

156203
fn normalize_native_structure_field_type(field_type: &str) -> String {

itest/rust/src/engine_tests/native_structures_test.rs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
use crate::framework::itest;
99

1010
use godot::builtin::{Rect2, Rid, Variant};
11-
use godot::classes::native::{AudioFrame, CaretInfo, Glyph, ObjectId};
11+
use godot::classes::native::{
12+
AudioFrame, CaretInfo, Glyph, ObjectId, PhysicsServer2DExtensionShapeResult,
13+
};
1214
use godot::classes::text_server::Direction;
13-
use godot::classes::{ITextServerExtension, TextServer, TextServerExtension};
14-
use godot::obj::{Base, NewGd};
15+
use godot::classes::{ITextServerExtension, Node3D, TextServer, TextServerExtension};
16+
use godot::obj::{Base, NewAlloc, NewGd};
1517
use godot::register::{godot_api, GodotClass};
1618

1719
use std::cell::Cell;
@@ -71,7 +73,7 @@ impl ITextServerExtension for TestTextServer {
7173
}
7274

7375
#[itest]
74-
fn test_native_structures_codegen() {
76+
fn native_structure_codegen() {
7577
// Test construction of a few simple types.
7678
let _ = AudioFrame {
7779
left: 0.0,
@@ -93,7 +95,7 @@ fn test_native_structures_codegen() {
9395
}
9496

9597
#[itest]
96-
fn test_native_structure_out_parameter() {
98+
fn native_structure_out_parameter() {
9799
// Instantiate a TextServerExtension and then have Godot call a
98100
// function which uses an 'out' pointer parameter.
99101
let mut ext = TestTextServer::new_gd();
@@ -127,7 +129,7 @@ fn test_native_structure_out_parameter() {
127129
}
128130

129131
#[itest]
130-
fn test_native_structure_pointer_to_array_parameter() {
132+
fn native_structure_pointer_to_array_parameter() {
131133
// Instantiate a TextServerExtension.
132134
let ext = TestTextServer::new_gd();
133135
let result = ext
@@ -142,7 +144,7 @@ fn test_native_structure_pointer_to_array_parameter() {
142144
}
143145

144146
#[itest]
145-
fn test_native_structure_clone() {
147+
fn native_structure_clone() {
146148
// Instantiate CaretInfo directly.
147149
let caret1 = CaretInfo {
148150
leading_caret: Rect2::from_components(0.0, 0.0, 0.0, 0.0),
@@ -169,15 +171,15 @@ fn test_native_structure_clone() {
169171
}
170172

171173
#[itest]
172-
fn test_native_structure_partialeq() {
174+
fn native_structure_partialeq() {
173175
// Test basic equality between two identically-constructed
174176
// (but distinct) native structures.
175177
assert_eq!(sample_glyph(5), sample_glyph(5));
176178
assert_ne!(sample_glyph(1), sample_glyph(2));
177179
}
178180

179181
#[itest]
180-
fn test_native_structure_debug() {
182+
fn native_structure_debug() {
181183
// Test debug output, both pretty-printed and not.
182184
let object_id = ObjectId { id: 256 };
183185
assert_eq!(
@@ -189,3 +191,24 @@ fn test_native_structure_debug() {
189191
String::from("ObjectId {\n id: 256,\n}")
190192
);
191193
}
194+
195+
#[itest]
196+
fn native_structure_object_pointers() {
197+
let object = Node3D::new_alloc();
198+
let raw_object_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
199+
200+
let result = PhysicsServer2DExtensionShapeResult {
201+
rid: Rid::new(12),
202+
collider_id: ObjectId {
203+
id: object.instance_id().to_i64() as u64,
204+
},
205+
__unused_collider_ptr: raw_object_ptr,
206+
shape: 0,
207+
};
208+
209+
let retrieved = result.collider();
210+
assert_eq!(retrieved, Some(object.clone().upcast()));
211+
212+
object.free();
213+
assert_eq!(result.collider(), None);
214+
}

0 commit comments

Comments
 (0)