1use crate::dsl::Program;
2use crate::error::Error;
3use crate::motor::MotorConfig;
4use crate::motor::MotorProfile;
5use crate::runtime::Runner;
6use ph_curves::MonotonicCurveLut256;
7
8#[derive(Copy, Clone, Debug)]
10pub struct CompiledHapticDef<'a> {
11 pub name: &'a str,
13 pub program: Program<'a, MonotonicCurveLut256>,
15 pub profile: Option<&'a MotorProfile>,
20}
21
22impl<'a> CompiledHapticDef<'a> {
23 pub const fn new(
28 name: &'a str,
29 program: Program<'a, MonotonicCurveLut256>,
30 profile: Option<&'a MotorProfile>,
31 ) -> Self {
32 Self {
33 name,
34 program,
35 profile,
36 }
37 }
38
39 pub fn runner(&'a self, motor: MotorConfig) -> Result<Runner<'a, MonotonicCurveLut256>, Error> {
41 Runner::from_compiled(self, motor)
42 }
43
44 pub fn runner_started(
46 &'a self,
47 motor: MotorConfig,
48 now_ms: u32,
49 ) -> Result<Runner<'a, MonotonicCurveLut256>, Error> {
50 Runner::from_compiled_started(self, motor, now_ms)
51 }
52}
53
54#[derive(Copy, Clone, Debug)]
56pub struct CompiledCatalog<'a> {
57 defs: &'a [CompiledHapticDef<'a>],
58}
59
60#[derive(Copy, Clone, Debug, Eq, PartialEq)]
62pub enum CompiledCatalogError {
63 NotFound,
65 Runner(Error),
67}
68
69impl From<Error> for CompiledCatalogError {
70 fn from(value: Error) -> Self {
71 Self::Runner(value)
72 }
73}
74
75impl core::fmt::Display for CompiledCatalogError {
76 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
77 match self {
78 Self::NotFound => f.write_str("no compiled haptic with that name"),
79 Self::Runner(error) => write!(f, "runner construction failed: {error}"),
80 }
81 }
82}
83
84impl core::error::Error for CompiledCatalogError {
85 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
86 match self {
87 Self::NotFound => None,
88 Self::Runner(error) => Some(error),
89 }
90 }
91}
92
93impl<'a> CompiledCatalog<'a> {
94 pub const fn new(defs: &'a [CompiledHapticDef<'a>]) -> Self {
96 Self { defs }
97 }
98
99 pub const fn definitions(&self) -> &'a [CompiledHapticDef<'a>] {
101 self.defs
102 }
103
104 pub const fn is_empty(&self) -> bool {
106 self.defs.is_empty()
107 }
108
109 pub fn find(&self, name: &str) -> Option<&'a CompiledHapticDef<'a>> {
111 let mut index = 0usize;
112 while index < self.defs.len() {
113 let def = &self.defs[index];
114 if def.name.eq_ignore_ascii_case(name) {
115 return Some(def);
116 }
117 index += 1;
118 }
119 None
120 }
121
122 pub fn require(&self, name: &str) -> Result<&'a CompiledHapticDef<'a>, CompiledCatalogError> {
124 self.find(name).ok_or(CompiledCatalogError::NotFound)
125 }
126
127 pub fn runner(
129 &self,
130 name: &str,
131 motor: MotorConfig,
132 ) -> Result<Runner<'a, MonotonicCurveLut256>, CompiledCatalogError> {
133 let def = self.require(name)?;
134 def.runner(motor).map_err(CompiledCatalogError::from)
135 }
136
137 pub fn runner_started(
139 &self,
140 name: &str,
141 motor: MotorConfig,
142 now_ms: u32,
143 ) -> Result<Runner<'a, MonotonicCurveLut256>, CompiledCatalogError> {
144 let def = self.require(name)?;
145 def.runner_started(motor, now_ms)
146 .map_err(CompiledCatalogError::from)
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::dsl::Instruction;
154 use crate::motor::{ErmConfig, LraConfig, MotorKind};
155
156 #[test]
157 fn compiled_definition_runner_helpers_work() {
158 let instructions: [Instruction<MonotonicCurveLut256>; 1] = [Instruction::hold(10, 20_000)];
159 let program = Program::new(MotorKind::Erm, &instructions);
160 let def = CompiledHapticDef::new("tap", program, None);
161
162 let mut runner = def
163 .runner_started(MotorConfig::Erm(ErmConfig::new(255)), 10)
164 .unwrap();
165 let frame = runner.poll(10);
166 assert!(frame.is_active());
167 }
168
169 #[test]
170 fn compiled_catalog_lookup_and_runner_work() {
171 let a_instructions: [Instruction<MonotonicCurveLut256>; 1] =
172 [Instruction::hold(10, 30_000)];
173 let b_instructions: [Instruction<MonotonicCurveLut256>; 1] =
174 [Instruction::hold(10, 40_000)];
175 let a = CompiledHapticDef::new("tap", Program::new(MotorKind::Erm, &a_instructions), None);
176 let b = CompiledHapticDef::new("buzz", Program::new(MotorKind::Lra, &b_instructions), None);
177 let defs = [a, b];
178 let catalog = CompiledCatalog::new(&defs);
179
180 let found = catalog.require("TAP").unwrap();
181 assert_eq!(found.name, "tap");
182
183 let mut runner = catalog
184 .runner_started("tap", MotorConfig::Erm(ErmConfig::new(255)), 0)
185 .unwrap();
186 assert!(runner.poll(0).is_active());
187
188 let err = catalog
189 .runner("buzz", MotorConfig::Erm(ErmConfig::new(255)))
190 .unwrap_err();
191 assert_eq!(
192 err,
193 CompiledCatalogError::Runner(Error::MotorKindMismatch {
194 expected: MotorKind::Lra,
195 got: MotorKind::Erm
196 })
197 );
198
199 let err = catalog
200 .runner("missing", MotorConfig::Lra(LraConfig::new(100, 200)))
201 .unwrap_err();
202 assert_eq!(err, CompiledCatalogError::NotFound);
203 }
204}