use std::fmt::{Display, Write};
use crate as sys;
#[cfg(feature = "debug-log")] #[cfg_attr(published_docs, doc(cfg(feature = "debug-log")))]
#[macro_export]
macro_rules! out {
() => (eprintln!());
($fmt:literal) => (eprintln!($fmt));
($fmt:literal, $($arg:tt)*) => (eprintln!($fmt, $($arg)*));
}
#[cfg(not(feature = "debug-log"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "debug-log"))))]
#[macro_export]
macro_rules! out {
() => ({});
($fmt:literal) => ({});
($fmt:literal, $($arg:tt)*) => {{
if false {
format_args!($fmt, $($arg)*);
}
}}
}
#[allow(unused)]
#[macro_export]
macro_rules! unsafe_cast_fn_ptr {
($option:ident as $ToType:ty) => {{
#[allow(unused_unsafe)]
let ptr: Option<_> = unsafe { std::mem::transmute::<Option<unsafe extern "C" fn()>, $ToType>($option) };
ptr.expect("null function pointer")
}};
}
#[allow(clippy::boxed_local)] pub fn unbox<T>(value: Box<T>) -> T {
*value
}
pub fn force_mut_ptr<T>(ptr: *const T) -> *mut T {
ptr as *mut T
}
pub fn to_const_ptr<T>(ptr: *mut T) -> *const T {
ptr as *const T
}
#[inline]
pub fn ptr_then<T, R, F>(ptr: *mut T, mapper: F) -> Option<R>
where
F: FnOnce(*mut T) -> R,
{
if ptr.is_null() {
None
} else {
Some(mapper(ptr))
}
}
#[inline]
pub fn c_str(s: &[u8]) -> *const std::ffi::c_char {
crate::strict_assert!(!s.is_empty() && s[s.len() - 1] == 0);
s.as_ptr() as *const std::ffi::c_char
}
#[inline]
pub fn c_str_from_str(s: &str) -> *const std::ffi::c_char {
c_str(s.as_bytes())
}
pub fn hash_value<T: std::hash::Hash>(t: &T) -> u64 {
use std::hash::Hasher;
let mut hasher = std::collections::hash_map::DefaultHasher::new();
t.hash(&mut hasher);
hasher.finish()
}
pub fn join<T, I>(iter: I) -> String
where
T: std::fmt::Display,
I: Iterator<Item = T>,
{
join_with(iter, ", ", |item| format!("{item}"))
}
pub fn join_debug<T, I>(iter: I) -> String
where
T: std::fmt::Debug,
I: Iterator<Item = T>,
{
join_with(iter, ", ", |item| format!("{item:?}"))
}
pub fn join_with<T, I, F, S>(mut iter: I, sep: &str, mut format_elem: F) -> String
where
I: Iterator<Item = T>,
F: FnMut(&T) -> S,
S: Display,
{
let mut result = String::new();
if let Some(first) = iter.next() {
write!(&mut result, "{first}", first = format_elem(&first))
.expect("Formatter should not fail!");
for item in iter {
write!(&mut result, "{sep}{item}", item = format_elem(&item))
.expect("Formatter should not fail!");
}
}
result
}
pub fn i64_to_ordering(value: i64) -> std::cmp::Ordering {
match value {
-1 => std::cmp::Ordering::Less,
0 => std::cmp::Ordering::Equal,
1 => std::cmp::Ordering::Greater,
_ => panic!("cannot convert value {value} to cmp::Ordering"),
}
}
pub fn found_to_option(index: i64) -> Option<usize> {
if index == -1 {
None
} else {
let index_usize = index
.try_into()
.unwrap_or_else(|_| panic!("unexpected index {index} returned from Godot function"));
Some(index_usize)
}
}
pub fn short_type_name<T: ?Sized>() -> String {
let full_name = std::any::type_name::<T>();
strip_module_paths(full_name)
}
pub fn short_type_name_of_val<T: ?Sized>(val: &T) -> String {
let full_name = std::any::type_name_of_val(val);
strip_module_paths(full_name)
}
fn strip_module_paths(full_name: &str) -> String {
let mut result = String::new();
let mut identifier = String::new();
let mut chars = full_name.chars().peekable();
while let Some(c) = chars.next() {
match c {
'<' | '>' | ',' | ' ' | '&' | '(' | ')' | '[' | ']' => {
if !identifier.is_empty() {
let short_name = identifier.split("::").last().unwrap_or(&identifier);
result.push_str(short_name);
identifier.clear();
}
result.push(c);
if c == ',' && chars.peek().is_some_and(|&next_c| next_c != ' ') {
result.push(' ');
}
}
':' => {
if chars.peek() == Some(&':') {
chars.next();
identifier.push_str("::");
} else {
identifier.push(c);
}
}
_ => {
identifier.push(c);
}
}
}
if !identifier.is_empty() {
let short_name = identifier.split("::").last().unwrap_or(&identifier);
result.push_str(short_name);
}
result
}
pub trait Inner: Sized {
type FnPtr: Sized;
}
impl<T> Inner for Option<T> {
type FnPtr = T;
}
pub(crate) type GetClassMethod = unsafe extern "C" fn(
p_classname: sys::GDExtensionConstStringNamePtr,
p_methodname: sys::GDExtensionConstStringNamePtr,
p_hash: sys::GDExtensionInt,
) -> sys::GDExtensionMethodBindPtr;
#[derive(Copy, Clone)]
pub struct ClassMethodBind(pub sys::GDExtensionMethodBindPtr);
unsafe impl Sync for ClassMethodBind {}
unsafe impl Send for ClassMethodBind {}
pub(crate) type GetBuiltinMethod = unsafe extern "C" fn(
p_type: sys::GDExtensionVariantType,
p_method: sys::GDExtensionConstStringNamePtr,
p_hash: sys::GDExtensionInt,
) -> sys::GDExtensionPtrBuiltInMethod;
pub type BuiltinMethodBind = unsafe extern "C" fn(
p_base: sys::GDExtensionTypePtr,
p_args: *const sys::GDExtensionConstTypePtr,
r_return: sys::GDExtensionTypePtr,
p_argument_count: std::os::raw::c_int,
);
pub(crate) type GetUtilityFunction = unsafe extern "C" fn(
p_function: sys::GDExtensionConstStringNamePtr,
p_hash: sys::GDExtensionInt,
) -> sys::GDExtensionPtrUtilityFunction;
pub type UtilityFunctionBind = unsafe extern "C" fn(
r_return: sys::GDExtensionTypePtr,
p_args: *const sys::GDExtensionConstTypePtr,
p_argument_count: std::os::raw::c_int,
);
pub(crate) fn load_class_method(
get_method_bind: GetClassMethod,
string_names: &mut sys::StringCache,
class_sname_ptr: Option<sys::GDExtensionStringNamePtr>,
class_name: &'static str,
method_name: &'static str,
hash: i64,
) -> ClassMethodBind {
let method_sname_ptr: sys::GDExtensionStringNamePtr = string_names.fetch(method_name);
let class_sname_ptr = class_sname_ptr.unwrap_or_else(|| string_names.fetch(class_name));
let method: sys::GDExtensionMethodBindPtr =
unsafe { get_method_bind(class_sname_ptr, method_sname_ptr, hash) };
if method.is_null() {
panic!("Failed to load class method {class_name}::{method_name} (hash {hash}).{INFO}")
}
ClassMethodBind(method)
}
pub(crate) fn load_builtin_method(
get_builtin_method: GetBuiltinMethod,
string_names: &mut sys::StringCache,
variant_type: sys::GDExtensionVariantType,
variant_type_str: &'static str,
method_name: &'static str,
hash: i64,
) -> BuiltinMethodBind {
let method_sname = string_names.fetch(method_name);
let method = unsafe { get_builtin_method(variant_type, method_sname, hash) };
method.unwrap_or_else(|| {
panic!(
"Failed to load builtin method {variant_type_str}::{method_name} (hash {hash}).{INFO}"
)
})
}
pub(crate) fn validate_builtin_lifecycle<T>(function: Option<T>, description: &str) -> T {
function.unwrap_or_else(|| {
panic!("Failed to load builtin lifecycle function {description}.{INFO}",)
})
}
pub(crate) fn load_utility_function(
get_utility_fn: GetUtilityFunction,
string_names: &mut sys::StringCache,
fn_name_str: &'static str,
hash: i64,
) -> UtilityFunctionBind {
let utility_fn = unsafe { get_utility_fn(string_names.fetch(fn_name_str), hash) };
utility_fn.unwrap_or_else(|| {
panic!("Failed to load utility function {fn_name_str} (hash {hash}).{INFO}")
})
}
pub(crate) unsafe fn read_version_string(char_ptr: *const std::ffi::c_char) -> String {
let c_str = unsafe { std::ffi::CStr::from_ptr(char_ptr) };
let full_version = c_str.to_str().unwrap_or("(invalid UTF-8 in version)");
full_version
.strip_prefix("Godot Engine ")
.unwrap_or(full_version)
.to_string()
}
const INFO: &str = "\nMake sure gdext and Godot are compatible: https://godot-rust.github.io/book/toolchain/compatibility.html";
mod manual_init_cell {
use std::cell::UnsafeCell;
use std::hint::unreachable_unchecked;
pub(crate) struct ManualInitCell<T> {
cell: UnsafeCell<Option<T>>,
}
impl<T> ManualInitCell<T> {
pub const fn new() -> Self {
Self {
cell: UnsafeCell::new(None),
}
}
#[inline]
pub unsafe fn set(&self, value: T) {
let option = unsafe { &mut *self.cell.get() };
if option.is_some() {
unsafe { unreachable_unchecked() }
}
*option = Some(value);
}
#[inline]
pub unsafe fn clear(&self) {
let option = unsafe { &mut *self.cell.get() };
if option.is_none() {
unsafe { unreachable_unchecked() }
}
*option = None;
}
#[inline]
pub unsafe fn get_unchecked(&self) -> &T {
let option = unsafe { &*self.cell.get() };
unsafe { option.as_ref().unwrap_unchecked() }
}
#[inline]
pub fn is_initialized(&self) -> bool {
let option = unsafe { &*self.cell.get() };
option.is_some()
}
}
unsafe impl<T: Send + Sync> Sync for ManualInitCell<T> {}
unsafe impl<T: Send> Send for ManualInitCell<T> {}
}
pub(crate) use manual_init_cell::ManualInitCell;
#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
mod tests {
use super::*;
#[test]
fn test_short_type_name() {
assert_eq!(short_type_name::<i32>(), "i32");
assert_eq!(short_type_name::<Option<i32>>(), "Option<i32>");
assert_eq!(
short_type_name::<Result<Option<i32>, String>>(),
"Result<Option<i32>, String>"
);
assert_eq!(
short_type_name::<Vec<Result<Option<i32>, String>>>(),
"Vec<Result<Option<i32>, String>>"
);
assert_eq!(
short_type_name::<std::collections::HashMap<String, Vec<i32>>>(),
"HashMap<String, Vec<i32>>"
);
assert_eq!(
short_type_name::<Result<Option<i32>, String>>(),
"Result<Option<i32>, String>"
);
assert_eq!(short_type_name::<i32>(), "i32");
assert_eq!(short_type_name::<Vec<String>>(), "Vec<String>");
}
#[test]
fn test_short_type_name_of_val() {
let value = Some(42);
assert_eq!(short_type_name_of_val(&value), "Option<i32>");
let result: Result<_, String> = Ok(Some(42));
assert_eq!(
short_type_name_of_val(&result),
"Result<Option<i32>, String>"
);
let vec = vec![result];
assert_eq!(
short_type_name_of_val(&vec),
"Vec<Result<Option<i32>, String>>"
);
}
}