Skip to content

Commit 0674858

Browse files
authored
Merge pull request #1270 from godot-rust/qol/refcount-tracking
Properly register GDExtension `reference`/`unreference` callbacks
2 parents 512c5a6 + 24041f9 commit 0674858

File tree

11 files changed

+64
-56
lines changed

11 files changed

+64
-56
lines changed

godot-core/src/builtin/rect2i.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,12 @@ impl Rect2i {
8383
}
8484

8585
/// The end of the `Rect2i` calculated as `position + size`.
86-
///
87-
/// _Godot equivalent: `Rect2i.size` property_
88-
#[doc(alias = "size")]
8986
#[inline]
9087
pub const fn end(self) -> Vector2i {
9188
Vector2i::new(self.position.x + self.size.x, self.position.y + self.size.y)
9289
}
9390

94-
/// Set size based on desired end-point.
95-
///
96-
/// _Godot equivalent: `Rect2i.size` property_
91+
/// Set end of the `Rect2i`, updating `size = end - position`.
9792
#[inline]
9893
pub fn set_end(&mut self, end: Vector2i) {
9994
self.size = end - self.position
@@ -110,8 +105,7 @@ impl Rect2i {
110105

111106
/// Returns `true` if this `Rect2i` completely encloses another one.
112107
///
113-
/// Any `Rect2i` encloses itself, i.e. an enclosed `Rect2i` does is not required to be a
114-
/// proper sub-rect.
108+
/// Any `Rect2i` encloses itself, i.e. an enclosed `Rect2i` does is not required to be a proper sub-rect.
115109
#[inline]
116110
pub const fn encloses(self, other: Self) -> bool {
117111
self.assert_nonnegative();

godot-core/src/obj/bounds.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,11 @@ pub use crate::implement_godot_bounds;
152152
// Memory bounds
153153

154154
/// Specifies the memory strategy of the static type.
155-
pub trait Memory: Sealed {}
155+
pub trait Memory: Sealed {
156+
/// True for everything inheriting `RefCounted`, false for `Object` and all other classes.
157+
#[doc(hidden)]
158+
const IS_REF_COUNTED: bool;
159+
}
156160

157161
/// Specifies the memory strategy of the dynamic type.
158162
///
@@ -205,7 +209,9 @@ pub trait DynMemory: Sealed {
205209
/// This is used for `RefCounted` classes and derived.
206210
pub struct MemRefCounted {}
207211
impl Sealed for MemRefCounted {}
208-
impl Memory for MemRefCounted {}
212+
impl Memory for MemRefCounted {
213+
const IS_REF_COUNTED: bool = true;
214+
}
209215
impl DynMemory for MemRefCounted {
210216
fn maybe_init_ref<T: GodotClass>(obj: &mut RawGd<T>) {
211217
out!(" MemRefc::init: {obj:?}");
@@ -327,7 +333,9 @@ impl DynMemory for MemDynamic {
327333
/// This is used for all `Object` derivates, which are not `RefCounted`. `Object` itself is also excluded.
328334
pub struct MemManual {}
329335
impl Sealed for MemManual {}
330-
impl Memory for MemManual {}
336+
impl Memory for MemManual {
337+
const IS_REF_COUNTED: bool = false;
338+
}
331339
impl DynMemory for MemManual {
332340
fn maybe_init_ref<T: GodotClass>(_obj: &mut RawGd<T>) {}
333341
fn maybe_inc_ref<T: GodotClass>(_obj: &mut RawGd<T>) {}

godot-core/src/obj/gd.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,10 +294,11 @@ impl<T: GodotClass> Gd<T> {
294294

295295
/// Returns the dynamic class name of the object as `StringName`.
296296
///
297-
/// This method retrieves the class name of the object at runtime, which can be different from [`T::class_name()`] if derived
298-
/// classes are involved.
297+
/// This method retrieves the class name of the object at runtime, which can be different from [`T::class_name()`][GodotClass::class_name]
298+
/// if derived classes are involved.
299299
///
300-
/// Unlike [`Object::get_class()`], this returns `StringName` instead of `GString` and needs no `Inherits<Object>` bound.
300+
/// Unlike [`Object::get_class()`][crate::classes::Object::get_class], this returns `StringName` instead of `GString` and needs no
301+
/// `Inherits<Object>` bound.
301302
pub(crate) fn dynamic_class_string(&self) -> StringName {
302303
unsafe {
303304
StringName::new_with_string_uninit(|ptr| {

godot-core/src/registry/class.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ impl ClassRegistrationInfo {
136136
}
137137

138138
/// Registers a class with static type information.
139-
// Currently dead code, but will be needed for builder API. Don't remove.
140-
pub fn register_class<
139+
#[expect(dead_code)] // Will be needed for builder API. Don't remove.
140+
pub(crate) fn register_class<
141141
T: cap::GodotDefault
142142
+ cap::ImplementsGodotVirtual
143143
+ cap::GodotToString
@@ -427,6 +427,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
427427
is_instantiable,
428428
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
429429
docs: _,
430+
reference_fn,
431+
unreference_fn,
430432
}) => {
431433
c.parent_class_name = Some(base_class_name);
432434
c.default_virtual_fn = default_get_virtual_fn;
@@ -443,6 +445,8 @@ fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
443445
// See also: https://github.com/godotengine/godot/pull/58972
444446
c.godot_params.is_abstract = sys::conv::bool_to_sys(!is_instantiable);
445447
c.godot_params.free_instance_func = Some(free_fn);
448+
c.godot_params.reference_func = reference_fn;
449+
c.godot_params.unreference_func = unreference_fn;
446450

447451
fill_into(
448452
&mut c.godot_params.create_instance_func,

godot-core/src/registry/plugin.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,12 @@ pub struct Struct {
167167
/// Callback to library-generated function which registers properties in the `struct` definition.
168168
pub(crate) register_properties_fn: ErasedRegisterFn,
169169

170+
/// Callback on refc-increment. Only for `RefCounted` classes.
171+
pub(crate) reference_fn: sys::GDExtensionClassReference,
172+
173+
/// Callback on refc-decrement. Only for `RefCounted` classes.
174+
pub(crate) unreference_fn: sys::GDExtensionClassUnreference,
175+
170176
/// Function called by Godot when an object of this class is freed.
171177
///
172178
/// Always implemented as [`callbacks::free`].
@@ -200,6 +206,8 @@ impl Struct {
200206
pub fn new<T: GodotClass + cap::ImplementsGodotExports>(
201207
#[cfg(all(since_api = "4.3", feature = "register-docs"))] docs: StructDocs,
202208
) -> Self {
209+
let refcounted = <T::Memory as bounds::Memory>::IS_REF_COUNTED;
210+
203211
Self {
204212
base_class_name: T::Base::class_name(),
205213
generated_create_fn: None,
@@ -215,6 +223,9 @@ impl Struct {
215223
is_instantiable: false,
216224
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
217225
docs,
226+
// While Godot doesn't do anything with these callbacks for non-RefCounted classes, we can avoid instantiating them in Rust.
227+
reference_fn: refcounted.then_some(callbacks::reference::<T>),
228+
unreference_fn: refcounted.then_some(callbacks::unreference::<T>),
218229
}
219230
}
220231

godot-core/src/storage/instance_storage.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,6 @@ pub unsafe trait Storage {
154154

155155
/// An internal trait for keeping track of reference counts for a storage.
156156
pub(crate) trait StorageRefCounted: Storage {
157-
#[allow(dead_code)] // used in `debug-log` feature. Don't micromanage.
158-
fn godot_ref_count(&self) -> u32;
159-
160157
fn on_inc_ref(&self);
161158

162159
fn on_dec_ref(&self);

godot-core/src/storage/mod.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,22 @@ mod log_active {
6565

6666
pub fn log_construct<T: GodotClass>(base: &Base<T::Base>) {
6767
out!(
68-
" Storage::construct: {base:?} (T={ty})",
68+
" Storage::construct: {base:?} (T={ty})",
6969
ty = type_name::<T>()
7070
);
7171
}
7272

7373
pub fn log_inc_ref<T: StorageRefCounted>(storage: &T) {
7474
out!(
75-
" Storage::on_inc_ref (rc={rc}): {base:?} (T={ty})",
76-
rc = T::godot_ref_count(storage),
75+
" Storage::on_inc_ref: {base:?} (T={ty})",
7776
base = storage.base(),
7877
ty = type_name::<T>(),
7978
);
8079
}
8180

8281
pub fn log_dec_ref<T: StorageRefCounted>(storage: &T) {
8382
out!(
84-
" | Storage::on_dec_ref (rc={rc}): {base:?} (T={ty})",
85-
rc = T::godot_ref_count(storage),
83+
" | Storage::on_dec_ref: {base:?} (T={ty})",
8684
base = storage.base(),
8785
ty = type_name::<T>(),
8886
);
@@ -103,8 +101,7 @@ mod log_active {
103101
// Do not Debug-fmt `self.base()` object here, see above.
104102

105103
out!(
106-
" Storage::drop (rc={rc}): {base_id}",
107-
rc = storage.godot_ref_count(),
104+
" Storage::drop: {base_id}",
108105
base_id = storage.base().debug_instance_id(),
109106
);
110107
}

godot-core/src/storage/multi_threaded.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use std::sync::atomic::{AtomicU32, Ordering};
9-
108
#[cfg(not(feature = "experimental-threads"))]
119
use godot_cell::panicking::{GdCell, InaccessibleGuard, MutGuard, RefGuard};
1210

@@ -22,7 +20,6 @@ pub struct InstanceStorage<T: GodotClass> {
2220

2321
// Declared after `user_instance`, is dropped last
2422
pub(super) lifecycle: AtomicLifecycle,
25-
godot_ref_count: AtomicU32,
2623

2724
// No-op in Release mode.
2825
borrow_tracker: DebugBorrowTracker,
@@ -49,7 +46,6 @@ unsafe impl<T: GodotClass> Storage for InstanceStorage<T> {
4946
user_instance: GdCell::new(user_instance),
5047
base,
5148
lifecycle: AtomicLifecycle::new(Lifecycle::Alive),
52-
godot_ref_count: AtomicU32::new(1),
5349
borrow_tracker: DebugBorrowTracker::new(),
5450
}
5551
}
@@ -104,19 +100,11 @@ unsafe impl<T: GodotClass> Storage for InstanceStorage<T> {
104100
}
105101

106102
impl<T: GodotClass> StorageRefCounted for InstanceStorage<T> {
107-
fn godot_ref_count(&self) -> u32 {
108-
self.godot_ref_count.load(Ordering::Relaxed)
109-
}
110-
111103
fn on_inc_ref(&self) {
112-
self.godot_ref_count.fetch_add(1, Ordering::Relaxed);
113-
114104
super::log_inc_ref(self);
115105
}
116106

117107
fn on_dec_ref(&self) {
118-
self.godot_ref_count.fetch_sub(1, Ordering::Relaxed);
119-
120108
super::log_dec_ref(self);
121109
}
122110
}

godot-core/src/storage/single_threaded.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ pub struct InstanceStorage<T: GodotClass> {
2222

2323
// Declared after `user_instance`, is dropped last
2424
pub(super) lifecycle: cell::Cell<Lifecycle>,
25-
godot_ref_count: cell::Cell<u32>,
2625

2726
// No-op in Release mode.
2827
borrow_tracker: DebugBorrowTracker,
@@ -49,7 +48,6 @@ unsafe impl<T: GodotClass> Storage for InstanceStorage<T> {
4948
user_instance: GdCell::new(user_instance),
5049
base,
5150
lifecycle: cell::Cell::new(Lifecycle::Alive),
52-
godot_ref_count: cell::Cell::new(1),
5351
borrow_tracker: DebugBorrowTracker::new(),
5452
}
5553
}
@@ -101,21 +99,11 @@ unsafe impl<T: GodotClass> Storage for InstanceStorage<T> {
10199
}
102100

103101
impl<T: GodotClass> StorageRefCounted for InstanceStorage<T> {
104-
fn godot_ref_count(&self) -> u32 {
105-
self.godot_ref_count.get()
106-
}
107-
108102
fn on_inc_ref(&self) {
109-
let refc = self.godot_ref_count.get() + 1;
110-
self.godot_ref_count.set(refc);
111-
112103
super::log_inc_ref(self);
113104
}
114105

115106
fn on_dec_ref(&self) {
116-
let refc = self.godot_ref_count.get() - 1;
117-
self.godot_ref_count.set(refc);
118-
119107
super::log_dec_ref(self);
120108
}
121109
}

godot-macros/src/itest.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,9 @@ pub fn attribute_itest(input_item: venial::Item) -> ParseResult<TokenStream> {
6464
quote! { __unused_context: &crate::framework::TestContext }
6565
};
6666

67+
let return_ty = func.return_ty.as_ref();
6768
if is_async
68-
&& func
69-
.return_ty
70-
.as_ref()
69+
&& return_ty
7170
.and_then(extract_typename)
7271
.is_none_or(|segment| segment.ident != "TaskHandle")
7372
{
@@ -78,7 +77,8 @@ pub fn attribute_itest(input_item: venial::Item) -> ParseResult<TokenStream> {
7877

7978
let (return_tokens, test_case_ty, plugin_name);
8079
if is_async {
81-
return_tokens = quote! { -> TaskHandle };
80+
let [arrow, arrow_head] = func.tk_return_arrow.unwrap();
81+
return_tokens = quote! { #arrow #arrow_head #return_ty }; // retain span.
8282
test_case_ty = quote! { crate::framework::AsyncRustTestCase };
8383
plugin_name = ident("__GODOT_ASYNC_ITEST");
8484
} else {

0 commit comments

Comments
 (0)