use crate::dsl::Program;
use crate::error::Error;
use crate::motor::MotorConfig;
use crate::motor::MotorProfile;
use crate::runtime::Runner;
use ph_curves::MonotonicCurveLut256;
#[derive(Copy, Clone, Debug)]
pub struct CompiledHapticDef<'a> {
pub name: &'a str,
pub program: Program<'a, MonotonicCurveLut256>,
pub profile: Option<&'a MotorProfile>,
}
impl<'a> CompiledHapticDef<'a> {
pub const fn new(
name: &'a str,
program: Program<'a, MonotonicCurveLut256>,
profile: Option<&'a MotorProfile>,
) -> Self {
Self {
name,
program,
profile,
}
}
pub fn runner(&'a self, motor: MotorConfig) -> Result<Runner<'a, MonotonicCurveLut256>, Error> {
Runner::from_compiled(self, motor)
}
pub fn runner_started(
&'a self,
motor: MotorConfig,
now_ms: u32,
) -> Result<Runner<'a, MonotonicCurveLut256>, Error> {
Runner::from_compiled_started(self, motor, now_ms)
}
}
#[derive(Copy, Clone, Debug)]
pub struct CompiledCatalog<'a> {
defs: &'a [CompiledHapticDef<'a>],
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum CompiledCatalogError {
NotFound,
Runner(Error),
}
impl From<Error> for CompiledCatalogError {
fn from(value: Error) -> Self {
Self::Runner(value)
}
}
impl core::fmt::Display for CompiledCatalogError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NotFound => f.write_str("no compiled haptic with that name"),
Self::Runner(error) => write!(f, "runner construction failed: {error}"),
}
}
}
impl core::error::Error for CompiledCatalogError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::NotFound => None,
Self::Runner(error) => Some(error),
}
}
}
impl<'a> CompiledCatalog<'a> {
pub const fn new(defs: &'a [CompiledHapticDef<'a>]) -> Self {
Self { defs }
}
pub const fn definitions(&self) -> &'a [CompiledHapticDef<'a>] {
self.defs
}
pub const fn is_empty(&self) -> bool {
self.defs.is_empty()
}
pub fn find(&self, name: &str) -> Option<&'a CompiledHapticDef<'a>> {
let mut index = 0usize;
while index < self.defs.len() {
let def = &self.defs[index];
if def.name.eq_ignore_ascii_case(name) {
return Some(def);
}
index += 1;
}
None
}
pub fn require(&self, name: &str) -> Result<&'a CompiledHapticDef<'a>, CompiledCatalogError> {
self.find(name).ok_or(CompiledCatalogError::NotFound)
}
pub fn runner(
&self,
name: &str,
motor: MotorConfig,
) -> Result<Runner<'a, MonotonicCurveLut256>, CompiledCatalogError> {
let def = self.require(name)?;
def.runner(motor).map_err(CompiledCatalogError::from)
}
pub fn runner_started(
&self,
name: &str,
motor: MotorConfig,
now_ms: u32,
) -> Result<Runner<'a, MonotonicCurveLut256>, CompiledCatalogError> {
let def = self.require(name)?;
def.runner_started(motor, now_ms)
.map_err(CompiledCatalogError::from)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dsl::Instruction;
use crate::motor::{ErmConfig, LraConfig, MotorKind};
#[test]
fn compiled_definition_runner_helpers_work() {
let instructions: [Instruction<MonotonicCurveLut256>; 1] = [Instruction::hold(10, 20_000)];
let program = Program::new(MotorKind::Erm, &instructions);
let def = CompiledHapticDef::new("tap", program, None);
let mut runner = def
.runner_started(MotorConfig::Erm(ErmConfig::new(255)), 10)
.unwrap();
let frame = runner.poll(10);
assert!(frame.is_active());
}
#[test]
fn compiled_catalog_lookup_and_runner_work() {
let a_instructions: [Instruction<MonotonicCurveLut256>; 1] =
[Instruction::hold(10, 30_000)];
let b_instructions: [Instruction<MonotonicCurveLut256>; 1] =
[Instruction::hold(10, 40_000)];
let a = CompiledHapticDef::new("tap", Program::new(MotorKind::Erm, &a_instructions), None);
let b = CompiledHapticDef::new("buzz", Program::new(MotorKind::Lra, &b_instructions), None);
let defs = [a, b];
let catalog = CompiledCatalog::new(&defs);
let found = catalog.require("TAP").unwrap();
assert_eq!(found.name, "tap");
let mut runner = catalog
.runner_started("tap", MotorConfig::Erm(ErmConfig::new(255)), 0)
.unwrap();
assert!(runner.poll(0).is_active());
let err = catalog
.runner("buzz", MotorConfig::Erm(ErmConfig::new(255)))
.unwrap_err();
assert_eq!(
err,
CompiledCatalogError::Runner(Error::MotorKindMismatch {
expected: MotorKind::Lra,
got: MotorKind::Erm
})
);
let err = catalog
.runner("missing", MotorConfig::Lra(LraConfig::new(100, 200)))
.unwrap_err();
assert_eq!(err, CompiledCatalogError::NotFound);
}
}