1use std::fmt::{Display, Write};
11
12use crate as sys;
13
14#[macro_export]
20macro_rules! static_assert {
21 ($cond:expr) => {
22 const _: () = assert!($cond);
23 };
24 ($cond:expr, $msg:literal) => {
25 const _: () = assert!($cond, $msg);
26 };
27}
28
29#[macro_export]
31macro_rules! static_assert_eq_size_align {
32 ($T:ty, $U:ty) => {
33 godot_ffi::static_assert!(
34 std::mem::size_of::<$T>() == std::mem::size_of::<$U>()
35 && std::mem::align_of::<$T>() == std::mem::align_of::<$U>()
36 );
37 };
38 ($T:ty, $U:ty, $msg:literal) => {
39 godot_ffi::static_assert!(
40 std::mem::size_of::<$T>() == std::mem::size_of::<$U>()
41 && std::mem::align_of::<$T>() == std::mem::align_of::<$U>(),
42 $msg
43 );
44 };
45}
46
47#[cfg(feature = "debug-log")] #[cfg_attr(published_docs, doc(cfg(feature = "debug-log")))]
49#[macro_export]
50macro_rules! out {
51 () => (eprintln!());
52 ($fmt:literal) => (eprintln!($fmt));
53 ($fmt:literal, $($arg:tt)*) => (eprintln!($fmt, $($arg)*));
54}
55
56#[cfg(not(feature = "debug-log"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "debug-log"))))]
58#[macro_export]
59macro_rules! out {
60 () => ({});
61 ($fmt:literal) => ({});
62 ($fmt:literal, $($arg:tt)*) => {{
63 if false {
65 format_args!($fmt, $($arg)*);
66 }
67 }}
68}
69
70#[allow(unused)]
81#[macro_export]
82macro_rules! unsafe_cast_fn_ptr {
83 ($option:ident as $ToType:ty) => {{
84 #[allow(unused_unsafe)]
87 let ptr: Option<_> = unsafe { std::mem::transmute::<Option<unsafe extern "C" fn()>, $ToType>($option) };
88 ptr.expect("null function pointer")
89 }};
90}
91
92#[allow(clippy::boxed_local)] pub fn unbox<T>(value: Box<T>) -> T {
98 *value
100}
101
102pub fn force_mut_ptr<T>(ptr: *const T) -> *mut T {
106 ptr as *mut T
107}
108
109pub fn to_const_ptr<T>(ptr: *mut T) -> *const T {
111 ptr as *const T
112}
113
114#[inline]
116pub fn ptr_then<T, R, F>(ptr: *mut T, mapper: F) -> Option<R>
117where
118 F: FnOnce(*mut T) -> R,
119{
120 if ptr.is_null() {
122 None
123 } else {
124 Some(mapper(ptr))
125 }
126}
127
128#[inline]
130pub fn c_str(s: &[u8]) -> *const std::ffi::c_char {
131 debug_assert!(!s.is_empty() && s[s.len() - 1] == 0);
133
134 s.as_ptr() as *const std::ffi::c_char
135}
136
137#[inline]
139pub fn c_str_from_str(s: &str) -> *const std::ffi::c_char {
140 c_str(s.as_bytes())
141}
142
143pub fn hash_value<T: std::hash::Hash>(t: &T) -> u64 {
145 use std::hash::Hasher;
146 let mut hasher = std::collections::hash_map::DefaultHasher::new();
147 t.hash(&mut hasher);
148 hasher.finish()
149}
150
151pub fn join<T, I>(iter: I) -> String
152where
153 T: std::fmt::Display,
154 I: Iterator<Item = T>,
155{
156 join_with(iter, ", ", |item| format!("{item}"))
157}
158
159pub fn join_debug<T, I>(iter: I) -> String
160where
161 T: std::fmt::Debug,
162 I: Iterator<Item = T>,
163{
164 join_with(iter, ", ", |item| format!("{item:?}"))
165}
166
167pub fn join_with<T, I, F, S>(mut iter: I, sep: &str, mut format_elem: F) -> String
168where
169 I: Iterator<Item = T>,
170 F: FnMut(&T) -> S,
171 S: Display,
172{
173 let mut result = String::new();
174
175 if let Some(first) = iter.next() {
176 write!(&mut result, "{first}", first = format_elem(&first))
180 .expect("Formatter should not fail!");
181 for item in iter {
182 write!(&mut result, "{sep}{item}", item = format_elem(&item))
183 .expect("Formatter should not fail!");
184 }
185 }
186 result
187}
188
189pub fn i64_to_ordering(value: i64) -> std::cmp::Ordering {
190 match value {
191 -1 => std::cmp::Ordering::Less,
192 0 => std::cmp::Ordering::Equal,
193 1 => std::cmp::Ordering::Greater,
194 _ => panic!("cannot convert value {value} to cmp::Ordering"),
195 }
196}
197
198pub fn short_type_name<T: ?Sized>() -> String {
207 let full_name = std::any::type_name::<T>();
208 strip_module_paths(full_name)
209}
210
211pub fn short_type_name_of_val<T: ?Sized>(val: &T) -> String {
213 let full_name = std::any::type_name_of_val(val);
214 strip_module_paths(full_name)
215}
216
217fn strip_module_paths(full_name: &str) -> String {
219 let mut result = String::new();
220 let mut identifier = String::new();
221
222 let mut chars = full_name.chars().peekable();
223
224 while let Some(c) = chars.next() {
225 match c {
226 '<' | '>' | ',' | ' ' | '&' | '(' | ')' | '[' | ']' => {
227 if !identifier.is_empty() {
229 let short_name = identifier.split("::").last().unwrap_or(&identifier);
230 result.push_str(short_name);
231 identifier.clear();
232 }
233 result.push(c);
234
235 if c == ',' && chars.peek().is_some_and(|&next_c| next_c != ' ') {
237 result.push(' ');
238 }
239 }
240 ':' => {
241 if chars.peek() == Some(&':') {
243 chars.next();
245 identifier.push_str("::");
246 } else {
247 identifier.push(c);
248 }
249 }
250 _ => {
251 identifier.push(c);
253 }
254 }
255 }
256
257 if !identifier.is_empty() {
259 let short_name = identifier.split("::").last().unwrap_or(&identifier);
260 result.push_str(short_name);
261 }
262
263 result
264}
265
266pub trait Inner: Sized {
272 type FnPtr: Sized;
273}
274
275impl<T> Inner for Option<T> {
276 type FnPtr = T;
277}
278
279pub(crate) type GetClassMethod = unsafe extern "C" fn(
283 p_classname: sys::GDExtensionConstStringNamePtr,
284 p_methodname: sys::GDExtensionConstStringNamePtr,
285 p_hash: sys::GDExtensionInt,
286) -> sys::GDExtensionMethodBindPtr;
287
288#[derive(Copy, Clone)]
290pub struct ClassMethodBind(pub sys::GDExtensionMethodBindPtr);
291
292unsafe impl Sync for ClassMethodBind {}
296unsafe impl Send for ClassMethodBind {}
298
299pub(crate) type GetBuiltinMethod = unsafe extern "C" fn(
300 p_type: sys::GDExtensionVariantType,
301 p_method: sys::GDExtensionConstStringNamePtr,
302 p_hash: sys::GDExtensionInt,
303) -> sys::GDExtensionPtrBuiltInMethod;
304
305pub type BuiltinMethodBind = unsafe extern "C" fn(
307 p_base: sys::GDExtensionTypePtr,
308 p_args: *const sys::GDExtensionConstTypePtr,
309 r_return: sys::GDExtensionTypePtr,
310 p_argument_count: std::os::raw::c_int,
311);
312
313pub(crate) type GetUtilityFunction = unsafe extern "C" fn(
314 p_function: sys::GDExtensionConstStringNamePtr,
315 p_hash: sys::GDExtensionInt,
316) -> sys::GDExtensionPtrUtilityFunction;
317
318pub type UtilityFunctionBind = unsafe extern "C" fn(
319 r_return: sys::GDExtensionTypePtr,
320 p_args: *const sys::GDExtensionConstTypePtr,
321 p_argument_count: std::os::raw::c_int,
322);
323
324pub(crate) fn load_class_method(
331 get_method_bind: GetClassMethod,
332 string_names: &mut sys::StringCache,
333 class_sname_ptr: Option<sys::GDExtensionStringNamePtr>,
334 class_name: &'static str,
335 method_name: &'static str,
336 hash: i64,
337) -> ClassMethodBind {
338 let method_sname_ptr: sys::GDExtensionStringNamePtr = string_names.fetch(method_name);
346 let class_sname_ptr = class_sname_ptr.unwrap_or_else(|| string_names.fetch(class_name));
347
348 let method: sys::GDExtensionMethodBindPtr =
350 unsafe { get_method_bind(class_sname_ptr, method_sname_ptr, hash) };
351
352 if method.is_null() {
353 panic!("Failed to load class method {class_name}::{method_name} (hash {hash}).{INFO}")
354 }
355
356 ClassMethodBind(method)
357}
358
359pub(crate) fn load_builtin_method(
360 get_builtin_method: GetBuiltinMethod,
361 string_names: &mut sys::StringCache,
362 variant_type: sys::GDExtensionVariantType,
363 variant_type_str: &'static str,
364 method_name: &'static str,
365 hash: i64,
366) -> BuiltinMethodBind {
367 let method_sname = string_names.fetch(method_name);
375 let method = unsafe { get_builtin_method(variant_type, method_sname, hash) };
377
378 method.unwrap_or_else(|| {
379 panic!(
380 "Failed to load builtin method {variant_type_str}::{method_name} (hash {hash}).{INFO}"
381 )
382 })
383}
384
385pub(crate) fn validate_builtin_lifecycle<T>(function: Option<T>, description: &str) -> T {
386 function.unwrap_or_else(|| {
387 panic!("Failed to load builtin lifecycle function {description}.{INFO}",)
388 })
389}
390
391pub(crate) fn load_utility_function(
392 get_utility_fn: GetUtilityFunction,
393 string_names: &mut sys::StringCache,
394 fn_name_str: &'static str,
395 hash: i64,
396) -> UtilityFunctionBind {
397 let utility_fn = unsafe { get_utility_fn(string_names.fetch(fn_name_str), hash) };
399
400 utility_fn.unwrap_or_else(|| {
401 panic!("Failed to load utility function {fn_name_str} (hash {hash}).{INFO}")
402 })
403}
404
405pub(crate) fn read_version_string(version_ptr: &sys::GDExtensionGodotVersion) -> String {
406 let char_ptr = version_ptr.string;
407
408 let c_str = unsafe { std::ffi::CStr::from_ptr(char_ptr) };
410
411 let full_version = c_str.to_str().unwrap_or("(invalid UTF-8 in version)");
412
413 full_version
414 .strip_prefix("Godot Engine ")
415 .unwrap_or(full_version)
416 .to_string()
417}
418
419const INFO: &str = "\nMake sure gdext and Godot are compatible: https://godot-rust.github.io/book/toolchain/compatibility.html";
420
421mod manual_init_cell {
428 use std::cell::UnsafeCell;
429 use std::hint::unreachable_unchecked;
430
431 pub(crate) struct ManualInitCell<T> {
438 cell: UnsafeCell<Option<T>>,
440 }
441
442 impl<T> ManualInitCell<T> {
443 pub const fn new() -> Self {
445 Self {
446 cell: UnsafeCell::new(None),
447 }
448 }
449
450 #[inline]
461 pub unsafe fn set(&self, value: T) {
462 let option = unsafe { &mut *self.cell.get() };
464
465 if option.is_some() {
467 unsafe { unreachable_unchecked() }
470 }
471
472 *option = Some(value);
473 }
474
475 #[inline]
482 pub unsafe fn clear(&self) {
483 let option = unsafe { &mut *self.cell.get() };
486
487 if option.is_none() {
489 unsafe { unreachable_unchecked() }
492 }
493
494 *option = None;
495 }
496
497 #[inline]
503 pub unsafe fn get_unchecked(&self) -> &T {
504 let option = unsafe { &*self.cell.get() };
506
507 unsafe { option.as_ref().unwrap_unchecked() }
510 }
511
512 #[inline]
514 pub fn is_initialized(&self) -> bool {
515 let option = unsafe { &*self.cell.get() };
517
518 option.is_some()
519 }
520 }
521
522 unsafe impl<T: Send + Sync> Sync for ManualInitCell<T> {}
525 unsafe impl<T: Send> Send for ManualInitCell<T> {}
527}
528
529pub(crate) use manual_init_cell::ManualInitCell;
530
531#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
535mod tests {
536 use super::*;
537
538 #[test]
539 fn test_short_type_name() {
540 assert_eq!(short_type_name::<i32>(), "i32");
541 assert_eq!(short_type_name::<Option<i32>>(), "Option<i32>");
542 assert_eq!(
543 short_type_name::<Result<Option<i32>, String>>(),
544 "Result<Option<i32>, String>"
545 );
546 assert_eq!(
547 short_type_name::<Vec<Result<Option<i32>, String>>>(),
548 "Vec<Result<Option<i32>, String>>"
549 );
550 assert_eq!(
551 short_type_name::<std::collections::HashMap<String, Vec<i32>>>(),
552 "HashMap<String, Vec<i32>>"
553 );
554 assert_eq!(
555 short_type_name::<Result<Option<i32>, String>>(),
556 "Result<Option<i32>, String>"
557 );
558 assert_eq!(short_type_name::<i32>(), "i32");
559 assert_eq!(short_type_name::<Vec<String>>(), "Vec<String>");
560 }
561
562 #[test]
563 fn test_short_type_name_of_val() {
564 let value = Some(42);
565 assert_eq!(short_type_name_of_val(&value), "Option<i32>");
566
567 let result: Result<_, String> = Ok(Some(42));
568 assert_eq!(
569 short_type_name_of_val(&result),
570 "Result<Option<i32>, String>"
571 );
572
573 let vec = vec![result];
574 assert_eq!(
575 short_type_name_of_val(&vec),
576 "Vec<Result<Option<i32>, String>>"
577 );
578 }
579}