godot_ffi/
toolbox.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
8//! Functions and macros that are not very specific to gdext, but come in handy.
9
10use std::fmt::{Display, Write};
11
12use crate as sys;
13
14// ----------------------------------------------------------------------------------------------------------------------------------------------
15// Macros
16
17/// Trace output.
18#[cfg(feature = "debug-log")] #[cfg_attr(published_docs, doc(cfg(feature = "debug-log")))]
19#[macro_export]
20macro_rules! out {
21    ()                          => (eprintln!());
22    ($fmt:literal)              => (eprintln!($fmt));
23    ($fmt:literal, $($arg:tt)*) => (eprintln!($fmt, $($arg)*));
24}
25
26/// Trace output.
27#[cfg(not(feature = "debug-log"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "debug-log"))))]
28#[macro_export]
29macro_rules! out {
30    ()                          => ({});
31    ($fmt:literal)              => ({});
32    ($fmt:literal, $($arg:tt)*) => {{
33        // Discard; should not generate any code.
34        if false {
35            format_args!($fmt, $($arg)*);
36        }
37    }}
38}
39
40/// Extract a function pointer from its `Option` and convert it to the (dereferenced) target type.
41///
42/// ```ignore
43///  let get_godot_version = get_proc_address(sys::c_str(b"get_godot_version\0"));
44///  let get_godot_version = sys::cast_fn_ptr!(get_godot_version as sys::GDExtensionInterfaceGetGodotVersion);
45/// ```
46///
47/// # Safety
48///
49/// `$ToType` must be an option of an `unsafe extern "C"` function pointer.
50#[allow(unused)]
51#[macro_export]
52macro_rules! unsafe_cast_fn_ptr {
53    ($option:ident as $ToType:ty) => {{
54        // SAFETY: `$ToType` is an `unsafe extern "C"` function pointer and is thus compatible with `unsafe extern "C" fn()`.
55        // And `Option<T>` is compatible with `Option<U>` when both `T` and `U` are compatible function pointers.
56        #[allow(unused_unsafe)]
57        let ptr: Option<_> = unsafe { std::mem::transmute::<Option<unsafe extern "C" fn()>, $ToType>($option) };
58        ptr.expect("null function pointer")
59    }};
60}
61
62// ----------------------------------------------------------------------------------------------------------------------------------------------
63// Utility functions
64
65/// Extract value from box before `into_inner()` is stable
66#[allow(clippy::boxed_local)] // false positive
67pub fn unbox<T>(value: Box<T>) -> T {
68    // Deref-move is a Box magic feature; see https://stackoverflow.com/a/42264074
69    *value
70}
71
72/// Explicitly cast away `const` from a pointer, similar to C++ `const_cast`.
73///
74/// The `as` conversion simultaneously doing 10 other things, potentially causing unintended transmutations.
75pub fn force_mut_ptr<T>(ptr: *const T) -> *mut T {
76    ptr as *mut T
77}
78
79/// Add `const` to a mut ptr.
80pub fn to_const_ptr<T>(ptr: *mut T) -> *const T {
81    ptr as *const T
82}
83
84/// If `ptr` is not null, returns `Some(mapper(ptr))`; otherwise `None`.
85#[inline]
86pub fn ptr_then<T, R, F>(ptr: *mut T, mapper: F) -> Option<R>
87where
88    F: FnOnce(*mut T) -> R,
89{
90    // Could also use NonNull in signature, but for this project we always deal with FFI raw pointers
91    if ptr.is_null() {
92        None
93    } else {
94        Some(mapper(ptr))
95    }
96}
97
98/// Returns a C `const char*` for a null-terminated byte string.
99#[inline]
100pub fn c_str(s: &[u8]) -> *const std::ffi::c_char {
101    // Ensure null-terminated
102    crate::strict_assert!(!s.is_empty() && s[s.len() - 1] == 0);
103
104    s.as_ptr() as *const std::ffi::c_char
105}
106
107/// Returns a C `const char*` for a null-terminated string slice. UTF-8 encoded.
108#[inline]
109pub fn c_str_from_str(s: &str) -> *const std::ffi::c_char {
110    c_str(s.as_bytes())
111}
112
113/// Returns an ad-hoc hash of any object.
114pub fn hash_value<T: std::hash::Hash>(t: &T) -> u64 {
115    use std::hash::Hasher;
116    let mut hasher = std::collections::hash_map::DefaultHasher::new();
117    t.hash(&mut hasher);
118    hasher.finish()
119}
120
121pub fn join<T, I>(iter: I) -> String
122where
123    T: std::fmt::Display,
124    I: Iterator<Item = T>,
125{
126    join_with(iter, ", ", |item| format!("{item}"))
127}
128
129pub fn join_debug<T, I>(iter: I) -> String
130where
131    T: std::fmt::Debug,
132    I: Iterator<Item = T>,
133{
134    join_with(iter, ", ", |item| format!("{item:?}"))
135}
136
137pub fn join_with<T, I, F, S>(mut iter: I, sep: &str, mut format_elem: F) -> String
138where
139    I: Iterator<Item = T>,
140    F: FnMut(&T) -> S,
141    S: Display,
142{
143    let mut result = String::new();
144
145    if let Some(first) = iter.next() {
146        // write! propagates error only if given formatter fails.
147        // String formatting by itself is an infallible operation.
148        // Read more at: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-traits
149        write!(&mut result, "{first}", first = format_elem(&first))
150            .expect("Formatter should not fail!");
151        for item in iter {
152            write!(&mut result, "{sep}{item}", item = format_elem(&item))
153                .expect("Formatter should not fail!");
154        }
155    }
156    result
157}
158
159pub fn i64_to_ordering(value: i64) -> std::cmp::Ordering {
160    match value {
161        -1 => std::cmp::Ordering::Less,
162        0 => std::cmp::Ordering::Equal,
163        1 => std::cmp::Ordering::Greater,
164        _ => panic!("cannot convert value {value} to cmp::Ordering"),
165    }
166}
167
168/// Converts a Godot "found" index `Option<usize>`, where -1 is mapped to `None`.
169pub fn found_to_option(index: i64) -> Option<usize> {
170    if index == -1 {
171        None
172    } else {
173        // If this fails, then likely because we overlooked a negative value.
174        let index_usize = index
175            .try_into()
176            .unwrap_or_else(|_| panic!("unexpected index {index} returned from Godot function"));
177
178        Some(index_usize)
179    }
180}
181
182/*
183pub fn unqualified_type_name<T>() -> &'static str {
184    let type_name = std::any::type_name::<T>();
185    type_name.split("::").last().unwrap()
186}
187*/
188
189/// Like [`std::any::type_name`], but returns a short type name without module paths.
190pub fn short_type_name<T: ?Sized>() -> String {
191    let full_name = std::any::type_name::<T>();
192    strip_module_paths(full_name)
193}
194
195/// Like [`std::any::type_name_of_val`], but returns a short type name without module paths.
196pub fn short_type_name_of_val<T: ?Sized>(val: &T) -> String {
197    let full_name = std::any::type_name_of_val(val);
198    strip_module_paths(full_name)
199}
200
201/// Helper function to strip module paths from a fully qualified type name.
202fn strip_module_paths(full_name: &str) -> String {
203    let mut result = String::new();
204    let mut identifier = String::new();
205
206    let mut chars = full_name.chars().peekable();
207
208    while let Some(c) = chars.next() {
209        match c {
210            '<' | '>' | ',' | ' ' | '&' | '(' | ')' | '[' | ']' => {
211                // Process the current identifier.
212                if !identifier.is_empty() {
213                    let short_name = identifier.split("::").last().unwrap_or(&identifier);
214                    result.push_str(short_name);
215                    identifier.clear();
216                }
217                result.push(c);
218
219                // Handle spaces after commas for readability.
220                if c == ',' && chars.peek().is_some_and(|&next_c| next_c != ' ') {
221                    result.push(' ');
222                }
223            }
224            ':' => {
225                // Check for '::' indicating module path separator.
226                if chars.peek() == Some(&':') {
227                    // Skip the second ':'
228                    chars.next();
229                    identifier.push_str("::");
230                } else {
231                    identifier.push(c);
232                }
233            }
234            _ => {
235                // Part of an identifier.
236                identifier.push(c);
237            }
238        }
239    }
240
241    // Process any remaining identifier.
242    if !identifier.is_empty() {
243        let short_name = identifier.split("::").last().unwrap_or(&identifier);
244        result.push_str(short_name);
245    }
246
247    result
248}
249
250// ----------------------------------------------------------------------------------------------------------------------------------------------
251// Private helpers
252
253/// Metafunction to extract inner function pointer types from all the bindgen `Option<F>` type names.
254/// Needed for `unsafe_cast_fn_ptr` macro.
255pub trait Inner: Sized {
256    type FnPtr: Sized;
257}
258
259impl<T> Inner for Option<T> {
260    type FnPtr = T;
261}
262
263// ----------------------------------------------------------------------------------------------------------------------------------------------
264// Function types used for table loaders
265
266pub(crate) type GetClassMethod = unsafe extern "C" fn(
267    p_classname: sys::GDExtensionConstStringNamePtr,
268    p_methodname: sys::GDExtensionConstStringNamePtr,
269    p_hash: sys::GDExtensionInt,
270) -> sys::GDExtensionMethodBindPtr;
271
272/// Newtype around `GDExtensionMethodBindPtr` so we can implement `Sync` and `Send` for it manually.    
273#[derive(Copy, Clone)]
274pub struct ClassMethodBind(pub sys::GDExtensionMethodBindPtr);
275
276// SAFETY: `sys::GDExtensionMethodBindPtr` is effectively the same as a `unsafe extern "C" fn`. So sharing it between
277// threads is fine, as using it in any way requires `unsafe` and it is up to the caller to ensure it is thread safe
278// to do so.
279unsafe impl Sync for ClassMethodBind {}
280// SAFETY: See `Sync` impl safety doc.
281unsafe impl Send for ClassMethodBind {}
282
283pub(crate) type GetBuiltinMethod = unsafe extern "C" fn(
284    p_type: sys::GDExtensionVariantType,
285    p_method: sys::GDExtensionConstStringNamePtr,
286    p_hash: sys::GDExtensionInt,
287) -> sys::GDExtensionPtrBuiltInMethod;
288
289// GDExtensionPtrBuiltInMethod
290pub type BuiltinMethodBind = unsafe extern "C" fn(
291    p_base: sys::GDExtensionTypePtr,
292    p_args: *const sys::GDExtensionConstTypePtr,
293    r_return: sys::GDExtensionTypePtr,
294    p_argument_count: std::os::raw::c_int,
295);
296
297pub(crate) type GetUtilityFunction = unsafe extern "C" fn(
298    p_function: sys::GDExtensionConstStringNamePtr,
299    p_hash: sys::GDExtensionInt,
300) -> sys::GDExtensionPtrUtilityFunction;
301
302pub type UtilityFunctionBind = unsafe extern "C" fn(
303    r_return: sys::GDExtensionTypePtr,
304    p_args: *const sys::GDExtensionConstTypePtr,
305    p_argument_count: std::os::raw::c_int,
306);
307
308// ----------------------------------------------------------------------------------------------------------------------------------------------
309// Utility functions
310
311// TODO: Most of these should be `unsafe` since the caller passes an `unsafe extern "C"` function pointer which it must be legal to call.
312// But for now we can just rely on knowing that these aren't called in the wrong context.
313
314pub(crate) fn load_class_method(
315    get_method_bind: GetClassMethod,
316    string_names: &mut sys::StringCache,
317    class_sname_ptr: Option<sys::GDExtensionStringNamePtr>,
318    class_name: &'static str,
319    method_name: &'static str,
320    hash: i64,
321) -> ClassMethodBind {
322    /*crate::out!(
323        "Load class method {}::{} (hash {})...",
324        class_name,
325        method_name,
326        hash
327    );*/
328
329    let method_sname_ptr: sys::GDExtensionStringNamePtr = string_names.fetch(method_name);
330    let class_sname_ptr = class_sname_ptr.unwrap_or_else(|| string_names.fetch(class_name));
331
332    // SAFETY: function pointers provided by Godot. We have no way to validate them.
333    let method: sys::GDExtensionMethodBindPtr =
334        unsafe { get_method_bind(class_sname_ptr, method_sname_ptr, hash) };
335
336    if method.is_null() {
337        panic!("Failed to load class method {class_name}::{method_name} (hash {hash}).{INFO}")
338    }
339
340    ClassMethodBind(method)
341}
342
343pub(crate) fn load_builtin_method(
344    get_builtin_method: GetBuiltinMethod,
345    string_names: &mut sys::StringCache,
346    variant_type: sys::GDExtensionVariantType,
347    variant_type_str: &'static str,
348    method_name: &'static str,
349    hash: i64,
350) -> BuiltinMethodBind {
351    /*crate::out!(
352        "Load builtin method {}::{} (hash {})...",
353        variant_type,
354        method_name,
355        hash
356    );*/
357
358    let method_sname = string_names.fetch(method_name);
359    // SAFETY: function pointers provided by Godot. We have no way to validate them.
360    let method = unsafe { get_builtin_method(variant_type, method_sname, hash) };
361
362    method.unwrap_or_else(|| {
363        panic!(
364            "Failed to load builtin method {variant_type_str}::{method_name} (hash {hash}).{INFO}"
365        )
366    })
367}
368
369pub(crate) fn validate_builtin_lifecycle<T>(function: Option<T>, description: &str) -> T {
370    function.unwrap_or_else(|| {
371        panic!("Failed to load builtin lifecycle function {description}.{INFO}",)
372    })
373}
374
375pub(crate) fn load_utility_function(
376    get_utility_fn: GetUtilityFunction,
377    string_names: &mut sys::StringCache,
378    fn_name_str: &'static str,
379    hash: i64,
380) -> UtilityFunctionBind {
381    // SAFETY: function pointers provided by Godot. We have no way to validate them.
382    let utility_fn = unsafe { get_utility_fn(string_names.fetch(fn_name_str), hash) };
383
384    utility_fn.unwrap_or_else(|| {
385        panic!("Failed to load utility function {fn_name_str} (hash {hash}).{INFO}")
386    })
387}
388
389/// Extracts the version string from a Godot version struct.
390///
391/// Works transparently with both `GDExtensionGodotVersion` and `GDExtensionGodotVersion2`.
392///
393/// # Safety
394/// The `char_ptr` must point to a valid C string.
395pub(crate) unsafe fn read_version_string(char_ptr: *const std::ffi::c_char) -> String {
396    // SAFETY: Caller guarantees the pointer is valid.
397    let c_str = unsafe { std::ffi::CStr::from_ptr(char_ptr) };
398
399    let full_version = c_str.to_str().unwrap_or("(invalid UTF-8 in version)");
400
401    full_version
402        .strip_prefix("Godot Engine ")
403        .unwrap_or(full_version)
404        .to_string()
405}
406
407const INFO: &str = "\nMake sure gdext and Godot are compatible: https://godot-rust.github.io/book/toolchain/compatibility.html";
408
409// ----------------------------------------------------------------------------------------------------------------------------------------------
410// Private abstractions
411// Don't use abstractions made here outside this crate, if needed then we should discuss making it more of a first-class
412// abstraction like `godot-cell`.
413
414/// Module to encapsulate `ManualInitCell`.
415mod manual_init_cell {
416    use std::cell::UnsafeCell;
417    use std::hint::unreachable_unchecked;
418
419    /// A cell which can be initialized and uninitialized, with manual synchronization from the caller.
420    ///
421    /// Similar to a [`OnceLock`](std::sync::OnceLock), but without the overhead of locking for initialization. In most cases the compiler
422    /// seems able to optimize `OnceLock` to equivalent code. But this guaranteed does not have any overhead at runtime.
423    ///
424    /// This cell additionally allows to deinitialize the value. Access to uninitialized values is UB, but checked in Debug mode.
425    pub(crate) struct ManualInitCell<T> {
426        // Invariant: Is `None` until initialized, and then never modified after (except, possibly, through interior mutability).
427        cell: UnsafeCell<Option<T>>,
428    }
429
430    impl<T> ManualInitCell<T> {
431        /// Creates a new empty cell.
432        pub const fn new() -> Self {
433            Self {
434                cell: UnsafeCell::new(None),
435            }
436        }
437
438        /// Initialize the value stored in this cell.
439        ///
440        /// # Safety
441        ///
442        /// - Must only be called once, unless a [`clear()`][Self::clear] call has happened in between.
443        /// - Calls to this method must not happen concurrently with a call to any other method on this cell.
444        ///
445        /// Note that the other methods of this cell do not have a safety invariant that they are not called concurrently with `set`.
446        /// This is because doing so would violate the safety invariants of `set` and so they do not need to explicitly have that as a
447        /// safety invariant as well. This has the added benefit that `is_initialized` can be a safe method.
448        #[inline]
449        pub unsafe fn set(&self, value: T) {
450            // SAFETY: `set` has exclusive access to the cell, per the safety requirements.
451            let option = unsafe { &mut *self.cell.get() };
452
453            // Tell the compiler that the cell is `None`, even if it can't prove that on its own.
454            if option.is_some() {
455                // SAFETY: `set` cannot be called multiple times without `clear` in between, so the cell must be `None` at this point.
456                // This panics in Debug mode.
457                unsafe { unreachable_unchecked() }
458            }
459
460            *option = Some(value);
461        }
462
463        /// Clear the value stored in this cell.
464        ///
465        /// # Safety
466        ///
467        /// - Must only be called after [`set`](Self::set) has been called.
468        /// - Calls to this method must not happen concurrently with a call to any other method on this cell.
469        #[inline]
470        pub unsafe fn clear(&self) {
471            // SAFETY: `set` is only ever called once, and is not called concurrently with any other methods. Therefore, we can take
472            // a mutable reference to the contents of the cell.
473            let option = unsafe { &mut *self.cell.get() };
474
475            // Tell the compiler that the cell is `Some`.
476            if option.is_none() {
477                // SAFETY: `set` has been called before this, so the option is known to be a `Some`.
478                // This panics in Debug mode.
479                unsafe { unreachable_unchecked() }
480            }
481
482            *option = None;
483        }
484
485        /// Gets the value stored in the cell.
486        ///
487        /// # Safety
488        ///
489        /// - [`set`](ManualInitCell::set) must have been called before calling this method.
490        #[inline]
491        pub unsafe fn get_unchecked(&self) -> &T {
492            // SAFETY: There are no `&mut` references, since only `set` can create one and this method cannot be called concurrently with `set`.
493            let option = unsafe { &*self.cell.get() };
494
495            // SAFETY: `set` has been called before this, so the option is known to be a `Some`.
496            // This panics in Debug mode.
497            unsafe { option.as_ref().unwrap_unchecked() }
498        }
499
500        /// Checks whether the cell contains a value.
501        #[inline]
502        pub fn is_initialized(&self) -> bool {
503            // SAFETY: There are no `&mut` references, since only `set` can create one and this method cannot be called concurrently with `set`.
504            let option = unsafe { &*self.cell.get() };
505
506            option.is_some()
507        }
508    }
509
510    // SAFETY: The user is responsible for ensuring thread safe initialization of the cell.
511    // This also requires `Send` for the same reasons `OnceLock` does.
512    unsafe impl<T: Send + Sync> Sync for ManualInitCell<T> {}
513    // SAFETY: See `Sync` impl.
514    unsafe impl<T: Send> Send for ManualInitCell<T> {}
515}
516
517pub(crate) use manual_init_cell::ManualInitCell;
518
519// ----------------------------------------------------------------------------------------------------------------------------------------------
520// Unit tests
521
522#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
523mod tests {
524    use super::*;
525
526    #[test]
527    fn test_short_type_name() {
528        assert_eq!(short_type_name::<i32>(), "i32");
529        assert_eq!(short_type_name::<Option<i32>>(), "Option<i32>");
530        assert_eq!(
531            short_type_name::<Result<Option<i32>, String>>(),
532            "Result<Option<i32>, String>"
533        );
534        assert_eq!(
535            short_type_name::<Vec<Result<Option<i32>, String>>>(),
536            "Vec<Result<Option<i32>, String>>"
537        );
538        assert_eq!(
539            short_type_name::<std::collections::HashMap<String, Vec<i32>>>(),
540            "HashMap<String, Vec<i32>>"
541        );
542        assert_eq!(
543            short_type_name::<Result<Option<i32>, String>>(),
544            "Result<Option<i32>, String>"
545        );
546        assert_eq!(short_type_name::<i32>(), "i32");
547        assert_eq!(short_type_name::<Vec<String>>(), "Vec<String>");
548    }
549
550    #[test]
551    fn test_short_type_name_of_val() {
552        let value = Some(42);
553        assert_eq!(short_type_name_of_val(&value), "Option<i32>");
554
555        let result: Result<_, String> = Ok(Some(42));
556        assert_eq!(
557            short_type_name_of_val(&result),
558            "Result<Option<i32>, String>"
559        );
560
561        let vec = vec![result];
562        assert_eq!(
563            short_type_name_of_val(&vec),
564            "Vec<Result<Option<i32>, String>>"
565        );
566    }
567}