Skip to content

Commit f52ea5e

Browse files
committed
Add WeakGd
1 parent 8526478 commit f52ea5e

File tree

8 files changed

+192
-22
lines changed

8 files changed

+192
-22
lines changed

godot-core/src/obj/base.rs

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

1717
use crate::builtin::{Callable, Variant};
18+
use crate::obj::weak_gd::WeakGd;
1819
use crate::obj::{bounds, Gd, GodotClass, InstanceId};
1920
use crate::{classes, sys};
2021

@@ -166,6 +167,10 @@ impl<T: GodotClass> Base<T> {
166167
(*self.obj).clone()
167168
}
168169

170+
pub fn to_weak_gd(&self) -> WeakGd<T> {
171+
WeakGd::from_base(self)
172+
}
173+
169174
/// Returns a [`Gd`] referencing the base object, for exclusive use during object initialization.
170175
///
171176
/// Can be used during an initialization function [`I*::init()`][crate::classes::IObject::init] or [`Gd::from_init_fn()`].
@@ -328,6 +333,10 @@ impl<T: GodotClass> Base<T> {
328333

329334
(*self.obj).clone()
330335
}
336+
337+
pub(crate) fn is_instance_valid(&self) -> bool {
338+
self.obj.is_instance_valid()
339+
}
331340
}
332341

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

godot-core/src/obj/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod on_editor;
2222
mod on_ready;
2323
mod raw_gd;
2424
mod traits;
25+
mod weak_gd;
2526

2627
pub(crate) mod rtti;
2728

@@ -35,6 +36,7 @@ pub use on_editor::*;
3536
pub use on_ready::*;
3637
pub use raw_gd::*;
3738
pub use traits::*;
39+
pub use weak_gd::*;
3840

3941
pub mod bounds;
4042
pub mod script;

godot-core/src/obj/raw_gd.rs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -358,27 +358,6 @@ impl<T: GodotClass> RawGd<T> {
358358
// Validation object identity.
359359
self.check_rtti("upcast_ref");
360360
debug_assert!(!self.is_null(), "cannot upcast null object refs");
361-
362-
// In Debug builds, go the long path via Godot FFI to verify the results are the same.
363-
#[cfg(debug_assertions)]
364-
{
365-
// SAFETY: we forget the object below and do not leave the function before.
366-
let ffi_dest = self.ffi_cast::<Base>().expect("failed FFI upcast");
367-
368-
// The ID check is not that expressive; we should do a complete comparison of the ObjectRtti, but currently the dynamic types can
369-
// be different (see comment in ObjectRtti struct). This at least checks that the transmuted object is not complete garbage.
370-
// We get direct_id from Self and not Base because the latter has no API with current bounds; but this equivalence is tested in Deref.
371-
let direct_id = self.instance_id_unchecked().expect("direct_id null");
372-
let ffi_id = ffi_dest
373-
.as_dest_ref()
374-
.instance_id_unchecked()
375-
.expect("ffi_id null");
376-
377-
assert_eq!(
378-
direct_id, ffi_id,
379-
"upcast_ref: direct and FFI IDs differ. This is a bug, please report to godot-rust maintainers."
380-
);
381-
}
382361
}
383362

384363
/// Verify that the object is non-null and alive. In Debug mode, additionally verify that it is of type `T` or derived.

godot-core/src/obj/traits.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::builtin::GString;
1212
use crate::init::InitLevel;
1313
use crate::meta::inspect::EnumConstant;
1414
use crate::meta::ClassName;
15-
use crate::obj::{bounds, Base, BaseMut, BaseRef, Bounds, Gd};
15+
use crate::obj::{bounds, Base, BaseMut, BaseRef, Bounds, Gd, WeakGd};
1616
#[cfg(since_api = "4.2")]
1717
use crate::registry::signal::SignalObject;
1818
use crate::storage::Storage;
@@ -364,6 +364,8 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
364364
#[doc(hidden)]
365365
fn base_field(&self) -> &Base<Self::Base>;
366366

367+
fn to_weak_gd(&self) -> WeakGd<Self>;
368+
367369
/// Returns a shared reference suitable for calling engine methods on this object.
368370
///
369371
/// Holding a shared guard prevents other code paths from obtaining a _mutable_ reference to `self`, as such it is recommended to drop the

godot-core/src/obj/weak_gd.rs

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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::fmt::{Debug, Formatter, Result as FmtResult};
9+
use std::mem::ManuallyDrop;
10+
use std::ops::{Deref, DerefMut};
11+
12+
use godot_ffi as sys;
13+
14+
use crate::classes;
15+
use crate::obj::{bounds, Base, Bounds, Gd, GdDerefTarget, GodotClass, RawGd};
16+
17+
/// Weak pointer to objects owned by the Godot engine.
18+
/// `WeakGd<T>` doesn't guarantee the validation of the object and can hold a dead object.
19+
/// For `RefCounted`, it doesn't affect the reference count, which means it doesn't decrease reference count when dropped and doesn't prevent the `RefCounted` from being released.
20+
/// Can be used during initialization [`I*::init()`][crate::classes::IObject::init] or [`Gd::from_init_fn()`], and deconstruction [Drop].
21+
///
22+
/// # Panics
23+
/// If the weak pointer is invalid when dereferencing to call a Godot method.
24+
pub struct WeakGd<T: GodotClass> {
25+
raw: Option<ManuallyDrop<RawGd<T>>>,
26+
}
27+
28+
impl<T: GodotClass> WeakGd<T> {
29+
#[doc(hidden)]
30+
pub unsafe fn from_obj_sys_weak(val: sys::GDExtensionObjectPtr) -> Self {
31+
Self {
32+
raw: if !val.is_null() {
33+
Some(ManuallyDrop::new(RawGd::from_obj_sys_weak(val)))
34+
} else {
35+
None
36+
},
37+
}
38+
}
39+
40+
pub(crate) fn from_raw_gd(val: &RawGd<T>) -> Self {
41+
unsafe {
42+
Self::from_obj_sys_weak(if val.is_instance_valid() {
43+
val.obj_sys()
44+
} else {
45+
std::ptr::null_mut()
46+
})
47+
}
48+
}
49+
50+
/// Create a weak pointer from a [Gd].
51+
pub fn from_gd(val: &Gd<T>) -> Self {
52+
Self::from_raw_gd(&val.raw)
53+
}
54+
55+
/// Create a weak pointer from a [Base].
56+
pub fn from_base(val: &Base<T>) -> Self {
57+
Self {
58+
raw: if val.is_instance_valid() {
59+
unsafe { Some(ManuallyDrop::new(RawGd::from_obj_sys_weak(val.obj_sys()))) }
60+
} else {
61+
None
62+
},
63+
}
64+
}
65+
66+
/// Checks if this weak pointer points to a live object.
67+
pub fn is_instance_valid(&self) -> bool {
68+
self.raw
69+
.as_ref()
70+
.map(|v| v.is_instance_valid())
71+
.unwrap_or(false)
72+
}
73+
}
74+
75+
impl<T: GodotClass> Deref for WeakGd<T>
76+
where
77+
GdDerefTarget<T>: Bounds<Declarer = bounds::DeclEngine>,
78+
{
79+
type Target = GdDerefTarget<T>;
80+
81+
fn deref(&self) -> &Self::Target {
82+
self.raw
83+
.as_ref()
84+
.expect("WeakGd points to an invalid instance")
85+
.as_target()
86+
}
87+
}
88+
89+
impl<T: GodotClass> DerefMut for WeakGd<T>
90+
where
91+
GdDerefTarget<T>: Bounds<Declarer = bounds::DeclEngine>,
92+
{
93+
fn deref_mut(&mut self) -> &mut Self::Target {
94+
self.raw
95+
.as_mut()
96+
.expect("WeakGd points to an invalid instance")
97+
.as_target_mut()
98+
}
99+
}
100+
101+
impl<T: GodotClass> Clone for WeakGd<T> {
102+
fn clone(&self) -> Self {
103+
if let Some(raw) = self.raw.as_ref() {
104+
Self::from_raw_gd(raw)
105+
} else {
106+
Self { raw: None }
107+
}
108+
}
109+
}
110+
111+
impl<T: GodotClass> Debug for WeakGd<T> {
112+
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
113+
if self.is_instance_valid() {
114+
classes::debug_string_nullable(self.raw.as_ref().unwrap(), f, "WeakGd")
115+
} else {
116+
write!(f, "WeakGd {{ null }}")
117+
}
118+
}
119+
}

godot-macros/src/class/derive_godot_class.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ pub fn derive_godot_class(item: venial::Item) -> ParseResult<TokenStream> {
9797
fn base_field(&self) -> &::godot::obj::Base<<#class_name as ::godot::obj::GodotClass>::Base> {
9898
&self.#name
9999
}
100+
101+
fn to_weak_gd(&self) -> ::godot::obj::WeakGd<#class_name> {
102+
let base = <#class_name as ::godot::obj::WithBaseField>::base_field(self);
103+
unsafe {
104+
// Force casting `Base` to `T`.
105+
::godot::obj::WeakGd::<#class_name>::from_obj_sys_weak(base.obj_sys())
106+
}
107+
}
100108
}
101109
}
102110
} else {

itest/rust/src/object_tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ mod base_init_test;
3535
mod validate_property_test;
3636
mod virtual_methods_niche_test;
3737
mod virtual_methods_test;
38+
mod weak_gd_test;
3839

3940
// Need to test this in the init level method.
4041
pub use init_level_test::initialize_init_level_test;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 godot::obj::WeakGd;
9+
use godot::prelude::*;
10+
11+
use crate::framework::{expect_panic, itest};
12+
13+
#[derive(GodotClass, Debug)]
14+
#[class(base=RefCounted)]
15+
struct RefcBasedDrop {
16+
pub base: Base<RefCounted>,
17+
}
18+
19+
#[godot_api]
20+
impl IRefCounted for RefcBasedDrop {
21+
fn init(base: Base<RefCounted>) -> Self {
22+
let mut obj = base.to_weak_gd();
23+
obj.set_meta("meta", &"inited".to_variant());
24+
assert_eq!(obj.get_reference_count(), 1);
25+
Self { base }
26+
}
27+
}
28+
29+
impl Drop for RefcBasedDrop {
30+
fn drop(&mut self) {
31+
let obj = self.to_weak_gd();
32+
assert_eq!(obj.get_meta("meta"), "inited".to_variant());
33+
34+
// FIXME: Accessing godot methods except Object is UB.
35+
// assert_eq!(obj.get_reference_count(), 0);
36+
}
37+
}
38+
39+
#[itest]
40+
fn weak_gd_init_drop_refcounted() {
41+
let obj = RefcBasedDrop::new_gd();
42+
let weak = WeakGd::from_gd(&obj);
43+
drop(obj);
44+
expect_panic(
45+
"WeakGd calling Godot method with dead object should panic",
46+
|| {
47+
weak.get_reference_count();
48+
},
49+
);
50+
}

0 commit comments

Comments
 (0)