1use std::sync::{Arc, atomic, mpsc};
4use std::thread;
5use std::time::{Duration, Instant};
6
7use crate::READ_ATOMIC_BOOL_ORDERING;
8use crate::devices::device;
9use crate::dsu::DSUFrame;
10use crate::errors::DeviceError;
11
12const FROZEN_DETECT_THRESHOLD: usize = 100;
15const REINIT_RETRY_INTERVAL: Duration = Duration::from_secs(1);
17const DISCONNECT_THRESHOLD: usize = 100;
20
21pub struct Reader {
23 handle: thread::JoinHandle<()>,
24}
25
26impl Reader {
27 pub fn start(
31 running: Arc<atomic::AtomicBool>,
32 device: impl device::Device + std::marker::Send + 'static,
33 ) -> (Self, mpsc::Receiver<DSUFrame>) {
34 let (tx, rx) = mpsc::channel::<DSUFrame>();
35
36 let handle = thread::spawn(move || {
37 let mut frame_state = FrameState::new();
38
39 log::debug!("Reader thread started");
40
41 while running.load(READ_ATOMIC_BOOL_ORDERING) {
42 if !read_frame(&device, &mut frame_state, &tx) {
43 break;
44 }
45 }
46
47 log::debug!(
48 "Reader thread finished after {} frames",
49 frame_state.total_frames
50 );
51 });
52
53 (Self { handle }, rx)
54 }
55
56 pub fn join(self) -> Result<(), Box<dyn std::any::Any + Send>> {
58 self.handle.join()
59 }
60}
61
62struct FrameState {
63 pub frozen_count: usize,
64 pub total_frames: usize,
65 pub prev_frame: Option<DSUFrame>,
66 pub fail_count: usize,
67 pub last_init_attempt: Option<Instant>,
68}
69
70impl FrameState {
71 pub fn new() -> Self {
72 Self {
73 frozen_count: 0,
74 total_frames: 0,
75 prev_frame: None,
76 fail_count: 0,
77 last_init_attempt: None,
78 }
79 }
80}
81
82fn read_frame<D>(device: &D, frame_state: &mut FrameState, tx: &mpsc::Sender<DSUFrame>) -> bool
84where
85 D: device::Device + std::marker::Send + 'static,
86{
87 match device.read_frame() {
88 Ok(frame) => {
89 frame_state.fail_count = 0;
90 frame_state.total_frames += 1;
91
92 let is_imu_frozen = frame_state
95 .prev_frame
96 .map(|prev| {
97 frame.accel_x == prev.accel_x
98 && frame.accel_y == prev.accel_y
99 && frame.accel_z == prev.accel_z
100 && frame.gyro_x == prev.gyro_x
101 && frame.gyro_y == prev.gyro_y
102 && frame.gyro_z == prev.gyro_z
103 })
104 .unwrap_or(false);
105
106 let mut frame_to_send = frame;
107
108 if is_imu_frozen {
109 frame_state.frozen_count += 1;
110
111 if frame_state.frozen_count == FROZEN_DETECT_THRESHOLD {
112 log::warn!(
113 "IMU data frozen ({} identical frames). Steam likely disabled the IMU.",
114 frame_state.frozen_count
115 );
116 }
117
118 if frame_state.frozen_count >= FROZEN_DETECT_THRESHOLD {
120 let should_try = frame_state
121 .last_init_attempt
122 .map(|t| t.elapsed() >= REINIT_RETRY_INTERVAL)
123 .unwrap_or(true);
124 if should_try {
125 frame_state.last_init_attempt = Some(Instant::now());
126 if let Err(e) = device.initialize() {
127 log::warn!("Failed to reinitialize device while IMU frozen: {e}");
128 } else {
129 log::info!("Reinitialized device while IMU was frozen.");
130 }
131 }
132 }
133
134 frame_to_send.accel_x = 0.0;
136 frame_to_send.accel_y = 0.0;
137 frame_to_send.accel_z = 0.0;
138 frame_to_send.gyro_x = 0.0;
139 frame_to_send.gyro_y = 0.0;
140 frame_to_send.gyro_z = 0.0;
141 } else {
142 frame_state.frozen_count = 0;
143 frame_state.last_init_attempt = None;
144 }
145
146 frame_state.prev_frame = Some(frame);
147
148 if tx.send(frame_to_send).is_err() {
149 log::debug!("Receiver has hung up, reader thread exiting");
150 return false;
151 }
152 }
153 Err(DeviceError::ShortRead(n, expected)) => {
154 log::trace!("Short read: {} bytes (expected {})", n, expected);
155 frame_state.fail_count += 1;
156 }
157 Err(DeviceError::InvalidReport(id)) => {
158 log::trace!("Ignoring invalid report (first byte: 0x{:02x})", id);
159 frame_state.fail_count = 0;
160 }
161 Err(e) => {
162 log::trace!("HID read error: {}", e);
163 frame_state.fail_count += 1;
164 }
165 }
166
167 if frame_state.fail_count >= DISCONNECT_THRESHOLD {
168 log::warn!(
169 "Controller appears disconnected ({} consecutive read failures). Exiting reader.",
170 frame_state.fail_count,
171 );
172 return false;
173 }
174
175 true
176}