use std::collections::HashMap;
use std::thread::sleep;
use std::time::Duration;
use windows_sys::Win32::Foundation::{CloseHandle, FALSE, HANDLE, WAIT_OBJECT_0};
use windows_sys::Win32::System::Threading::{OpenEventW, WaitForSingleObject};
use crate::iracing::structs::{IRSDK_MAX_BUFS, VarType, irsdk_header, irsdk_varHeader};
use crate::types::TelemetryValue;
const SYNCHRONIZE: u32 = 0x00100000;
const SHM_MEM_MAP_FILE: &str = "Local\\IRSDKMemMapFileName";
const SHM_DATA_VALID_EVENT: &str = "Local\\IRSDKDataValidEventName";
fn parse_c_str(bytes: &[u8]) -> String {
let len = bytes.iter().position(|&x| x == 0).unwrap_or(bytes.len());
crate::decode_cp1252(&bytes[..len])
}
pub struct IRsdkConnection {
shm: crate::shm::SharedMemRegion,
h_event: HANDLE,
pub(crate) vars: HashMap<String, irsdk_varHeader>,
cached_session: std::cell::RefCell<Option<(i32, crate::iracing::session::IracingSession)>>,
pub(crate) offsets: crate::iracing::types::IracingOffsets,
}
impl IRsdkConnection {
#[doc(hidden)]
pub unsafe fn new_mock(
view_address: *mut std::ffi::c_void,
vars: HashMap<String, irsdk_varHeader>,
) -> Self {
let offsets = crate::iracing::types::IracingOffsets::resolve(&vars);
Self {
shm: unsafe { crate::shm::SharedMemRegion::new_mock(view_address) },
h_event: 0 as _,
vars,
cached_session: std::cell::RefCell::new(None),
offsets,
}
}
#[doc(hidden)]
pub fn connect() -> Result<Self, crate::error::SimError> {
let shm = crate::shm::SharedMemRegion::open(SHM_MEM_MAP_FILE)
.map_err(crate::error::SimError::NotConnected)?;
let shared_mem = shm.as_ptr();
unsafe {
let header = std::ptr::read_unaligned(shared_mem as *const irsdk_header);
let event_name: Vec<u16> = SHM_DATA_VALID_EVENT
.encode_utf16()
.chain(std::iter::once(0))
.collect();
let h_event = OpenEventW(SYNCHRONIZE, FALSE, event_name.as_ptr());
if header.ver <= 0 || header.ver > 10 || header.num_vars <= 0 {
if !h_event.is_null() {
CloseHandle(h_event);
}
return Err(crate::error::SimError::InvalidHeader(format!(
"Invalid iRacing telemetry header (ver={}, num_vars={})",
header.ver, header.num_vars
)));
}
let shm_size = 32 * 1024 * 1024usize; let var_offset = header.var_header_offset as usize;
let element_size = std::mem::size_of::<irsdk_varHeader>();
let num = header.num_vars as usize;
if var_offset.saturating_add(num.saturating_mul(element_size)) > shm_size {
if !h_event.is_null() {
CloseHandle(h_event);
}
return Err(crate::error::SimError::InvalidHeader(
"var_header_offset out of SHM bounds".into(),
));
}
let mut vars = HashMap::new();
for i in 0..num {
let offset = var_offset + i * element_size;
let var_header_ptr = shared_mem.add(offset) as *const irsdk_varHeader;
let var_header = std::ptr::read_unaligned(var_header_ptr);
let name_str = parse_c_str(&var_header.name);
vars.insert(name_str, var_header);
}
let offsets = crate::iracing::types::IracingOffsets::resolve(&vars);
Ok(Self {
shm,
h_event,
vars,
cached_session: std::cell::RefCell::new(None),
offsets,
})
}
}
pub fn is_connected(&self) -> bool {
unsafe {
let offset = std::mem::offset_of!(irsdk_header, status);
let status = std::ptr::read_unaligned(self.shm.as_ptr().add(offset) as *const i32);
(status & 1) != 0
}
}
pub fn wait_for_data(&self, timeout_ms: u32) -> bool {
unsafe {
if self.h_event.is_null() {
sleep(Duration::from_millis(16));
true
} else {
let wait_result = WaitForSingleObject(self.h_event, timeout_ms);
wait_result == WAIT_OBJECT_0
}
}
}
pub fn session_info_update(&self) -> i32 {
unsafe {
let shared_mem = self.shm.as_ptr();
if shared_mem.is_null() {
return -1;
}
let offset = std::mem::offset_of!(irsdk_header, session_info_update);
std::ptr::read_unaligned(shared_mem.add(offset) as *const i32)
}
}
fn get_latest_data_ptr(&self) -> Option<*const u8> {
unsafe {
let shared_mem = self.shm.as_ptr();
let header = std::ptr::read_unaligned(shared_mem as *const irsdk_header);
if header.num_buf <= 0 || header.num_buf as usize > IRSDK_MAX_BUFS {
return None;
}
let mut latest_buf_idx = 0;
let mut max_tick_count = -1;
for i in 0..header.num_buf as usize {
let tick_count = header.var_buf[i].tick_count;
if tick_count > max_tick_count {
max_tick_count = tick_count;
latest_buf_idx = i;
}
}
if max_tick_count < 0 {
return None;
}
let buf_offset = header.var_buf[latest_buf_idx].buf_offset as usize;
Some(shared_mem.add(buf_offset))
}
}
#[doc(hidden)]
pub fn read_variable(&self, name: &str) -> Option<TelemetryValue> {
let var = self.vars.get(name)?;
let data_ptr = self.get_latest_data_ptr()?;
let offset = var.offset as usize;
unsafe {
let ptr = data_ptr.add(offset);
let count = var.count as usize;
match VarType::from_i32(var.type_)? {
VarType::Char => {
if var.count_as_char != 0 {
let slice = std::slice::from_raw_parts(ptr, count);
Some(TelemetryValue::String(parse_c_str(slice)))
} else if count == 1 {
Some(TelemetryValue::Char(std::ptr::read_unaligned(ptr)))
} else {
let mut vec = Vec::with_capacity(count);
for idx in 0..count {
vec.push(std::ptr::read_unaligned(ptr.add(idx)));
}
Some(TelemetryValue::String(crate::decode_cp1252(&vec)))
}
}
VarType::Bool => {
if count == 1 {
Some(TelemetryValue::Bool(std::ptr::read_unaligned(ptr) != 0))
} else {
let mut vec = Vec::with_capacity(count);
for idx in 0..count {
vec.push(std::ptr::read_unaligned(ptr.add(idx)) != 0);
}
Some(TelemetryValue::BoolArray(vec))
}
}
VarType::Int => {
let int_ptr = ptr as *const i32;
if count == 1 {
Some(TelemetryValue::Int(std::ptr::read_unaligned(int_ptr)))
} else {
let mut vec = Vec::with_capacity(count);
for idx in 0..count {
vec.push(std::ptr::read_unaligned(int_ptr.add(idx)));
}
Some(TelemetryValue::IntArray(vec))
}
}
VarType::BitField => {
let uint_ptr = ptr as *const u32;
if count == 1 {
Some(TelemetryValue::BitField(std::ptr::read_unaligned(uint_ptr)))
} else {
let mut vec = Vec::with_capacity(count);
for idx in 0..count {
let u32_val = std::ptr::read_unaligned(uint_ptr.add(idx));
vec.push(u32_val as i32);
}
Some(TelemetryValue::IntArray(vec))
}
}
VarType::Float => {
let float_ptr = ptr as *const f32;
if count == 1 {
Some(TelemetryValue::Float(std::ptr::read_unaligned(float_ptr)))
} else {
let mut vec = Vec::with_capacity(count);
for idx in 0..count {
vec.push(std::ptr::read_unaligned(float_ptr.add(idx)));
}
Some(TelemetryValue::FloatArray(vec))
}
}
VarType::Double => {
let double_ptr = ptr as *const f64;
if count == 1 {
Some(TelemetryValue::Double(std::ptr::read_unaligned(double_ptr)))
} else {
let mut vec = Vec::with_capacity(count);
for idx in 0..count {
vec.push(std::ptr::read_unaligned(double_ptr.add(idx)));
}
Some(TelemetryValue::DoubleArray(vec))
}
}
}
}
}
pub(crate) fn read_all_variables(&self) -> HashMap<String, TelemetryValue> {
let mut map = HashMap::with_capacity(self.vars.len());
for name in self.vars.keys() {
if let Some(val) = self.read_variable(name) {
map.insert(name.clone(), val);
}
}
map
}
pub fn session_yaml(&self) -> Option<String> {
unsafe {
let shared_mem = self.shm.as_ptr();
let header = std::ptr::read_unaligned(shared_mem as *const irsdk_header);
if header.session_info_len <= 0 {
return None;
}
let info_ptr = shared_mem.add(header.session_info_offset as usize);
let bytes = std::slice::from_raw_parts(info_ptr, header.session_info_len as usize);
let len = bytes.iter().position(|&x| x == 0).unwrap_or(bytes.len());
Some(crate::decode_cp1252(&bytes[..len]))
}
}
pub fn telemetry_snapshot(
&self,
) -> std::collections::HashMap<String, crate::types::TelemetryValue> {
self.read_all_variables()
}
pub fn var_list_snapshot(&self) -> Vec<crate::types::VarMeta> {
use crate::iracing::structs::VarType;
self.vars
.iter()
.map(|(name, hdr)| {
let type_name = match VarType::from_i32(hdr.type_) {
Some(VarType::Char) => "char",
Some(VarType::Bool) => "bool",
Some(VarType::Int) => "int",
Some(VarType::BitField) => "bitfield",
Some(VarType::Float) => "float",
Some(VarType::Double) => "double",
None => "unknown",
};
let unit = {
let len = hdr
.unit
.iter()
.position(|&x| x == 0)
.unwrap_or(hdr.unit.len());
crate::decode_cp1252(&hdr.unit[..len])
};
let desc = {
let len = hdr
.desc
.iter()
.position(|&x| x == 0)
.unwrap_or(hdr.desc.len());
crate::decode_cp1252(&hdr.desc[..len])
};
crate::types::VarMeta {
name: name.clone(),
type_name,
unit,
desc,
count: hdr.count as u32,
}
})
.collect()
}
pub fn frame(&self) -> Result<crate::iracing::types::IracingFrame, crate::error::SimError> {
let data_ptr = self
.get_latest_data_ptr()
.ok_or_else(|| crate::error::SimError::InvalidHeader("No valid data buffer".into()))?;
Ok(crate::iracing::types::IracingFrame::from_raw(
data_ptr,
&self.offsets,
))
}
pub fn session_info(&self) -> Option<crate::iracing::session::IracingSession> {
let current_version = self.session_info_update();
if let Some((_, session)) = self
.cached_session
.borrow()
.as_ref()
.filter(|(v, _)| *v == current_version)
{
return Some(session.clone());
}
let yaml = self.session_yaml()?;
let session = crate::iracing::session::IracingSession::from_yaml(&yaml)?;
*self.cached_session.borrow_mut() = Some((current_version, session.clone()));
Some(session)
}
}
impl Drop for IRsdkConnection {
fn drop(&mut self) {
unsafe {
if !self.h_event.is_null() {
CloseHandle(self.h_event);
}
}
}
}