use std::collections::{HashMap, HashSet};
use repame_input::{HapticBus, HapticSink};
use repose_app::ReposeRuntime;
use repose_core::input::GamepadId;
use web_time::{Duration, Instant};
pub const RUMBLE_UPLOAD_MS: u32 = 1000;
pub const RUMBLE_REFRESH: Duration = Duration::from_millis(800);
const MOTOR_EPSILON: f32 = 0.001;
#[derive(Clone, Copy, Debug)]
struct Live {
strong: f32,
weak: f32,
uploaded: Instant,
}
#[derive(Default)]
pub struct RumbleBridge {
live: HashMap<u64, Live>,
queued: Vec<(GamepadId, f32, f32, u32)>,
}
impl RumbleBridge {
pub fn new() -> Self {
Self::default()
}
pub fn sync(&mut self, bus: &HapticBus) {
let now = Instant::now();
self.sync_at(bus, now);
}
pub fn sync_at(&mut self, bus: &HapticBus, now: Instant) {
let active: HashSet<u64> = bus.active_devices().collect();
let stopped: Vec<u64> = self
.live
.keys()
.copied()
.filter(|device| !active.contains(device))
.collect();
for device in stopped {
self.live.remove(&device);
self.queued.push((GamepadId(device as u32), 0.0, 0.0, 0));
}
for device in active {
let (strong, weak) = bus.motors(device);
self.set(device, strong, weak, now);
}
}
pub fn forward_to(&mut self, runtime: &mut ReposeRuntime) {
for (id, strong, weak, duration_ms) in self.drain() {
runtime.request_rumble(id, strong, weak, duration_ms);
}
}
pub fn drain(&mut self) -> Vec<(GamepadId, f32, f32, u32)> {
std::mem::take(&mut self.queued)
}
fn set(&mut self, device: u64, strong: f32, weak: f32, now: Instant) {
let strong = clamp_motor(strong);
let weak = clamp_motor(weak);
let stopped = strong <= MOTOR_EPSILON && weak <= MOTOR_EPSILON;
let stale = self
.live
.get(&device)
.is_some_and(|live| now.duration_since(live.uploaded) >= RUMBLE_REFRESH);
let changed = self
.live
.get(&device)
.is_none_or(|live| live.strong != strong || live.weak != weak);
if !stopped && !stale && !changed {
return;
}
if stopped {
if self.live.remove(&device).is_some() {
self.queued.push((GamepadId(device as u32), 0.0, 0.0, 0));
}
return;
}
self.live.insert(
device,
Live {
strong,
weak,
uploaded: now,
},
);
self.queued
.push((GamepadId(device as u32), strong, weak, RUMBLE_UPLOAD_MS));
}
}
impl HapticSink for RumbleBridge {
fn apply(&mut self, device: u64, strong: f32, weak: f32) {
let now = Instant::now();
self.set(device, strong, weak, now);
}
}
fn clamp_motor(value: f32) -> f32 {
if value.is_nan() {
return 0.0;
}
value.clamp(0.0, 1.0)
}