Skip to content

Commit 376eeaf

Browse files
committed
Use thread_local! instead of field to store Base's strong ref
1 parent 15527d0 commit 376eeaf

File tree

1 file changed

+42
-18
lines changed

1 file changed

+42
-18
lines changed

godot-core/src/obj/base.rs

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,24 @@
88
#[cfg(debug_assertions)]
99
use std::cell::Cell;
1010
use std::cell::RefCell;
11+
use std::collections::hash_map::Entry;
12+
use std::collections::HashMap;
1113
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
1214
use std::mem::ManuallyDrop;
1315
use std::rc::Rc;
1416

1517
use crate::builtin::{Callable, Variant};
16-
use crate::obj::{bounds, Gd, GodotClass};
18+
use crate::obj::{bounds, Gd, GodotClass, InstanceId};
1719
use 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

Comments
 (0)