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}