dualsense-tools-bevy 0.3.0

Bevy integration for dualsense_tools
Documentation
use std::sync::{Arc, Mutex};

use bevy::{
    input::gamepad::{GamepadConnection, GamepadConnectionEvent},
    log,
    prelude::*,
};
use dualsense_tools::*;
use hidapi::HidApi;

/// A plugin that integrate the tilt estimation algorithm implemented
/// in the dualsense-tools crate as a bevy plugin.
#[derive(Default, Debug)]
pub struct DualsenseTiltPlugin<const SAMPLES: usize> {
    estimator_config: TiltEstimatorConfig<SAMPLES>,
}

impl<const SAMPLES: usize> DualsenseTiltPlugin<SAMPLES> {
    pub fn new(estimator_config: TiltEstimatorConfig<SAMPLES>) -> DualsenseTiltPlugin<SAMPLES> {
        DualsenseTiltPlugin { estimator_config }
    }
}

impl<const SAMPLES: usize> Plugin for DualsenseTiltPlugin<SAMPLES> {
    fn build(&self, app: &mut bevy::app::App) {
        app.insert_resource(DualsenseTilt::default())
            .insert_resource(DualsenseResource::default())
            .insert_resource(TiltEstimatorResource::new(self.estimator_config))
            .add_systems(Update, handle_connection)
            .add_systems(
                Update,
                update_tilt_tilt_system::<SAMPLES>.pipe(handle_results),
            );
    }
}

/// A resource that will be updated by the plugin with the latest
/// values produced by the tilt estimator
#[derive(Resource, Default, Clone, Copy, Debug)]
pub struct DualsenseTilt(TiltEstimates);

impl DualsenseTilt {
    pub fn estimates(&self) -> TiltEstimates {
        self.0
    }
}

#[derive(Resource, Clone, Debug, Default)]
struct DualsenseResource {
    dualsense: Option<Arc<Mutex<Dualsense>>>,
}

#[derive(Resource, Clone, Debug)]
struct TiltEstimatorResource<const SAMPLES: usize> {
    state_buffer: DualsenseStatesBuffer<SAMPLES>,
    tilt_estimator: TiltEstimator<SAMPLES>,
}

impl<const SAMPLES: usize> TiltEstimatorResource<SAMPLES> {
    fn new(config: TiltEstimatorConfig<SAMPLES>) -> TiltEstimatorResource<SAMPLES> {
        TiltEstimatorResource {
            state_buffer: default(),
            tilt_estimator: TiltEstimator::new(config),
        }
    }
}

fn update_tilt_tilt_system<const SAMPLES: usize>(
    controller_res: Res<DualsenseResource>,
    tilt: ResMut<DualsenseTilt>,
    estimator_res: ResMut<TiltEstimatorResource<SAMPLES>>,
) -> Result<(), BevyError> {
    tilt.into_inner().0 = if let Some(controller) = &controller_res.into_inner().dualsense {
        let estimator = estimator_res.into_inner();
        let state = controller.lock().unwrap().read()?;
        let event = estimator.state_buffer.push(state);

        estimator.tilt_estimator.next_estimate(event)
    } else {
        TiltEstimates::default()
    };

    Ok(())
}

fn handle_connection(
    mut connection_events: MessageReader<GamepadConnectionEvent>,
    controller_res: ResMut<DualsenseResource>,
) -> Result<(), BevyError> {
    for ev in connection_events.read() {
        match &ev.connection {
            GamepadConnection::Connected {
                name: _,
                vendor_id,
                product_id,
            } => {
                if *vendor_id == Some(VENDOR_ID) && *product_id == Some(PRODUCT_ID) {
                    log::info!("Connecting Dualsense Controller");
                    let mut hidapi = HidApi::new()?;
                    let dualsense = Dualsense::new(&mut hidapi)?;
                    controller_res.into_inner().dualsense = Some(Arc::new(Mutex::new(dualsense)));
                    break;
                }
            }

            GamepadConnection::Disconnected => (),
        }
    }

    Ok(())
}

fn handle_results(r: In<Result<(), BevyError>>, controller_res: ResMut<DualsenseResource>) {
    match r.0 {
        Ok(()) => (),
        Err(err) => {
            bevy::log::error!(
                "Error in the dualsense-tilt plugin, disconnecting controller: {}",
                err
            );
            controller_res.into_inner().dualsense = None;
        }
    }
}