Skip to content

Commit 7559474

Browse files
authored
Merge pull request #754 from godot-rust/bugfix/native-structure-objects
Fix native structures exposing `*mut Gd<Object>`, provide accessor
2 parents feff365 + f57143f commit 7559474

File tree

4 files changed

+187
-36
lines changed

4 files changed

+187
-36
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: 90 additions & 15 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

@@ -64,13 +71,20 @@ fn make_native_structure(
6471
) -> builtins::GeneratedBuiltin {
6572
let class_name = &class_name.rust_ty;
6673

74+
// TODO for native structures holding object pointers, we should encapsulate the raw object, as follows:
75+
// - Make raw object pointer field private (and maybe also ID field, to keep in sync).
76+
// - Add constructor that takes Gd<Object> instead of ID/raw-ptr fields.
77+
// - Add getter and setter methods with Gd<Object> (already present).
78+
// - Add Drop impl, which decrements refcount if the constructor was used, and does nothing if FromGodot pointer conversion was used.
79+
6780
let imports = util::make_imports();
68-
let fields = make_native_structure_fields(&structure.format, ctx);
81+
let (fields, methods) = make_native_structure_fields_and_methods(structure, ctx);
6982
let doc = format!("[`ToGodot`] and [`FromGodot`] are implemented for `*mut {class_name}` and `*const {class_name}`.");
7083

7184
// mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub
7285
let tokens = quote! {
7386
#imports
87+
use std::ffi::c_void; // for opaque object pointer fields
7488
use crate::meta::{GodotConvert, FromGodot, ToGodot};
7589

7690
/// Native structure; can be passed via pointer in APIs that are not exposed to GDScript.
@@ -82,6 +96,10 @@ fn make_native_structure(
8296
#fields
8397
}
8498

99+
impl #class_name {
100+
#methods
101+
}
102+
85103
impl GodotConvert for *mut #class_name {
86104
type Via = i64;
87105
}
@@ -119,25 +137,36 @@ fn make_native_structure(
119137
builtins::GeneratedBuiltin { code: tokens }
120138
}
121139

122-
fn make_native_structure_fields(format_str: &str, ctx: &mut Context) -> TokenStream {
123-
let fields = parse_native_structures_format(format_str)
140+
fn make_native_structure_fields_and_methods(
141+
structure: &NativeStructure,
142+
ctx: &mut Context,
143+
) -> (TokenStream, TokenStream) {
144+
let fields = parse_native_structures_format(&structure.format)
124145
.expect("Could not parse native_structures format field");
125146

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

130-
quote! {
131-
#( #field_definitions )*
150+
for field in fields {
151+
let (field_def, accessor) = make_native_structure_field_and_accessor(field, ctx);
152+
field_definitions.push(field_def);
153+
if let Some(accessor) = accessor {
154+
accessors.push(accessor);
155+
}
132156
}
157+
158+
let fields = quote! { #( #field_definitions )* };
159+
let methods = quote! { #( #accessors )* };
160+
161+
(fields, methods)
133162
}
134163

135-
fn make_native_structure_field_definition(
164+
fn make_native_structure_field_and_accessor(
136165
field: NativeStructuresField,
137166
ctx: &mut Context,
138-
) -> TokenStream {
167+
) -> (TokenStream, Option<TokenStream>) {
139168
let field_type = normalize_native_structure_field_type(&field.field_type);
140-
let field_type = conv::to_rust_type_abi(&field_type, ctx);
169+
let (field_type, is_object_ptr) = conv::to_rust_type_abi(&field_type, ctx);
141170

142171
// Make array if needed.
143172
let field_type = if let Some(size) = field.array_size {
@@ -146,11 +175,57 @@ fn make_native_structure_field_definition(
146175
quote! { #field_type }
147176
};
148177

149-
let field_name = ident(&conv::to_snake_case(&field.field_name));
178+
let snake_field_name = ident(&conv::to_snake_case(&field.field_name));
179+
180+
let (field_name, accessor);
181+
if is_object_ptr {
182+
// Highlight that the pointer field is internal/opaque.
183+
field_name = format_ident!("raw_{}_ptr", snake_field_name);
184+
185+
// Generate method that converts from instance ID.
186+
let getter_name = format_ident!("get_{}", snake_field_name);
187+
let setter_name = format_ident!("set_{}", snake_field_name);
188+
let id_field_name = format_ident!("{}_id", snake_field_name);
189+
190+
// Current native structures treat all object pointers as Object (even if concrete ones like `collider` might be Node).
191+
// Having node also avoids the lifetime issue with reference-counted objects (unclear if gdext should increment refcount or not).
192+
// If other fields use different classes in the future, we'll need to update this.
193+
accessor = Some(quote! {
194+
/// Returns the object as a `Gd<Node>`, or `None` if it no longer exists.
195+
pub fn #getter_name(&self) -> Option<Gd<Object>> {
196+
crate::obj::InstanceId::try_from_u64(self.#id_field_name.id)
197+
.and_then(|id| Gd::try_from_instance_id(id).ok())
198+
199+
// Sanity check for consistency (if Some(...)):
200+
// let ptr = self.#field_name as sys::GDExtensionObjectPtr;
201+
// unsafe { Gd::from_obj_sys(ptr) }
202+
}
203+
204+
/// Sets the object from a `Gd` pointer holding `Node` or a derived class.
205+
pub fn #setter_name<T>(&mut self, #snake_field_name: Gd<T>)
206+
where T: crate::obj::Inherits<Object> {
207+
use crate::meta::GodotType as _;
150208

151-
quote! {
209+
let obj = #snake_field_name.upcast();
210+
211+
assert!(obj.is_instance_valid(), "provided node is dead");
212+
213+
let id = obj.instance_id().to_u64();
214+
215+
self.#id_field_name = ObjectId { id };
216+
self.#field_name = obj.obj_sys() as *mut std::ffi::c_void;
217+
}
218+
});
219+
} else {
220+
field_name = snake_field_name;
221+
accessor = None;
222+
};
223+
224+
let field_def = quote! {
152225
pub #field_name: #field_type,
153-
}
226+
};
227+
228+
(field_def, accessor)
154229
}
155230

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

godot-core/src/obj/gd.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ use crate::{classes, out};
4646
/// - **Manual**<br>
4747
/// Objects inheriting from [`Object`] which are not `RefCounted` (or inherited) are **manually-managed**.
4848
/// Their destructor is not automatically called (unless they are part of the scene tree). Creating a `Gd<T>` means that
49-
/// you are responsible of explicitly deallocating such objects using [`free()`][Self::free].<br><br>
49+
/// you are responsible for explicitly deallocating such objects using [`free()`][Self::free].<br><br>
5050
///
5151
/// - **Dynamic**<br>
5252
/// For `T=Object`, the memory strategy is determined **dynamically**. Due to polymorphism, a `Gd<Object>` can point to either
@@ -77,7 +77,7 @@ use crate::{classes, out};
7777
/// These provide interior mutability similar to [`RefCell`][std::cell::RefCell], with the addition that `Gd` simultaneously handles reference
7878
/// counting (for some types `T`).
7979
///
80-
/// When you declare a `#[func]` method on your own class and it accepts `&self` or `&mut self`, an implicit `bind()` or `bind_mut()` call
80+
/// When you declare a `#[func]` method on your own class, and it accepts `&self` or `&mut self`, an implicit `bind()` or `bind_mut()` call
8181
/// on the owning `Gd<T>` is performed. This is important to keep in mind, as you can get into situations that violate dynamic borrow rules; for
8282
/// example if you are inside a `&mut self` method, make a call to GDScript and indirectly call another method on the same object (re-entrancy).
8383
///
@@ -414,6 +414,13 @@ impl<T: GodotClass> Gd<T> {
414414
}
415415
}
416416

417+
/// Returns a callable referencing a method from this object named `method_name`.
418+
///
419+
/// This is shorter syntax for [`Callable::from_object_method(self, method_name)`][Callable::from_object_method].
420+
pub fn callable<S: Into<StringName>>(&self, method_name: S) -> Callable {
421+
Callable::from_object_method(self, method_name)
422+
}
423+
417424
pub(crate) unsafe fn from_obj_sys_or_none(
418425
ptr: sys::GDExtensionObjectPtr,
419426
) -> Result<Self, ConvertError> {
@@ -451,13 +458,6 @@ impl<T: GodotClass> Gd<T> {
451458
{
452459
self.raw.script_sys()
453460
}
454-
455-
/// Returns a callable referencing a method from this object named `method_name`.
456-
///
457-
/// This is shorter syntax for [`Callable::from_object_method(self, method_name)`][Callable::from_object_method].
458-
pub fn callable<S: Into<StringName>>(&self, method_name: S) -> Callable {
459-
Callable::from_object_method(self, method_name)
460-
}
461461
}
462462

463463
impl<T: GodotClass> Deref for Gd<T> {

itest/rust/src/engine_tests/native_structures_test.rs

Lines changed: 74 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, RefCounted, 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,66 @@ 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 mut result = PhysicsServer2DExtensionShapeResult {
198+
rid: Rid::new(12),
199+
collider_id: ObjectId { id: 0 },
200+
raw_collider_ptr: std::ptr::null_mut(),
201+
shape: 0,
202+
};
203+
204+
let retrieved = result.get_collider();
205+
assert_eq!(retrieved, None);
206+
207+
let object = Node3D::new_alloc();
208+
result.set_collider(object.clone());
209+
assert_eq!(result.collider_id.id, object.instance_id().to_i64() as u64);
210+
211+
let retrieved = result.get_collider();
212+
assert_eq!(retrieved, Some(object.clone().upcast()));
213+
214+
object.free();
215+
assert_eq!(result.get_collider(), None);
216+
}
217+
218+
#[itest(skip)] // Not yet implemented.
219+
fn native_structure_refcounted_pointers() {
220+
let mut result = PhysicsServer2DExtensionShapeResult {
221+
rid: Rid::new(12),
222+
collider_id: ObjectId { id: 0 },
223+
raw_collider_ptr: std::ptr::null_mut(),
224+
shape: 0,
225+
};
226+
227+
// RefCounted, increment ref-count.
228+
let object = RefCounted::new_gd();
229+
let id = object.instance_id();
230+
result.set_collider(object.clone());
231+
assert_eq!(result.collider_id.id, id.to_i64() as u64);
232+
233+
drop(object); // Test if Godot keeps ref-count.
234+
235+
let retrieved = result
236+
.get_collider()
237+
.expect("Ref-counted objects don't drop if ref-count is incremented");
238+
assert_eq!(retrieved.instance_id(), id);
239+
240+
// Manually decrement refcount (method unexposed).
241+
//Gd::<RefCounted>::from_instance_id(id).call("unreference".into(), &[]);
242+
243+
// RefCounted, do NOT increment ref-count.
244+
let object = RefCounted::new_gd();
245+
let id = object.instance_id();
246+
result.set_collider(object.clone());
247+
assert_eq!(result.collider_id.id, id.to_i64() as u64);
248+
249+
drop(object); // Test if Godot keeps ref-count.
250+
251+
let retrieved = result.get_collider();
252+
assert!(
253+
retrieved.is_none(),
254+
"Ref-counted objects drop if ref-count is not incremented"
255+
);
256+
}

0 commit comments

Comments
 (0)