use crate::error::SimError;
use crate::lmu::structs::{rF2Extended, rF2Scoring, rF2Telemetry};
use crate::lmu::types::{
LmuExtended, LmuFrame, LmuScoringInfo, LmuVehicleScoring, LmuVehicleTelemetry,
};
use crate::shm::SharedMemRegion;
const SHM_TELEMETRY: &str = "$rFactor2SMMP_Telemetry$";
const SHM_SCORING: &str = "$rFactor2SMMP_Scoring$";
const SHM_EXTENDED: &str = "$rFactor2SMMP_Extended$";
unsafe fn alloc_zeroed_box<T>() -> Box<T> {
let layout = std::alloc::Layout::new::<T>();
let ptr = unsafe { std::alloc::alloc_zeroed(layout) as *mut T };
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
unsafe { Box::from_raw(ptr) }
}
unsafe fn read_region_consistent_into<T: Copy>(
region: &crate::shm::SharedMemRegion,
begin_offset: usize,
end_offset: usize,
out: &mut T,
) -> Result<(), crate::error::SimError> {
let ptr = region.as_ptr();
let raw = out as *mut T;
let start = std::time::Instant::now();
let mut retries = 0u32;
loop {
let begin = unsafe { std::ptr::read_volatile(ptr.add(begin_offset) as *const u32) };
let end_pre = unsafe { std::ptr::read_volatile(ptr.add(end_offset) as *const u32) };
if begin != end_pre {
retries += 1;
if retries.is_multiple_of(1000) {
if start.elapsed() > std::time::Duration::from_millis(100) {
return Err(crate::error::SimError::InvalidHeader(
"Torn read timeout in LMU region".into(),
));
}
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
continue;
}
unsafe { std::ptr::copy_nonoverlapping(ptr, raw as *mut u8, std::mem::size_of::<T>()) };
let end = unsafe { std::ptr::read_volatile(ptr.add(end_offset) as *const u32) };
if begin == end {
return Ok(());
}
retries += 1;
if retries.is_multiple_of(1000) {
if start.elapsed() > std::time::Duration::from_millis(100) {
return Err(crate::error::SimError::InvalidHeader(
"Torn read timeout in LMU region".into(),
));
}
std::thread::yield_now();
} else {
std::hint::spin_loop();
}
}
}
struct LmuScratch {
telemetry: Box<rF2Telemetry>,
scoring: Box<rF2Scoring>,
extended: Box<rF2Extended>,
}
impl LmuScratch {
fn new() -> Self {
unsafe {
Self {
telemetry: alloc_zeroed_box(),
scoring: alloc_zeroed_box(),
extended: alloc_zeroed_box(),
}
}
}
}
pub struct LmuConnection {
telemetry: SharedMemRegion,
scoring: SharedMemRegion,
extended: SharedMemRegion,
scratch: std::cell::RefCell<Option<LmuScratch>>,
}
impl LmuConnection {
pub fn frame(&self) -> Result<Box<LmuFrame>, crate::error::SimError> {
let mut frame = LmuFrame::new_boxed();
self.frame_into(&mut frame)?;
Ok(frame)
}
pub fn frame_into(&self, out: &mut LmuFrame) -> Result<(), crate::error::SimError> {
use crate::lmu::structs::{RF2_MAX_VEHICLES, rF2ScoringHeader, rF2TelemetryHeader};
let mut scratch_slot = self.scratch.borrow_mut();
let scratch = scratch_slot.get_or_insert_with(LmuScratch::new);
unsafe {
read_region_consistent_into::<rF2Telemetry>(
&self.telemetry,
std::mem::offset_of!(rF2Telemetry, header)
+ std::mem::offset_of!(rF2TelemetryHeader, version_update_begin),
std::mem::offset_of!(rF2Telemetry, header)
+ std::mem::offset_of!(rF2TelemetryHeader, version_update_end),
&mut scratch.telemetry,
)?;
read_region_consistent_into::<rF2Scoring>(
&self.scoring,
std::mem::offset_of!(rF2Scoring, header)
+ std::mem::offset_of!(rF2ScoringHeader, version_update_begin),
std::mem::offset_of!(rF2Scoring, header)
+ std::mem::offset_of!(rF2ScoringHeader, version_update_end),
&mut scratch.scoring,
)?;
read_region_consistent_into::<rF2Extended>(
&self.extended,
std::mem::offset_of!(rF2Extended, version_update_begin),
std::mem::offset_of!(rF2Extended, version_update_end),
&mut scratch.extended,
)?;
}
let num = (scratch.scoring.scoring_info.num_vehicles as usize).min(RF2_MAX_VEHICLES);
for i in 0..num {
out.vehicles_telemetry[i] = LmuVehicleTelemetry::from(scratch.telemetry.vehicles[i]);
out.vehicles_scoring[i] = LmuVehicleScoring::from(scratch.scoring.vehicles[i]);
}
out.num_vehicles = num;
out.scoring_info = LmuScoringInfo::from(scratch.scoring.scoring_info);
out.extended = LmuExtended::from(*scratch.extended);
Ok(())
}
pub fn telemetry_snapshot(
&self,
) -> std::collections::HashMap<String, crate::types::TelemetryValue> {
match self.frame() {
Ok(frame) => crate::lmu::snapshot::build_snapshot(&frame),
Err(_) => std::collections::HashMap::new(),
}
}
pub fn var_list_snapshot(&self) -> Vec<crate::types::VarMeta> {
crate::lmu::snapshot::var_list_snapshot()
}
pub(crate) fn is_plugin_active(&self) -> bool {
unsafe { std::ptr::read_volatile(self.extended.as_ptr() as *const u32) != 0 }
}
pub(crate) fn is_session_started(&self) -> bool {
unsafe {
let offset = std::mem::offset_of!(rF2Extended, session_started);
let ptr = self.extended.as_ptr().add(offset);
std::ptr::read_volatile(ptr) != 0
}
}
pub fn is_connected(&self) -> bool {
self.is_plugin_active() && self.is_session_started()
}
pub fn wait_for_data(&self, timeout_ms: u32) {
std::thread::sleep(std::time::Duration::from_millis(timeout_ms as u64));
}
}
impl LmuConnection {
pub(crate) fn connect() -> Result<Self, SimError> {
let telemetry = SharedMemRegion::open(SHM_TELEMETRY).map_err(|e| {
SimError::NotConnected(format!(
"{}. Is rFactor2SharedMemoryMapPlugin installed in LMU/Plugins/?",
e
))
})?;
let scoring = SharedMemRegion::open(SHM_SCORING).map_err(SimError::NotConnected)?;
let extended = SharedMemRegion::open(SHM_EXTENDED).map_err(SimError::NotConnected)?;
Ok(Self {
telemetry,
scoring,
extended,
scratch: std::cell::RefCell::new(None),
})
}
}