gdnative_core/
macros.rs

1#![macro_use]
2
3/// Print a message using the engine's logging system (visible in the editor).
4#[macro_export]
5macro_rules! godot_print {
6    ($($args:tt)*) => ({
7        $crate::log::print(::std::format_args!($($args)*));
8    });
9}
10
11/// Prints and returns the value of a given expression for quick and dirty debugging,
12/// using the engine's logging system (visible in the editor).
13///
14/// This behaves similarly to the `std::dbg!` macro.
15#[macro_export]
16macro_rules! godot_dbg {
17    () => {
18        $crate::godot_print!("[{}:{}]", ::std::file!(), ::std::line!());
19    };
20    ($val:expr) => {
21        // Use of `match` here is intentional because it affects the lifetimes
22        // of temporaries - https://stackoverflow.com/a/48732525/1063961
23        match $val {
24            tmp => {
25                $crate::godot_print!("[{}:{}] {} = {:#?}",
26                    ::std::file!(), ::std::line!(), ::std::stringify!($val), &tmp);
27                tmp
28            }
29        }
30    };
31    // Trailing comma with single argument is ignored
32    ($val:expr,) => { $crate::godot_dbg!($val) };
33    ($($val:expr),+ $(,)?) => {
34        ($($crate::godot_dbg!($val)),+,)
35    };
36}
37
38/// Creates a [`Site`][crate::log::Site] value from the current position in code,
39/// optionally with a function path for identification.
40///
41/// # Examples
42///
43/// ```ignore
44/// use gdnative::log;
45///
46/// // WARN: <unset>: foo At: path/to/file.rs:123
47/// log::warn(log::godot_site!(), "foo");
48///
49/// // WARN: Foo::my_func: bar At: path/to/file.rs:123
50/// log::error(log::godot_site!(Foo::my_func), "bar");
51/// ```
52#[macro_export]
53macro_rules! godot_site {
54    () => {{
55        // SAFETY: I guess we can assume that all sane file systems don't allow
56        // NUL-bytes in paths?
57        #[allow(unused_unsafe)]
58        let site: $crate::log::Site<'static> = unsafe {
59            let file = ::std::ffi::CStr::from_bytes_with_nul_unchecked(
60                ::std::concat!(::std::file!(), "\0").as_bytes(),
61            );
62            let func = ::std::ffi::CStr::from_bytes_with_nul_unchecked(b"<unset>\0");
63            $crate::log::Site::new(file, func, ::std::line!())
64        };
65
66        site
67    }};
68    ($($path:tt)+) => {{
69        // SAFETY: I guess we can assume that all sane file systems don't allow
70        // NUL-bytes in paths?
71        #[allow(unused_unsafe)]
72        let site: $crate::log::Site<'static> = unsafe {
73            let file = ::std::ffi::CStr::from_bytes_with_nul_unchecked(
74                ::std::concat!(::std::file!(), "\0").as_bytes(),
75            );
76            let func = ::std::ffi::CStr::from_bytes_with_nul_unchecked(
77                ::std::concat!(::std::stringify!($($path)+), "\0").as_bytes(),
78            );
79            $crate::log::Site::new(file, func, ::std::line!())
80        };
81
82        site
83    }};
84}
85
86/// Print a warning using the engine's logging system (visible in the editor).
87///
88/// # Guarantees
89///
90/// It's guaranteed that the expansion result of this macro may *only* panic if:
91///
92/// - Any of the arguments for the message panicked in `fmt`.
93/// - The formatted message contains the NUL byte (`\0`) anywhere.
94#[macro_export]
95macro_rules! godot_warn {
96    ($($args:tt)*) => ({
97        $crate::log::warn($crate::godot_site!(), ::std::format_args!($($args)*));
98    });
99}
100
101/// Print an error using the engine's logging system (visible in the editor).
102///
103/// # Guarantees
104///
105/// It's guaranteed that the expansion result of this macro may *only* panic if:
106///
107/// - Any of the arguments for the message panicked in `fmt`.
108/// - The formatted message contains the NUL byte (`\0`) anywhere.
109#[macro_export]
110macro_rules! godot_error {
111    ($($args:tt)*) => ({
112        $crate::log::error($crate::godot_site!(), ::std::format_args!($($args)*));
113    });
114}
115
116macro_rules! impl_basic_trait_as_sys {
117    (
118        Drop for $Type:ty as $GdType:ident : $gd_method:ident
119    ) => {
120        impl Drop for $Type {
121            #[inline]
122            fn drop(&mut self) {
123                unsafe { (get_api().$gd_method)(self.sys_mut()) }
124            }
125        }
126    };
127
128    (
129        Clone for $Type:ty as $GdType:ident : $gd_method:ident
130    ) => {
131        impl Clone for $Type {
132            #[inline]
133            fn clone(&self) -> Self {
134                unsafe {
135                    let mut result = sys::$GdType::default();
136                    (get_api().$gd_method)(&mut result, self.sys());
137                    <$Type>::from_sys(result)
138                }
139            }
140        }
141    };
142
143    (
144        Default for $Type:ty as $GdType:ident : $gd_method:ident
145    ) => {
146        impl Default for $Type {
147            #[inline]
148            fn default() -> Self {
149                unsafe {
150                    let mut gd_val = sys::$GdType::default();
151                    (get_api().$gd_method)(&mut gd_val);
152                    <$Type>::from_sys(gd_val)
153                }
154            }
155        }
156    };
157
158    (
159        PartialEq for $Type:ty as $GdType:ident : $gd_method:ident
160    ) => {
161        impl PartialEq for $Type {
162            #[inline]
163            fn eq(&self, other: &Self) -> bool {
164                unsafe { (get_api().$gd_method)(self.sys(), other.sys()) }
165            }
166        }
167    };
168
169    (
170        Eq for $Type:ty as $GdType:ident : $gd_method:ident
171    ) => {
172        impl_basic_trait_as_sys!(PartialEq for $Type as $GdType : $gd_method);
173        impl Eq for $Type {}
174    };
175
176    (
177        Ord for $Type:ty as $GdType:ident : $gd_method:ident
178    ) => {
179        impl PartialOrd for $Type {
180            #[inline]
181            fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
182                Some(self.cmp(other))
183            }
184        }
185        impl Ord for $Type {
186            #[inline]
187            fn cmp(&self, other: &Self) -> std::cmp::Ordering {
188                let op_less = get_api().$gd_method;
189                if unsafe { op_less(&self.0, &other.0) } {
190                    std::cmp::Ordering::Less
191                } else if unsafe { op_less(&other.0, &self.0) } {
192                    std::cmp::Ordering::Greater
193                } else {
194                    std::cmp::Ordering::Equal
195                }
196            }
197        }
198    };
199
200    (
201        NewRef for $Type:ty as $GdType:ident : $gd_method:ident
202    ) => {
203        impl NewRef for $Type {
204            #[inline]
205            fn new_ref(&self) -> $Type {
206                unsafe {
207                    let mut result = Default::default();
208                    (get_api().$gd_method)(&mut result, self.sys());
209                    <$Type>::from_sys(result)
210                }
211            }
212        }
213    };
214}
215
216macro_rules! impl_basic_traits_as_sys {
217    (
218        for $Type:ty as $GdType:ident {
219            $( $Trait:ident => $gd_method:ident; )*
220        }
221    ) => (
222        $(
223            impl_basic_trait_as_sys!(
224                $Trait for $Type as $GdType : $gd_method
225            );
226        )*
227    )
228}
229
230#[doc(hidden)]
231#[macro_export]
232macro_rules! godot_test_impl {
233    ( $( $test_name:ident $body:block $($attrs:tt)* )* ) => {
234        $(
235            $($attrs)*
236            #[doc(hidden)]
237            #[inline]
238            #[must_use]
239            pub fn $test_name() -> bool {
240                let str_name = stringify!($test_name);
241                println!("   -- {}", str_name);
242
243                let ok = ::std::panic::catch_unwind(
244                    || $body
245                ).is_ok();
246
247                if !ok {
248                    if ::std::panic::catch_unwind(|| {
249                        $crate::godot_error!("   !! Test {} failed", str_name);
250                    }).is_err() {
251                        eprintln!("   !! Test {} failed", str_name);
252                        eprintln!("   !! And failed to call Godot API to log error message");
253                    }
254                }
255
256                ok
257            }
258        )*
259    }
260}
261
262/// Declares a test to be run with the Godot engine (i.e. not a pure Rust unit test).
263///
264/// Creates a wrapper function that catches panics, prints errors and returns true/false.
265/// To be manually invoked in higher-level test routine.
266///
267/// This macro is designed to be used within the current crate only, hence the #[cfg] attribute.
268#[doc(hidden)]
269macro_rules! godot_test {
270    ($($test_name:ident $body:block)*) => {
271        $(
272            godot_test_impl!($test_name $body #[cfg(feature = "gd-test")]);
273        )*
274    }
275}
276
277/// Declares a test to be run with the Godot engine (i.e. not a pure Rust unit test).
278///
279/// Creates a wrapper function that catches panics, prints errors and returns true/false.
280/// To be manually invoked in higher-level test routine.
281///
282/// This macro is designed to be used within the `test` crate, hence the method is always declared (not only in certain features).
283#[doc(hidden)]
284#[macro_export]
285macro_rules! godot_itest {
286    ($($test_name:ident $body:block)*) => {
287        $(
288            $crate::godot_test_impl!($test_name $body);
289        )*
290    }
291}