godot_core/init/
mod.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use std::sync::atomic::{AtomicBool, Ordering};
9
10use godot_ffi as sys;
11use sys::GodotFfi;
12
13use crate::builtin::{GString, StringName};
14use crate::out;
15
16mod reexport_pub {
17    #[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
18    pub use super::sys::main_thread_id;
19    pub use super::sys::{is_main_thread, GdextBuild, InitStage};
20}
21pub use reexport_pub::*;
22
23#[repr(C)]
24struct InitUserData {
25    library: sys::GDExtensionClassLibraryPtr,
26    #[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
27    main_loop_callbacks: sys::GDExtensionMainLoopCallbacks,
28}
29
30#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
31unsafe extern "C" fn startup_func<E: ExtensionLibrary>() {
32    E::on_stage_init(InitStage::MainLoop);
33}
34
35#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
36unsafe extern "C" fn frame_func<E: ExtensionLibrary>() {
37    E::on_main_loop_frame();
38}
39
40#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
41unsafe extern "C" fn shutdown_func<E: ExtensionLibrary>() {
42    E::on_stage_deinit(InitStage::MainLoop);
43}
44
45#[doc(hidden)]
46#[deny(unsafe_op_in_unsafe_fn)]
47pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
48    get_proc_address: sys::GDExtensionInterfaceGetProcAddress,
49    library: sys::GDExtensionClassLibraryPtr,
50    init: *mut sys::GDExtensionInitialization,
51) -> sys::GDExtensionBool {
52    let init_code = || {
53        // Make sure the first thing we do is check whether hot reloading should be enabled or not. This is to ensure that if we do anything to
54        // cause TLS-destructors to run then we have a setting already for how to deal with them. Otherwise, this could cause the default
55        // behavior to kick in and disable hot reloading.
56        #[cfg(target_os = "linux")] #[cfg_attr(published_docs, doc(cfg(target_os = "linux")))]
57        sys::linux_reload_workaround::default_set_hot_reload();
58
59        let tool_only_in_editor = match E::editor_run_behavior() {
60            EditorRunBehavior::ToolClassesOnly => true,
61            EditorRunBehavior::AllClasses => false,
62        };
63
64        let config = sys::GdextConfig::new(tool_only_in_editor);
65
66        // SAFETY: no custom code has run yet + no other thread is accessing global handle.
67        unsafe {
68            sys::initialize(get_proc_address, library, config);
69        }
70
71        // With experimental-features enabled, we can always print panics to godot_print!
72        #[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
73        crate::private::set_gdext_hook(|| true);
74
75        // Without experimental-features enabled, we can only print panics with godot_print! if the panic occurs on the main (Godot) thread.
76        #[cfg(not(feature = "experimental-threads"))]
77        {
78            let main_thread = std::thread::current().id();
79            crate::private::set_gdext_hook(move || std::thread::current().id() == main_thread);
80        }
81
82        // Currently no way to express failure; could be exposed to E if necessary.
83        // No early exit, unclear if Godot still requires output parameters to be set.
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")] #[cfg_attr(published_docs, doc(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        }));
95
96        let godot_init_params = sys::GDExtensionInitialization {
97            minimum_initialization_level: E::min_level().to_sys(),
98            userdata: userdata.cast::<std::ffi::c_void>(),
99            initialize: Some(ffi_initialize_layer::<E>),
100            deinitialize: Some(ffi_deinitialize_layer::<E>),
101        };
102
103        // SAFETY: Godot is responsible for passing us a valid pointer.
104        unsafe {
105            *init = godot_init_params;
106        }
107
108        success as u8
109    };
110
111    // Use std::panic::catch_unwind instead of handle_panic: handle_panic uses TLS, which
112    // calls `thread_atexit` on linux, which sets the hot reloading flag on linux.
113    // Using std::panic::catch_unwind avoids this, although we lose out on context information
114    // for debugging.
115    let is_success = std::panic::catch_unwind(init_code);
116
117    is_success.unwrap_or(0)
118}
119
120static LEVEL_SERVERS_CORE_LOADED: AtomicBool = AtomicBool::new(false);
121
122unsafe extern "C" fn ffi_initialize_layer<E: ExtensionLibrary>(
123    userdata: *mut std::ffi::c_void,
124    init_level: sys::GDExtensionInitializationLevel,
125) {
126    let userdata = userdata.cast::<InitUserData>().as_ref().unwrap();
127    let level = InitLevel::from_sys(init_level);
128    let ctx = || format!("failed to initialize GDExtension level `{level:?}`");
129
130    fn try_load<E: ExtensionLibrary>(level: InitLevel, userdata: &InitUserData) {
131        // Workaround for https://github.com/godot-rust/gdext/issues/629:
132        // When using editor plugins, Godot may unload all levels but only reload from Scene upward.
133        // Manually run initialization of lower levels.
134
135        // TODO: Remove this workaround once after the upstream issue is resolved.
136        if level == InitLevel::Scene {
137            if !LEVEL_SERVERS_CORE_LOADED.load(Ordering::Relaxed) {
138                try_load::<E>(InitLevel::Core, userdata);
139                try_load::<E>(InitLevel::Servers, userdata);
140            }
141        } else if level == InitLevel::Core {
142            // When it's normal initialization, the `Servers` level is normally initialized.
143            LEVEL_SERVERS_CORE_LOADED.store(true, Ordering::Relaxed);
144        }
145
146        // SAFETY: Godot will call this from the main thread, after `__gdext_load_library` where the library is initialized,
147        // and only once per level.
148        unsafe { gdext_on_level_init(level, userdata) };
149        E::on_stage_init(level.to_stage());
150    }
151
152    // Swallow panics. TODO consider crashing if gdext init fails.
153    let _ = crate::private::handle_panic(ctx, || {
154        try_load::<E>(level, userdata);
155    });
156}
157
158unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
159    userdata: *mut std::ffi::c_void,
160    init_level: sys::GDExtensionInitializationLevel,
161) {
162    let level = InitLevel::from_sys(init_level);
163    let ctx = || format!("failed to deinitialize GDExtension level `{level:?}`");
164
165    // Swallow panics.
166    let _ = crate::private::handle_panic(ctx, || {
167        if level == InitLevel::Core {
168            // Once the CORE api is unloaded, reset the flag to initial state.
169            LEVEL_SERVERS_CORE_LOADED.store(false, Ordering::Relaxed);
170
171            // Drop the userdata.
172            drop(Box::from_raw(userdata.cast::<InitUserData>()));
173        }
174
175        E::on_stage_deinit(level.to_stage());
176        gdext_on_level_deinit(level);
177    });
178}
179
180/// Tasks needed to be done by gdext internally upon loading an initialization level. Called before user code.
181///
182/// # Safety
183///
184/// - Must be called from the main thread.
185/// - The interface must have been initialized.
186/// - Must only be called once per level.
187#[deny(unsafe_op_in_unsafe_fn)]
188unsafe fn gdext_on_level_init(level: InitLevel, userdata: &InitUserData) {
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
190    // (e.g. class registration). This would break the assumption that the load_class_method_table() calls are exclusive.
191    // We could maybe protect globals with a mutex until initialization is complete, and then move it to a directly-accessible, read-only static.
192
193    // SAFETY: we are in the main thread, initialize has been called, has never been called with this level before.
194    unsafe { sys::load_class_method_table(level) };
195
196    match level {
197        InitLevel::Core => {
198            #[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(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        }
206        InitLevel::Servers => {
207            // SAFETY: called from the main thread, sys::initialized has already been called.
208            unsafe { sys::discover_main_thread() };
209        }
210        InitLevel::Scene => {
211            // SAFETY: On the main thread, api initialized, `Scene` was initialized above.
212            unsafe { ensure_godot_features_compatible() };
213        }
214        InitLevel::Editor => {
215            #[cfg(all(since_api = "4.3", feature = "register-docs"))]
216            // SAFETY: Godot binding is initialized, and this is called from the main thread.
217            unsafe {
218                crate::docs::register();
219            }
220        }
221    }
222
223    crate::registry::class::auto_register_classes(level);
224}
225
226/// Tasks needed to be done by gdext internally upon unloading an initialization level. Called after user code.
227fn gdext_on_level_deinit(level: InitLevel) {
228    crate::registry::class::unregister_classes(level);
229
230    if level == InitLevel::Core {
231        // If lowest level is unloaded, call global deinitialization.
232        // No business logic by itself, but ensures consistency if re-initialization (hot-reload on Linux) occurs.
233
234        crate::task::cleanup();
235        crate::tools::cleanup();
236
237        // Garbage-collect various statics.
238        // SAFETY: this is the last time meta APIs are used.
239        unsafe {
240            crate::meta::cleanup();
241        }
242
243        // SAFETY: called after all other logic, so no concurrent access.
244        // TODO: multithreading must make sure other threads are joined/stopped here.
245        unsafe {
246            sys::deinitialize();
247        }
248    }
249}
250
251// ----------------------------------------------------------------------------------------------------------------------------------------------
252
253/// Defines the entry point for a GDExtension Rust library.
254///
255/// Every library should have exactly one implementation of this trait. It is always used in combination with the
256/// [`#[gdextension]`][gdextension] proc-macro attribute.
257///
258/// # Example
259/// The simplest usage is as follows. This will automatically perform the necessary init and cleanup routines, and register
260/// all classes marked with `#[derive(GodotClass)]`, without needing to mention them in a central list. The order in which
261/// classes are registered is not specified.
262///
263/// ```
264/// use godot::init::*;
265///
266/// // This is just a type tag without any functionality.
267/// // Its name is irrelevant.
268/// struct MyExtension;
269///
270/// #[gdextension]
271/// unsafe impl ExtensionLibrary for MyExtension {}
272/// ```
273///
274/// # Custom entry symbol
275/// There is usually no reason to, but you can use a different entry point (C function in the dynamic library). This must match the key
276/// that you specify in the `.gdextension` file. Let's say your `.gdextension` file has such a section:
277/// ```toml
278/// [configuration]
279/// entry_symbol = "custom_name"
280/// ```
281/// then you can implement the trait like this:
282/// ```no_run
283/// # use godot::init::*;
284/// struct MyExtension;
285///
286/// #[gdextension(entry_symbol = custom_name)]
287/// unsafe impl ExtensionLibrary for MyExtension {}
288/// ```
289/// Note that this only changes the name. You cannot provide your own function -- use the [`on_level_init()`][ExtensionLibrary::on_level_init]
290/// hook for custom startup logic.
291///
292/// # Availability of Godot APIs during init and deinit
293// Init order: see also special_cases.rs > classify_codegen_level().
294/// Godot loads functionality gradually during its startup routines, and unloads it during shutdown. As a result, Godot classes are only
295/// available above a certain level. Trying to access a class API when it's not available will panic (if not, please report it as a bug).
296///
297/// A few singletons (`Engine`, `Os`, `Time`, `ProjectSettings`) are available from the `Core` level onward and can be used inside
298/// this method. Most other singletons are **not available during init** at all, and will only become accessible once the first frame has
299/// run.
300///
301/// The exact time a class is available depends on the Godot initialization logic, which is quite complex and may change between versions.
302/// To get an up-to-date view, inspect the Godot source code of [main.cpp], particularly `Main::setup()`, `Main::setup2()` and
303/// `Main::cleanup()` methods. Make sure to look at the correct version of the file.
304///
305/// In case of doubt, do not rely on classes being available during init/deinit.
306///
307/// [main.cpp]: https://github.com/godotengine/godot/blob/master/main/main.cpp
308///
309/// # Safety
310/// The library cannot enforce any safety guarantees outside Rust code, which means that **you as a user** are
311/// responsible to uphold them: namely in GDScript code or other GDExtension bindings loaded by the engine.
312/// Violating this may cause undefined behavior, even when invoking _safe_ functions.
313///
314/// If you use the `disengaged` [safeguard level], you accept that UB becomes possible even **in safe Rust APIs**, if you use them wrong
315/// (e.g. accessing a destroyed object).
316///
317/// [gdextension]: attr.gdextension.html
318/// [safety]: https://godot-rust.github.io/book/gdext/advanced/safety.html
319/// [safeguard level]: ../index.html#safeguard-levels
320// FIXME intra-doc link
321#[doc(alias = "entry_symbol", alias = "entry_point")]
322pub unsafe trait ExtensionLibrary {
323    /// Determines if and how an extension's code is run in the editor.
324    fn editor_run_behavior() -> EditorRunBehavior {
325        EditorRunBehavior::ToolClassesOnly
326    }
327
328    /// Determines the initialization level at which the extension is loaded (`Scene` by default).
329    ///
330    /// If the level is lower than [`InitLevel::Scene`], the engine needs to be restarted to take effect.
331    fn min_level() -> InitLevel {
332        InitLevel::Scene
333    }
334
335    /// Custom logic when a certain initialization stage is loaded.
336    ///
337    /// This will be invoked for stages >= [`Self::min_level()`], in ascending order. Use `if` or `match` to hook to specific stages.
338    ///
339    /// The stages are loaded in order: `Core` → `Servers` → `Scene` → `Editor` (if in editor) → `MainLoop` (4.5+).  \
340    /// The `MainLoop` stage represents the fully initialized state of Godot, after all initialization levels and classes have been loaded.
341    ///
342    /// See also [`on_main_loop_frame()`][Self::on_main_loop_frame] for per-frame processing.
343    ///
344    /// # Panics
345    /// If the overridden method panics, an error will be printed, but GDExtension loading is **not** aborted.
346    #[allow(unused_variables)]
347    #[expect(deprecated)] // Fall back to older API.
348    fn on_stage_init(stage: InitStage) {
349        stage
350            .try_to_level()
351            .inspect(|&level| Self::on_level_init(level));
352
353        #[cfg(since_api = "4.5")] // Compat layer.
354        if stage == InitStage::MainLoop {
355            Self::on_main_loop_startup();
356        }
357    }
358
359    /// Custom logic when a certain initialization stage is unloaded.
360    ///
361    /// This will be invoked for stages >= [`Self::min_level()`], in descending order. Use `if` or `match` to hook to specific stages.
362    ///
363    /// The stages are unloaded in reverse order: `MainLoop` (4.5+) → `Editor` (if in editor) → `Scene` → `Servers` → `Core`.  \
364    /// At the time `MainLoop` is deinitialized, all classes are still available.
365    ///
366    /// # Panics
367    /// If the overridden method panics, an error will be printed, but GDExtension unloading is **not** aborted.
368    #[allow(unused_variables)]
369    #[expect(deprecated)] // Fall back to older API.
370    fn on_stage_deinit(stage: InitStage) {
371        #[cfg(since_api = "4.5")] // Compat layer.
372        if stage == InitStage::MainLoop {
373            Self::on_main_loop_shutdown();
374        }
375
376        stage
377            .try_to_level()
378            .inspect(|&level| Self::on_level_deinit(level));
379    }
380
381    /// Old callback before [`on_stage_init()`][Self::on_stage_deinit] was added. Does not support `MainLoop` stage.
382    #[deprecated = "Use `on_stage_init()` instead, which also includes the MainLoop stage."]
383    #[allow(unused_variables)]
384    fn on_level_init(level: InitLevel) {
385        // Nothing by default.
386    }
387
388    /// Old callback before [`on_stage_deinit()`][Self::on_stage_deinit] was added. Does not support `MainLoop` stage.
389    #[deprecated = "Use `on_stage_deinit()` instead, which also includes the MainLoop stage."]
390    #[allow(unused_variables)]
391    fn on_level_deinit(level: InitLevel) {
392        // Nothing by default.
393    }
394
395    #[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
396    #[deprecated = "Use `on_stage_init(InitStage::MainLoop)` instead."]
397    #[doc(hidden)] // Added by mistake -- works but don't advertise.
398    fn on_main_loop_startup() {
399        // Nothing by default.
400    }
401
402    #[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
403    #[deprecated = "Use `on_stage_deinit(InitStage::MainLoop)` instead."]
404    #[doc(hidden)] // Added by mistake -- works but don't advertise.
405    fn on_main_loop_shutdown() {
406        // Nothing by default.
407    }
408
409    /// Callback invoked for every process frame.
410    ///
411    /// This is called during the main loop, after Godot is fully initialized. It runs after all
412    /// [`process()`][crate::classes::INode::process] methods on Node, and before the Godot-internal `ScriptServer::frame()`.
413    /// This is intended to be the equivalent of [`IScriptLanguageExtension::frame()`][`crate::classes::IScriptLanguageExtension::frame()`]
414    /// for GDExtension language bindings that don't use the script API.
415    ///
416    /// # Example
417    /// To hook into startup/shutdown of the main loop, use [`on_stage_init()`][Self::on_stage_init] and
418    /// [`on_stage_deinit()`][Self::on_stage_deinit] and watch for [`InitStage::MainLoop`].
419    ///
420    /// ```no_run
421    /// # use godot::init::*;
422    /// # struct MyExtension;
423    /// #[gdextension]
424    /// unsafe impl ExtensionLibrary for MyExtension {
425    ///     fn on_stage_init(stage: InitStage) {
426    ///         if stage == InitStage::MainLoop {
427    ///             // Startup code after fully initialized.
428    ///         }
429    ///     }
430    ///
431    ///     fn on_main_loop_frame() {
432    ///         // Per-frame logic.
433    ///     }
434    ///
435    ///     fn on_stage_deinit(stage: InitStage) {
436    ///         if stage == InitStage::MainLoop {
437    ///             // Cleanup code before shutdown.
438    ///         }
439    ///     }
440    /// }
441    /// ```
442    ///
443    /// # Panics
444    /// If the overridden method panics, an error will be printed, but execution continues.
445    #[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
446    fn on_main_loop_frame() {
447        // Nothing by default.
448    }
449
450    /// Whether to override the Wasm binary filename used by your GDExtension which the library should expect at runtime. Return `None`
451    /// to use the default where gdext expects either `{YourCrate}.wasm` (default binary name emitted by Rust) or
452    /// `{YourCrate}.threads.wasm` (for builds producing separate single-threaded and multi-threaded binaries).
453    ///
454    /// Upon exporting a game to the web, the library has to know at runtime the exact name of the `.wasm` binary file being used to load
455    /// each GDExtension. By default, Rust exports the binary as `cratename.wasm`, so that is the name checked by godot-rust by default.
456    ///
457    /// However, if you need to rename that binary, you can make the library aware of the new binary name by returning
458    /// `Some("newname.wasm")` (don't forget to **include the `.wasm` extension**).
459    ///
460    /// For example, to have two simultaneous versions, one supporting multi-threading and the other not, you could add a suffix to the
461    /// filename of the Wasm binary of the multi-threaded version in your build process. If you choose the suffix `.threads.wasm`,
462    /// you're in luck as godot-rust already accepts this suffix by default, but let's say you want to use a different suffix, such as
463    /// `-with-threads.wasm`. For this, you can have a `"nothreads"` feature which, when absent, should produce a suffixed binary,
464    /// which can be informed to gdext as follows:
465    ///
466    /// ```no_run
467    /// # use godot::init::*;
468    /// struct MyExtension;
469    ///
470    /// #[gdextension]
471    /// unsafe impl ExtensionLibrary for MyExtension {
472    ///     fn override_wasm_binary() -> Option<&'static str> {
473    ///         // Binary name unchanged ("mycrate.wasm") without thread support.
474    ///         #[cfg(feature = "nothreads")]
475    ///         return None;
476    ///
477    ///         // Tell godot-rust we add a custom suffix to the binary with thread support.
478    ///         // Please note that this is not needed if "mycrate.threads.wasm" is used.
479    ///         // (You could return `None` as well in that particular case.)
480    ///         #[cfg(not(feature = "nothreads"))]
481    ///         Some("mycrate-with-threads.wasm")
482    ///     }
483    /// }
484    /// ```
485    /// Note that simply overriding this method won't change the name of the Wasm binary produced by Rust automatically: you'll still
486    /// have to rename it by yourself in your build process, as well as specify the updated binary name in your `.gdextension` file.
487    /// This is just to ensure gdext is aware of the new name given to the binary, avoiding runtime errors.
488    fn override_wasm_binary() -> Option<&'static str> {
489        None
490    }
491}
492
493/// Determines if and how an extension's code is run in the editor.
494///
495/// By default, Godot 4 runs all virtual lifecycle callbacks (`_ready`, `_process`, `_physics_process`, ...)
496/// for extensions. This behavior is different from Godot 3, where extension classes needed to be explicitly
497/// marked as "tool".
498///
499/// In many cases, users write extension code with the intention to run in games, not inside the editor.
500/// This is why the default behavior in gdext deviates from Godot: lifecycle callbacks are disabled inside the
501/// editor (see [`ToolClassesOnly`][Self::ToolClassesOnly]). It is possible to configure this.
502///
503/// See also [`ExtensionLibrary::editor_run_behavior()`].
504#[derive(Copy, Clone, Debug)]
505#[non_exhaustive]
506pub enum EditorRunBehavior {
507    /// Only runs `#[class(tool)]` classes in the editor.
508    ///
509    /// All classes are registered, and calls from GDScript to Rust are possible. However, virtual lifecycle callbacks
510    /// (`_ready`, `_process`, `_physics_process`, ...) are not run unless the class is annotated with `#[class(tool)]`.
511    ToolClassesOnly,
512
513    /// Runs the extension with full functionality in editor.
514    ///
515    /// Ignores any `#[class(tool)]` annotations.
516    AllClasses,
517}
518
519// ----------------------------------------------------------------------------------------------------------------------------------------------
520
521pub use sys::InitLevel;
522
523// ----------------------------------------------------------------------------------------------------------------------------------------------
524
525/// # Safety
526///
527/// - Must be called from the main thread.
528/// - The interface must be initialized.
529/// - The `Scene` api level must have been initialized.
530#[deny(unsafe_op_in_unsafe_fn)]
531unsafe fn ensure_godot_features_compatible() {
532    // 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
533    // later, and godot-core would only depend on godot-sys. This makes future migrations easier. We still have access to builtins though.
534
535    out!("Check Godot precision setting...");
536
537    #[cfg(feature = "debug-log")] // Display safeguards level in debug log.
538    let safeguards_level = if cfg!(safeguards_strict) {
539        "strict"
540    } else if cfg!(safeguards_balanced) {
541        "balanced"
542    } else {
543        "disengaged"
544    };
545    out!("Safeguards: {safeguards_level}");
546
547    let os_class = StringName::from("OS");
548    let single = GString::from("single");
549    let double = GString::from("double");
550
551    let gdext_is_double = cfg!(feature = "double-precision");
552
553    // SAFETY: main thread, after initialize(), valid string pointers, `Scene` initialized.
554    let godot_is_double = unsafe {
555        let is_single = sys::godot_has_feature(os_class.string_sys(), single.sys());
556        let is_double = sys::godot_has_feature(os_class.string_sys(), double.sys());
557
558        assert_ne!(
559            is_single, is_double,
560            "Godot has invalid configuration: single={is_single}, double={is_double}"
561        );
562
563        is_double
564    };
565
566    let s = |is_double: bool| -> &'static str {
567        if is_double {
568            "double"
569        } else {
570            "single"
571        }
572    };
573
574    out!(
575        "Is double precision: Godot={}, gdext={}",
576        s(godot_is_double),
577        s(gdext_is_double)
578    );
579
580    if godot_is_double != gdext_is_double {
581        panic!(
582            "Godot runs with {} precision, but gdext was compiled with {} precision.\n\
583            Cargo feature `double-precision` must be used if and only if Godot is compiled with `precision=double`.\n",
584            s(godot_is_double), s(gdext_is_double),
585        );
586    }
587}