godot_ffi/
lib.rs

1#![cfg_attr(published_docs, feature(doc_cfg))]
2/*
3 * Copyright (c) godot-rust; Bromeon and contributors.
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
7 */
8
9//! # Internal crate of [**godot-rust**](https://godot-rust.github.io)
10//!
11//! Do not depend on this crate directly, instead use the `godot` crate.
12//! No SemVer or other guarantees are provided.
13//!
14//! # Contributor docs
15//!
16//! Low level bindings to the provided C core API.
17
18#![cfg_attr(test, allow(unused))]
19
20// ----------------------------------------------------------------------------------------------------------------------------------------------
21// Validations
22
23// More validations in godot crate. #[cfg]s are checked in godot-core.
24
25#[cfg(all(feature = "codegen-lazy-fptrs", feature = "experimental-threads"))] #[cfg_attr(published_docs, doc(cfg(all(feature = "codegen-lazy-fptrs", feature = "experimental-threads"))))]
26compile_error!(
27    "Cannot combine `lazy-function-tables` and `experimental-threads` features;\n\
28    thread safety for lazy-loaded function pointers is not yet implemented."
29);
30
31#[cfg(all(
32    feature = "experimental-wasm-nothreads",
33    feature = "experimental-threads"
34))]
35compile_error!("Cannot use 'experimental-threads' with a nothreads Wasm build yet.");
36
37// ----------------------------------------------------------------------------------------------------------------------------------------------
38
39// Output of generated code. Mimics the file structure, symbols are re-exported.
40#[rustfmt::skip]
41#[allow(
42    non_camel_case_types,
43    non_upper_case_globals,
44    non_snake_case,
45    deref_nullptr,
46    clippy::redundant_static_lifetimes,
47)]
48pub(crate) mod gen {
49    include!(concat!(env!("OUT_DIR"), "/mod.rs"));
50}
51
52pub mod conv;
53
54mod assertions;
55mod extras;
56mod global;
57mod godot_ffi;
58mod interface_init;
59#[cfg(target_os = "linux")] #[cfg_attr(published_docs, doc(cfg(target_os = "linux")))]
60pub mod linux_reload_workaround;
61mod opaque;
62mod plugins;
63mod string_cache;
64mod toolbox;
65
66#[doc(hidden)]
67#[cfg(target_family = "wasm")] #[cfg_attr(published_docs, doc(cfg(target_family = "wasm")))]
68pub use godot_macros::wasm_declare_init_fn;
69
70// No-op otherwise.
71#[doc(hidden)]
72#[cfg(not(target_family = "wasm"))] #[cfg_attr(published_docs, doc(cfg(not(target_family = "wasm"))))]
73#[macro_export]
74macro_rules! wasm_declare_init_fn {
75    () => {};
76}
77
78// Other
79pub use extras::*;
80pub use gen::central::*;
81pub use gen::gdextension_interface::*;
82pub use gen::interface::*;
83// Method tables
84pub use gen::table_builtins::*;
85pub use gen::table_builtins_lifecycle::*;
86pub use gen::table_core_classes::*;
87pub use gen::table_editor_classes::*;
88pub use gen::table_scene_classes::*;
89pub use gen::table_servers_classes::*;
90pub use gen::table_utilities::*;
91pub use global::*;
92pub use init_level::*;
93pub use string_cache::StringCache;
94pub use toolbox::*;
95
96pub use crate::godot_ffi::{
97    ExtVariantType, GodotFfi, GodotNullableFfi, PrimitiveConversionError, PtrcallType,
98};
99
100// ----------------------------------------------------------------------------------------------------------------------------------------------
101// API to access Godot via FFI
102
103mod binding;
104mod init_level;
105
106pub use binding::*;
107use binding::{
108    initialize_binding, initialize_builtin_method_table, initialize_class_core_method_table,
109    initialize_class_editor_method_table, initialize_class_scene_method_table,
110    initialize_class_server_method_table, runtime_metadata,
111};
112
113#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
114static MAIN_THREAD_ID: ManualInitCell<std::thread::ThreadId> = ManualInitCell::new();
115
116// ----------------------------------------------------------------------------------------------------------------------------------------------
117
118pub struct GdextRuntimeMetadata {
119    version_string: String,
120    version_triple: (u8, u8, u8),
121}
122
123impl GdextRuntimeMetadata {
124    pub fn load(sys_version: GDExtensionGodotVersion) -> Self {
125        // SAFETY: GDExtensionGodotVersion always contains valid string.
126        let version_string = unsafe { read_version_string(sys_version.string) };
127
128        let version_triple = (
129            sys_version.major as u8,
130            sys_version.minor as u8,
131            sys_version.patch as u8,
132        );
133
134        Self {
135            version_string,
136            version_triple,
137        }
138    }
139
140    // TODO(v0.5): CowStr, also in GdextBuild.
141    pub fn version_string(&self) -> &str {
142        &self.version_string
143    }
144
145    pub fn version_triple(&self) -> (u8, u8, u8) {
146        self.version_triple
147    }
148}
149
150// SAFETY: The `string` pointer in `godot_version` is only ever read from while the struct exists, so we cannot have any race conditions.
151unsafe impl Sync for GdextRuntimeMetadata {}
152// SAFETY: See `Sync` impl safety doc.
153unsafe impl Send for GdextRuntimeMetadata {}
154
155/// Initializes the library.
156///
157/// # Safety
158///
159/// - The `get_proc_address` pointer must be a function pointer of type [`GDExtensionInterfaceGetProcAddress`] (valid for Godot 4.1+).
160/// - The `library` pointer must be the pointer given by Godot at initialisation.
161/// - This function must not be called from multiple threads.
162/// - This function must be called before any use of [`get_library`].
163pub unsafe fn initialize(
164    get_proc_address: GDExtensionInterfaceGetProcAddress,
165    library: GDExtensionClassLibraryPtr,
166    config: GdextConfig,
167) {
168    out!("Initialize godot-rust...");
169
170    out!(
171        "Godot version against which godot-rust was compiled: {}",
172        GdextBuild::godot_static_version_string()
173    );
174
175    // We want to initialize the main thread ID as early as possible.
176    //
177    // SAFETY: We set the main thread ID exactly once here and never again.
178    #[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
179    unsafe {
180        MAIN_THREAD_ID.set(std::thread::current().id())
181    };
182
183    // Before anything else: if we run into a Godot binary that's compiled differently from gdext, proceeding would be UB -> panic.
184    interface_init::ensure_static_runtime_compatibility(get_proc_address);
185
186    // SAFETY: `ensure_static_runtime_compatibility` succeeded.
187    let version = unsafe { interface_init::runtime_version(get_proc_address) };
188    out!("Godot version of GDExtension API at runtime: {version:?}");
189
190    // SAFETY: `ensure_static_runtime_compatibility` succeeded.
191    let interface = unsafe { interface_init::load_interface(get_proc_address) };
192    out!("Loaded interface.");
193
194    // SAFETY: The interface was successfully loaded from Godot, so we should be able to load the builtin lifecycle table.
195    let global_method_table = unsafe { BuiltinLifecycleTable::load(&interface) };
196    out!("Loaded global method table.");
197
198    let mut string_names = StringCache::new(&interface, &global_method_table);
199
200    // SAFETY: The interface was successfully loaded from Godot, so we should be able to load the utility function table.
201    let utility_function_table =
202        unsafe { UtilityFunctionTable::load(&interface, &mut string_names) };
203    out!("Loaded utility function table.");
204
205    let runtime_metadata = GdextRuntimeMetadata::load(version);
206
207    let builtin_method_table = {
208        #[cfg(feature = "codegen-lazy-fptrs")]
209        {
210            None // loaded later
211        }
212        #[cfg(not(feature = "codegen-lazy-fptrs"))]
213        {
214            // SAFETY: The interface was successfully loaded from Godot, so we should be able to load the builtin function table.
215            let table = unsafe { BuiltinMethodTable::load(&interface, &mut string_names) };
216            out!("Loaded builtin method table.");
217            Some(table)
218        }
219    };
220
221    drop(string_names);
222
223    // SAFETY: This function is only called at initialization and not from multiple threads.
224    unsafe {
225        initialize_binding(GodotBinding::new(
226            interface,
227            library,
228            global_method_table,
229            utility_function_table,
230            runtime_metadata,
231            config,
232        ))
233    }
234
235    if let Some(table) = builtin_method_table {
236        // SAFETY: We initialized the bindings above and haven't called this function before.
237        unsafe { initialize_builtin_method_table(table) }
238    }
239
240    out!("Assigned binding.");
241
242    // Lazy case: load afterward because table's internal StringCache stores &'static references to the interface.
243    #[cfg(feature = "codegen-lazy-fptrs")]
244    {
245        // SAFETY: The interface was successfully loaded from Godot, so we should be able to load the builtin function table.
246        let table = unsafe { BuiltinMethodTable::load() };
247
248        unsafe { initialize_builtin_method_table(table) }
249
250        out!("Loaded builtin method table (lazily).");
251    }
252
253    print_preamble(version);
254}
255
256/// Deinitializes the library.
257///
258/// Does not perform much logic, mostly used for consistency:
259/// - Ensure that the binding is not accessed after it has been deinitialized.
260/// - Allow re-initialization for hot-reloading on Linux.
261///
262/// # Safety
263/// See [`initialize`].
264pub unsafe fn deinitialize() {
265    deinitialize_binding();
266
267    // MACOS-PARTIAL-RELOAD: Clear the main thread ID to allow re-initialization during hot reload.
268    #[cfg(not(wasm_nothreads))]
269    {
270        if MAIN_THREAD_ID.is_initialized() {
271            MAIN_THREAD_ID.clear();
272        }
273    }
274}
275
276fn safeguards_level_string() -> &'static str {
277    if cfg!(safeguards_strict) {
278        "strict"
279    } else if cfg!(safeguards_balanced) {
280        "balanced"
281    } else {
282        "disengaged"
283    }
284}
285
286fn print_preamble(version: GDExtensionGodotVersion) {
287    // SAFETY: GDExtensionGodotVersion always contains valid string.
288    let runtime_version = unsafe { read_version_string(version.string) };
289
290    let api_version: &'static str = GdextBuild::godot_static_version_string();
291    let safeguards_level = safeguards_level_string();
292    println!("Initialize godot-rust (API {api_version}, runtime {runtime_version}, safeguards {safeguards_level})");
293}
294
295/// # Safety
296///
297/// - Must be called from the main thread.
298/// - The interface must have been initialized with [`initialize`] before calling this function.
299/// - Must only be called once for each `api_level`.
300#[inline]
301pub unsafe fn load_class_method_table(api_level: InitLevel) {
302    out!("Load class method table for level '{:?}'...", api_level);
303    let begin = std::time::Instant::now();
304
305    #[cfg(not(feature = "codegen-lazy-fptrs"))]
306    // SAFETY: The interface has been initialized.
307    let interface = unsafe { get_interface() };
308
309    #[cfg(not(feature = "codegen-lazy-fptrs"))]
310    // SAFETY: The interface has been initialized.
311    let mut string_names = StringCache::new(interface, unsafe { builtin_lifecycle_api() });
312
313    let (class_count, method_count);
314    match api_level {
315        InitLevel::Core => {
316            // SAFETY: The interface has been initialized and this function hasn't been called before.
317            unsafe {
318                #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
319                initialize_class_core_method_table(ClassCoreMethodTable::load());
320                #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
321                initialize_class_core_method_table(ClassCoreMethodTable::load(
322                    interface,
323                    &mut string_names,
324                ));
325            }
326            class_count = ClassCoreMethodTable::CLASS_COUNT;
327            method_count = ClassCoreMethodTable::METHOD_COUNT;
328        }
329        InitLevel::Servers => {
330            // SAFETY: The interface has been initialized and this function hasn't been called before.
331            unsafe {
332                #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
333                initialize_class_server_method_table(ClassServersMethodTable::load());
334                #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
335                initialize_class_server_method_table(ClassServersMethodTable::load(
336                    interface,
337                    &mut string_names,
338                ));
339            }
340            class_count = ClassServersMethodTable::CLASS_COUNT;
341            method_count = ClassServersMethodTable::METHOD_COUNT;
342        }
343        InitLevel::Scene => {
344            // SAFETY: The interface has been initialized and this function hasn't been called before.
345            unsafe {
346                #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
347                initialize_class_scene_method_table(ClassSceneMethodTable::load());
348                #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
349                initialize_class_scene_method_table(ClassSceneMethodTable::load(
350                    interface,
351                    &mut string_names,
352                ));
353            }
354            class_count = ClassSceneMethodTable::CLASS_COUNT;
355            method_count = ClassSceneMethodTable::METHOD_COUNT;
356        }
357        InitLevel::Editor => {
358            // SAFETY: The interface has been initialized and this function hasn't been called before.
359            unsafe {
360                #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
361                initialize_class_editor_method_table(ClassEditorMethodTable::load());
362                #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
363                initialize_class_editor_method_table(ClassEditorMethodTable::load(
364                    interface,
365                    &mut string_names,
366                ));
367            }
368            class_count = ClassEditorMethodTable::CLASS_COUNT;
369            method_count = ClassEditorMethodTable::METHOD_COUNT;
370        }
371    }
372
373    let _elapsed = std::time::Instant::now() - begin;
374    out!(
375        "{:?} level: loaded {} classes and {} methods in {}s.",
376        api_level,
377        class_count,
378        method_count,
379        _elapsed.as_secs_f64()
380    );
381}
382
383/// # Safety
384///
385/// - Must be accessed from the main thread.
386/// - The interface must have been initialized.
387/// - The `Scene` api level must have been initialized.
388/// - `os_class_sname` must be a valid `StringName` pointer.
389/// - `tag_string` must be a valid type pointer of a `String` instance.
390#[inline]
391pub unsafe fn godot_has_feature(
392    os_class_sname: GDExtensionConstStringNamePtr,
393    tag_string: GDExtensionConstTypePtr,
394) -> bool {
395    // Issue a raw C call to OS.has_feature(tag_string).
396
397    // SAFETY: Called from main thread, interface has been initialized, and the scene api has been initialized.
398    let method_bind = unsafe { class_core_api() }.os__has_feature();
399
400    // SAFETY: Called from main thread, and interface has been initialized.
401    let interface = unsafe { get_interface() };
402    let get_singleton = interface.global_get_singleton.unwrap();
403    let class_ptrcall = interface.object_method_bind_ptrcall.unwrap();
404
405    // SAFETY: Interface has been initialized, and `Scene` has been initialized, so `get_singleton` can be called. `os_class_sname` is a valid
406    // `StringName` pointer.
407    let object_ptr = unsafe { get_singleton(os_class_sname) };
408    let mut return_ptr = false;
409    let type_ptrs = [tag_string];
410
411    // SAFETY: We are properly passing arguments to make a ptrcall.
412    unsafe {
413        class_ptrcall(
414            method_bind.0,
415            object_ptr,
416            type_ptrs.as_ptr(),
417            return_ptr.sys_mut(),
418        )
419    }
420
421    return_ptr
422}
423
424/// Get the [`ThreadId`](std::thread::ThreadId) of the main thread.
425///
426/// # Panics
427/// - If it is called before the engine bindings have been initialized.
428#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
429pub fn main_thread_id() -> std::thread::ThreadId {
430    assert!(
431        MAIN_THREAD_ID.is_initialized(),
432        "Godot engine not available; make sure you are not calling it from unit/doc tests"
433    );
434
435    // SAFETY: We initialized the cell during library initialization, before any other code is executed.
436    let thread_id = unsafe { MAIN_THREAD_ID.get_unchecked() };
437
438    *thread_id
439}
440
441/// Check if the current thread is the main thread.
442///
443/// # Panics
444/// - If it is called before the engine bindings have been initialized.
445pub fn is_main_thread() -> bool {
446    #[cfg(not(wasm_nothreads))]
447    {
448        std::thread::current().id() == main_thread_id()
449    }
450
451    #[cfg(wasm_nothreads)]
452    {
453        true
454    }
455}
456
457/// Assign the current thread id to be the main thread.
458///
459/// This is required for platforms on which Godot runs the main loop on a different thread than the thread the library was loaded on.
460/// Android is one such platform.
461///
462/// # Safety
463///
464/// - must only be called after [`initialize`] has been called.
465pub unsafe fn discover_main_thread() {
466    #[cfg(not(wasm_nothreads))]
467    {
468        if is_main_thread() {
469            // we don't have to do anything if the current thread is already the main thread.
470            return;
471        }
472
473        let thread_id = std::thread::current().id();
474
475        // SAFETY: initialize must have already been called before this function is called. By clearing and setting the cell again we can reinitialize it.
476        unsafe {
477            MAIN_THREAD_ID.clear();
478            MAIN_THREAD_ID.set(thread_id);
479        }
480    }
481}
482
483/// Construct Godot object.
484///
485/// "NOTIFICATION_POSTINITIALIZE" must be sent after construction since 4.4.
486///
487/// # Safety
488/// `class_name` is assumed to be valid.
489pub unsafe fn classdb_construct_object(
490    class_name: GDExtensionConstStringNamePtr,
491) -> GDExtensionObjectPtr {
492    #[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
493    return interface_fn!(classdb_construct_object)(class_name);
494
495    #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
496    return interface_fn!(classdb_construct_object2)(class_name);
497}
498
499// ----------------------------------------------------------------------------------------------------------------------------------------------
500// Macros to access low-level function bindings
501
502#[macro_export]
503#[doc(hidden)]
504macro_rules! builtin_fn {
505    ($name:ident $(@1)?) => {
506        $crate::builtin_lifecycle_api().$name
507    };
508}
509
510#[macro_export]
511#[doc(hidden)]
512macro_rules! builtin_call {
513        ($name:ident ( $($args:expr),* $(,)? )) => {
514            ($crate::builtin_lifecycle_api().$name)( $($args),* )
515        };
516    }
517
518#[macro_export]
519#[doc(hidden)]
520macro_rules! interface_fn {
521    ($name:ident) => {{
522        unsafe { $crate::get_interface().$name.unwrap_unchecked() }
523    }};
524}