@@ -20,6 +20,24 @@ mod reexport_pub {
20
20
}
21
21
pub use reexport_pub:: * ;
22
22
23
+ #[ repr( C ) ]
24
+ struct InitUserData {
25
+ library : sys:: GDExtensionClassLibraryPtr ,
26
+ main_loop_callbacks : sys:: GDExtensionMainLoopCallbacks ,
27
+ }
28
+
29
+ unsafe extern "C" fn startup_func < E : ExtensionLibrary > ( ) {
30
+ E :: on_main_loop_startup ( ) ;
31
+ }
32
+
33
+ unsafe extern "C" fn frame_func < E : ExtensionLibrary > ( ) {
34
+ E :: on_main_loop_frame ( ) ;
35
+ }
36
+
37
+ unsafe extern "C" fn shutdown_func < E : ExtensionLibrary > ( ) {
38
+ E :: on_main_loop_shutdown ( ) ;
39
+ }
40
+
23
41
#[ doc( hidden) ]
24
42
#[ deny( unsafe_op_in_unsafe_fn) ]
25
43
pub unsafe fn __gdext_load_library < E : ExtensionLibrary > (
@@ -60,10 +78,19 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
60
78
// Currently no way to express failure; could be exposed to E if necessary.
61
79
// No early exit, unclear if Godot still requires output parameters to be set.
62
80
let success = true ;
81
+ // Userdata will be dropped in core level deinitialization.
82
+ let userdata = Box :: into_raw ( Box :: new ( InitUserData {
83
+ library,
84
+ main_loop_callbacks : sys:: GDExtensionMainLoopCallbacks {
85
+ startup_func : Some ( startup_func :: < E > ) ,
86
+ frame_func : Some ( frame_func :: < E > ) ,
87
+ shutdown_func : Some ( shutdown_func :: < E > ) ,
88
+ } ,
89
+ } ) ) ;
63
90
64
91
let godot_init_params = sys:: GDExtensionInitialization {
65
92
minimum_initialization_level : E :: min_level ( ) . to_sys ( ) ,
66
- userdata : std:: ptr :: null_mut ( ) ,
93
+ userdata : userdata as * mut std:: ffi :: c_void ,
67
94
initialize : Some ( ffi_initialize_layer :: < E > ) ,
68
95
deinitialize : Some ( ffi_deinitialize_layer :: < E > ) ,
69
96
} ;
@@ -88,22 +115,23 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
88
115
static LEVEL_SERVERS_CORE_LOADED : AtomicBool = AtomicBool :: new ( false ) ;
89
116
90
117
unsafe extern "C" fn ffi_initialize_layer < E : ExtensionLibrary > (
91
- _userdata : * mut std:: ffi:: c_void ,
118
+ userdata : * mut std:: ffi:: c_void ,
92
119
init_level : sys:: GDExtensionInitializationLevel ,
93
120
) {
121
+ let userdata = ( userdata as * mut InitUserData ) . as_ref ( ) . unwrap ( ) ;
94
122
let level = InitLevel :: from_sys ( init_level) ;
95
123
let ctx = || format ! ( "failed to initialize GDExtension level `{level:?}`" ) ;
96
124
97
- fn try_load < E : ExtensionLibrary > ( level : InitLevel ) {
125
+ fn try_load < E : ExtensionLibrary > ( level : InitLevel , userdata : & InitUserData ) {
98
126
// Workaround for https://github.com/godot-rust/gdext/issues/629:
99
127
// When using editor plugins, Godot may unload all levels but only reload from Scene upward.
100
128
// Manually run initialization of lower levels.
101
129
102
130
// TODO: Remove this workaround once after the upstream issue is resolved.
103
131
if level == InitLevel :: Scene {
104
132
if !LEVEL_SERVERS_CORE_LOADED . load ( Ordering :: Relaxed ) {
105
- try_load :: < E > ( InitLevel :: Core ) ;
106
- try_load :: < E > ( InitLevel :: Servers ) ;
133
+ try_load :: < E > ( InitLevel :: Core , userdata ) ;
134
+ try_load :: < E > ( InitLevel :: Servers , userdata ) ;
107
135
}
108
136
} else if level == InitLevel :: Core {
109
137
// When it's normal initialization, the `Servers` level is normally initialized.
@@ -112,18 +140,18 @@ unsafe extern "C" fn ffi_initialize_layer<E: ExtensionLibrary>(
112
140
113
141
// SAFETY: Godot will call this from the main thread, after `__gdext_load_library` where the library is initialized,
114
142
// and only once per level.
115
- unsafe { gdext_on_level_init ( level) } ;
143
+ unsafe { gdext_on_level_init ( level, userdata ) } ;
116
144
E :: on_level_init ( level) ;
117
145
}
118
146
119
147
// Swallow panics. TODO consider crashing if gdext init fails.
120
148
let _ = crate :: private:: handle_panic ( ctx, || {
121
- try_load :: < E > ( level) ;
149
+ try_load :: < E > ( level, userdata ) ;
122
150
} ) ;
123
151
}
124
152
125
153
unsafe extern "C" fn ffi_deinitialize_layer < E : ExtensionLibrary > (
126
- _userdata : * mut std:: ffi:: c_void ,
154
+ userdata : * mut std:: ffi:: c_void ,
127
155
init_level : sys:: GDExtensionInitializationLevel ,
128
156
) {
129
157
let level = InitLevel :: from_sys ( init_level) ;
@@ -134,6 +162,9 @@ unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
134
162
if level == InitLevel :: Core {
135
163
// Once the CORE api is unloaded, reset the flag to initial state.
136
164
LEVEL_SERVERS_CORE_LOADED . store ( false , Ordering :: Relaxed ) ;
165
+
166
+ // Drop the userdata.
167
+ drop ( Box :: from_raw ( userdata as * mut InitUserData ) ) ;
137
168
}
138
169
139
170
E :: on_level_deinit ( level) ;
@@ -149,7 +180,7 @@ unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
149
180
/// - The interface must have been initialized.
150
181
/// - Must only be called once per level.
151
182
#[ deny( unsafe_op_in_unsafe_fn) ]
152
- unsafe fn gdext_on_level_init ( level : InitLevel ) {
183
+ unsafe fn gdext_on_level_init ( level : InitLevel , userdata : & InitUserData ) {
153
184
// TODO: in theory, a user could start a thread in one of the early levels, and run concurrent code that messes with the global state
154
185
// (e.g. class registration). This would break the assumption that the load_class_method_table() calls are exclusive.
155
186
// We could maybe protect globals with a mutex until initialization is complete, and then move it to a directly-accessible, read-only static.
@@ -158,6 +189,14 @@ unsafe fn gdext_on_level_init(level: InitLevel) {
158
189
unsafe { sys:: load_class_method_table ( level) } ;
159
190
160
191
match level {
192
+ InitLevel :: Core => {
193
+ unsafe {
194
+ sys:: interface_fn!( register_main_loop_callbacks) (
195
+ userdata. library ,
196
+ & raw const userdata. main_loop_callbacks ,
197
+ )
198
+ } ;
199
+ }
161
200
InitLevel :: Servers => {
162
201
// SAFETY: called from the main thread, sys::initialized has already been called.
163
202
unsafe { sys:: discover_main_thread ( ) } ;
@@ -173,7 +212,6 @@ unsafe fn gdext_on_level_init(level: InitLevel) {
173
212
crate :: docs:: register ( ) ;
174
213
}
175
214
}
176
- _ => ( ) ,
177
215
}
178
216
179
217
crate :: registry:: class:: auto_register_classes ( level) ;
@@ -303,6 +341,23 @@ pub unsafe trait ExtensionLibrary {
303
341
// Nothing by default.
304
342
}
305
343
344
+ /// Callback that is called after all initialization levels when Godot is fully initialized.
345
+ fn on_main_loop_startup ( ) {
346
+ // Nothing by default.
347
+ }
348
+
349
+ /// Callback that is called for every process frame.
350
+ ///
351
+ /// This will run after all `_process()` methods on Node, and before `ScriptServer::frame()`.
352
+ fn on_main_loop_frame ( ) {
353
+ // Nothing by default.
354
+ }
355
+
356
+ /// Callback that is called before Godot is shutdown when it is still fully initialized.
357
+ fn on_main_loop_shutdown ( ) {
358
+ // Nothing by default.
359
+ }
360
+
306
361
/// Whether to override the Wasm binary filename used by your GDExtension which the library should expect at runtime. Return `None`
307
362
/// to use the default where gdext expects either `{YourCrate}.wasm` (default binary name emitted by Rust) or
308
363
/// `{YourCrate}.threads.wasm` (for builds producing separate single-threaded and multi-threaded binaries).
0 commit comments