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