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};
20}
21pub use reexport_pub::*;
22
23#[doc(hidden)]
24#[deny(unsafe_op_in_unsafe_fn)]
25pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
26 get_proc_address: sys::GDExtensionInterfaceGetProcAddress,
27 library: sys::GDExtensionClassLibraryPtr,
28 init: *mut sys::GDExtensionInitialization,
29) -> sys::GDExtensionBool {
30 let init_code = || {
31 // 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
32 // cause TLS-destructors to run then we have a setting already for how to deal with them. Otherwise, this could cause the default
33 // behavior to kick in and disable hot reloading.
34 #[cfg(target_os = "linux")] #[cfg_attr(published_docs, doc(cfg(target_os = "linux")))]
35 sys::linux_reload_workaround::default_set_hot_reload();
36
37 let tool_only_in_editor = match E::editor_run_behavior() {
38 EditorRunBehavior::ToolClassesOnly => true,
39 EditorRunBehavior::AllClasses => false,
40 };
41
42 let config = sys::GdextConfig::new(tool_only_in_editor);
43
44 // SAFETY: no custom code has run yet + no other thread is accessing global handle.
45 unsafe {
46 sys::initialize(get_proc_address, library, config);
47 }
48
49 // With experimental-features enabled, we can always print panics to godot_print!
50 #[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
51 crate::private::set_gdext_hook(|| true);
52
53 // Without experimental-features enabled, we can only print panics with godot_print! if the panic occurs on the main (Godot) thread.
54 #[cfg(not(feature = "experimental-threads"))]
55 {
56 let main_thread = std::thread::current().id();
57 crate::private::set_gdext_hook(move || std::thread::current().id() == main_thread);
58 }
59
60 // Currently no way to express failure; could be exposed to E if necessary.
61 // No early exit, unclear if Godot still requires output parameters to be set.
62 let success = true;
63
64 let godot_init_params = sys::GDExtensionInitialization {
65 minimum_initialization_level: E::min_level().to_sys(),
66 userdata: std::ptr::null_mut(),
67 initialize: Some(ffi_initialize_layer::<E>),
68 deinitialize: Some(ffi_deinitialize_layer::<E>),
69 };
70
71 // SAFETY: Godot is responsible for passing us a valid pointer.
72 unsafe {
73 *init = godot_init_params;
74 }
75
76 success as u8
77 };
78
79 // Use std::panic::catch_unwind instead of handle_panic: handle_panic uses TLS, which
80 // calls `thread_atexit` on linux, which sets the hot reloading flag on linux.
81 // Using std::panic::catch_unwind avoids this, although we lose out on context information
82 // for debugging.
83 let is_success = std::panic::catch_unwind(init_code);
84
85 is_success.unwrap_or(0)
86}
87
88static LEVEL_SERVERS_CORE_LOADED: AtomicBool = AtomicBool::new(false);
89
90unsafe extern "C" fn ffi_initialize_layer<E: ExtensionLibrary>(
91 _userdata: *mut std::ffi::c_void,
92 init_level: sys::GDExtensionInitializationLevel,
93) {
94 let level = InitLevel::from_sys(init_level);
95 let ctx = || format!("failed to initialize GDExtension level `{level:?}`");
96
97 fn try_load<E: ExtensionLibrary>(level: InitLevel) {
98 // Workaround for https://github.com/godot-rust/gdext/issues/629:
99 // When using editor plugins, Godot may unload all levels but only reload from Scene upward.
100 // Manually run initialization of lower levels.
101
102 // TODO: Remove this workaround once after the upstream issue is resolved.
103 if level == InitLevel::Scene {
104 if !LEVEL_SERVERS_CORE_LOADED.load(Ordering::Relaxed) {
105 try_load::<E>(InitLevel::Core);
106 try_load::<E>(InitLevel::Servers);
107 }
108 } else if level == InitLevel::Core {
109 // When it's normal initialization, the `Servers` level is normally initialized.
110 LEVEL_SERVERS_CORE_LOADED.store(true, Ordering::Relaxed);
111 }
112
113 // SAFETY: Godot will call this from the main thread, after `__gdext_load_library` where the library is initialized,
114 // and only once per level.
115 unsafe { gdext_on_level_init(level) };
116 E::on_level_init(level);
117 }
118
119 // Swallow panics. TODO consider crashing if gdext init fails.
120 let _ = crate::private::handle_panic(ctx, || {
121 try_load::<E>(level);
122 });
123}
124
125unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
126 _userdata: *mut std::ffi::c_void,
127 init_level: sys::GDExtensionInitializationLevel,
128) {
129 let level = InitLevel::from_sys(init_level);
130 let ctx = || format!("failed to deinitialize GDExtension level `{level:?}`");
131
132 // Swallow panics.
133 let _ = crate::private::handle_panic(ctx, || {
134 if level == InitLevel::Core {
135 // Once the CORE api is unloaded, reset the flag to initial state.
136 LEVEL_SERVERS_CORE_LOADED.store(false, Ordering::Relaxed);
137 }
138
139 E::on_level_deinit(level);
140 gdext_on_level_deinit(level);
141 });
142}
143
144/// Tasks needed to be done by gdext internally upon loading an initialization level. Called before user code.
145///
146/// # Safety
147///
148/// - Must be called from the main thread.
149/// - The interface must have been initialized.
150/// - Must only be called once per level.
151#[deny(unsafe_op_in_unsafe_fn)]
152unsafe fn gdext_on_level_init(level: InitLevel) {
153 // 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 // (e.g. class registration). This would break the assumption that the load_class_method_table() calls are exclusive.
155 // We could maybe protect globals with a mutex until initialization is complete, and then move it to a directly-accessible, read-only static.
156
157 // SAFETY: we are in the main thread, initialize has been called, has never been called with this level before.
158 unsafe { sys::load_class_method_table(level) };
159
160 match level {
161 InitLevel::Servers => {
162 // SAFETY: called from the main thread, sys::initialized has already been called.
163 unsafe { sys::discover_main_thread() };
164 }
165 InitLevel::Scene => {
166 // SAFETY: On the main thread, api initialized, `Scene` was initialized above.
167 unsafe { ensure_godot_features_compatible() };
168 }
169 InitLevel::Editor => {
170 #[cfg(all(since_api = "4.3", feature = "register-docs"))]
171 // SAFETY: Godot binding is initialized, and this is called from the main thread.
172 unsafe {
173 crate::docs::register();
174 }
175 }
176 _ => (),
177 }
178
179 crate::registry::class::auto_register_classes(level);
180}
181
182/// Tasks needed to be done by gdext internally upon unloading an initialization level. Called after user code.
183fn gdext_on_level_deinit(level: InitLevel) {
184 crate::registry::class::unregister_classes(level);
185
186 if level == InitLevel::Core {
187 // If lowest level is unloaded, call global deinitialization.
188 // No business logic by itself, but ensures consistency if re-initialization (hot-reload on Linux) occurs.
189
190 #[cfg(since_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.2")))]
191 crate::task::cleanup();
192
193 // Garbage-collect various statics.
194 // SAFETY: this is the last time meta APIs are used.
195 unsafe {
196 crate::meta::cleanup();
197 }
198
199 // SAFETY: called after all other logic, so no concurrent access.
200 // TODO: multithreading must make sure other threads are joined/stopped here.
201 unsafe {
202 sys::deinitialize();
203 }
204 }
205}
206
207// ----------------------------------------------------------------------------------------------------------------------------------------------
208
209/// Defines the entry point for a GDExtension Rust library.
210///
211/// Every library should have exactly one implementation of this trait. It is always used in combination with the
212/// [`#[gdextension]`][gdextension] proc-macro attribute.
213///
214/// # Example
215/// The simplest usage is as follows. This will automatically perform the necessary init and cleanup routines, and register
216/// all classes marked with `#[derive(GodotClass)]`, without needing to mention them in a central list. The order in which
217/// classes are registered is not specified.
218///
219/// ```
220/// use godot::init::*;
221///
222/// // This is just a type tag without any functionality.
223/// // Its name is irrelevant.
224/// struct MyExtension;
225///
226/// #[gdextension]
227/// unsafe impl ExtensionLibrary for MyExtension {}
228/// ```
229///
230/// # Custom entry symbol
231/// 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
232/// that you specify in the `.gdextension` file. Let's say your `.gdextension` file has such a section:
233/// ```toml
234/// [configuration]
235/// entry_symbol = "custom_name"
236/// ```
237/// then you can implement the trait like this:
238/// ```no_run
239/// # use godot::init::*;
240/// struct MyExtension;
241///
242/// #[gdextension(entry_symbol = custom_name)]
243/// unsafe impl ExtensionLibrary for MyExtension {}
244/// ```
245/// Note that this only changes the name. You cannot provide your own function -- use the [`on_level_init()`][ExtensionLibrary::on_level_init]
246/// hook for custom startup logic.
247///
248/// # Safety
249/// The library cannot enforce any safety guarantees outside Rust code, which means that **you as a user** are
250/// responsible to uphold them: namely in GDScript code or other GDExtension bindings loaded by the engine.
251/// Violating this may cause undefined behavior, even when invoking _safe_ functions.
252///
253/// [gdextension]: attr.gdextension.html
254/// [safety]: https://godot-rust.github.io/book/gdext/advanced/safety.html
255// FIXME intra-doc link
256#[doc(alias = "entry_symbol", alias = "entry_point")]
257pub unsafe trait ExtensionLibrary {
258 /// Determines if and how an extension's code is run in the editor.
259 fn editor_run_behavior() -> EditorRunBehavior {
260 EditorRunBehavior::ToolClassesOnly
261 }
262
263 /// Determines the initialization level at which the extension is loaded (`Scene` by default).
264 ///
265 /// If the level is lower than [`InitLevel::Scene`], the engine needs to be restarted to take effect.
266 fn min_level() -> InitLevel {
267 InitLevel::Scene
268 }
269
270 /// Custom logic when a certain init-level of Godot is loaded.
271 ///
272 /// This will only be invoked for levels >= [`Self::min_level()`], in ascending order. Use `if` or `match` to hook to specific levels.
273 #[allow(unused_variables)]
274 fn on_level_init(level: InitLevel) {
275 // Nothing by default.
276 }
277
278 /// Custom logic when a certain init-level of Godot is unloaded.
279 ///
280 /// This will only be invoked for levels >= [`Self::min_level()`], in descending order. Use `if` or `match` to hook to specific levels.
281 #[allow(unused_variables)]
282 fn on_level_deinit(level: InitLevel) {
283 // Nothing by default.
284 }
285
286 /// Whether to override the Wasm binary filename used by your GDExtension which the library should expect at runtime. Return `None`
287 /// to use the default where gdext expects either `{YourCrate}.wasm` (default binary name emitted by Rust) or
288 /// `{YourCrate}.threads.wasm` (for builds producing separate single-threaded and multi-threaded binaries).
289 ///
290 /// 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
291 /// each GDExtension. By default, Rust exports the binary as `cratename.wasm`, so that is the name checked by godot-rust by default.
292 ///
293 /// However, if you need to rename that binary, you can make the library aware of the new binary name by returning
294 /// `Some("newname.wasm")` (don't forget to **include the `.wasm` extension**).
295 ///
296 /// For example, to have two simultaneous versions, one supporting multi-threading and the other not, you could add a suffix to the
297 /// filename of the Wasm binary of the multi-threaded version in your build process. If you choose the suffix `.threads.wasm`,
298 /// 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
299 /// `-with-threads.wasm`. For this, you can have a `"nothreads"` feature which, when absent, should produce a suffixed binary,
300 /// which can be informed to gdext as follows:
301 ///
302 /// ```no_run
303 /// # use godot::init::*;
304 /// struct MyExtension;
305 ///
306 /// #[gdextension]
307 /// unsafe impl ExtensionLibrary for MyExtension {
308 /// fn override_wasm_binary() -> Option<&'static str> {
309 /// // Binary name unchanged ("mycrate.wasm") without thread support.
310 /// #[cfg(feature = "nothreads")]
311 /// return None;
312 ///
313 /// // Tell gdext we add a custom suffix to the binary with thread support.
314 /// // Please note that this is not needed if "mycrate.threads.wasm" is used.
315 /// // (You could return `None` as well in that particular case.)
316 /// #[cfg(not(feature = "nothreads"))]
317 /// Some("mycrate-with-threads.wasm")
318 /// }
319 /// }
320 /// ```
321 /// Note that simply overriding this method won't change the name of the Wasm binary produced by Rust automatically: you'll still
322 /// have to rename it by yourself in your build process, as well as specify the updated binary name in your `.gdextension` file.
323 /// This is just to ensure gdext is aware of the new name given to the binary, avoiding runtime errors.
324 fn override_wasm_binary() -> Option<&'static str> {
325 None
326 }
327}
328
329/// Determines if and how an extension's code is run in the editor.
330///
331/// By default, Godot 4 runs all virtual lifecycle callbacks (`_ready`, `_process`, `_physics_process`, ...)
332/// for extensions. This behavior is different from Godot 3, where extension classes needed to be explicitly
333/// marked as "tool".
334///
335/// In many cases, users write extension code with the intention to run in games, not inside the editor.
336/// This is why the default behavior in gdext deviates from Godot: lifecycle callbacks are disabled inside the
337/// editor (see [`ToolClassesOnly`][Self::ToolClassesOnly]). It is possible to configure this.
338///
339/// See also [`ExtensionLibrary::editor_run_behavior()`].
340#[derive(Copy, Clone, Debug)]
341#[non_exhaustive]
342pub enum EditorRunBehavior {
343 /// Only runs `#[class(tool)]` classes in the editor.
344 ///
345 /// All classes are registered, and calls from GDScript to Rust are possible. However, virtual lifecycle callbacks
346 /// (`_ready`, `_process`, `_physics_process`, ...) are not run unless the class is annotated with `#[class(tool)]`.
347 ToolClassesOnly,
348
349 /// Runs the extension with full functionality in editor.
350 ///
351 /// Ignores any `#[class(tool)]` annotations.
352 AllClasses,
353}
354
355// ----------------------------------------------------------------------------------------------------------------------------------------------
356
357/// Stage of the Godot initialization process.
358///
359/// Godot's initialization and deinitialization processes are split into multiple stages, like a stack. At each level,
360/// a different amount of engine functionality is available. Deinitialization happens in reverse order.
361///
362/// See also:
363/// - [`ExtensionLibrary::on_level_init()`]
364/// - [`ExtensionLibrary::on_level_deinit()`]
365pub type InitLevel = sys::InitLevel;
366
367// ----------------------------------------------------------------------------------------------------------------------------------------------
368
369/// # Safety
370///
371/// - Must be called from the main thread.
372/// - The interface must be initialized.
373/// - The `Scene` api level must have been initialized.
374#[deny(unsafe_op_in_unsafe_fn)]
375unsafe fn ensure_godot_features_compatible() {
376 // 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
377 // later, and godot-core would only depend on godot-sys. This makes future migrations easier. We still have access to builtins though.
378
379 out!("Check Godot precision setting...");
380
381 let os_class = StringName::from("OS");
382 let single = GString::from("single");
383 let double = GString::from("double");
384
385 let gdext_is_double = cfg!(feature = "double-precision");
386
387 // SAFETY: main thread, after initialize(), valid string pointers, `Scene` initialized.
388 let godot_is_double = unsafe {
389 let is_single = sys::godot_has_feature(os_class.string_sys(), single.sys());
390 let is_double = sys::godot_has_feature(os_class.string_sys(), double.sys());
391
392 assert_ne!(
393 is_single, is_double,
394 "Godot has invalid configuration: single={is_single}, double={is_double}"
395 );
396
397 is_double
398 };
399
400 let s = |is_double: bool| -> &'static str {
401 if is_double {
402 "double"
403 } else {
404 "single"
405 }
406 };
407
408 out!(
409 "Is double precision: Godot={}, gdext={}",
410 s(godot_is_double),
411 s(gdext_is_double)
412 );
413
414 if godot_is_double != gdext_is_double {
415 panic!(
416 "Godot runs with {} precision, but gdext was compiled with {} precision.\n\
417 Cargo feature `double-precision` must be used if and only if Godot is compiled with `precision=double`.\n",
418 s(godot_is_double), s(gdext_is_double),
419 );
420 }
421}