Skip to main content

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