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