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}