fidget_core/eval/
bulk.rs

1//! Evaluates many points in a single call
2//!
3//! Doing bulk evaluations helps limit to overhead of instruction dispatch, and
4//! can take advantage of SIMD.
5//!
6//! It is unlikely that you'll want to use these traits or types directly;
7//! they're implementation details to minimize code duplication.
8
9use crate::{Error, eval::Tape};
10
11/// Trait for bulk evaluation returning the given type `T`
12///
13/// Bulk evaluators should usually be constructed on a per-thread basis.
14///
15/// They contain (at minimum) output array storage, which is borrowed in the
16/// return from [`eval`](BulkEvaluator::eval).  They may also contain
17/// intermediate storage (e.g. an array of VM registers).
18pub trait BulkEvaluator: Default {
19    /// Data type used during evaluation
20    type Data: From<f32> + Copy + Clone;
21
22    /// Instruction tape used during evaluation
23    ///
24    /// This may be a literal instruction tape (in the case of VM evaluation),
25    /// or a metaphorical instruction tape (e.g. a JIT function).
26    type Tape: Tape<Storage = Self::TapeStorage>;
27
28    /// Associated type for tape storage
29    ///
30    /// This is a workaround for plumbing purposes
31    type TapeStorage;
32
33    /// Evaluates many points using the given instruction tape
34    ///
35    /// `vars` should be a slice-of-slices (or a slice-of-`Vec`s) representing
36    /// input arguments for each of the tape's variables; use [`Tape::vars`] to
37    /// map from [`Var`](crate::var::Var) to position in the list.
38    ///
39    /// The returned slice is borrowed from the evaluator.
40    ///
41    /// Returns an error if any of the `var` slices are of different lengths, or
42    /// if all variables aren't present.
43    fn eval<V: std::ops::Deref<Target = [Self::Data]>>(
44        &mut self,
45        tape: &Self::Tape,
46        vars: &[V],
47    ) -> Result<BulkOutput<'_, Self::Data>, Error>;
48
49    /// Build a new empty evaluator
50    fn new() -> Self {
51        Self::default()
52    }
53}
54
55/// Container for bulk output results
56///
57/// This container represents an array-of-arrays.  It is indexed first by
58/// output index, then by index within the evaluation array.
59pub struct BulkOutput<'a, T> {
60    data: &'a Vec<Vec<T>>,
61    len: usize,
62}
63
64impl<'a, T> BulkOutput<'a, T> {
65    /// Builds a new output handle
66    ///
67    /// Within each array in `data`, only the first `len` values are valid
68    pub fn new(data: &'a Vec<Vec<T>>, len: usize) -> Self {
69        Self { data, len }
70    }
71
72    /// Returns the number of output variables
73    ///
74    /// Note that this is **not** the length of each individual output slice;
75    /// that can be found with `out[0].len()` (assuming there is at least one
76    /// output variable).
77    pub fn len(&self) -> usize {
78        self.data.len()
79    }
80
81    /// Checks whether the output contains zero variables
82    pub fn is_empty(&self) -> bool {
83        self.data.is_empty()
84    }
85}
86
87impl<'a, T> std::ops::Index<usize> for BulkOutput<'a, T> {
88    type Output = [T];
89    fn index(&self, i: usize) -> &'a Self::Output {
90        &self.data[i][0..self.len]
91    }
92}
93
94impl<'a, T> BulkOutput<'a, T> {
95    /// Helper function to borrow using the original reference lifetime
96    pub(crate) fn borrow(&self, i: usize) -> &'a [T] {
97        &self.data[i][0..self.len]
98    }
99}