Skip to content

Commit f57143f

Browse files
committed
Postpone the ref-counting problem for native structures
Also rename collider() -> get_collider().
1 parent 61fa5d6 commit f57143f

File tree

4 files changed

+48
-40
lines changed

4 files changed

+48
-40
lines changed

godot-codegen/src/generator/native_structures.rs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,20 @@ fn make_native_structure(
7171
) -> builtins::GeneratedBuiltin {
7272
let class_name = &class_name.rust_ty;
7373

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+
7480
let imports = util::make_imports();
75-
let (fields, methods) = make_native_structure_fields_and_methods(&structure.format, ctx);
81+
let (fields, methods) = make_native_structure_fields_and_methods(structure, ctx);
7682
let doc = format!("[`ToGodot`] and [`FromGodot`] are implemented for `*mut {class_name}` and `*const {class_name}`.");
7783

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

8490
/// Native structure; can be passed via pointer in APIs that are not exposed to GDScript.
@@ -132,10 +138,10 @@ fn make_native_structure(
132138
}
133139

134140
fn make_native_structure_fields_and_methods(
135-
format_str: &str,
141+
structure: &NativeStructure,
136142
ctx: &mut Context,
137143
) -> (TokenStream, TokenStream) {
138-
let fields = parse_native_structures_format(format_str)
144+
let fields = parse_native_structures_format(&structure.format)
139145
.expect("Could not parse native_structures format field");
140146

141147
let mut field_definitions = vec![];
@@ -151,6 +157,7 @@ fn make_native_structure_fields_and_methods(
151157

152158
let fields = quote! { #( #field_definitions )* };
153159
let methods = quote! { #( #accessors )* };
160+
154161
(fields, methods)
155162
}
156163

@@ -176,12 +183,15 @@ fn make_native_structure_field_and_accessor(
176183
field_name = format_ident!("raw_{}_ptr", snake_field_name);
177184

178185
// Generate method that converts from instance ID.
179-
let getter_name = &snake_field_name;
186+
let getter_name = format_ident!("get_{}", snake_field_name);
180187
let setter_name = format_ident!("set_{}", snake_field_name);
181188
let id_field_name = format_ident!("{}_id", snake_field_name);
182189

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.
183193
accessor = Some(quote! {
184-
/// Returns the object as a `Gd<T>`, or `None` if it no longer exists.
194+
/// Returns the object as a `Gd<Node>`, or `None` if it no longer exists.
185195
pub fn #getter_name(&self) -> Option<Gd<Object>> {
186196
crate::obj::InstanceId::try_from_u64(self.#id_field_name.id)
187197
.and_then(|id| Gd::try_from_instance_id(id).ok())
@@ -191,22 +201,16 @@ fn make_native_structure_field_and_accessor(
191201
// unsafe { Gd::from_obj_sys(ptr) }
192202
}
193203

194-
/// Sets the object from a `Gd<T>` pointer.
195-
///
196-
/// `increment_refcount` is only relevant for ref-counted objects (inheriting `RefCounted`). It is ignored otherwise.
197-
/// - Set it to true if you transfer `self` to Godot, e.g. via output parameter in a virtual function call.
198-
/// In this case, you can drop your own references and the object will remain alive.
199-
/// However, if you drop the native structure `self` without handing it over to Godot, you'll have a memory leak.
200-
/// - Set it to false if you just manage the native structure yourself.
201-
pub fn #setter_name(&mut self, mut obj: Gd<Object>, increment_refcount: bool) {
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> {
202207
use crate::meta::GodotType as _;
203208

204-
assert!(obj.is_instance_valid(), "provided object is dead");
209+
let obj = #snake_field_name.upcast();
210+
211+
assert!(obj.is_instance_valid(), "provided node is dead");
205212

206213
let id = obj.instance_id().to_u64();
207-
if increment_refcount {
208-
obj = obj.with_inc_refcount();
209-
}
210214

211215
self.#id_field_name = ObjectId { id };
212216
self.#field_name = obj.obj_sys() as *mut std::ffi::c_void;

godot-core/src/obj/gd.rs

Lines changed: 2 additions & 8 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
///
@@ -424,12 +424,6 @@ impl<T: GodotClass> Gd<T> {
424424
{
425425
self.raw.script_sys()
426426
}
427-
428-
pub(crate) fn with_inc_refcount(self) -> Self {
429-
Self {
430-
raw: self.raw.with_inc_refcount(),
431-
}
432-
}
433427
}
434428

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

godot-core/src/obj/raw.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ impl<T: GodotClass> RawGd<T> {
8282
}
8383

8484
/// Returns `self` but with initialized ref-count.
85-
// Could be private if not for Gd::with_inc_refcount(), which is used by native structure setters.
86-
pub(crate) fn with_inc_refcount(mut self) -> Self {
85+
fn with_inc_refcount(mut self) -> Self {
8786
// Note: use init_ref and not inc_ref, since this might be the first reference increment.
8887
// Godot expects RefCounted::init_ref to be called instead of RefCounted::reference in that case.
8988
// init_ref also doesn't hurt (except 1 possibly unnecessary check).

itest/rust/src/engine_tests/native_structures_test.rs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use godot::classes::native::{
1313
};
1414
use godot::classes::text_server::Direction;
1515
use godot::classes::{ITextServerExtension, Node3D, RefCounted, TextServer, TextServerExtension};
16-
use godot::obj::{Base, Gd, NewAlloc, NewGd};
16+
use godot::obj::{Base, NewAlloc, NewGd};
1717
use godot::register::{godot_api, GodotClass};
1818

1919
use std::cell::Cell;
@@ -194,50 +194,61 @@ fn native_structure_debug() {
194194

195195
#[itest]
196196
fn native_structure_object_pointers() {
197-
// Object.
198-
let object = Node3D::new_alloc();
199-
200197
let mut result = PhysicsServer2DExtensionShapeResult {
201198
rid: Rid::new(12),
202199
collider_id: ObjectId { id: 0 },
203200
raw_collider_ptr: std::ptr::null_mut(),
204201
shape: 0,
205202
};
206203

207-
result.set_collider(object.clone().upcast(), true);
204+
let retrieved = result.get_collider();
205+
assert_eq!(retrieved, None);
206+
207+
let object = Node3D::new_alloc();
208+
result.set_collider(object.clone());
208209
assert_eq!(result.collider_id.id, object.instance_id().to_i64() as u64);
209210

210-
let retrieved = result.collider();
211+
let retrieved = result.get_collider();
211212
assert_eq!(retrieved, Some(object.clone().upcast()));
212213

213214
object.free();
214-
assert_eq!(result.collider(), None);
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+
};
215226

216227
// RefCounted, increment ref-count.
217228
let object = RefCounted::new_gd();
218229
let id = object.instance_id();
219-
result.set_collider(object.clone().upcast(), true);
230+
result.set_collider(object.clone());
220231
assert_eq!(result.collider_id.id, id.to_i64() as u64);
221232

222233
drop(object); // Test if Godot keeps ref-count.
223234

224235
let retrieved = result
225-
.collider()
236+
.get_collider()
226237
.expect("Ref-counted objects don't drop if ref-count is incremented");
227238
assert_eq!(retrieved.instance_id(), id);
228239

229240
// Manually decrement refcount (method unexposed).
230-
Gd::<RefCounted>::from_instance_id(id).call("unreference".into(), &[]);
241+
//Gd::<RefCounted>::from_instance_id(id).call("unreference".into(), &[]);
231242

232243
// RefCounted, do NOT increment ref-count.
233244
let object = RefCounted::new_gd();
234245
let id = object.instance_id();
235-
result.set_collider(object.clone().upcast(), false);
246+
result.set_collider(object.clone());
236247
assert_eq!(result.collider_id.id, id.to_i64() as u64);
237248

238249
drop(object); // Test if Godot keeps ref-count.
239250

240-
let retrieved = result.collider();
251+
let retrieved = result.get_collider();
241252
assert!(
242253
retrieved.is_none(),
243254
"Ref-counted objects drop if ref-count is not incremented"

0 commit comments

Comments
 (0)