Skip to content

Commit e3485d2

Browse files
authored
Merge pull request #1273 from godot-rust/feature/base-during-init
Access to base pointer during initialization
2 parents e8ce511 + 3eb8e79 commit e3485d2

File tree

15 files changed

+527
-42
lines changed

15 files changed

+527
-42
lines changed

check.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Commands:
3535
3636
Options:
3737
-h, --help print this help text
38-
--double run check with double-precision
38+
--double run check with double-precision (implies `api-custom` feature)
3939
-f, --filter <arg> only run integration tests which contain any of the
4040
args (comma-separated). requires itest.
4141
-a, --api-version <ver> specify the Godot API version to use (e.g. 4.3, 4.3.1).
@@ -209,7 +209,7 @@ while [[ $# -gt 0 ]]; do
209209
extraCargoArgs+=("--features" "serde")
210210
;;
211211
--double)
212-
extraCargoArgs+=("--features" "godot/double-precision")
212+
extraCargoArgs+=("--features" "godot/double-precision,godot/api-custom")
213213
;;
214214
fmt | test | itest | clippy | klippy | doc | dok)
215215
cmds+=("$arg")

godot-core/src/obj/base.rs

Lines changed: 225 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,55 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8+
#[cfg(debug_assertions)]
9+
use std::cell::Cell;
10+
use std::cell::RefCell;
11+
use std::collections::hash_map::Entry;
12+
use std::collections::HashMap;
813
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
914
use std::mem::ManuallyDrop;
15+
use std::rc::Rc;
1016

11-
use crate::obj::{Gd, GodotClass};
17+
use crate::builtin::{Callable, Variant};
18+
use crate::obj::{bounds, Gd, GodotClass, InstanceId};
1219
use crate::{classes, sys};
1320

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+
29+
/// Represents the initialization state of a `Base<T>` object.
30+
#[cfg(debug_assertions)]
31+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
32+
enum InitState {
33+
/// Object is being constructed (inside `I*::init()` or `Gd::from_init_fn()`).
34+
ObjectConstructing,
35+
/// Object construction is complete.
36+
ObjectInitialized,
37+
/// `ScriptInstance` context - always considered initialized (bypasses lifecycle checks).
38+
Script,
39+
}
40+
41+
#[cfg(debug_assertions)]
42+
macro_rules! base_from_obj {
43+
($obj:expr, $state:expr) => {
44+
Base::from_obj($obj, $state)
45+
};
46+
}
47+
48+
#[cfg(not(debug_assertions))]
49+
macro_rules! base_from_obj {
50+
($obj:expr, $state:expr) => {
51+
Base::from_obj($obj)
52+
};
53+
}
54+
55+
// ----------------------------------------------------------------------------------------------------------------------------------------------
56+
1457
/// Restricted version of `Gd`, to hold the base instance inside a user's `GodotClass`.
1558
///
1659
/// Behaves similarly to [`Gd`][crate::obj::Gd], but is more constrained. Cannot be constructed by the user.
@@ -35,6 +78,12 @@ pub struct Base<T: GodotClass> {
3578
// 1. Gd<T> -- triggers InstanceStorage destruction
3679
// 2.
3780
obj: ManuallyDrop<Gd<T>>,
81+
82+
/// Tracks the initialization state of this `Base<T>` in Debug mode.
83+
///
84+
/// Rc allows to "copy-construct" the base from an existing one, while still affecting the user-instance through the original `Base<T>`.
85+
#[cfg(debug_assertions)]
86+
init_state: Rc<Cell<InitState>>,
3887
}
3988

4089
impl<T: GodotClass> Base<T> {
@@ -47,7 +96,14 @@ impl<T: GodotClass> Base<T> {
4796
/// If `base` is destroyed while the returned `Base<T>` is in use, that constitutes a logic error, not a safety issue.
4897
pub(crate) unsafe fn from_base(base: &Base<T>) -> Base<T> {
4998
debug_assert!(base.obj.is_instance_valid());
50-
Base::from_obj(Gd::from_obj_sys_weak(base.obj.obj_sys()))
99+
100+
let obj = Gd::from_obj_sys_weak(base.obj.obj_sys());
101+
102+
Self {
103+
obj: ManuallyDrop::new(obj),
104+
#[cfg(debug_assertions)]
105+
init_state: Rc::clone(&base.init_state),
106+
}
51107
}
52108

53109
/// Create base from existing object (used in script instances).
@@ -57,9 +113,11 @@ impl<T: GodotClass> Base<T> {
57113
/// # Safety
58114
/// `gd` must be alive at the time of invocation. If it is destroyed while the returned `Base<T>` is in use, that constitutes a logic
59115
/// error, not a safety issue.
60-
pub(crate) unsafe fn from_gd(gd: &Gd<T>) -> Self {
116+
pub(crate) unsafe fn from_script_gd(gd: &Gd<T>) -> Self {
61117
debug_assert!(gd.is_instance_valid());
62-
Base::from_obj(Gd::from_obj_sys_weak(gd.obj_sys()))
118+
119+
let obj = Gd::from_obj_sys_weak(gd.obj_sys());
120+
base_from_obj!(obj, InitState::Script)
63121
}
64122

65123
/// Create new base from raw Godot object.
@@ -72,17 +130,26 @@ impl<T: GodotClass> Base<T> {
72130
pub(crate) unsafe fn from_sys(base_ptr: sys::GDExtensionObjectPtr) -> Self {
73131
assert!(!base_ptr.is_null(), "instance base is null pointer");
74132

75-
// Initialize only as weak pointer (don't increment reference count)
133+
// Initialize only as weak pointer (don't increment reference count).
76134
let obj = Gd::from_obj_sys_weak(base_ptr);
77135

78136
// This obj does not contribute to the strong count, otherwise we create a reference cycle:
79137
// 1. RefCounted (dropped in GDScript)
80138
// 2. holds user T (via extension instance and storage)
81139
// 3. holds Base<T> RefCounted (last ref, dropped in T destructor, but T is never destroyed because this ref keeps storage alive)
82140
// Note that if late-init never happened on self, we have the same behavior (still a raw pointer instead of weak Gd)
83-
Base::from_obj(obj)
141+
base_from_obj!(obj, InitState::ObjectConstructing)
84142
}
85143

144+
#[cfg(debug_assertions)]
145+
fn from_obj(obj: Gd<T>, init_state: InitState) -> Self {
146+
Self {
147+
obj: ManuallyDrop::new(obj),
148+
init_state: Rc::new(Cell::new(init_state)),
149+
}
150+
}
151+
152+
#[cfg(not(debug_assertions))]
86153
fn from_obj(obj: Gd<T>) -> Self {
87154
Self {
88155
obj: ManuallyDrop::new(obj),
@@ -94,10 +161,132 @@ impl<T: GodotClass> Base<T> {
94161
/// Using this method to call methods on the base field of a Rust object is discouraged, instead use the
95162
/// methods from [`WithBaseField`](super::WithBaseField) when possible.
96163
#[doc(hidden)]
164+
#[deprecated = "Private API. Use `Base::to_init_gd()` or `WithBaseField::to_gd()` instead."] // TODO(v0.4): remove.
97165
pub fn to_gd(&self) -> Gd<T> {
98166
(*self.obj).clone()
99167
}
100168

169+
/// Returns a [`Gd`] referencing the base object, for exclusive use during object initialization.
170+
///
171+
/// Can be used during an initialization function [`I*::init()`][crate::classes::IObject::init] or [`Gd::from_init_fn()`].
172+
///
173+
/// The base pointer is only pointing to a base object; you cannot yet downcast it to the object being constructed.
174+
/// The instance ID is the same as the one the in-construction object will have.
175+
///
176+
/// # Lifecycle for ref-counted classes
177+
/// If `T: Inherits<RefCounted>`, then the ref-counted object is not yet fully-initialized at the time of the `init` function running.
178+
/// Accessing the base object without further measures would be dangerous. Here, godot-rust employs a workaround: the `Base` object (which
179+
/// holds a weak pointer to the actual instance) is temporarily upgraded to a strong pointer, preventing use-after-free.
180+
///
181+
/// This additional reference is automatically dropped at an implementation-defined point in time (which may change, and technically delay
182+
/// destruction of your object as soon as you use `Base::to_init_gd()`). Right now, this refcount-decrement is deferred to the next frame.
183+
///
184+
/// For now, ref-counted bases can only use `to_init_gd()` on the main thread.
185+
///
186+
/// # Panics (Debug)
187+
/// If called outside an initialization function, or for ref-counted objects on a non-main thread.
188+
#[cfg(since_api = "4.2")]
189+
pub fn to_init_gd(&self) -> Gd<T> {
190+
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
191+
assert!(
192+
self.is_initializing(),
193+
"Base::to_init_gd() can only be called during object initialization, inside I*::init() or Gd::from_init_fn()"
194+
);
195+
196+
// For manually-managed objects, regular clone is fine.
197+
// Only static type matters, because this happens immediately after initialization, so T is both static and dynamic type.
198+
if !<T::Memory as bounds::Memory>::IS_REF_COUNTED {
199+
return Gd::clone(&self.obj);
200+
}
201+
202+
debug_assert!(
203+
sys::is_main_thread(),
204+
"Base::to_init_gd() can only be called on the main thread for ref-counted objects (for now)"
205+
);
206+
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.
208+
// During the initialization phase (derived object being constructed), increment refcount by 1.
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+
});
226+
227+
let name = format!("Base<{}> deferred unref", T::class_name());
228+
let callable = Callable::from_once_fn(&name, move |_args| {
229+
Self::drop_strong_ref(instance_id);
230+
Ok(Variant::nil())
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.
235+
callable.call_deferred(&[]);
236+
237+
(*self.obj).clone()
238+
}
239+
240+
/// Drops any extra strong references, possibly causing object destruction.
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+
);
249+
250+
// Triggers RawGd::drop() -> dec-ref -> possibly object destruction.
251+
});
252+
}
253+
254+
/// Finalizes the initialization of this `Base<T>` and returns whether
255+
pub(crate) fn mark_initialized(&mut self) {
256+
#[cfg(debug_assertions)]
257+
{
258+
assert_eq!(
259+
self.init_state.get(),
260+
InitState::ObjectConstructing,
261+
"Base<T> is already initialized, or holds a script instance"
262+
);
263+
264+
self.init_state.set(InitState::ObjectInitialized);
265+
}
266+
267+
// May return whether there is a "surplus" strong ref in the future, as self.extra_strong_ref.borrow().is_some().
268+
}
269+
270+
/// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
271+
#[doc(hidden)]
272+
pub fn __fully_constructed_gd(&self) -> Gd<T> {
273+
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
274+
assert!(
275+
!self.is_initializing(),
276+
"WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
277+
);
278+
279+
(*self.obj).clone()
280+
}
281+
282+
/// Returns a [`Gd`] referencing the base object, for use in script contexts only.
283+
#[doc(hidden)]
284+
pub fn __script_gd(&self) -> Gd<T> {
285+
// Used internally by `SiMut::base()` and `SiMut::base_mut()` for script re-entrancy.
286+
// Could maybe add debug validation to ensure script context in the future.
287+
(*self.obj).clone()
288+
}
289+
101290
// Currently only used in outbound virtual calls (for scripts); search for: base_field(self).obj_sys().
102291
#[doc(hidden)]
103292
pub fn obj_sys(&self) -> sys::GDExtensionObjectPtr {
@@ -109,6 +298,36 @@ impl<T: GodotClass> Base<T> {
109298
pub(crate) fn debug_instance_id(&self) -> crate::obj::InstanceId {
110299
self.obj.instance_id()
111300
}
301+
302+
/// Returns a [`Gd`] referencing the base object, for use in script contexts only.
303+
pub(crate) fn to_script_gd(&self) -> Gd<T> {
304+
#[cfg(debug_assertions)]
305+
assert_eq!(
306+
self.init_state.get(),
307+
InitState::Script,
308+
"to_script_gd() can only be called on script-context Base objects"
309+
);
310+
311+
(*self.obj).clone()
312+
}
313+
314+
/// Returns `true` if this `Base<T>` is currently in the initializing state.
315+
#[cfg(debug_assertions)]
316+
fn is_initializing(&self) -> bool {
317+
self.init_state.get() == InitState::ObjectConstructing
318+
}
319+
320+
/// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
321+
#[doc(hidden)]
322+
pub fn __constructed_gd(&self) -> Gd<T> {
323+
#[cfg(debug_assertions)] // debug_assert! still checks existence of symbols.
324+
assert!(
325+
!self.is_initializing(),
326+
"WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
327+
);
328+
329+
(*self.obj).clone()
330+
}
112331
}
113332

114333
impl<T: GodotClass> Debug for Base<T> {

godot-core/src/obj/call_deferred.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ use crate::registry::signal::ToSignalObj;
2020
#[cfg(before_api = "4.2")]
2121
pub trait WithDeferredCall<T: GodotClass> {}
2222

23+
// TODO(v0.4): seal this and similar traits.
24+
2325
/// Enables `Gd::apply_deferred()` for type-safe deferred calls.
2426
///
2527
/// The trait is automatically available for all engine-defined Godot classes and user classes containing a `Base<T>` field.

godot-core/src/obj/casts.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ impl<T: GodotClass, U: GodotClass> CastSuccess<T, U> {
4848
}
4949

5050
/// Access shared reference to destination, without consuming object.
51+
#[cfg(debug_assertions)]
5152
pub fn as_dest_ref(&self) -> &RawGd<U> {
5253
self.check_validity();
5354
&self.dest

godot-core/src/obj/script.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ pub unsafe fn create_script_instance<T: ScriptInstance>(
319319
method_lists: BoundedPtrList::new(),
320320
// SAFETY: The script instance is always freed before the base object is destroyed. The weak reference should therefore never be
321321
// accessed after it has been freed.
322-
base: unsafe { Base::from_gd(&for_object) },
322+
base: unsafe { Base::from_script_gd(&for_object) },
323323
};
324324

325325
let data_ptr = Box::into_raw(Box::new(data));
@@ -454,7 +454,7 @@ impl<'a, T: ScriptInstance> SiMut<'a, T> {
454454
/// }
455455
/// ```
456456
pub fn base(&self) -> ScriptBaseRef<'_, T> {
457-
ScriptBaseRef::new(self.base_ref.to_gd(), self.mut_ref)
457+
ScriptBaseRef::new(self.base_ref.to_script_gd(), self.mut_ref)
458458
}
459459

460460
/// Returns a mutable reference suitable for calling engine methods on this object.
@@ -518,7 +518,7 @@ impl<'a, T: ScriptInstance> SiMut<'a, T> {
518518
pub fn base_mut(&mut self) -> ScriptBaseMut<'_, T> {
519519
let guard = self.cell.make_inaccessible(self.mut_ref).unwrap();
520520

521-
ScriptBaseMut::new(self.base_ref.to_gd(), guard)
521+
ScriptBaseMut::new(self.base_ref.to_script_gd(), guard)
522522
}
523523
}
524524

godot-core/src/obj/traits.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,13 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
355355
///
356356
/// This is intended to be stored or passed to engine methods. You cannot call `bind()` or `bind_mut()` on it, while the method
357357
/// calling `to_gd()` is still running; that would lead to a double borrow panic.
358+
///
359+
/// # Panics
360+
/// If called during initialization (the `init()` function or `Gd::from_init_fn()`). Use [`Base::to_init_gd()`] instead.
358361
fn to_gd(&self) -> Gd<Self>;
359362

360363
/// Returns a reference to the `Base` stored by this object.
364+
#[doc(hidden)]
361365
fn base_field(&self) -> &Base<Self::Base>;
362366

363367
/// Returns a shared reference suitable for calling engine methods on this object.
@@ -419,7 +423,7 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
419423
///
420424
/// For this, use [`base_mut()`](WithBaseField::base_mut()) instead.
421425
fn base(&self) -> BaseRef<'_, Self> {
422-
let gd = self.base_field().to_gd();
426+
let gd = self.base_field().__constructed_gd();
423427

424428
BaseRef::new(gd, self)
425429
}
@@ -490,7 +494,7 @@ pub trait WithBaseField: GodotClass + Bounds<Declarer = bounds::DeclUser> {
490494
/// ```
491495
#[allow(clippy::let_unit_value)]
492496
fn base_mut(&mut self) -> BaseMut<'_, Self> {
493-
let base_gd = self.base_field().to_gd();
497+
let base_gd = self.base_field().__constructed_gd();
494498

495499
let gd = self.to_gd();
496500
// SAFETY:

0 commit comments

Comments
 (0)