Skip to main content

godot_core/meta/param_tuple/
impls.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#![deny(unsafe_op_in_unsafe_fn)]
9#![allow(unused_attributes)]
10
11use std::fmt;
12
13use godot_ffi as sys;
14use sys::GodotFfi;
15
16use crate::builtin::Variant;
17use crate::meta::error::{CallError, CallResult};
18use crate::meta::{
19    ArgPassing, CallContext, EngineFromGodot, EngineToGodot, FromGodot, GodotConvert, GodotType,
20    InParamTuple, OutParamTuple, ParamTuple, TupleFromGodot,
21};
22use crate::registry::method::MethodParamOrReturnInfo;
23
24macro_rules! count_idents {
25    () => { 0 };
26    ($id:ident $($rest:ident)*) => { 1 + count_idents!($($rest)*)};
27}
28
29macro_rules! unsafe_impl_param_tuple {
30    ($(($p:ident, $n:tt): $P:ident),*) => {
31        impl<$($P: FromGodot + fmt::Debug),*> TupleFromGodot for ($($P,)*) {}
32
33        impl<$($P),*> ParamTuple for ($($P,)*) where $($P: GodotConvert + fmt::Debug),* {
34            const LEN: usize = count_idents!($($P)*);
35
36            #[doc(hidden)]
37            fn param_info(
38                index: usize,
39                param_name: &str,
40            ) -> Option<MethodParamOrReturnInfo> {
41                match index {
42                    $(
43                        $n => Some(MethodParamOrReturnInfo::for_parameter::<$P>(param_name)),
44                    )*
45                    _ => None,
46                }
47            }
48
49            fn format_args(&self) -> String {
50                format!(
51                    // This repeat expression is basically just `"{$n:?}"`, the rest is only needed so that
52                    // the repetition separator can be `", "` instead of `,`.
53                    concat!("" $(, "{", $n, ":?}",)", "*),
54                    $(self.$n),*
55                )
56            }
57        }
58
59        impl<$($P),*> InParamTuple for ($($P,)*) where $($P: EngineFromGodot + fmt::Debug),* {
60            unsafe fn from_varcall_args(
61                args_ptr: *const sys::GDExtensionConstVariantPtr,
62                arg_count: usize,
63                default_values: &[Variant],
64                call_ctx: &crate::meta::CallContext,
65            ) -> CallResult<Self> {
66                // Fast path: all args provided, no defaults needed (zero allocations).
67                if arg_count == Self::LEN {
68                    let param_tuple = (
69                        $(
70                            unsafe { varcall_arg::<$P>(*args_ptr.add($n), call_ctx, $n)? },
71                        )*
72                    );
73                    return Ok(param_tuple);
74                }
75
76                // Slow path: merge provided args with defaults (requires allocation).
77                let mut all_args = Vec::with_capacity(Self::LEN);
78
79                // Copy all provided args.
80                for i in 0..arg_count {
81                    all_args.push(unsafe { *args_ptr.add(i) });
82                }
83
84                // Fill remaining parameters with default values.
85                let required_param_count = Self::LEN - default_values.len();
86                let first_missing_index = arg_count - required_param_count;
87                for i in first_missing_index..default_values.len() {
88                    all_args.push(default_values[i].var_sys());
89                }
90
91                // Convert all args to the tuple.
92                let param_tuple = (
93                    $(
94                        // SAFETY: Each pointer in `args_ptr` is borrowable as a &Variant for the duration of this call.
95                        unsafe { varcall_arg::<$P>(all_args[$n], call_ctx, $n)? },
96                    )*
97                );
98
99                Ok(param_tuple)
100            }
101
102            unsafe fn from_ptrcall_args(
103                args_ptr: *const sys::GDExtensionConstTypePtr,
104                call_type: sys::PtrcallType,
105                call_ctx: &crate::meta::CallContext,
106            ) -> CallResult<Self>
107            where
108                $($P: EngineFromGodot,)*
109            {
110                let tuple = (
111                    $(
112                        // SAFETY: `args_ptr` has length `Self::LEN` and `$n` is less than `Self::LEN`, and `args_ptr` must be an array whose
113                        // `$n`-th element is of type `$P`.
114                        unsafe { ptrcall_arg::<$P, $n>(args_ptr, call_ctx, call_type)? },
115                    )*
116                );
117
118                Ok(tuple) // If none of the `?` above were hit.
119            }
120
121            fn from_variant_array(array: &[&Variant]) -> Self {
122                assert_array_length::<Self>(array);
123                let mut iter = array.iter();
124                (
125                    $(
126                        {
127                            let variant = iter.next().unwrap_or_else(
128                                || panic!("ParamTuple: {} access out-of-bounds (len {})", stringify!($p), array.len()));
129
130                            variant.to_relaxed_or_panic(
131                                || format!("ParamTuple: failed to convert parameter {}", stringify!($p)))
132                        },
133                    )*
134                )
135            }
136        }
137
138        impl<$($P),*> OutParamTuple for ($($P,)*) where $($P: EngineToGodot + fmt::Debug,)* {
139            fn with_variants<F, R>(self, f: F) -> R
140            where
141                F: FnOnce(&[Variant]) -> R,
142            {
143                let variant_args = [
144                    $(
145                        <$P::Pass as ArgPassing>::ref_to_variant(&self.$n),
146                    )*
147                ];
148
149                f(&variant_args)
150            }
151
152            fn with_variant_pointers<F, R>(self, f: F) -> R
153            where
154                F: FnOnce(&[godot_ffi::GDExtensionConstVariantPtr]) -> R,
155            {
156                self.with_variants(|variants| {
157                    let sys_args = [
158                        $(
159                            Variant::var_sys(&variants[$n]),
160                        )*
161                    ];
162                    f(&sys_args)
163                })
164            }
165
166            fn with_type_pointers<F, R>(self, f: F) -> R
167            where
168                F: FnOnce(&[godot_ffi::GDExtensionConstTypePtr]) -> R,
169            {
170                // Must be separate declarations, as pointers become invalid otherwise (UAF).
171                let ffi_args = (
172                    $(
173                        <$P::Pass as ArgPassing>::ref_to_ffi(&self.$n),
174                    )*
175                );
176
177                let ptr_args = [
178                    $(
179                        sys::GodotFfi::as_arg_ptr(&ffi_args.$n),
180                    )*
181                ];
182
183                f(&ptr_args)
184            }
185
186            fn to_variant_array(&self) -> Vec<Variant> {
187                // Using ArgPassing::ref_to_variant which works with EngineToGodot.
188                vec![
189                    $(
190                        <$P::Pass as ArgPassing>::ref_to_variant(&self.$n),
191                    )*
192                ]
193            }
194        }
195    };
196}
197
198#[allow(unused_variables, unused_mut, clippy::unused_unit)]
199mod unit_impl {
200    use super::*;
201    unsafe_impl_param_tuple!();
202}
203unsafe_impl_param_tuple!((p0, 0): P0);
204unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1);
205unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2);
206unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3);
207unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4);
208unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5);
209unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6);
210unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6, (p7, 7): P7);
211unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6, (p7, 7): P7, (p8, 8): P8);
212unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6, (p7, 7): P7, (p8, 8): P8, (p9, 9): P9);
213unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6, (p7, 7): P7, (p8, 8): P8, (p9, 9): P9, (p10, 10): P10);
214unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6, (p7, 7): P7, (p8, 8): P8, (p9, 9): P9, (p10, 10): P10, (p11, 11): P11);
215unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6, (p7, 7): P7, (p8, 8): P8, (p9, 9): P9, (p10, 10): P10, (p11, 11): P11, (p12, 12): P12);
216unsafe_impl_param_tuple!((p0, 0): P0, (p1, 1): P1, (p2, 2): P2, (p3, 3): P3, (p4, 4): P4, (p5, 5): P5, (p6, 6): P6, (p7, 7): P7, (p8, 8): P8, (p9, 9): P9, (p10, 10): P10, (p11, 11): P11, (p12, 12): P12, (p13, 13): P13);
217
218/// Convert the `N`th argument of `args_ptr` into a value of type `P`.
219///
220/// # Safety
221/// - It must be safe to dereference the address at `args_ptr.add(N)`.
222/// - The pointer at `args_ptr.add(N)` must follow the safety requirements as laid out in
223///   [`GodotFfi::from_arg_ptr`].
224pub(super) unsafe fn ptrcall_arg<P: EngineFromGodot, const N: usize>(
225    args_ptr: *const sys::GDExtensionConstTypePtr,
226    call_ctx: &CallContext,
227    call_type: sys::PtrcallType,
228) -> CallResult<P> {
229    // SAFETY: It is safe to dereference `args_ptr` at `N`.
230    let offset_ptr = unsafe { *args_ptr.add(N) };
231
232    // SAFETY: The pointer follows the safety requirements from `GodotFfi::from_arg_ptr`.
233    let ffi = unsafe {
234        <P::Via as GodotType>::Ffi::from_arg_ptr(sys::force_mut_ptr(offset_ptr), call_type)
235    };
236
237    <P::Via as GodotType>::try_from_ffi(ffi)
238        .and_then(P::engine_try_from_godot)
239        .map_err(|err| CallError::failed_param_conversion::<P>(call_ctx, N, err))
240}
241
242/// Converts `arg` into a value of type `P`.
243///
244/// # Safety
245///
246/// - It must be safe to reborrow `arg` as a `&Variant` with a lifetime that lasts for the duration of the call.
247pub(super) unsafe fn varcall_arg<P: EngineFromGodot>(
248    arg: sys::GDExtensionConstVariantPtr,
249    call_ctx: &CallContext,
250    param_index: usize,
251) -> CallResult<P> {
252    // SAFETY: It is safe to dereference `args_ptr` at `N` as a `Variant`.
253    let variant_ref = unsafe { Variant::borrow_var_sys(arg) };
254
255    variant_ref
256        .engine_try_to_relaxed::<P>()
257        .map_err(|err| CallError::failed_param_conversion::<P>(call_ctx, param_index, err))
258}
259
260fn assert_array_length<P: ParamTuple>(array: &[&Variant]) {
261    assert_eq!(
262        array.len(),
263        P::LEN,
264        "array {array:?} has wrong length, expected {} got {}",
265        P::LEN,
266        array.len()
267    );
268}
269
270// ----------------------------------------------------------------------------------------------------------------------------------------------
271
272#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
273mod test {
274    use super::*;
275
276    #[test]
277    fn format_args_test() {
278        assert_eq!(&().format_args(), "");
279        assert_eq!(&(1, 2, 3).format_args(), "1, 2, 3");
280    }
281
282    #[test]
283    fn count_idents_test() {
284        assert_eq!(2, count_idents!(a b));
285        assert_eq!(0, count_idents!());
286        assert_eq!(5, count_idents!(a b b a d));
287    }
288}