88#[ cfg( debug_assertions) ]
99use std:: cell:: Cell ;
1010use std:: cell:: RefCell ;
11+ use std:: collections:: hash_map:: Entry ;
12+ use std:: collections:: HashMap ;
1113use std:: fmt:: { Debug , Display , Formatter , Result as FmtResult } ;
1214use std:: mem:: ManuallyDrop ;
1315use std:: rc:: Rc ;
1416
1517use crate :: builtin:: { Callable , Variant } ;
16- use crate :: obj:: { bounds, Gd , GodotClass } ;
18+ use crate :: obj:: { bounds, Gd , GodotClass , InstanceId } ;
1719use crate :: { classes, sys} ;
1820
21+ thread_local ! {
22+ /// Extra strong references for each instance ID, needed for [`Base::to_init_gd()`].
23+ ///
24+ /// At the moment, all Godot objects must be accessed from the main thread, because their deferred destruction (`Drop`) runs on the
25+ /// main thread, too. This may be relaxed in the future, and a `sys::Global` could be used instead of a `thread_local!`.
26+ static PENDING_STRONG_REFS : RefCell <HashMap <InstanceId , Gd <classes:: RefCounted >>> = RefCell :: new( HashMap :: new( ) ) ;
27+ }
28+
1929/// Represents the initialization state of a `Base<T>` object.
2030#[ cfg( debug_assertions) ]
2131#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
@@ -69,9 +79,6 @@ pub struct Base<T: GodotClass> {
6979 // 2.
7080 obj : ManuallyDrop < Gd < T > > ,
7181
72- /// Additional strong ref, needed to prevent destruction if [`Self::to_init_gd()`] is called on ref-counted objects.
73- extra_strong_ref : Rc < RefCell < Option < Gd < T > > > > ,
74-
7582 /// Tracks the initialization state of this `Base<T>` in Debug mode.
7683 ///
7784 /// Rc allows to "copy-construct" the base from an existing one, while still affecting the user-instance through the original `Base<T>`.
@@ -94,7 +101,6 @@ impl<T: GodotClass> Base<T> {
94101
95102 Self {
96103 obj : ManuallyDrop :: new ( obj) ,
97- extra_strong_ref : Rc :: clone ( & base. extra_strong_ref ) , // Before user init(), no handing out of Gd pointers occurs.
98104 #[ cfg( debug_assertions) ]
99105 init_state : Rc :: clone ( & base. init_state ) ,
100106 }
@@ -139,7 +145,6 @@ impl<T: GodotClass> Base<T> {
139145 fn from_obj ( obj : Gd < T > , init_state : InitState ) -> Self {
140146 Self {
141147 obj : ManuallyDrop :: new ( obj) ,
142- extra_strong_ref : Rc :: new ( RefCell :: new ( None ) ) ,
143148 init_state : Rc :: new ( Cell :: new ( init_state) ) ,
144149 }
145150 }
@@ -148,7 +153,6 @@ impl<T: GodotClass> Base<T> {
148153 fn from_obj ( obj : Gd < T > ) -> Self {
149154 Self {
150155 obj : ManuallyDrop :: new ( obj) ,
151- extra_strong_ref : Rc :: new ( RefCell :: new ( None ) ) ,
152156 }
153157 }
154158
@@ -202,29 +206,49 @@ impl<T: GodotClass> Base<T> {
202206
203207 // First time handing out a Gd<T>, we need to take measures to temporarily upgrade the Base's weak pointer to a strong one.
204208 // During the initialization phase (derived object being constructed), increment refcount by 1.
205- if self . extra_strong_ref . borrow ( ) . is_none ( ) {
206- let strong_ref = unsafe { Gd :: from_obj_sys ( self . obj . obj_sys ( ) ) } ;
207- * self . extra_strong_ref . borrow_mut ( ) = Some ( strong_ref) ;
208- }
209+ let instance_id = self . obj . instance_id ( ) ;
210+ PENDING_STRONG_REFS . with ( |refs| {
211+ let mut pending_refs = refs. borrow_mut ( ) ;
212+ if let Entry :: Vacant ( e) = pending_refs. entry ( instance_id) {
213+ let strong_ref: Gd < T > = unsafe { Gd :: from_obj_sys ( self . obj . obj_sys ( ) ) } ;
214+
215+ // We know that T: Inherits<RefCounted> due to check above, but don't it as a static bound for Gd::upcast().
216+ // Thus fall back to low-level FFI cast on RawGd.
217+ let strong_ref_raw = strong_ref. raw ;
218+ let raw = strong_ref_raw
219+ . ffi_cast :: < classes:: RefCounted > ( )
220+ . expect ( "Base must be RefCounted" )
221+ . into_dest ( strong_ref_raw) ;
222+
223+ e. insert ( Gd { raw } ) ;
224+ }
225+ } ) ;
209226
210- // Can't use Gd::apply_deferred(), as that implicitly borrows &mut self, causing a "destroyed while bind was active" panic.
211227 let name = format ! ( "Base<{}> deferred unref" , T :: class_name( ) ) ;
212- let rc = Rc :: clone ( & self . extra_strong_ref ) ;
213228 let callable = Callable :: from_once_fn ( & name, move |_args| {
214- Self :: drop_strong_ref ( rc ) ;
229+ Self :: drop_strong_ref ( instance_id ) ;
215230 Ok ( Variant :: nil ( ) )
216231 } ) ;
232+
233+ // Use Callable::call_deferred() instead of Gd::apply_deferred(). The latter implicitly borrows &mut self,
234+ // causing a "destroyed while bind was active" panic.
217235 callable. call_deferred ( & [ ] ) ;
218236
219237 ( * self . obj ) . clone ( )
220238 }
221239
222240 /// Drops any extra strong references, possibly causing object destruction.
223- fn drop_strong_ref ( extra_strong_ref : Rc < RefCell < Option < Gd < T > > > > ) {
224- let mut r = extra_strong_ref. borrow_mut ( ) ;
225- assert ! ( r. is_some( ) ) ;
241+ fn drop_strong_ref ( instance_id : InstanceId ) {
242+ PENDING_STRONG_REFS . with ( |refs| {
243+ let mut pending_refs = refs. borrow_mut ( ) ;
244+ let strong_ref = pending_refs. remove ( & instance_id) ;
245+ assert ! (
246+ strong_ref. is_some( ) ,
247+ "Base unexpectedly had its strong ref rug-pulled"
248+ ) ;
226249
227- * r = None ; // Triggers RawGd::drop() -> dec-ref -> possibly object destruction.
250+ // Triggers RawGd::drop() -> dec-ref -> possibly object destruction.
251+ } ) ;
228252 }
229253
230254 /// Finalizes the initialization of this `Base<T>` and returns whether
0 commit comments