Skip to main content

dualsense_tools_bevy/
lib.rs

1use std::sync::{Arc, Mutex};
2
3use bevy::{
4    input::gamepad::{GamepadConnection, GamepadConnectionEvent},
5    log,
6    prelude::*,
7};
8use dualsense_tools::*;
9use hidapi::HidApi;
10
11/// A plugin that integrate the tilt estimation algorithm implemented
12/// in the dualsense-tools crate as a bevy plugin.
13#[derive(Default, Debug)]
14pub struct DualsenseTiltPlugin<const SAMPLES: usize> {
15    estimator_config: TiltEstimatorConfig<SAMPLES>,
16}
17
18impl<const SAMPLES: usize> DualsenseTiltPlugin<SAMPLES> {
19    pub fn new(estimator_config: TiltEstimatorConfig<SAMPLES>) -> DualsenseTiltPlugin<SAMPLES> {
20        DualsenseTiltPlugin { estimator_config }
21    }
22}
23
24impl<const SAMPLES: usize> Plugin for DualsenseTiltPlugin<SAMPLES> {
25    fn build(&self, app: &mut bevy::app::App) {
26        app.insert_resource(DualsenseTilt::default())
27            .insert_resource(DualsenseResource::default())
28            .insert_resource(TiltEstimatorResource::new(self.estimator_config))
29            .add_systems(Update, handle_connection)
30            .add_systems(
31                Update,
32                update_tilt_tilt_system::<SAMPLES>.pipe(handle_results),
33            );
34    }
35}
36
37/// A resource that will be updated by the plugin with the latest
38/// values produced by the tilt estimator
39#[derive(Resource, Default, Clone, Copy, Debug)]
40pub struct DualsenseTilt(TiltEstimates);
41
42impl DualsenseTilt {
43    pub fn estimates(&self) -> TiltEstimates {
44        self.0
45    }
46}
47
48#[derive(Resource, Clone, Debug, Default)]
49struct DualsenseResource {
50    dualsense: Option<Arc<Mutex<Dualsense>>>,
51}
52
53#[derive(Resource, Clone, Debug)]
54struct TiltEstimatorResource<const SAMPLES: usize> {
55    state_buffer: DualsenseStatesBuffer<SAMPLES>,
56    tilt_estimator: TiltEstimator<SAMPLES>,
57}
58
59impl<const SAMPLES: usize> TiltEstimatorResource<SAMPLES> {
60    fn new(config: TiltEstimatorConfig<SAMPLES>) -> TiltEstimatorResource<SAMPLES> {
61        TiltEstimatorResource {
62            state_buffer: default(),
63            tilt_estimator: TiltEstimator::new(config),
64        }
65    }
66}
67
68fn update_tilt_tilt_system<const SAMPLES: usize>(
69    controller_res: Res<DualsenseResource>,
70    tilt: ResMut<DualsenseTilt>,
71    estimator_res: ResMut<TiltEstimatorResource<SAMPLES>>,
72) -> Result<(), BevyError> {
73    tilt.into_inner().0 = if let Some(controller) = &controller_res.into_inner().dualsense {
74        let estimator = estimator_res.into_inner();
75        let state = controller.lock().unwrap().read()?;
76        let event = estimator.state_buffer.push(state);
77
78        estimator.tilt_estimator.next_estimate(event)
79    } else {
80        TiltEstimates::default()
81    };
82
83    Ok(())
84}
85
86fn handle_connection(
87    mut connection_events: MessageReader<GamepadConnectionEvent>,
88    controller_res: ResMut<DualsenseResource>,
89) -> Result<(), BevyError> {
90    for ev in connection_events.read() {
91        match &ev.connection {
92            GamepadConnection::Connected {
93                name: _,
94                vendor_id,
95                product_id,
96            } => {
97                if *vendor_id == Some(VENDOR_ID) && *product_id == Some(PRODUCT_ID) {
98                    log::info!("Connecting Dualsense Controller");
99                    let mut hidapi = HidApi::new()?;
100                    let dualsense = Dualsense::new(&mut hidapi)?;
101                    controller_res.into_inner().dualsense = Some(Arc::new(Mutex::new(dualsense)));
102                    break;
103                }
104            }
105
106            GamepadConnection::Disconnected => (),
107        }
108    }
109
110    Ok(())
111}
112
113fn handle_results(r: In<Result<(), BevyError>>, controller_res: ResMut<DualsenseResource>) {
114    match r.0 {
115        Ok(()) => (),
116        Err(err) => {
117            bevy::log::error!(
118                "Error in the dualsense-tilt plugin, disconnecting controller: {}",
119                err
120            );
121            controller_res.into_inner().dualsense = None;
122        }
123    }
124}