@@ -16,6 +16,7 @@ use crate::meta::{
16
16
CallContext , ClassName , FromGodot , GodotConvert , GodotFfiVariant , GodotType , RefArg , ToGodot ,
17
17
} ;
18
18
use crate :: obj:: bounds:: { Declarer , DynMemory as _} ;
19
+ use crate :: obj:: casts:: CastSuccess ;
19
20
use crate :: obj:: rtti:: ObjectRtti ;
20
21
use crate :: obj:: { bounds, Bounds , GdDerefTarget , GdMut , GdRef , GodotClass , InstanceId } ;
21
22
use crate :: storage:: { InstanceCache , InstanceStorage , Storage } ;
@@ -137,21 +138,17 @@ impl<T: GodotClass> RawGd<T> {
137
138
// The Deref/DerefMut impls for T implement an "implicit upcast" on the object (not Gd) level and
138
139
// rely on this (e.g. &Node3D -> &Node).
139
140
140
- let result = unsafe { self . ffi_cast :: < U > ( ) } ;
141
- match result {
142
- Some ( cast_obj) => {
143
- // duplicated ref, one must be wiped
144
- std:: mem:: forget ( self ) ;
145
- Ok ( cast_obj)
146
- }
147
- None => Err ( self ) ,
141
+ match self . ffi_cast :: < U > ( ) {
142
+ Ok ( success) => Ok ( success. into_dest ( self ) ) ,
143
+ Err ( _) => Err ( self ) ,
148
144
}
149
145
}
150
146
151
- /// # Safety
152
- /// Does not transfer ownership and is thus unsafe. Also operates on shared ref. Either the parameter or
153
- /// the return value *must* be forgotten (since reference counts are not updated).
154
- 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 > , ( ) >
155
152
where
156
153
U : GodotClass ,
157
154
{
@@ -164,7 +161,7 @@ impl<T: GodotClass> RawGd<T> {
164
161
if self . is_null ( ) {
165
162
// Null can be cast to anything.
166
163
// Forgetting a null doesn't do anything, since dropping a null also does nothing.
167
- return Some ( RawGd :: null ( ) ) ;
164
+ return Ok ( CastSuccess :: null ( ) ) ;
168
165
}
169
166
170
167
// Before Godot API calls, make sure the object is alive (and in Debug mode, of the correct type).
@@ -173,23 +170,32 @@ impl<T: GodotClass> RawGd<T> {
173
170
// a bug that must be solved by the user.
174
171
self . check_rtti ( "ffi_cast" ) ;
175
172
176
- let class_tag = interface_fn ! ( classdb_get_class_tag) ( U :: class_name ( ) . string_sys ( ) ) ;
177
- 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
+ }
178
181
179
182
// Create weak object, as ownership will be moved and reference-counter stays the same.
180
- 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) )
181
185
}
182
186
183
187
pub ( crate ) fn with_ref_counted < R > ( & self , apply : impl Fn ( & mut classes:: RefCounted ) -> R ) -> R {
184
188
// Note: this previously called Declarer::scoped_mut() - however, no need to go through bind() for changes in base RefCounted.
185
189
// Any accesses to user objects (e.g. destruction if refc=0) would bind anyway.
186
190
187
- let tmp = unsafe { self . ffi_cast :: < classes :: RefCounted > ( ) } ;
188
- let mut tmp = tmp . expect ( "object expected to inherit RefCounted" ) ;
189
- 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" ) ;
190
194
191
- std:: mem:: forget ( tmp) ; // no ownership transfer
192
- 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 ( ) )
193
199
}
194
200
195
201
// TODO replace the above with this -- last time caused UB; investigate.
@@ -301,21 +307,21 @@ impl<T: GodotClass> RawGd<T> {
301
307
#[ cfg( debug_assertions) ]
302
308
{
303
309
// SAFETY: we forget the object below and do not leave the function before.
304
- let ffi_ref: RawGd < Base > =
305
- unsafe { self . ffi_cast :: < Base > ( ) . expect ( "failed FFI upcast" ) } ;
310
+ let ffi_dest = self . ffi_cast :: < Base > ( ) . expect ( "failed FFI upcast" ) ;
306
311
307
312
// The ID check is not that expressive; we should do a complete comparison of the ObjectRtti, but currently the dynamic types can
308
313
// be different (see comment in ObjectRtti struct). This at least checks that the transmuted object is not complete garbage.
309
314
// 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.
310
315
let direct_id = self . instance_id_unchecked ( ) . expect ( "direct_id null" ) ;
311
- 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" ) ;
312
320
313
321
assert_eq ! (
314
322
direct_id, ffi_id,
315
- "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."
316
324
) ;
317
-
318
- std:: mem:: forget ( ffi_ref) ;
319
325
}
320
326
}
321
327
0 commit comments