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