diff --git a/src/additional_rfactor.rs b/src/additional_rfactor.rs index 2d5caa4..5e95a1b 100644 --- a/src/additional_rfactor.rs +++ b/src/additional_rfactor.rs @@ -64,6 +64,24 @@ impl From<&[u8]> for rF2Telemetry { } } +impl From<&[u8]> for ScoringInfoV01 { + fn from(value: &[u8]) -> Self { + debug_assert!(value.len() == mem::size_of::()); + let fixed_size: [u8; mem::size_of::()] = value.try_into().unwrap(); + + unsafe { mem::transmute(fixed_size) } + } +} + +impl From<&[u8]> for VehicleScoringInfoV01 { + fn from(value: &[u8]) -> Self { + debug_assert!(value.len() == mem::size_of::()); + let fixed_size: [u8; mem::size_of::()] = value.try_into().unwrap(); + + unsafe { mem::transmute(fixed_size) } + } +} + #[repr(C, packed(4))] #[derive(Debug, Copy, Clone)] pub struct rF2Scoring { @@ -233,5 +251,5 @@ pub struct rF2Wheel { pub tire_carcass_temperature: f64, pub tire_inner_layer_temperature: [f64; 3], - expansion: [::std::os::raw::c_uchar; 24], + expansion: [u8; 24], } diff --git a/src/lib.rs b/src/lib.rs index c115a74..89cb754 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub mod additional_rfactor; #[allow(warnings)] mod rfactor_structs; -use std::{cell::Cell, fs::File, marker::PhantomData, path::Path}; +use std::{cell::Cell, fs::File, mem, path::Path}; use additional_rfactor::*; pub use additional_rfactor::{rF2Vec3, rF2VehicleTelemetry, rF2Wheel}; @@ -12,19 +12,17 @@ pub use rfactor_structs::{ScoringInfoV01, VehicleScoringInfoV01}; const RFACTOR_SHM_FILE: &str = "/dev/shm"; -struct ShMReader { +struct ShMReader { _file: File, shm: Mmap, version: Cell, - - data: PhantomData, + last_query: Cell, + time_between_writes: f32, } -impl ShMReader { - const SIZE: usize = std::mem::size_of::(); - - fn new(mm_file_name: &str) -> Result { +impl ShMReader { + fn new(mm_file_name: &str, now: f32, time_between_writes: f32) -> Result { let file = File::open(Path::new(RFACTOR_SHM_FILE).join(mm_file_name))?; let mmap = unsafe { Mmap::map(&file)? }; @@ -33,67 +31,243 @@ impl ShMReader { shm: mmap, version: Cell::new(VersionHeader::default()), - - data: PhantomData, + last_query: Cell::new(now), + time_between_writes, }) } fn check_version_update(&self) -> bool { + log("In ShMReader: start check version update"); let new_version = VersionHeader::from(&self.shm[0..VersionHeader::SIZE]); let are_different = self.version.get().check_for_update(&new_version); if are_different { + log("In ShMReader: new version found"); self.version.set(new_version); } are_different } -} -impl<'a, T> ShMReader -where - T: From<&'a [u8]>, -{ - fn read(&'a self) -> Option { - if self.check_version_update() { - Some(T::from(&self.shm[0..Self::SIZE])) - } else { - None + fn check_time(&self, now: f32) -> bool { + log("In ShMReader: start time check"); + + if now < self.last_query.get() + self.time_between_writes { + log("In ShMReader: time check failed"); + return false; } + + self.last_query.set(now); + + true + } + + fn read(&self, offset: usize, length: usize) -> &[u8] { + log(format!( + "In ShMReader: read (offset: {}, length: {})", + offset, length + )); + let b = &self.shm[offset..offset + length]; + + debug_assert_eq!(b.len(), length); + log(format!("In ShMReader: successfully read {} bytes", b.len())); + + b } } pub struct TelemetryReader { - mm_reader: ShMReader, + mm_reader: ShMReader, } impl TelemetryReader { - pub fn new() -> Result { + pub fn new(now: f32) -> Result { + // telemetry gets updated 50 times per second + const WAIT_TIME: f32 = 1.0 / 50.0; + Ok(Self { - mm_reader: ShMReader::new(MM_TELEMETRY_FILE_NAME)?, + mm_reader: ShMReader::new(MM_TELEMETRY_FILE_NAME, now, WAIT_TIME)?, }) } - pub fn query_telemetry(&self) -> Option> { - self.mm_reader.read().map(|v| v.vehicles().to_vec()) + pub fn query_telemetry(&self, now: f32) -> Option> { + log("In TelemetryReader: start query telemetry"); + + if !self.mm_reader.check_time(now) || !self.mm_reader.check_version_update() { + return None; + } + + log("In TelemetryReader: read num vehicles"); + + // first: query only the amount of vehicles + let num_vehicles = i32::from_be_bytes( + self.mm_reader + .read(VersionHeader::SIZE, mem::size_of::()) + .try_into() + .unwrap(), + ); + + log(format!( + "In TelemetryReader: vehicle count {}", + num_vehicles + )); + + debug_assert!(num_vehicles >= 0); + debug_assert!(num_vehicles < MAX_MAPPED_VEHICLES as i32); + + log("In TelemetryReader: read vehicles"); + + // query only the required amount of memory based on vehicle count + let vehicles: &[rF2VehicleTelemetry] = unsafe { + mem::transmute(self.mm_reader.read( + VersionHeader::SIZE + mem::size_of::(), + mem::size_of::() * num_vehicles as usize, + )) + }; + + log(format!( + "In TelemetryReader: successfully read vehicles ({})", + vehicles.len() + )); + + debug_assert_eq!(num_vehicles as usize, vehicles.len()); + + Some(vehicles.to_vec()) } } pub struct ScoringReader { - mm_reader: ShMReader, + mm_reader: ShMReader, } impl ScoringReader { - pub fn new() -> Result { + pub fn new(now: f32) -> Result { + // scoring gets updated 5 times per second + const WAIT_TIME: f32 = 1.0 / 5.0; + Ok(Self { - mm_reader: ShMReader::new(MM_SCORING_FILE_NAME)?, + mm_reader: ShMReader::new(MM_SCORING_FILE_NAME, now, WAIT_TIME)?, }) } - pub fn vehicle_scoring(&self) -> Option<(ScoringInfoV01, Vec)> { - self.mm_reader - .read() - .map(|s| (s.scoring_info, s.vehicles().to_vec())) + pub fn vehicle_scoring( + &self, + now: f32, + ) -> Option<(ScoringInfoV01, Vec)> { + log("In ScoringReader: start vehicle scoring"); + + if !self.mm_reader.check_time(now) || !self.mm_reader.check_version_update() { + return None; + } + + log("In ScoringReader: read scoring info"); + + let scoring_info = ScoringInfoV01::from( + self.mm_reader + .read(VersionHeader::SIZE, mem::size_of::()), + ); + + log(format!( + "In ScoringReader: successfully read scoring info (num vehicles {})", + scoring_info.mNumVehicles + )); + + let vehicle_info: &[VehicleScoringInfoV01] = unsafe { + mem::transmute(self.mm_reader.read( + VersionHeader::SIZE + mem::size_of::(), + mem::size_of::() * scoring_info.mNumVehicles as usize, + )) + }; + + debug_assert_eq!(scoring_info.mNumVehicles as usize, vehicle_info.len()); + + Some((scoring_info, vehicle_info.to_vec())) + } +} + +pub fn log(msg: impl ToString) { + let home = std::env::var("HOME").unwrap(); + let log_file = format!("{}/rf2_vk_hud.log", home); + + if let Ok(mut file) = std::fs::OpenOptions::new() + .append(true) + .create(true) + .open(&log_file) + { + if let Err(_) = + std::io::Write::write_all(&mut file, format!("{}\n", msg.to_string()).as_bytes()) + { + } + } +} + +#[cfg(test)] +mod tests { + use std::{ + thread::sleep, + time::{Duration, Instant}, + }; + + use super::*; + + #[test] + fn test_telemetry() { + let start = Instant::now(); + let telemetry_reader = TelemetryReader::new(start.elapsed().as_secs_f32()).unwrap(); + + for _ in 0..25 { + telemetry_reader.query_telemetry(start.elapsed().as_secs_f32()); + sleep(Duration::from_secs(2)); + } + } + + #[test] + fn test_scoring() { + let start = Instant::now(); + let scoring_reader = ScoringReader::new(start.elapsed().as_secs_f32()).unwrap(); + + for _ in 0..25 { + scoring_reader.vehicle_scoring(start.elapsed().as_secs_f32()); + sleep(Duration::from_secs(2)); + } + } + + #[test] + fn test_combined() { + let start = Instant::now(); + let scoring_reader = ScoringReader::new(start.elapsed().as_secs_f32()).unwrap(); + let telemetry_reader = TelemetryReader::new(start.elapsed().as_secs_f32()).unwrap(); + + let mut player_id = None; + + for _ in 0..25 { + if let Some((score, vehicles)) = + scoring_reader.vehicle_scoring(start.elapsed().as_secs_f32()) + { + if score.mNumVehicles == 0 { + player_id = None; + } else if player_id.is_none() { + for vehicle in vehicles { + if vehicle.mIsPlayer != 0 { + player_id = Some(vehicle.mID); + break; + } + } + } + } + + if player_id.is_some() { + if let Some(telemetries) = + telemetry_reader.query_telemetry(start.elapsed().as_secs_f32()) + { + for telemetry in telemetries { + println!("ID: {}", telemetry.id); + } + } + } + + sleep(Duration::from_secs(2)); + } } }