5
5
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
*/
7
7
8
+ #[ cfg( debug_assertions) ]
9
+ use std:: cell:: Cell ;
10
+ use std:: cell:: RefCell ;
8
11
use std:: fmt:: { Debug , Display , Formatter , Result as FmtResult } ;
9
12
use std:: mem:: ManuallyDrop ;
13
+ use std:: rc:: Rc ;
10
14
11
- use crate :: obj:: { Gd , GodotClass } ;
15
+ use crate :: builtin:: { Callable , Variant } ;
16
+ use crate :: obj:: { bounds, Gd , GodotClass } ;
12
17
use crate :: { classes, sys} ;
13
18
19
+ /// Represents the initialization state of a `Base<T>` object.
20
+ #[ cfg( debug_assertions) ]
21
+ #[ derive( Debug , Copy , Clone , PartialEq , Eq ) ]
22
+ enum InitState {
23
+ /// Object is being constructed (inside `I*::init()` or `Gd::from_init_fn()`).
24
+ ObjectConstructing ,
25
+ /// Object construction is complete.
26
+ ObjectInitialized ,
27
+ /// `ScriptInstance` context - always considered initialized (bypasses lifecycle checks).
28
+ Script ,
29
+ }
30
+
31
+ #[ cfg( debug_assertions) ]
32
+ macro_rules! base_from_obj {
33
+ ( $obj: expr, $state: expr) => {
34
+ Base :: from_obj( $obj, $state)
35
+ } ;
36
+ }
37
+
38
+ #[ cfg( not( debug_assertions) ) ]
39
+ macro_rules! base_from_obj {
40
+ ( $obj: expr, $state: expr) => {
41
+ Base :: from_obj( $obj)
42
+ } ;
43
+ }
44
+
45
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
46
+
14
47
/// Restricted version of `Gd`, to hold the base instance inside a user's `GodotClass`.
15
48
///
16
49
/// Behaves similarly to [`Gd`][crate::obj::Gd], but is more constrained. Cannot be constructed by the user.
@@ -35,6 +68,15 @@ pub struct Base<T: GodotClass> {
35
68
// 1. Gd<T> -- triggers InstanceStorage destruction
36
69
// 2.
37
70
obj : ManuallyDrop < Gd < T > > ,
71
+
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
+ /// Tracks the initialization state of this `Base<T>` in Debug mode.
76
+ ///
77
+ /// Rc allows to "copy-construct" the base from an existing one, while still affecting the user-instance through the original `Base<T>`.
78
+ #[ cfg( debug_assertions) ]
79
+ init_state : Rc < Cell < InitState > > ,
38
80
}
39
81
40
82
impl < T : GodotClass > Base < T > {
@@ -47,7 +89,15 @@ impl<T: GodotClass> Base<T> {
47
89
/// If `base` is destroyed while the returned `Base<T>` is in use, that constitutes a logic error, not a safety issue.
48
90
pub ( crate ) unsafe fn from_base ( base : & Base < T > ) -> Base < T > {
49
91
debug_assert ! ( base. obj. is_instance_valid( ) ) ;
50
- Base :: from_obj ( Gd :: from_obj_sys_weak ( base. obj . obj_sys ( ) ) )
92
+
93
+ let obj = Gd :: from_obj_sys_weak ( base. obj . obj_sys ( ) ) ;
94
+
95
+ Self {
96
+ 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
+ #[ cfg( debug_assertions) ]
99
+ init_state : Rc :: clone ( & base. init_state ) ,
100
+ }
51
101
}
52
102
53
103
/// Create base from existing object (used in script instances).
@@ -57,9 +107,11 @@ impl<T: GodotClass> Base<T> {
57
107
/// # Safety
58
108
/// `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
59
109
/// error, not a safety issue.
60
- pub ( crate ) unsafe fn from_gd ( gd : & Gd < T > ) -> Self {
110
+ pub ( crate ) unsafe fn from_script_gd ( gd : & Gd < T > ) -> Self {
61
111
debug_assert ! ( gd. is_instance_valid( ) ) ;
62
- Base :: from_obj ( Gd :: from_obj_sys_weak ( gd. obj_sys ( ) ) )
112
+
113
+ let obj = Gd :: from_obj_sys_weak ( gd. obj_sys ( ) ) ;
114
+ base_from_obj ! ( obj, InitState :: Script )
63
115
}
64
116
65
117
/// Create new base from raw Godot object.
@@ -72,20 +124,31 @@ impl<T: GodotClass> Base<T> {
72
124
pub ( crate ) unsafe fn from_sys ( base_ptr : sys:: GDExtensionObjectPtr ) -> Self {
73
125
assert ! ( !base_ptr. is_null( ) , "instance base is null pointer" ) ;
74
126
75
- // Initialize only as weak pointer (don't increment reference count)
127
+ // Initialize only as weak pointer (don't increment reference count).
76
128
let obj = Gd :: from_obj_sys_weak ( base_ptr) ;
77
129
78
130
// This obj does not contribute to the strong count, otherwise we create a reference cycle:
79
131
// 1. RefCounted (dropped in GDScript)
80
132
// 2. holds user T (via extension instance and storage)
81
133
// 3. holds Base<T> RefCounted (last ref, dropped in T destructor, but T is never destroyed because this ref keeps storage alive)
82
134
// 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)
135
+ base_from_obj ! ( obj, InitState :: ObjectConstructing )
84
136
}
85
137
138
+ #[ cfg( debug_assertions) ]
139
+ fn from_obj ( obj : Gd < T > , init_state : InitState ) -> Self {
140
+ Self {
141
+ obj : ManuallyDrop :: new ( obj) ,
142
+ extra_strong_ref : Rc :: new ( RefCell :: new ( None ) ) ,
143
+ init_state : Rc :: new ( Cell :: new ( init_state) ) ,
144
+ }
145
+ }
146
+
147
+ #[ cfg( not( debug_assertions) ) ]
86
148
fn from_obj ( obj : Gd < T > ) -> Self {
87
149
Self {
88
150
obj : ManuallyDrop :: new ( obj) ,
151
+ extra_strong_ref : Rc :: new ( RefCell :: new ( None ) ) ,
89
152
}
90
153
}
91
154
@@ -94,10 +157,112 @@ impl<T: GodotClass> Base<T> {
94
157
/// Using this method to call methods on the base field of a Rust object is discouraged, instead use the
95
158
/// methods from [`WithBaseField`](super::WithBaseField) when possible.
96
159
#[ doc( hidden) ]
160
+ #[ deprecated = "Private API. Use `Base::to_init_gd()` or `WithBaseField::to_gd()` instead." ] // TODO(v0.4): remove.
97
161
pub fn to_gd ( & self ) -> Gd < T > {
98
162
( * self . obj ) . clone ( )
99
163
}
100
164
165
+ /// Returns a [`Gd`] referencing the base object, for exclusive use during object initialization.
166
+ ///
167
+ /// Can be used during an initialization function [`I*::init()`][crate::classes::IObject::init] or [`Gd::from_init_fn()`].
168
+ ///
169
+ /// The base pointer is only pointing to a base object; you cannot yet downcast it to the object being constructed.
170
+ /// The instance ID is the same as the one the in-construction object will have.
171
+ ///
172
+ /// # Lifecycle for ref-counted classes
173
+ /// If `T: Inherits<RefCounted>`, then the ref-counted object is not yet fully-initialized at the time of the `init` function running.
174
+ /// Accessing the base object without further measures would be dangerous. Here, godot-rust employs a workaround: the `Base` object (which
175
+ /// holds a weak pointer to the actual instance) is temporarily upgraded to a strong pointer, preventing use-after-free.
176
+ ///
177
+ /// This additional reference is automatically dropped at an implementation-defined point in time (which may change, and technically delay
178
+ /// destruction of your object as soon as you use `Base::to_init_gd()`). Right now, this refcount-decrement is deferred to the next frame.
179
+ ///
180
+ /// For now, ref-counted bases can only use `to_init_gd()` on the main thread.
181
+ ///
182
+ /// # Panics (Debug)
183
+ /// If called outside an initialization function, or for ref-counted objects on a non-main thread.
184
+ #[ cfg( since_api = "4.2" ) ]
185
+ pub fn to_init_gd ( & self ) -> Gd < T > {
186
+ #[ cfg( debug_assertions) ] // debug_assert! still checks existence of symbols.
187
+ assert ! (
188
+ self . is_initializing( ) ,
189
+ "Base::to_init_gd() can only be called during object initialization, inside I*::init() or Gd::from_init_fn()"
190
+ ) ;
191
+
192
+ // For manually-managed objects, regular clone is fine.
193
+ // Only static type matters, because this happens immediately after initialization, so T is both static and dynamic type.
194
+ if !<T :: Memory as bounds:: Memory >:: IS_REF_COUNTED {
195
+ return Gd :: clone ( & self . obj ) ;
196
+ }
197
+
198
+ debug_assert ! (
199
+ sys:: is_main_thread( ) ,
200
+ "Base::to_init_gd() can only be called on the main thread for ref-counted objects (for now)"
201
+ ) ;
202
+
203
+ // 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
+ // 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
+
210
+ // Can't use Gd::apply_deferred(), as that implicitly borrows &mut self, causing a "destroyed while bind was active" panic.
211
+ let name = format ! ( "Base<{}> deferred unref" , T :: class_name( ) ) ;
212
+ let rc = Rc :: clone ( & self . extra_strong_ref ) ;
213
+ let callable = Callable :: from_once_fn ( & name, move |_args| {
214
+ Self :: drop_strong_ref ( rc) ;
215
+ Ok ( Variant :: nil ( ) )
216
+ } ) ;
217
+ callable. call_deferred ( & [ ] ) ;
218
+
219
+ ( * self . obj ) . clone ( )
220
+ }
221
+
222
+ /// 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( ) ) ;
226
+
227
+ * r = None ; // Triggers RawGd::drop() -> dec-ref -> possibly object destruction.
228
+ }
229
+
230
+ /// Finalizes the initialization of this `Base<T>` and returns whether
231
+ pub ( crate ) fn mark_initialized ( & mut self ) -> bool {
232
+ #[ cfg( debug_assertions) ]
233
+ {
234
+ assert_eq ! (
235
+ self . init_state. get( ) ,
236
+ InitState :: ObjectConstructing ,
237
+ "Base<T> is already initialized, or holds a script instance"
238
+ ) ;
239
+
240
+ self . init_state . set ( InitState :: ObjectInitialized ) ;
241
+ }
242
+
243
+ self . extra_strong_ref . borrow ( ) . is_some ( )
244
+ }
245
+
246
+ /// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
247
+ #[ doc( hidden) ]
248
+ pub fn __fully_constructed_gd ( & self ) -> Gd < T > {
249
+ #[ cfg( debug_assertions) ] // debug_assert! still checks existence of symbols.
250
+ assert ! (
251
+ !self . is_initializing( ) ,
252
+ "WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
253
+ ) ;
254
+
255
+ ( * self . obj ) . clone ( )
256
+ }
257
+
258
+ /// Returns a [`Gd`] referencing the base object, for use in script contexts only.
259
+ #[ doc( hidden) ]
260
+ pub fn __script_gd ( & self ) -> Gd < T > {
261
+ // Used internally by `SiMut::base()` and `SiMut::base_mut()` for script re-entrancy.
262
+ // Could maybe add debug validation to ensure script context in the future.
263
+ ( * self . obj ) . clone ( )
264
+ }
265
+
101
266
// Currently only used in outbound virtual calls (for scripts); search for: base_field(self).obj_sys().
102
267
#[ doc( hidden) ]
103
268
pub fn obj_sys ( & self ) -> sys:: GDExtensionObjectPtr {
@@ -109,6 +274,36 @@ impl<T: GodotClass> Base<T> {
109
274
pub ( crate ) fn debug_instance_id ( & self ) -> crate :: obj:: InstanceId {
110
275
self . obj . instance_id ( )
111
276
}
277
+
278
+ /// Returns a [`Gd`] referencing the base object, for use in script contexts only.
279
+ pub ( crate ) fn to_script_gd ( & self ) -> Gd < T > {
280
+ #[ cfg( debug_assertions) ]
281
+ assert_eq ! (
282
+ self . init_state. get( ) ,
283
+ InitState :: Script ,
284
+ "to_script_gd() can only be called on script-context Base objects"
285
+ ) ;
286
+
287
+ ( * self . obj ) . clone ( )
288
+ }
289
+
290
+ /// Returns `true` if this `Base<T>` is currently in the initializing state.
291
+ #[ cfg( debug_assertions) ]
292
+ fn is_initializing ( & self ) -> bool {
293
+ self . init_state . get ( ) == InitState :: ObjectConstructing
294
+ }
295
+
296
+ /// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
297
+ #[ doc( hidden) ]
298
+ pub fn __constructed_gd ( & self ) -> Gd < T > {
299
+ #[ cfg( debug_assertions) ] // debug_assert! still checks existence of symbols.
300
+ assert ! (
301
+ !self . is_initializing( ) ,
302
+ "WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
303
+ ) ;
304
+
305
+ ( * self . obj ) . clone ( )
306
+ }
112
307
}
113
308
114
309
impl < T : GodotClass > Debug for Base < T > {
0 commit comments