Skip to main content

ferroui_base/rendering/composition/expressions/
delegate_expression_ffi.rs

1//! Foreign function interface for composition animations based on calling closures.
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use super::expression_evaluation_context::IExpressionForeignFunctionInterface;
8use super::expression_variant::{ExpressionVariant, ExpressionVariantValue, VariantType};
9
10/// A registered function taking its arguments as variants.
11type FfiDelegate = Arc<dyn Fn(&[ExpressionVariant]) -> ExpressionVariant + Send + Sync>;
12
13struct FfiRecord {
14    types: Vec<VariantType>,
15    delegate: FfiDelegate,
16}
17
18/// Foreign function interface for composition animations based on calling closures.
19///
20/// Functions are overloaded by name, argument count and argument types. A
21/// call first looks for an overload whose parameter types are exactly the
22/// argument types, in registration order; then for one reachable by widening
23/// single-precision vector arguments to the double-precision vector types;
24/// then for one reachable by converting between the two vector families in
25/// either direction.
26///
27/// Names are compared exactly, or ignoring ASCII case for a registry created
28/// with [`DelegateExpressionFfi::new_ignoring_case`].
29#[derive(Default)]
30pub struct DelegateExpressionFfi {
31    registry: HashMap<String, HashMap<usize, Vec<FfiRecord>>>,
32    ignore_case: bool,
33}
34
35/// The variant type a parameter of type `T` is matched against.
36///
37/// # Panics
38/// Panics when `T` cannot be a foreign function parameter (a programmer error).
39fn type_of<T: ExpressionVariantValue>() -> VariantType {
40    match T::FFI_VARIANT_TYPE {
41        Some(t) => t,
42        None => panic!(
43            "{} cannot be a parameter of an expression foreign function",
44            std::any::type_name::<T>()
45        ),
46    }
47}
48
49/// Generates a typed `add` overload. Each parameter is given with the index
50/// of the argument it is read from.
51macro_rules! add_overload {
52    ($(#[$meta:meta])* $name:ident; $($t:ident => $index:expr),+) => {
53        $(#[$meta])*
54        pub fn $name<$($t,)+ R, F>(&mut self, name: &str, cb: F)
55        where
56            $($t: ExpressionVariantValue,)+
57            R: Into<ExpressionVariant>,
58            F: Fn($($t),+) -> R + Send + Sync + 'static,
59        {
60            self.add(
61                name,
62                Arc::new(move |args: &[ExpressionVariant]| {
63                    cb($(args[$index].cast_or_default::<$t>()),+).into()
64                }),
65                vec![$(type_of::<$t>()),+],
66            );
67        }
68    };
69}
70
71impl DelegateExpressionFfi {
72    /// Creates an empty registry whose function names are case-sensitive.
73    pub fn new() -> Self {
74        Self::default()
75    }
76
77    /// Creates an empty registry whose function names are compared ignoring
78    /// ASCII case (`lerp` calls the function registered as `Lerp`), as the
79    /// functions of WinUI composition expressions are.
80    pub fn new_ignoring_case() -> Self {
81        Self {
82            ignore_case: true,
83            ..Self::default()
84        }
85    }
86
87    /// The key a function name is registered and looked up under.
88    fn normalized_name<'n>(&self, name: &'n str) -> Cow<'n, str> {
89        if self.ignore_case && name.bytes().any(|b| b.is_ascii_uppercase()) {
90            Cow::Owned(name.to_ascii_lowercase())
91        } else {
92            Cow::Borrowed(name)
93        }
94    }
95
96    fn call_with_cast(
97        count_group: &[FfiRecord],
98        arguments: &[ExpressionVariant],
99        any_cast: bool,
100    ) -> Option<ExpressionVariant> {
101        for record in count_group {
102            let mut matches = true;
103            for (c, argument) in arguments.iter().enumerate() {
104                let parameter = record.types[c];
105                let arg = argument.variant_type();
106                if parameter != arg {
107                    let can_cast = (parameter == VariantType::Vector3D && arg == VariantType::Vector3)
108                        || (parameter == VariantType::Vector && arg == VariantType::Vector2)
109                        || (any_cast
110                            && ((arg == VariantType::Vector3D && parameter == VariantType::Vector3)
111                                || (arg == VariantType::Vector && parameter == VariantType::Vector2)));
112                    if !can_cast {
113                        matches = false;
114                        break;
115                    }
116                }
117            }
118
119            if matches {
120                return Some((record.delegate)(arguments));
121            }
122        }
123
124        if !any_cast {
125            return Self::call_with_cast(count_group, arguments, true);
126        }
127        None
128    }
129
130    fn add(&mut self, name: &str, cb: FfiDelegate, types: Vec<VariantType>) {
131        let key = self.normalized_name(name).into_owned();
132        self.registry
133            .entry(key)
134            .or_default()
135            .entry(types.len())
136            .or_default()
137            .push(FfiRecord { types, delegate: cb });
138    }
139
140    /// Registers a function without parameters.
141    pub fn add0<R, F>(&mut self, name: &str, cb: F)
142    where
143        R: Into<ExpressionVariant>,
144        F: Fn() -> R + Send + Sync + 'static,
145    {
146        self.add(name, Arc::new(move |_: &[ExpressionVariant]| cb().into()), Vec::new());
147    }
148
149    add_overload!(
150        /// Registers a function with one parameter.
151        add1; T1 => 0
152    );
153    add_overload!(
154        /// Registers a function with two parameters.
155        add2; T1 => 0, T2 => 1
156    );
157    add_overload!(
158        /// Registers a function with three parameters.
159        add3; T1 => 0, T2 => 1, T3 => 2
160    );
161    add_overload!(
162        /// Registers a function with four parameters.
163        add4; T1 => 0, T2 => 1, T3 => 2, T4 => 3
164    );
165    add_overload!(
166        /// Registers a function with five parameters.
167        add5; T1 => 0, T2 => 1, T3 => 2, T4 => 3, T5 => 4
168    );
169    add_overload!(
170        /// Registers a function with six parameters.
171        ///
172        /// The reference implementation reads the sixth parameter from the
173        /// fifth argument; here each parameter reads its own argument, so
174        /// `Matrix3x2(m11, m12, m21, m22, m31, m32)` builds the matrix WinUI
175        /// builds.
176        add6; T1 => 0, T2 => 1, T3 => 2, T4 => 3, T5 => 4, T6 => 5
177    );
178    add_overload!(
179        /// Registers a function with sixteen parameters.
180        ///
181        /// The reference implementation reads the sixth to sixteenth
182        /// parameters from the fifth argument; here each parameter reads its
183        /// own argument, as `Matrix4x4(m11, ..., m44)` of WinUI needs.
184        add16;
185        T1 => 0, T2 => 1, T3 => 2, T4 => 3,
186        T5 => 4, T6 => 5, T7 => 6, T8 => 7,
187        T9 => 8, T10 => 9, T11 => 10, T12 => 11,
188        T13 => 12, T14 => 13, T15 => 14, T16 => 15
189    );
190}
191
192impl IExpressionForeignFunctionInterface for DelegateExpressionFfi {
193    fn call(&self, name: &str, arguments: &[ExpressionVariant]) -> Option<ExpressionVariant> {
194        let key = self.normalized_name(name);
195        let name_group = self.registry.get(key.as_ref())?;
196        let count_group = name_group.get(&arguments.len())?;
197        for record in count_group {
198            let mut matches = true;
199            for (c, argument) in arguments.iter().enumerate() {
200                if record.types[c] != argument.variant_type() {
201                    matches = false;
202                    break;
203                }
204            }
205
206            if matches {
207                return Some((record.delegate)(arguments));
208            }
209        }
210
211        Self::call_with_cast(count_group, arguments, false)
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::numerics::{Vector2, Vector3};
219    use crate::{Matrix, Vector, Vector3D};
220
221    fn call(ffi: &DelegateExpressionFfi, name: &str, args: &[ExpressionVariant]) -> Option<ExpressionVariant> {
222        ffi.call(name, args)
223    }
224
225    #[test]
226    fn selects_the_overload_by_name_count_and_types() {
227        let mut ffi = DelegateExpressionFfi::new();
228        ffi.add0("F", || 0.0f32);
229        ffi.add1("F", |a: f32| a + 1.0);
230        ffi.add1("F", |a: Vector2| a.x + a.y);
231        ffi.add2("F", |a: f32, b: bool| if b { a } else { -a });
232
233        assert_eq!(call(&ffi, "F", &[]), Some(0.0.into()));
234        assert_eq!(call(&ffi, "F", &[2.0.into()]), Some(3.0.into()));
235        assert_eq!(call(&ffi, "F", &[Vector2::new(1.0, 2.0).into()]), Some(3.0.into()));
236        assert_eq!(call(&ffi, "F", &[2.0.into(), false.into()]), Some((-2.0).into()));
237        assert_eq!(call(&ffi, "F", &[true.into()]), None);
238        assert_eq!(call(&ffi, "F", &[2.0.into(), 2.0.into()]), None);
239        assert_eq!(call(&ffi, "F", &[2.0.into(), false.into(), 1.0.into()]), None);
240        assert_eq!(call(&ffi, "G", &[]), None);
241        assert_eq!(call(&ffi, "f", &[]), None);
242    }
243
244    #[test]
245    fn the_first_registered_of_equal_signatures_wins() {
246        let mut ffi = DelegateExpressionFfi::new();
247        ffi.add1("F", |a: f32| a);
248        ffi.add1("F", |a: f64| a * 2.0);
249        // Both are matched against a Double argument; the single-precision
250        // overload narrows the argument.
251        assert_eq!(call(&ffi, "F", &[0.1.into()]), Some((0.1f32 as f64).into()));
252    }
253
254    #[test]
255    fn widens_single_precision_vectors_before_narrowing() {
256        let mut ffi = DelegateExpressionFfi::new();
257        ffi.add1("Wide", |a: Vector| a.x);
258        ffi.add1("Wide3", |a: Vector3D| a.z);
259        ffi.add1("Narrow", |a: Vector2| a.x);
260        ffi.add1("Narrow3", |a: Vector3| a.z);
261        ffi.add2("Both", |a: Vector2, _b: Vector2| a.x);
262        ffi.add2("Both", |a: Vector, _b: Vector| a.x + 100.0);
263
264        assert_eq!(call(&ffi, "Wide", &[Vector2::new(1.5, 0.0).into()]), Some(1.5.into()));
265        assert_eq!(
266            call(&ffi, "Wide3", &[Vector3::new(0.0, 0.0, 2.5).into()]),
267            Some(2.5.into())
268        );
269        assert_eq!(call(&ffi, "Narrow", &[Vector::new(1.5, 0.0).into()]), Some(1.5.into()));
270        assert_eq!(
271            call(&ffi, "Narrow3", &[Vector3D::new(0.0, 0.0, 2.5).into()]),
272            Some(2.5.into())
273        );
274        // Mixed arguments: only the widening overload is reachable in the first pass.
275        assert_eq!(
276            call(
277                &ffi,
278                "Both",
279                &[Vector2::new(1.0, 0.0).into(), Vector::new(0.0, 0.0).into()]
280            ),
281            Some(101.0.into())
282        );
283        assert_eq!(call(&ffi, "Wide", &[Vector3::new(1.0, 2.0, 3.0).into()]), None);
284    }
285
286    #[test]
287    fn every_parameter_reads_its_own_argument() {
288        let mut ffi = DelegateExpressionFfi::new();
289        ffi.add5("F", |a: f32, b: f32, c: f32, d: f32, e: f32| {
290            a + 10.0 * b + 100.0 * c + 1000.0 * d + 10000.0 * e
291        });
292        ffi.add6("F", |_a: f32, _b: f32, _c: f32, _d: f32, e: f32, f: f32| e * 10.0 + f);
293        #[allow(clippy::too_many_arguments)]
294        ffi.add16(
295            "F",
296            |a: f32,
297             _b: f32,
298             _c: f32,
299             _d: f32,
300             e: f32,
301             f: f32,
302             _g: f32,
303             _h: f32,
304             _i: f32,
305             j: f32,
306             _k: f32,
307             _l: f32,
308             _m: f32,
309             _n: f32,
310             _o: f32,
311             p: f32| a + e * 10.0 + f * 100.0 + j * 1000.0 + p * 10000.0,
312        );
313        let args: Vec<ExpressionVariant> = (1..=16).map(|v| (v as f64).into()).collect();
314        assert_eq!(call(&ffi, "F", &args[..5]), Some(54321.0.into()));
315        assert_eq!(call(&ffi, "F", &args[..6]), Some(56.0.into()));
316        assert_eq!(call(&ffi, "F", &args), Some(170651.0.into()));
317    }
318
319    #[test]
320    fn a_registry_ignoring_case_matches_names_in_any_case() {
321        let mut ffi = DelegateExpressionFfi::new_ignoring_case();
322        ffi.add1("Lerp", |a: f32| a);
323        ffi.add1("Matrix3x2.CreateScale", |a: f32| a * 2.0);
324        assert_eq!(call(&ffi, "Lerp", &[1.0.into()]), Some(1.0.into()));
325        assert_eq!(call(&ffi, "lerp", &[1.0.into()]), Some(1.0.into()));
326        assert_eq!(call(&ffi, "LERP", &[1.0.into()]), Some(1.0.into()));
327        assert_eq!(call(&ffi, "matrix3x2.createscale", &[1.0.into()]), Some(2.0.into()));
328        assert_eq!(call(&ffi, "Lerp2", &[1.0.into()]), None);
329        // Overloads registered under different cases of a name are one group.
330        ffi.add1("LERP", |a: Vector2| a.x);
331        assert_eq!(call(&ffi, "lerp", &[Vector2::new(3.0, 0.0).into()]), Some(3.0.into()));
332    }
333
334    #[test]
335    #[should_panic(expected = "cannot be a parameter")]
336    fn the_3x3_matrix_cannot_be_a_parameter() {
337        let mut ffi = DelegateExpressionFfi::new();
338        ffi.add1("F", |m: Matrix| m);
339    }
340
341    #[test]
342    fn is_shareable_between_threads() {
343        fn assert_send_sync<T: Send + Sync>() {}
344        assert_send_sync::<DelegateExpressionFfi>();
345    }
346}