1use std::collections::BTreeMap;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct HapticEffect {
6 pub strong: f32,
7 pub weak: f32,
8 pub duration: f32,
9}
10
11impl HapticEffect {
12 pub fn constant(strong: f32, weak: f32) -> Self {
13 Self {
14 strong,
15 weak,
16 duration: 0.0,
17 }
18 }
19
20 pub fn for_seconds(strong: f32, weak: f32, duration: f32) -> Self {
21 Self {
22 strong,
23 weak,
24 duration: duration.max(0.0),
25 }
26 }
27}
28
29pub trait HapticSink {
31 fn apply(&mut self, device: u64, strong: f32, weak: f32);
32}
33
34#[derive(Debug, Clone, Copy, PartialEq)]
35struct Motor {
36 strong: f32,
37 weak: f32,
38 remaining: f32,
39 timed: bool,
40}
41
42#[derive(Debug, Clone, Default, PartialEq)]
44pub struct HapticBus {
45 devices: BTreeMap<u64, Motor>,
46}
47
48impl HapticBus {
49 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn play(&mut self, device: u64, effect: HapticEffect) {
55 self.devices.insert(
56 device,
57 Motor {
58 strong: clamp_motor(effect.strong),
59 weak: clamp_motor(effect.weak),
60 remaining: effect.duration,
61 timed: effect.duration > 0.0,
62 },
63 );
64 }
65
66 pub fn stop(&mut self, device: u64) {
67 self.devices.remove(&device);
68 }
69
70 pub fn stop_all(&mut self) {
71 self.devices.clear();
72 }
73
74 pub fn advance(&mut self, dt_seconds: f32) {
76 if dt_seconds <= 0.0 {
77 return;
78 }
79 for motor in self.devices.values_mut() {
80 if motor.timed {
81 motor.remaining = (motor.remaining - dt_seconds).max(0.0);
82 }
83 }
84 self.devices
85 .retain(|_, motor| !motor.timed || motor.remaining > 0.0);
86 }
87
88 pub fn motors(&self, device: u64) -> (f32, f32) {
90 match self.devices.get(&device) {
91 Some(motor) => (motor.strong, motor.weak),
92 None => (0.0, 0.0),
93 }
94 }
95
96 pub fn is_playing(&self, device: u64) -> bool {
97 self.devices.contains_key(&device)
98 }
99
100 pub fn active_devices(&self) -> impl Iterator<Item = u64> + '_ {
101 self.devices.keys().copied()
102 }
103
104 pub fn apply_to(&self, sink: &mut dyn HapticSink) {
106 for (&device, motor) in &self.devices {
107 sink.apply(device, motor.strong, motor.weak);
108 }
109 }
110}
111
112fn clamp_motor(value: f32) -> f32 {
113 if value.is_nan() {
114 return 0.0;
115 }
116 value.clamp(0.0, 1.0)
117}