Skip to content

Commit 61fa5d6

Browse files
committed
Add setter to native structure's object pointer, allow to specify ref-count semantics
1 parent 0bf0ad0 commit 61fa5d6

File tree

4 files changed

+78
-16
lines changed

4 files changed

+78
-16
lines changed

godot-codegen/src/generator/native_structures.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,20 +173,44 @@ fn make_native_structure_field_and_accessor(
173173
let (field_name, accessor);
174174
if is_object_ptr {
175175
// Highlight that the pointer field is internal/opaque.
176-
field_name = format_ident!("__unused_{}_ptr", snake_field_name);
176+
field_name = format_ident!("raw_{}_ptr", snake_field_name);
177177

178178
// Generate method that converts from instance ID.
179+
let getter_name = &snake_field_name;
180+
let setter_name = format_ident!("set_{}", snake_field_name);
179181
let id_field_name = format_ident!("{}_id", snake_field_name);
182+
180183
accessor = Some(quote! {
181184
/// Returns the object as a `Gd<T>`, or `None` if it no longer exists.
182-
pub fn #snake_field_name(&self) -> Option<Gd<Object>> {
185+
pub fn #getter_name(&self) -> Option<Gd<Object>> {
183186
crate::obj::InstanceId::try_from_u64(self.#id_field_name.id)
184187
.and_then(|id| Gd::try_from_instance_id(id).ok())
185188

186189
// Sanity check for consistency (if Some(...)):
187190
// let ptr = self.#field_name as sys::GDExtensionObjectPtr;
188191
// unsafe { Gd::from_obj_sys(ptr) }
189192
}
193+
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) {
202+
use crate::meta::GodotType as _;
203+
204+
assert!(obj.is_instance_valid(), "provided object is dead");
205+
206+
let id = obj.instance_id().to_u64();
207+
if increment_refcount {
208+
obj = obj.with_inc_refcount();
209+
}
210+
211+
self.#id_field_name = ObjectId { id };
212+
self.#field_name = obj.obj_sys() as *mut std::ffi::c_void;
213+
}
190214
});
191215
} else {
192216
field_name = snake_field_name;

godot-core/src/obj/gd.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,13 @@ impl<T: GodotClass> Gd<T> {
380380
}
381381
}
382382

383+
/// Returns a callable referencing a method from this object named `method_name`.
384+
///
385+
/// This is shorter syntax for [`Callable::from_object_method(self, method_name)`][Callable::from_object_method].
386+
pub fn callable<S: Into<StringName>>(&self, method_name: S) -> Callable {
387+
Callable::from_object_method(self, method_name)
388+
}
389+
383390
pub(crate) unsafe fn from_obj_sys_or_none(
384391
ptr: sys::GDExtensionObjectPtr,
385392
) -> Result<Self, ConvertError> {
@@ -418,11 +425,10 @@ impl<T: GodotClass> Gd<T> {
418425
self.raw.script_sys()
419426
}
420427

421-
/// Returns a callable referencing a method from this object named `method_name`.
422-
///
423-
/// This is shorter syntax for [`Callable::from_object_method(self, method_name)`][Callable::from_object_method].
424-
pub fn callable<S: Into<StringName>>(&self, method_name: S) -> Callable {
425-
Callable::from_object_method(self, method_name)
428+
pub(crate) fn with_inc_refcount(self) -> Self {
429+
Self {
430+
raw: self.raw.with_inc_refcount(),
431+
}
426432
}
427433
}
428434

godot-core/src/obj/raw.rs

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

8484
/// Returns `self` but with initialized ref-count.
85-
fn with_inc_refcount(mut self) -> Self {
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 {
8687
// Note: use init_ref and not inc_ref, since this might be the first reference increment.
8788
// Godot expects RefCounted::init_ref to be called instead of RefCounted::reference in that case.
8889
// init_ref also doesn't hurt (except 1 possibly unnecessary check).

itest/rust/src/engine_tests/native_structures_test.rs

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use godot::classes::native::{
1212
AudioFrame, CaretInfo, Glyph, ObjectId, PhysicsServer2DExtensionShapeResult,
1313
};
1414
use godot::classes::text_server::Direction;
15-
use godot::classes::{ITextServerExtension, Node3D, TextServer, TextServerExtension};
16-
use godot::obj::{Base, NewAlloc, NewGd};
15+
use godot::classes::{ITextServerExtension, Node3D, RefCounted, TextServer, TextServerExtension};
16+
use godot::obj::{Base, Gd, NewAlloc, NewGd};
1717
use godot::register::{godot_api, GodotClass};
1818

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

195195
#[itest]
196196
fn native_structure_object_pointers() {
197+
// Object.
197198
let object = Node3D::new_alloc();
198-
let raw_object_ptr: *mut std::ffi::c_void = std::ptr::null_mut();
199199

200-
let result = PhysicsServer2DExtensionShapeResult {
200+
let mut result = PhysicsServer2DExtensionShapeResult {
201201
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,
202+
collider_id: ObjectId { id: 0 },
203+
raw_collider_ptr: std::ptr::null_mut(),
206204
shape: 0,
207205
};
208206

207+
result.set_collider(object.clone().upcast(), true);
208+
assert_eq!(result.collider_id.id, object.instance_id().to_i64() as u64);
209+
209210
let retrieved = result.collider();
210211
assert_eq!(retrieved, Some(object.clone().upcast()));
211212

212213
object.free();
213214
assert_eq!(result.collider(), None);
215+
216+
// RefCounted, increment ref-count.
217+
let object = RefCounted::new_gd();
218+
let id = object.instance_id();
219+
result.set_collider(object.clone().upcast(), true);
220+
assert_eq!(result.collider_id.id, id.to_i64() as u64);
221+
222+
drop(object); // Test if Godot keeps ref-count.
223+
224+
let retrieved = result
225+
.collider()
226+
.expect("Ref-counted objects don't drop if ref-count is incremented");
227+
assert_eq!(retrieved.instance_id(), id);
228+
229+
// Manually decrement refcount (method unexposed).
230+
Gd::<RefCounted>::from_instance_id(id).call("unreference".into(), &[]);
231+
232+
// RefCounted, do NOT increment ref-count.
233+
let object = RefCounted::new_gd();
234+
let id = object.instance_id();
235+
result.set_collider(object.clone().upcast(), false);
236+
assert_eq!(result.collider_id.id, id.to_i64() as u64);
237+
238+
drop(object); // Test if Godot keeps ref-count.
239+
240+
let retrieved = result.collider();
241+
assert!(
242+
retrieved.is_none(),
243+
"Ref-counted objects drop if ref-count is not incremented"
244+
);
214245
}

0 commit comments

Comments
 (0)