@@ -18,7 +18,7 @@ use crate::meta::{
18
18
use crate :: obj:: bounds:: { Declarer , DynMemory as _} ;
19
19
use crate :: obj:: casts:: CastSuccess ;
20
20
use crate :: obj:: rtti:: ObjectRtti ;
21
- use crate :: obj:: { bounds, Bounds , GdDerefTarget , GdMut , GdRef , GodotClass , InstanceId } ;
21
+ use crate :: obj:: { bounds, Bounds , Gd , GdDerefTarget , GdMut , GdRef , GodotClass , InstanceId } ;
22
22
use crate :: storage:: { InstanceCache , InstanceStorage , Storage } ;
23
23
use crate :: { classes, out} ;
24
24
@@ -95,8 +95,7 @@ impl<T: GodotClass> RawGd<T> {
95
95
96
96
/// Returns `true` if the object is null.
97
97
///
98
- /// This does not check if the object is dead, for that use
99
- /// [`instance_id_or_none()`](Self::instance_id_or_none).
98
+ /// This does not check if the object is dead. For that, use [`is_instance_valid()`](Self::is_instance_valid).
100
99
pub ( crate ) fn is_null ( & self ) -> bool {
101
100
self . obj . is_null ( ) || self . cached_rtti . is_none ( )
102
101
}
@@ -148,10 +147,15 @@ impl<T: GodotClass> RawGd<T> {
148
147
///
149
148
/// 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
149
/// a strong `RawGd<T>` for it, to maintain the balance.
150
+ ///
151
+ /// This function is unreliable when invoked _during_ destruction (e.g. C++ `~RefCounted()` destructor). This can occur when debug-logging
152
+ /// instances during cleanups. `Object::object_cast_to()` is a virtual function, but virtual dispatch during destructor doesn't work in C++.
151
153
pub ( super ) fn ffi_cast < U > ( & self ) -> Result < CastSuccess < T , U > , ( ) >
152
154
where
153
155
U : GodotClass ,
154
156
{
157
+ //eprintln!("ffi_cast: {} (dyn {}) -> {}", T::class_name(), self.as_non_null().dynamic_class_string(), U::class_name());
158
+
155
159
// `self` may be null when we convert a null-variant into a `Option<Gd<T>>`, since we use `ffi_cast`
156
160
// in the `ffi_from_variant` conversion function to ensure type-correctness. So the chain would be as follows:
157
161
// - Variant::nil()
@@ -184,24 +188,57 @@ impl<T: GodotClass> RawGd<T> {
184
188
Ok ( CastSuccess :: from_weak ( weak) )
185
189
}
186
190
191
+ /// Executes a function, assuming that `self` inherits `RefCounted`.
192
+ ///
193
+ /// This function is unreliable when invoked _during_ destruction (e.g. C++ `~RefCounted()` destructor). This can occur when debug-logging
194
+ /// instances during cleanups. `Object::object_cast_to()` is a virtual function, but virtual dispatch during destructor doesn't work in C++.
195
+ ///
196
+ /// # Panics
197
+ /// If `self` does not inherit `RefCounted` or is null.
187
198
pub ( crate ) fn with_ref_counted < R > ( & self , apply : impl Fn ( & mut classes:: RefCounted ) -> R ) -> R {
188
199
// Note: this previously called Declarer::scoped_mut() - however, no need to go through bind() for changes in base RefCounted.
189
200
// Any accesses to user objects (e.g. destruction if refc=0) would bind anyway.
201
+ //
202
+ // Might change implementation as follows -- but last time caused UB; investigate.
203
+ // pub(crate) unsafe fn as_ref_counted_unchecked(&mut self) -> &mut classes::RefCounted {
204
+ // self.as_target_mut()
205
+ // }
206
+
207
+ let mut ref_counted = match self . ffi_cast :: < classes:: RefCounted > ( ) {
208
+ Ok ( cast_success) => cast_success,
209
+ Err ( ( ) ) if self . is_null ( ) => {
210
+ panic ! ( "RawGd::with_ref_counted(): expected to inherit RefCounted, encountered null pointer" ) ;
211
+ }
212
+ Err ( ( ) ) => {
213
+ // SAFETY: this branch implies non-null.
214
+ let gd_ref = unsafe { self . as_non_null ( ) } ;
215
+ let class = gd_ref. dynamic_class_string ( ) ;
216
+
217
+ // One way how this may panic is when invoked during destruction of a RefCounted object. The C++ `Object::object_cast_to()`
218
+ // function is virtual but cannot be dynamically dispatched in a C++ destructor.
219
+ panic ! ( "RawGd::with_ref_counted(): expected to inherit RefCounted, but encountered {class}" ) ;
220
+ }
221
+ } ;
190
222
191
- let mut cast_obj = self
192
- . ffi_cast :: < classes:: RefCounted > ( )
193
- . expect ( "object expected to inherit RefCounted" ) ;
223
+ let return_val = apply ( ref_counted. as_dest_mut ( ) . as_target_mut ( ) ) ;
194
224
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 ( ) )
225
+ // CastSuccess is forgotten when dropped, so no ownership transfer.
226
+ return_val
199
227
}
200
228
201
- // TODO replace the above with this -- last time caused UB; investigate.
202
- // pub(crate) unsafe fn as_ref_counted_unchecked(&mut self) -> &mut classes::RefCounted {
203
- // self.as_target_mut()
204
- // }
229
+ /// Enables outer `Gd` APIs or bypasses additional null checks, in cases where `RawGd` is guaranteed non-null.
230
+ ///
231
+ /// # Safety
232
+ /// `self` must not be null.
233
+ pub ( crate ) unsafe fn as_non_null ( & self ) -> & Gd < T > {
234
+ debug_assert ! (
235
+ !self . is_null( ) ,
236
+ "RawGd::as_non_null() called on null pointer; this is UB"
237
+ ) ;
238
+
239
+ // SAFETY: layout of Gd<T> is currently equivalent to RawGd<T>.
240
+ unsafe { std:: mem:: transmute :: < & RawGd < T > , & Gd < T > > ( self ) }
241
+ }
205
242
206
243
pub ( crate ) fn as_object_ref ( & self ) -> & classes:: Object {
207
244
// SAFETY: Object is always a valid upcast target.
@@ -654,7 +691,7 @@ impl<T: GodotClass> Drop for RawGd<T> {
654
691
fn drop ( & mut self ) {
655
692
// No-op for manually managed objects
656
693
657
- out ! ( "RawGd::drop <{}>" , std :: any :: type_name :: < T > ( ) ) ;
694
+ out ! ( "RawGd::drop: {self:?}" ) ;
658
695
659
696
// SAFETY: This `Gd` won't be dropped again after this.
660
697
// If destruction is triggered by Godot, Storage already knows about it, no need to notify it
@@ -669,7 +706,7 @@ impl<T: GodotClass> Drop for RawGd<T> {
669
706
670
707
impl < T : GodotClass > Clone for RawGd < T > {
671
708
fn clone ( & self ) -> Self {
672
- out ! ( "RawGd::clone" ) ;
709
+ out ! ( "RawGd::clone: {self:?} (before clone) " ) ;
673
710
674
711
if self . is_null ( ) {
675
712
Self :: null ( )
@@ -689,12 +726,7 @@ impl<T: GodotClass> Clone for RawGd<T> {
689
726
690
727
impl < T : GodotClass > fmt:: Debug for RawGd < T > {
691
728
fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
692
- if self . is_null ( ) {
693
- return write ! ( f, "{} {{ null obj }}" , std:: any:: type_name:: <T >( ) ) ;
694
- }
695
-
696
- let gd = super :: Gd :: from_ffi ( self . clone ( ) ) ;
697
- write ! ( f, "{gd:?}" )
729
+ classes:: debug_string_nullable ( self , f, "RawGd" )
698
730
}
699
731
}
700
732
0 commit comments