Skip to content

Commit 369e9d3

Browse files
authored
Merge pull request #1302 from godot-rust/perf/base-no-clone
`base()` + `base_mut()` no longer clone `Gd` pointer
2 parents 8f17fff + fc389f8 commit 369e9d3

File tree

8 files changed

+186
-25
lines changed

8 files changed

+186
-25
lines changed

godot-core/src/obj/base.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::mem::ManuallyDrop;
1515
use std::rc::Rc;
1616

1717
use crate::builtin::{Callable, Variant};
18-
use crate::obj::{bounds, Gd, GodotClass, InstanceId};
18+
use crate::obj::{bounds, Gd, GodotClass, InstanceId, PassiveGd};
1919
use crate::{classes, sys};
2020

2121
thread_local! {
@@ -305,16 +305,16 @@ impl<T: GodotClass> Base<T> {
305305
self.obj.instance_id()
306306
}
307307

308-
/// Returns a [`Gd`] referencing the base object, for use in script contexts only.
309-
pub(crate) fn to_script_gd(&self) -> Gd<T> {
308+
/// Returns a passive reference to the base object, for use in script contexts only.
309+
pub(crate) fn to_script_passive(&self) -> PassiveGd<'_, T> {
310310
#[cfg(debug_assertions)]
311311
assert_eq!(
312312
self.init_state.get(),
313313
InitState::Script,
314-
"to_script_gd() can only be called on script-context Base objects"
314+
"to_script_passive() can only be called on script-context Base objects"
315315
);
316316

317-
(*self.obj).clone()
317+
PassiveGd::from_strong_ref(&self.obj)
318318
}
319319

320320
/// Returns `true` if this `Base<T>` is currently in the initializing state.
@@ -334,6 +334,37 @@ impl<T: GodotClass> Base<T> {
334334

335335
(*self.obj).clone()
336336
}
337+
338+
/// Returns a [`PassiveGd`] referencing the base object, assuming the derived object is fully constructed.
339+
///
340+
/// This method directly creates a PassiveGd from a weak reference, providing clean lifetime management
341+
/// without the need for manual `drop_weak()` calls.
342+
pub(crate) fn constructed_passive(&self) -> PassiveGd<'_, T> {
343+
// SAFETY: returned lifetime here is re-bound to self. Covariant lifetime conversion 'static -> 'self.
344+
unsafe { self.constructed_passive_unbounded() }
345+
}
346+
347+
/// Returns a weak [`Gd`] referencing the base object, assuming the derived object is fully constructed.
348+
///
349+
/// Unlike [`Self::__constructed_gd()`], this does not increment the reference count for ref-counted `T`s.
350+
/// The returned weak reference is safe to use only as long as the associated instance remains alive.
351+
///
352+
/// # Safety
353+
/// This method disconnects the lifetime, as opposed to [`Self::constructed_passive()]. Caller is responsible of re-binding the
354+
/// lifetime to the instance.
355+
pub(crate) unsafe fn constructed_passive_unbounded(&self) -> PassiveGd<'static, T> {
356+
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
357+
assert!(
358+
!self.is_initializing(),
359+
"WithBaseField::base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
360+
);
361+
362+
// Create weak reference from the same object pointer without cloning (incrementing refcount).
363+
let weak_gd = unsafe { Gd::from_obj_sys_weak(self.obj.obj_sys()) };
364+
365+
// SAFETY: weak_gd is a weakly created Gd, and remains valid as long as self is alive (per safety precondition of this fn).
366+
unsafe { PassiveGd::from_weak_owned(weak_gd) }
367+
}
337368
}
338369

339370
impl<T: GodotClass> Debug for Base<T> {

godot-core/src/obj/gd.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,15 @@ impl<T: GodotClass> Gd<T> {
335335
Some(rc.map(|i| i as usize))
336336
}
337337

338+
/// Create a non-owning pointer from this.
339+
///
340+
/// # Safety
341+
/// Must be destroyed with [`drop_weak()`][Self::drop_weak]; regular `Drop` will cause use-after-free.
342+
pub(crate) unsafe fn clone_weak(&self) -> Self {
343+
// SAFETY: delegated to caller.
344+
unsafe { Gd::from_obj_sys_weak(self.obj_sys()) }
345+
}
346+
338347
/// Drop without decrementing ref-counter.
339348
///
340349
/// Needed in situations where the instance should effectively be forgotten, but without leaking other associated data.

godot-core/src/obj/guards.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ use godot_cell::panicking::{InaccessibleGuard, MutGuard, RefGuard};
1515
use godot_ffi::out;
1616

1717
use crate::obj::script::ScriptInstance;
18-
use crate::obj::{AsDyn, Gd, GodotClass};
18+
use crate::obj::{AsDyn, Gd, GodotClass, PassiveGd};
1919

2020
/// Immutably/shared bound reference guard for a [`Gd`][crate::obj::Gd] smart pointer.
2121
///
2222
/// See [`Gd::bind`][crate::obj::Gd::bind] for usage.
23+
// GdRef could technically implement Clone, but it wasn't needed so far.
2324
#[derive(Debug)]
2425
pub struct GdRef<'a, T: GodotClass> {
2526
guard: RefGuard<'a, T>,
@@ -45,8 +46,6 @@ impl<T: GodotClass> Drop for GdRef<'_, T> {
4546
}
4647
}
4748

48-
// TODO Clone or Share
49-
5049
// ----------------------------------------------------------------------------------------------------------------------------------------------
5150

5251
/// Mutably/exclusively bound reference guard for a [`Gd`][crate::obj::Gd] smart pointer.
@@ -199,14 +198,14 @@ macro_rules! make_base_ref {
199198
#[doc = concat!("This can be used to call methods on the base object of a ", $object_name, " that takes `&self` as the receiver.\n\n")]
200199
#[doc = concat!("See [`", stringify!($doc_type), "::base()`](", stringify!($doc_path), "::base()) for usage.")]
201200
pub struct $ident<'a, T: $bound> {
202-
gd: Gd<T::Base>,
201+
passive_gd: PassiveGd<'a, T::Base>,
203202
_instance: &'a T,
204203
}
205204

206205
impl<'a, T: $bound> $ident<'a, T> {
207-
pub(crate) fn new(gd: Gd<T::Base>, instance: &'a T) -> Self {
206+
pub(crate) fn new(passive_gd: PassiveGd<'a, T::Base>, instance: &'a T) -> Self {
208207
Self {
209-
gd,
208+
passive_gd,
210209
_instance: instance,
211210
}
212211
}
@@ -216,7 +215,7 @@ macro_rules! make_base_ref {
216215
type Target = Gd<T::Base>;
217216

218217
fn deref(&self) -> &Gd<T::Base> {
219-
&self.gd
218+
&self.passive_gd
220219
}
221220
}
222221
};
@@ -232,17 +231,17 @@ macro_rules! make_base_mut {
232231
///
233232
#[doc = concat!("See [`", stringify!($doc_type), "::base_mut()`](", stringify!($doc_path), "::base_mut()) for usage.\n")]
234233
pub struct $ident<'a, T: $bound> {
235-
gd: Gd<T::Base>,
234+
passive_gd: PassiveGd<'a, T::Base>,
236235
_inaccessible_guard: InaccessibleGuard<'a, T>,
237236
}
238237

239238
impl<'a, T: $bound> $ident<'a, T> {
240239
pub(crate) fn new(
241-
gd: Gd<T::Base>,
240+
passive_gd: PassiveGd<'a, T::Base>,
242241
inaccessible_guard: InaccessibleGuard<'a, T>,
243242
) -> Self {
244243
Self {
245-
gd,
244+
passive_gd,
246245
_inaccessible_guard: inaccessible_guard,
247246
}
248247
}
@@ -252,13 +251,13 @@ macro_rules! make_base_mut {
252251
type Target = Gd<T::Base>;
253252

254253
fn deref(&self) -> &Gd<T::Base> {
255-
&self.gd
254+
&self.passive_gd
256255
}
257256
}
258257

259258
impl<T: $bound> DerefMut for $ident<'_, T> {
260259
fn deref_mut(&mut self) -> &mut Gd<T::Base> {
261-
&mut self.gd
260+
&mut self.passive_gd
262261
}
263262
}
264263
};

godot-core/src/obj/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod guards;
2020
mod instance_id;
2121
mod on_editor;
2222
mod on_ready;
23+
mod passive_gd;
2324
mod raw_gd;
2425
mod traits;
2526

@@ -33,6 +34,7 @@ pub use guards::{BaseMut, BaseRef, DynGdMut, DynGdRef, GdMut, GdRef};
3334
pub use instance_id::*;
3435
pub use on_editor::*;
3536
pub use on_ready::*;
37+
pub(crate) use passive_gd::PassiveGd;
3638
pub use raw_gd::*;
3739
pub use traits::*;
3840

godot-core/src/obj/passive_gd.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
use std::marker::PhantomData;
9+
use std::mem::ManuallyDrop;
10+
use std::ops::{Deref, DerefMut};
11+
12+
use crate::obj::{Gd, GodotClass};
13+
14+
/// Passive (non-owning) reference to a Godot object.
15+
///
16+
/// `PassiveGd<'gd, T>` provides a safe abstraction for weak references to Godot objects. Unlike `Gd<T>`, it does not increment/decrement
17+
/// the reference count for `RefCounted` objects, and its `Drop` impl only cleans up metadata, not the Godot object.
18+
///
19+
/// The lifetime `'gd` can be used to tie it to a _strong_ `Gd<T>` reference, however it can also be `'static` if more flexibility is needed.
20+
///
21+
/// This type is primarily used internally for base object access in guards and traits, providing a clean alternative to manual
22+
/// [`Gd::clone_weak()`] and [`Gd::drop_weak()`] patterns.
23+
pub(crate) struct PassiveGd<'gd, T: GodotClass> {
24+
weak_gd: ManuallyDrop<Gd<T>>,
25+
26+
// Covariant lifetime: PassiveGd<'a, T> can be used wherever PassiveGd<'b, T> is needed, if 'a: 'b.
27+
_phantom: PhantomData<&'gd ()>,
28+
}
29+
30+
impl<'gd, T: GodotClass> PassiveGd<'gd, T> {
31+
pub fn from_strong_ref(gd: &Gd<T>) -> Self {
32+
// SAFETY:
33+
// - `clone_weak()` creates a pointer conforming to `from_weak_gd()` requirements.
34+
// - PassiveGd will destroy the pointer with `drop_weak()`.
35+
unsafe {
36+
let weak_gd = gd.clone_weak();
37+
Self::from_weak_owned(weak_gd)
38+
}
39+
}
40+
41+
/// Creates a passive reference directly from a weak `Gd<T>`.
42+
///
43+
/// Will invoke `Gd::drop_weak()` when dropped. Since the parameter has no lifetime, you need to provide the lifetime `'gd` explicitly.
44+
///
45+
/// # Safety
46+
/// - `weak_gd` must be a weakly created `Gd`, e.g. from [`Gd::clone_weak()`] or [`Gd::from_obj_sys_weak()`].
47+
/// - The caller must ensure that the `weak_gd` remains valid for the lifetime `'gd`.
48+
pub unsafe fn from_weak_owned(weak_gd: Gd<T>) -> Self {
49+
Self {
50+
weak_gd: ManuallyDrop::new(weak_gd),
51+
_phantom: PhantomData,
52+
}
53+
}
54+
}
55+
56+
impl<T: GodotClass> Drop for PassiveGd<'_, T> {
57+
fn drop(&mut self) {
58+
// SAFETY: Only extracted once, in Drop.
59+
let weak = unsafe { ManuallyDrop::take(&mut self.weak_gd) };
60+
61+
weak.drop_weak();
62+
}
63+
}
64+
65+
impl<T: GodotClass> Deref for PassiveGd<'_, T> {
66+
type Target = Gd<T>;
67+
68+
fn deref(&self) -> &Self::Target {
69+
&self.weak_gd
70+
}
71+
}
72+
73+
impl<T: GodotClass> DerefMut for PassiveGd<'_, T> {
74+
fn deref_mut(&mut self) -> &mut Self::Target {
75+
&mut self.weak_gd
76+
}
77+
}
78+
79+
// Note: We intentionally do NOT implement Clone for PassiveGd, as cloning weak references requires careful lifetime management that
80+
// should be explicit.

godot-core/src/obj/script.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,8 @@ impl<'a, T: ScriptInstance> SiMut<'a, T> {
442442
/// }
443443
/// ```
444444
pub fn base(&self) -> ScriptBaseRef<'_, T> {
445-
ScriptBaseRef::new(self.base_ref.to_script_gd(), self.mut_ref)
445+
let passive_gd = self.base_ref.to_script_passive();
446+
ScriptBaseRef::new(passive_gd, self.mut_ref)
446447
}
447448

448449
/// Returns a mutable reference suitable for calling engine methods on this object.
@@ -505,8 +506,9 @@ impl<'a, T: ScriptInstance> SiMut<'a, T> {
505506
/// ```
506507
pub fn base_mut(&mut self) -> ScriptBaseMut<'_, T> {
507508
let guard = self.cell.make_inaccessible(self.mut_ref).unwrap();
509+
let passive_gd = self.base_ref.to_script_passive();
508510

509-
ScriptBaseMut::new(self.base_ref.to_script_gd(), guard)
511+
ScriptBaseMut::new(passive_gd, guard)
510512
}
511513
}
512514

godot-core/src/obj/traits.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -422,9 +422,8 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
422422
///
423423
/// For this, use [`base_mut()`](WithBaseField::base_mut()) instead.
424424
fn base(&self) -> BaseRef<'_, Self> {
425-
let gd = self.base_field().__constructed_gd();
426-
427-
BaseRef::new(gd, self)
425+
let passive_gd = self.base_field().constructed_passive();
426+
BaseRef::new(passive_gd, self)
428427
}
429428

430429
/// Returns a mutable reference suitable for calling engine methods on this object.
@@ -493,9 +492,12 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
493492
/// ```
494493
#[allow(clippy::let_unit_value)]
495494
fn base_mut(&mut self) -> BaseMut<'_, Self> {
496-
let base_gd = self.base_field().__constructed_gd();
495+
// We need to construct this first, as the mut-borrow below will block all other access.
496+
// SAFETY: lifetime is re-established at the bottom BaseMut construction, since return type of this fn has lifetime bound to instance.
497+
let passive_gd = unsafe { self.base_field().constructed_passive_unbounded() };
497498

498499
let gd = self.to_gd();
500+
499501
// SAFETY:
500502
// - We have a `Gd<Self>` so, provided that `storage_unbounded` succeeds, the associated instance
501503
// storage has been created.
@@ -509,12 +511,13 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
509511
let storage = unsafe {
510512
gd.raw
511513
.storage_unbounded()
512-
.expect("we have a `Gd<Self>` so the raw should not be null")
514+
.expect("we have Gd<Self>; its RawGd should not be null")
513515
};
514516

515517
let guard = storage.get_inaccessible(self);
516518

517-
BaseMut::new(base_gd, guard)
519+
// Narrows lifetime again from 'static to 'self.
520+
BaseMut::new(passive_gd, guard)
518521
}
519522
}
520523

itest/rust/src/object_tests/base_test.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,36 @@ fn base_swapping() {
171171
two.free();
172172
}
173173

174+
#[itest]
175+
fn base_refcounted_weak_reference() {
176+
// Not new_gd(), to not interfere with to_init_gd() ref-count handling.
177+
let obj = RefcBased::create_one();
178+
179+
let initial_refcount = obj.get_reference_count();
180+
assert_eq!(initial_refcount, 1);
181+
182+
{
183+
let bind_guard = obj.bind();
184+
let base_guard = bind_guard.base();
185+
186+
let intermediate_refcount = obj.get_reference_count();
187+
assert_eq!(
188+
intermediate_refcount, 1,
189+
"base() should not increment refcount"
190+
);
191+
192+
// Call an API to ensure Base is functional.
193+
let class_name = base_guard.get_class();
194+
assert_eq!(class_name, "RefcBased".into());
195+
}
196+
197+
let final_refcount = obj.get_reference_count();
198+
assert_eq!(
199+
final_refcount, 1,
200+
"refcount should remain unchanged after dropping base guard"
201+
);
202+
}
203+
174204
fn create_object_with_extracted_base() -> (Gd<Baseless>, Base<Node2D>) {
175205
let mut extracted_base = None;
176206
let obj = Baseless::smuggle_out(&mut extracted_base);
@@ -267,6 +297,11 @@ impl IRefCounted for RefcBased {
267297
// Only needed in base_init_test.rs.
268298
#[godot_api(no_typed_signals)]
269299
impl RefcBased {
300+
/// No `to_init_gd()` call, so the reference count is 1 after initialization.
301+
pub fn create_one() -> Gd<Self> {
302+
Gd::from_init_fn(|base| Self { base })
303+
}
304+
270305
/// Used in `base_init_test.rs` to test that a base pointer can be extracted during initialization.
271306
pub fn split_simple() -> (Gd<Self>, Gd<RefCounted>) {
272307
let mut moved_out = None;

0 commit comments

Comments
 (0)