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,88 @@ 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
+ #[ cfg( since_api = "4.2" ) ]
166
+ pub fn to_init_gd ( & self ) -> Gd < T > {
167
+ #[ cfg( debug_assertions) ]
168
+ assert ! (
169
+ self . is_initializing( ) ,
170
+ "Base::to_init_gd() can only be called during object initialization, inside I*::init() or Gd::from_init_fn()"
171
+ ) ;
172
+
173
+ // For manually-managed objects, regular clone is fine.
174
+ // Only static type matters, because this happens immediately after initialization, so T is both static and dynamic type.
175
+ if !<T :: Memory as bounds:: Memory >:: IS_REF_COUNTED {
176
+ return Gd :: clone ( & self . obj ) ;
177
+ }
178
+
179
+ // First time handing out a Gd<T>, we need to take measures to temporarily upgrade the Base's weak pointer to a strong one.
180
+ // During the initialization phase (derived object being constructed), increment refcount by 1.
181
+ if self . extra_strong_ref . borrow ( ) . is_none ( ) {
182
+ let strong_ref = unsafe { Gd :: from_obj_sys ( self . obj . obj_sys ( ) ) } ;
183
+ * self . extra_strong_ref . borrow_mut ( ) = Some ( strong_ref) ;
184
+ }
185
+
186
+ // Can't use Gd::apply_deferred(), as that implicitly borrows &mut self, causing a "destroyed while bind was active" panic.
187
+ let name = format ! ( "Base<{}> deferred unref" , T :: class_name( ) ) ;
188
+ let rc = Rc :: clone ( & self . extra_strong_ref ) ;
189
+ let callable = Callable :: from_once_fn ( & name, move |_args| {
190
+ Self :: drop_strong_ref ( rc) ;
191
+ Ok ( Variant :: nil ( ) )
192
+ } ) ;
193
+ callable. call_deferred ( & [ ] ) ;
194
+
195
+ ( * self . obj ) . clone ( )
196
+ }
197
+
198
+ /// Drops any extra strong references, possibly causing object destruction.
199
+ fn drop_strong_ref ( extra_strong_ref : Rc < RefCell < Option < Gd < T > > > > ) {
200
+ let mut r = extra_strong_ref. borrow_mut ( ) ;
201
+ assert ! ( r. is_some( ) ) ;
202
+
203
+ * r = None ; // Triggers RawGd::drop() -> dec-ref -> possibly object destruction.
204
+ }
205
+
206
+ /// Finalizes the initialization of this `Base<T>` and returns whether
207
+ pub ( crate ) fn mark_initialized ( & mut self ) -> bool {
208
+ #[ cfg( debug_assertions) ]
209
+ {
210
+ assert_eq ! (
211
+ self . init_state. get( ) ,
212
+ InitState :: ObjectConstructing ,
213
+ "Base<T> is already initialized, or holds a script instance"
214
+ ) ;
215
+
216
+ self . init_state . set ( InitState :: ObjectInitialized ) ;
217
+ }
218
+
219
+ self . extra_strong_ref . borrow ( ) . is_some ( )
220
+ }
221
+
222
+ /// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
223
+ #[ doc( hidden) ]
224
+ pub fn __fully_constructed_gd ( & self ) -> Gd < T > {
225
+ #[ cfg( debug_assertions) ]
226
+ assert ! (
227
+ !self . is_initializing( ) ,
228
+ "WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
229
+ ) ;
230
+
231
+ ( * self . obj ) . clone ( )
232
+ }
233
+
234
+ /// Returns a [`Gd`] referencing the base object, for use in script contexts only.
235
+ #[ doc( hidden) ]
236
+ pub fn __script_gd ( & self ) -> Gd < T > {
237
+ // Used internally by `SiMut::base()` and `SiMut::base_mut()` for script re-entrancy.
238
+ // Could maybe add debug validation to ensure script context in the future.
239
+ ( * self . obj ) . clone ( )
240
+ }
241
+
101
242
// Currently only used in outbound virtual calls (for scripts); search for: base_field(self).obj_sys().
102
243
#[ doc( hidden) ]
103
244
pub fn obj_sys ( & self ) -> sys:: GDExtensionObjectPtr {
@@ -109,6 +250,36 @@ impl<T: GodotClass> Base<T> {
109
250
pub ( crate ) fn debug_instance_id ( & self ) -> crate :: obj:: InstanceId {
110
251
self . obj . instance_id ( )
111
252
}
253
+
254
+ /// Returns a [`Gd`] referencing the base object, for use in script contexts only.
255
+ pub ( crate ) fn to_script_gd ( & self ) -> Gd < T > {
256
+ #[ cfg( debug_assertions) ]
257
+ assert_eq ! (
258
+ self . init_state. get( ) ,
259
+ InitState :: Script ,
260
+ "to_script_gd() can only be called on script-context Base objects"
261
+ ) ;
262
+
263
+ ( * self . obj ) . clone ( )
264
+ }
265
+
266
+ /// Returns `true` if this `Base<T>` is currently in the initializing state.
267
+ #[ cfg( debug_assertions) ]
268
+ fn is_initializing ( & self ) -> bool {
269
+ self . init_state . get ( ) == InitState :: ObjectConstructing
270
+ }
271
+
272
+ /// Returns a [`Gd`] referencing the base object, assuming the derived object is fully constructed.
273
+ #[ doc( hidden) ]
274
+ pub fn __constructed_gd ( & self ) -> Gd < T > {
275
+ #[ cfg( debug_assertions) ]
276
+ assert ! (
277
+ !self . is_initializing( ) ,
278
+ "WithBaseField::to_gd(), base(), base_mut() can only be called on fully-constructed objects, after I*::init() or Gd::from_init_fn()"
279
+ ) ;
280
+
281
+ ( * self . obj ) . clone ( )
282
+ }
112
283
}
113
284
114
285
impl < T : GodotClass > Debug for Base < T > {
0 commit comments