8
8
#[ cfg( debug_assertions) ]
9
9
use std:: cell:: Cell ;
10
10
use std:: cell:: RefCell ;
11
+ use std:: collections:: hash_map:: Entry ;
12
+ use std:: collections:: HashMap ;
11
13
use std:: fmt:: { Debug , Display , Formatter , Result as FmtResult } ;
12
14
use std:: mem:: ManuallyDrop ;
13
15
use std:: rc:: Rc ;
14
16
15
17
use crate :: builtin:: { Callable , Variant } ;
16
- use crate :: obj:: { bounds, Gd , GodotClass } ;
18
+ use crate :: obj:: { bounds, Gd , GodotClass , InstanceId } ;
17
19
use crate :: { classes, sys} ;
18
20
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
+
19
29
/// Represents the initialization state of a `Base<T>` object.
20
30
#[ cfg( debug_assertions) ]
21
31
#[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
@@ -69,9 +79,6 @@ pub struct Base<T: GodotClass> {
69
79
// 2.
70
80
obj : ManuallyDrop < Gd < T > > ,
71
81
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
-
75
82
/// Tracks the initialization state of this `Base<T>` in Debug mode.
76
83
///
77
84
/// 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> {
94
101
95
102
Self {
96
103
obj : ManuallyDrop :: new ( obj) ,
97
- extra_strong_ref : Rc :: clone ( & base. extra_strong_ref ) , // Before user init(), no handing out of Gd pointers occurs.
98
104
#[ cfg( debug_assertions) ]
99
105
init_state : Rc :: clone ( & base. init_state ) ,
100
106
}
@@ -139,7 +145,6 @@ impl<T: GodotClass> Base<T> {
139
145
fn from_obj ( obj : Gd < T > , init_state : InitState ) -> Self {
140
146
Self {
141
147
obj : ManuallyDrop :: new ( obj) ,
142
- extra_strong_ref : Rc :: new ( RefCell :: new ( None ) ) ,
143
148
init_state : Rc :: new ( Cell :: new ( init_state) ) ,
144
149
}
145
150
}
@@ -148,7 +153,6 @@ impl<T: GodotClass> Base<T> {
148
153
fn from_obj ( obj : Gd < T > ) -> Self {
149
154
Self {
150
155
obj : ManuallyDrop :: new ( obj) ,
151
- extra_strong_ref : Rc :: new ( RefCell :: new ( None ) ) ,
152
156
}
153
157
}
154
158
@@ -202,29 +206,49 @@ impl<T: GodotClass> Base<T> {
202
206
203
207
// First time handing out a Gd<T>, we need to take measures to temporarily upgrade the Base's weak pointer to a strong one.
204
208
// 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
+ } ) ;
209
226
210
- // Can't use Gd::apply_deferred(), as that implicitly borrows &mut self, causing a "destroyed while bind was active" panic.
211
227
let name = format ! ( "Base<{}> deferred unref" , T :: class_name( ) ) ;
212
- let rc = Rc :: clone ( & self . extra_strong_ref ) ;
213
228
let callable = Callable :: from_once_fn ( & name, move |_args| {
214
- Self :: drop_strong_ref ( rc ) ;
229
+ Self :: drop_strong_ref ( instance_id ) ;
215
230
Ok ( Variant :: nil ( ) )
216
231
} ) ;
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.
217
235
callable. call_deferred ( & [ ] ) ;
218
236
219
237
( * self . obj ) . clone ( )
220
238
}
221
239
222
240
/// 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
+ ) ;
226
249
227
- * r = None ; // Triggers RawGd::drop() -> dec-ref -> possibly object destruction.
250
+ // Triggers RawGd::drop() -> dec-ref -> possibly object destruction.
251
+ } ) ;
228
252
}
229
253
230
254
/// Finalizes the initialization of this `Base<T>` and returns whether
0 commit comments