Skip to content

Commit 8268cff

Browse files
authored
Merge pull request #1226 from godot-rust/qol/ffi-cast
`RawGd` casting is now simpler and safer
2 parents e8a4529 + fdfedcf commit 8268cff

File tree

3 files changed

+115
-42
lines changed

3 files changed

+115
-42
lines changed

godot-core/src/obj/casts.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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 crate::obj::{GodotClass, RawGd};
9+
use godot_ffi::GodotNullableFfi;
10+
use std::marker::PhantomData;
11+
use std::mem::ManuallyDrop;
12+
13+
/// Represents a successful low-level cast from `T` to `U`.
14+
///
15+
/// This exists to provide a safe API for casting, without the need for clone (and thus ref-count increments).
16+
///
17+
/// It achieves this by keeping the destination (cast result) as a weak pointer. If dropped, nothing happens.
18+
/// To extract the destination, the caller must submit a strong pointer of the source type `T` in exchange.
19+
///
20+
/// See [`RawGd::ffi_cast()`].
21+
pub(crate) struct CastSuccess<T: GodotClass, U: GodotClass> {
22+
_phantom: PhantomData<*mut T>,
23+
24+
/// Weak pointer. Drop does not decrement ref-count.
25+
dest: ManuallyDrop<RawGd<U>>,
26+
}
27+
28+
impl<T: GodotClass, U: GodotClass> CastSuccess<T, U> {
29+
/// Create from weak pointer.
30+
pub(crate) fn from_weak(weak: RawGd<U>) -> CastSuccess<T, U>
31+
where
32+
U: GodotClass,
33+
{
34+
Self {
35+
_phantom: PhantomData,
36+
dest: ManuallyDrop::new(weak),
37+
}
38+
}
39+
40+
/// Successful cast from null to null.
41+
pub fn null() -> Self {
42+
Self {
43+
_phantom: PhantomData,
44+
dest: ManuallyDrop::new(RawGd::null()),
45+
}
46+
}
47+
48+
/// Access shared reference to destination, without consuming object.
49+
pub fn as_dest_ref(&self) -> &RawGd<U> {
50+
self.check_validity();
51+
&self.dest
52+
}
53+
54+
/// Access exclusive reference to destination, without consuming object.
55+
pub fn as_dest_mut(&mut self) -> &mut RawGd<U> {
56+
self.check_validity();
57+
&mut self.dest
58+
}
59+
60+
/// Extracts destination object, sacrificing the source in exchange.
61+
///
62+
/// This trade is needed because the result is a weak pointer (no ref-count increment). By submitting a strong pointer in its place,
63+
/// we can retain the overall ref-count balance.
64+
pub fn into_dest(self, traded_source: RawGd<T>) -> RawGd<U> {
65+
debug_assert_eq!(
66+
traded_source.instance_id_unchecked(),
67+
self.dest.instance_id_unchecked(),
68+
"traded_source must point to the same object as the destination"
69+
);
70+
self.check_validity();
71+
72+
std::mem::forget(traded_source);
73+
ManuallyDrop::into_inner(self.dest)
74+
}
75+
76+
fn check_validity(&self) {
77+
debug_assert!(self.dest.is_null() || self.dest.is_instance_valid());
78+
}
79+
}

godot-core/src/obj/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//! * [`Gd`], a smart pointer that manages instances of Godot classes.
1313
1414
mod base;
15+
mod casts;
1516
mod dyn_gd;
1617
mod gd;
1718
mod guards;

godot-core/src/obj/raw_gd.rs

Lines changed: 35 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::meta::{
1616
CallContext, ClassName, FromGodot, GodotConvert, GodotFfiVariant, GodotType, RefArg, ToGodot,
1717
};
1818
use crate::obj::bounds::{Declarer, DynMemory as _};
19+
use crate::obj::casts::CastSuccess;
1920
use crate::obj::rtti::ObjectRtti;
2021
use crate::obj::{bounds, Bounds, GdDerefTarget, GdMut, GdRef, GodotClass, InstanceId};
2122
use crate::storage::{InstanceCache, InstanceStorage, Storage};
@@ -115,21 +116,8 @@ impl<T: GodotClass> RawGd<T> {
115116
where
116117
U: GodotClass,
117118
{
118-
if self.is_null() {
119-
// Null can be cast to anything.
120-
return true;
121-
}
122-
123-
// SAFETY: object is forgotten below.
124-
let as_obj =
125-
unsafe { self.ffi_cast::<classes::Object>() }.expect("everything inherits Object");
126-
127-
// SAFETY: Object is always a base class.
128-
let cast_is_valid = unsafe { as_obj.as_upcast_ref::<classes::Object>() }
129-
.is_class(&U::class_name().to_gstring());
130-
131-
std::mem::forget(as_obj);
132-
cast_is_valid
119+
self.is_null() // Null can be cast to anything.
120+
|| self.as_object_ref().is_class(&U::class_name().to_gstring())
133121
}
134122

135123
/// Returns `Ok(cast_obj)` on success, `Err(self)` on error
@@ -150,21 +138,17 @@ impl<T: GodotClass> RawGd<T> {
150138
// The Deref/DerefMut impls for T implement an "implicit upcast" on the object (not Gd) level and
151139
// rely on this (e.g. &Node3D -> &Node).
152140

153-
let result = unsafe { self.ffi_cast::<U>() };
154-
match result {
155-
Some(cast_obj) => {
156-
// duplicated ref, one must be wiped
157-
std::mem::forget(self);
158-
Ok(cast_obj)
159-
}
160-
None => Err(self),
141+
match self.ffi_cast::<U>() {
142+
Ok(success) => Ok(success.into_dest(self)),
143+
Err(_) => Err(self),
161144
}
162145
}
163146

164-
/// # Safety
165-
/// Does not transfer ownership and is thus unsafe. Also operates on shared ref. Either the parameter or
166-
/// the return value *must* be forgotten (since reference counts are not updated).
167-
pub(super) unsafe fn ffi_cast<U>(&self) -> Option<RawGd<U>>
147+
/// Low-level cast that allows selective use of either input or output type.
148+
///
149+
/// On success, you'll get a `CastSuccess<T, U>` instance, which holds a weak `RawGd<U>`. You can only extract that one by trading
150+
/// a strong `RawGd<T>` for it, to maintain the balance.
151+
pub(super) fn ffi_cast<U>(&self) -> Result<CastSuccess<T, U>, ()>
168152
where
169153
U: GodotClass,
170154
{
@@ -177,7 +161,7 @@ impl<T: GodotClass> RawGd<T> {
177161
if self.is_null() {
178162
// Null can be cast to anything.
179163
// Forgetting a null doesn't do anything, since dropping a null also does nothing.
180-
return Some(RawGd::null());
164+
return Ok(CastSuccess::null());
181165
}
182166

183167
// Before Godot API calls, make sure the object is alive (and in Debug mode, of the correct type).
@@ -186,23 +170,32 @@ impl<T: GodotClass> RawGd<T> {
186170
// a bug that must be solved by the user.
187171
self.check_rtti("ffi_cast");
188172

189-
let class_tag = interface_fn!(classdb_get_class_tag)(U::class_name().string_sys());
190-
let cast_object_ptr = interface_fn!(object_cast_to)(self.obj_sys(), class_tag);
173+
let cast_object_ptr = unsafe {
174+
let class_tag = interface_fn!(classdb_get_class_tag)(U::class_name().string_sys());
175+
interface_fn!(object_cast_to)(self.obj_sys(), class_tag)
176+
};
177+
178+
if cast_object_ptr.is_null() {
179+
return Err(());
180+
}
191181

192182
// Create weak object, as ownership will be moved and reference-counter stays the same.
193-
sys::ptr_then(cast_object_ptr, |ptr| RawGd::from_obj_sys_weak(ptr))
183+
let weak = unsafe { RawGd::from_obj_sys_weak(cast_object_ptr) };
184+
Ok(CastSuccess::from_weak(weak))
194185
}
195186

196187
pub(crate) fn with_ref_counted<R>(&self, apply: impl Fn(&mut classes::RefCounted) -> R) -> R {
197188
// Note: this previously called Declarer::scoped_mut() - however, no need to go through bind() for changes in base RefCounted.
198189
// Any accesses to user objects (e.g. destruction if refc=0) would bind anyway.
199190

200-
let tmp = unsafe { self.ffi_cast::<classes::RefCounted>() };
201-
let mut tmp = tmp.expect("object expected to inherit RefCounted");
202-
let return_val = apply(tmp.as_target_mut());
191+
let mut cast_obj = self
192+
.ffi_cast::<classes::RefCounted>()
193+
.expect("object expected to inherit RefCounted");
203194

204-
std::mem::forget(tmp); // no ownership transfer
205-
return_val
195+
// Using as_dest_mut() ensures that there is no refcount increment happening, i.e. any apply() function happens on *current* object.
196+
// Apart from performance considerations, this is relevant when examining RefCounted::get_reference_count() -- otherwise we have an
197+
// Observer effect, where reading the RefCounted object changes its reference count -- e.g. in Debug impl.
198+
apply(cast_obj.as_dest_mut().as_target_mut())
206199
}
207200

208201
// TODO replace the above with this -- last time caused UB; investigate.
@@ -314,21 +307,21 @@ impl<T: GodotClass> RawGd<T> {
314307
#[cfg(debug_assertions)]
315308
{
316309
// SAFETY: we forget the object below and do not leave the function before.
317-
let ffi_ref: RawGd<Base> =
318-
unsafe { self.ffi_cast::<Base>().expect("failed FFI upcast") };
310+
let ffi_dest = self.ffi_cast::<Base>().expect("failed FFI upcast");
319311

320312
// The ID check is not that expressive; we should do a complete comparison of the ObjectRtti, but currently the dynamic types can
321313
// be different (see comment in ObjectRtti struct). This at least checks that the transmuted object is not complete garbage.
322314
// 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.
323315
let direct_id = self.instance_id_unchecked().expect("direct_id null");
324-
let ffi_id = ffi_ref.instance_id_unchecked().expect("ffi_id null");
316+
let ffi_id = ffi_dest
317+
.as_dest_ref()
318+
.instance_id_unchecked()
319+
.expect("ffi_id null");
325320

326321
assert_eq!(
327322
direct_id, ffi_id,
328-
"upcast_ref: direct and FFI IDs differ. This is a bug, please report to gdext maintainers."
323+
"upcast_ref: direct and FFI IDs differ. This is a bug, please report to godot-rust maintainers."
329324
);
330-
331-
std::mem::forget(ffi_ref);
332325
}
333326
}
334327

0 commit comments

Comments
 (0)