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}