// Copyright 2016-2018 Mateusz Sieczko and other GilRs Developers // // Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be // copied, modified, or distributed except according to those terms. use super::effect_source::{DistanceModel, EffectSource, EffectState, Magnitude}; use super::time::{Repeat, Ticks, TICK_DURATION}; use std::ops::{Deref, DerefMut}; use std::sync::mpsc::{self, Receiver, Sender}; use std::thread; use std::time::{Duration, Instant}; use crate::gamepad::GamepadId; use crate::Event; use gilrs_core::FfDevice; use vec_map::VecMap; #[derive(Debug)] pub(crate) enum Message { Create { id: usize, effect: Box, }, HandleCloned { id: usize, }, HandleDropped { id: usize, }, Play { id: usize, }, Stop { id: usize, }, Open { id: usize, device: FfDevice, }, Close { id: usize, }, SetListenerPosition { id: usize, position: [f32; 3], }, SetGamepads { id: usize, gamepads: VecMap<()>, }, AddGamepad { id: usize, gamepad_id: GamepadId, }, SetRepeat { id: usize, repeat: Repeat, }, SetDistanceModel { id: usize, model: DistanceModel, }, SetPosition { id: usize, position: [f32; 3], }, SetGain { id: usize, gain: f32, }, } pub(crate) enum FfMessage { EffectCompleted { event: Event }, } impl Message { // Whether to use trace level logging or debug fn use_trace_level(&self) -> bool { use self::Message::*; matches!( self, &SetListenerPosition { .. } | &HandleCloned { .. } | &HandleDropped { .. } ) } } #[derive(Debug)] struct Device { inner: FfDevice, position: [f32; 3], } struct Effect { source: EffectSource, /// Number of created effect's handles. count: usize, } impl Effect { fn inc(&mut self) -> usize { self.count += 1; self.count } fn dec(&mut self) -> usize { self.count -= 1; self.count } } impl From for Effect { fn from(source: EffectSource) -> Self { Effect { source, count: 1 } } } impl Deref for Effect { type Target = EffectSource; fn deref(&self) -> &Self::Target { &self.source } } impl DerefMut for Effect { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.source } } impl From for Device { fn from(inner: FfDevice) -> Self { Device { inner, position: [0.0, 0.0, 0.0], } } } pub(crate) fn run(tx: Sender, rx: Receiver) { let mut effects = VecMap::::new(); let mut devices = VecMap::::new(); let sleep_dur = Duration::from_millis(TICK_DURATION.into()); let mut tick = Ticks(0); let mut completion_events = Vec::::new(); loop { let t1 = Instant::now(); while let Ok(ev) = rx.try_recv() { if ev.use_trace_level() { trace!("New ff event: {:?}", ev); } else { debug!("New ff event: {:?}", ev); } match ev { Message::Create { id, effect } => { effects.insert(id, (*effect).into()); } Message::Play { id } => { if let Some(effect) = effects.get_mut(id) { effect.source.state = EffectState::Playing { since: tick } } else { error!("{:?} with wrong ID", ev); } } Message::Stop { id } => { if let Some(effect) = effects.get_mut(id) { effect.source.state = EffectState::Stopped } else { error!("{:?} with wrong ID", ev); } } Message::Open { id, device } => { devices.insert(id, device.into()); } Message::Close { id } => { devices.remove(id); } Message::SetListenerPosition { id, position } => { if let Some(device) = devices.get_mut(id) { device.position = position; } else { error!("{:?} with wrong ID", ev); } } Message::HandleCloned { id } => { if let Some(effect) = effects.get_mut(id) { effect.inc(); } else { error!("{:?} with wrong ID", ev); } } Message::HandleDropped { id } => { let mut drop = false; if let Some(effect) = effects.get_mut(id) { if effect.dec() == 0 { drop = true; } } else { error!("{:?} with wrong ID", ev); } if drop { effects.remove(id); } } Message::SetGamepads { id, gamepads } => { if let Some(eff) = effects.get_mut(id) { eff.source.devices = gamepads; } else { error!("Invalid effect id {} when changing gamepads.", id); } } Message::AddGamepad { id, gamepad_id } => { if let Some(eff) = effects.get_mut(id) { eff.source.devices.insert(gamepad_id.0, ()); } else { error!("Invalid effect id {} when changing gamepads.", id); } } Message::SetRepeat { id, repeat } => { if let Some(eff) = effects.get_mut(id) { eff.source.repeat = repeat; } else { error!("Invalid effect id {} when changing repeat mode.", id); } } Message::SetDistanceModel { id, model } => { if let Some(eff) = effects.get_mut(id) { eff.source.distance_model = model; } else { error!("Invalid effect id {} when changing distance model.", id); } } Message::SetPosition { id, position } => { if let Some(eff) = effects.get_mut(id) { eff.source.position = position; } else { error!("Invalid effect id {}.", id); } } Message::SetGain { id, gain } => { if let Some(eff) = effects.get_mut(id) { eff.source.gain = gain; } else { error!("Invalid effect id {} when changing effect gain.", id); } } } } combine_and_play(&mut effects, &mut devices, tick, &mut completion_events); completion_events.iter().for_each(|ev| { let _ = tx.send(FfMessage::EffectCompleted { event: *ev }); }); completion_events.clear(); let dur = Instant::now().duration_since(t1); if dur > sleep_dur { // TODO: Should we add dur - sleep_dur to next iteration's dur? warn!( "One iteration of a force feedback loop took more than {}ms!", TICK_DURATION ); } else { thread::sleep(sleep_dur - dur); } tick.inc(); } } pub(crate) fn init() -> (Sender, Receiver) { let (tx, _rx) = mpsc::channel(); let (_tx2, rx2) = mpsc::channel(); // Wasm doesn't support threads and force feedback #[cfg(not(target_arch = "wasm32"))] std::thread::Builder::new() .name("gilrs".to_owned()) .spawn(move || run(_tx2, _rx)) .expect("failed to spawn thread"); (tx, rx2) } fn combine_and_play( effects: &mut VecMap, devices: &mut VecMap, tick: Ticks, completion_events: &mut Vec, ) { for (dev_id, dev) in devices { let mut magnitude = Magnitude::zero(); for (_, ref mut effect) in effects.iter_mut() { if effect.devices.contains_key(dev_id) { magnitude += effect.combine_base_effects(tick, dev.position); completion_events.extend(effect.flush_completion_events()); } } trace!( "({:?}) Setting ff state of {:?} to {:?}", tick, dev, magnitude ); dev.inner.set_ff_state( magnitude.strong, magnitude.weak, Duration::from_millis(u64::from(TICK_DURATION) * 2), ); } }