@@ -90,7 +90,9 @@ unsafe extern "C" fn ffi_initialize_layer<E: ExtensionLibrary>(
90
90
LEVEL_SERVERS_CORE_LOADED . store ( true , Relaxed ) ;
91
91
}
92
92
93
- gdext_on_level_init ( level) ;
93
+ // SAFETY: Godot will call this from the main thread, after `__gdext_load_library` where the library is initialized,
94
+ // and only once per level.
95
+ unsafe { gdext_on_level_init ( level) } ;
94
96
E :: on_level_init ( level) ;
95
97
}
96
98
@@ -120,27 +122,27 @@ unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
120
122
}
121
123
122
124
/// Tasks needed to be done by gdext internally upon loading an initialization level. Called before user code.
123
- fn gdext_on_level_init ( level : InitLevel ) {
124
- // SAFETY: we are in the main thread, during initialization, no other logic is happening.
125
+ ///
126
+ /// # Safety
127
+ ///
128
+ /// - Must be called from the main thread.
129
+ /// - The interface must have been initialized.
130
+ /// - Must only be called once per level.
131
+ #[ deny( unsafe_op_in_unsafe_fn) ]
132
+ unsafe fn gdext_on_level_init ( level : InitLevel ) {
125
133
// 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
126
134
// (e.g. class registration). This would break the assumption that the load_class_method_table() calls are exclusive.
127
135
// We could maybe protect globals with a mutex until initialization is complete, and then move it to a directly-accessible, read-only static.
128
- unsafe {
129
- match level {
130
- InitLevel :: Core => { }
131
- InitLevel :: Servers => {
132
- sys:: load_class_method_table ( sys:: ClassApiLevel :: Server ) ;
133
- }
134
- InitLevel :: Scene => {
135
- sys:: load_class_method_table ( sys:: ClassApiLevel :: Scene ) ;
136
- ensure_godot_features_compatible ( ) ;
137
- }
138
- InitLevel :: Editor => {
139
- sys:: load_class_method_table ( sys:: ClassApiLevel :: Editor ) ;
140
- }
141
- }
142
- crate :: registry:: class:: auto_register_classes ( level) ;
136
+
137
+ // SAFETY: we are in the main thread, initialize has been called, has never been called with this level before.
138
+ unsafe { sys:: load_class_method_table ( level) } ;
139
+
140
+ if level == InitLevel :: Scene {
141
+ // SAFETY: On the main thread, api initialized, `Scene` was initialized above.
142
+ unsafe { ensure_godot_features_compatible ( ) } ;
143
143
}
144
+
145
+ crate :: registry:: class:: auto_register_classes ( level) ;
144
146
}
145
147
146
148
/// Tasks needed to be done by gdext internally upon unloading an initialization level. Called after user code.
@@ -271,49 +273,17 @@ pub enum EditorRunBehavior {
271
273
/// See also:
272
274
/// - [`ExtensionLibrary::on_level_init()`]
273
275
/// - [`ExtensionLibrary::on_level_deinit()`]
274
- #[ derive( Copy , Clone , Eq , PartialEq , Ord , PartialOrd , Hash , Debug ) ]
275
- pub enum InitLevel {
276
- /// First level loaded by Godot. Builtin types are available, classes are not.
277
- Core ,
278
-
279
- /// Second level loaded by Godot. Only server classes and builtins are available.
280
- Servers ,
281
-
282
- /// Third level loaded by Godot. Most classes are available.
283
- Scene ,
284
-
285
- /// Fourth level loaded by Godot, only in the editor. All classes are available.
286
- Editor ,
287
- }
288
-
289
- impl InitLevel {
290
- #[ doc( hidden) ]
291
- pub fn from_sys ( level : godot_ffi:: GDExtensionInitializationLevel ) -> Self {
292
- match level {
293
- sys:: GDEXTENSION_INITIALIZATION_CORE => Self :: Core ,
294
- sys:: GDEXTENSION_INITIALIZATION_SERVERS => Self :: Servers ,
295
- sys:: GDEXTENSION_INITIALIZATION_SCENE => Self :: Scene ,
296
- sys:: GDEXTENSION_INITIALIZATION_EDITOR => Self :: Editor ,
297
- _ => {
298
- eprintln ! ( "WARNING: unknown initialization level {level}" ) ;
299
- Self :: Scene
300
- }
301
- }
302
- }
303
- #[ doc( hidden) ]
304
- pub fn to_sys ( self ) -> godot_ffi:: GDExtensionInitializationLevel {
305
- match self {
306
- Self :: Core => sys:: GDEXTENSION_INITIALIZATION_CORE ,
307
- Self :: Servers => sys:: GDEXTENSION_INITIALIZATION_SERVERS ,
308
- Self :: Scene => sys:: GDEXTENSION_INITIALIZATION_SCENE ,
309
- Self :: Editor => sys:: GDEXTENSION_INITIALIZATION_EDITOR ,
310
- }
311
- }
312
- }
276
+ pub type InitLevel = sys:: InitLevel ;
313
277
314
278
// ----------------------------------------------------------------------------------------------------------------------------------------------
315
279
316
- fn ensure_godot_features_compatible ( ) {
280
+ /// # Safety
281
+ ///
282
+ /// - Must be called from the main thread.
283
+ /// - The interface must be initialized.
284
+ /// - The `Scene` api level must have been initialized.
285
+ #[ deny( unsafe_op_in_unsafe_fn) ]
286
+ unsafe fn ensure_godot_features_compatible ( ) {
317
287
// The reason why we don't simply call Os::has_feature() here is that we might move the high-level engine classes out of godot-core
318
288
// later, and godot-core would only depend on godot-sys. This makes future migrations easier. We still have access to builtins though.
319
289
@@ -323,8 +293,9 @@ fn ensure_godot_features_compatible() {
323
293
let single = GString :: from ( "single" ) ;
324
294
let double = GString :: from ( "double" ) ;
325
295
326
- // SAFETY: main thread, after initialize(), valid string pointers.
327
296
let gdext_is_double = cfg ! ( feature = "double-precision" ) ;
297
+
298
+ // SAFETY: main thread, after initialize(), valid string pointers, `Scene` initialized.
328
299
let godot_is_double = unsafe {
329
300
let is_single = sys:: godot_has_feature ( os_class. string_sys ( ) , single. sys ( ) ) ;
330
301
let is_double = sys:: godot_has_feature ( os_class. string_sys ( ) , double. sys ( ) ) ;
0 commit comments