From 1b2114e8d6ceacff28f14c2d322c4b0fb12478ab Mon Sep 17 00:00:00 2001 From: Alex van de Sandt Date: Wed, 31 Dec 2025 21:07:57 -0600 Subject: [PATCH] Move client to module --- crates/irsdk/src/client.rs | 170 ++++++++++++++++++++++++++++++++++++ crates/irsdk/src/lib.rs | 171 +------------------------------------ 2 files changed, 172 insertions(+), 169 deletions(-) create mode 100644 crates/irsdk/src/client.rs diff --git a/crates/irsdk/src/client.rs b/crates/irsdk/src/client.rs new file mode 100644 index 0000000..d02dc8b --- /dev/null +++ b/crates/irsdk/src/client.rs @@ -0,0 +1,170 @@ +use ibt::raw; +use ibt::telemetry::{Header, Sample, VarBufInfo, VarHeader, VarSet}; +use itertools::Itertools; +use windows::Win32::Foundation::{CloseHandle, HANDLE}; +use windows::Win32::System::Memory::{ + FILE_MAP_READ, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, OpenFileMappingW, UnmapViewOfFile, +}; +use windows::Win32::System::Threading::{ + OpenEventW, SYNCHRONIZATION_SYNCHRONIZE, WaitForSingleObject, +}; +use windows::core::{PCWSTR, w}; + +const MEM_MAP_FILE_NAME: PCWSTR = w!(r"Local\IRSDKMemMapFileName"); +const DATA_VALID_EVENT_NAME: PCWSTR = w!(r"Local\IRSDKDataValidEvent"); +const TIMEOUT_MS: u32 = 1000; +const FILE_NOT_FOUND_CODE: i32 = 0x80070002u32 as i32; + +#[derive(Clone, Debug, thiserror::Error)] +pub enum IRacingClientError { + #[error("Unknown windows error")] + Windows(#[source] windows::core::Error), + + #[error("iRacing is not running")] + Disconnected(#[source] Option), + + #[error(transparent)] + RawTelemError(#[from] ibt::RawTelemError), + + #[error(transparent)] + RawConversionError(#[from] ibt::telemetry::RawConversionError), +} + +impl From for IRacingClientError { + fn from(err: windows::core::Error) -> Self { + if err.code().0 == FILE_NOT_FOUND_CODE { + Self::Disconnected(Some(err)) + } else { + Self::Windows(err) + } + } +} + +#[derive(Debug)] +pub struct IRacingClient { + file_mapping_handle: HANDLE, + mem_map_address: MEMORY_MAPPED_VIEW_ADDRESS, + event_handle: HANDLE, + + vars: VarSet, + buf_len: usize, +} + +impl IRacingClient { + pub fn connect() -> Result { + let file_mapping_handle = + unsafe { OpenFileMappingW(FILE_MAP_READ.0, false, MEM_MAP_FILE_NAME)? }; + let mem_map_address = unsafe { MapViewOfFile(file_mapping_handle, FILE_MAP_READ, 0, 0, 0) }; + let event_handle = + unsafe { OpenEventW(SYNCHRONIZATION_SYNCHRONIZE, false, DATA_VALID_EVENT_NAME)? }; + + let mut client = Self { + file_mapping_handle, + mem_map_address, + event_handle, + vars: VarSet::new(vec![]), + buf_len: 0, + }; + + let raw_header = client.next_raw_header()?; + let header = Header::from_raw(&raw_header)?; + client.buf_len = header.buf_len; + + // Read the var headers once + let vh_offset = raw_header.var_header_offset as usize; + let vh_len = raw::VAR_HEADER_SIZE * raw_header.num_vars as usize; + let vh_slice = unsafe { client.slice(vh_offset, vh_len) }; + + let var_headers = raw::VarHeader::slice_from_fraw_bytes(vh_slice) + .iter() + .map(VarHeader::from_raw) + .collect(); + client.vars = VarSet::new(var_headers); + + Ok(client) + } + + unsafe fn slice(&self, offset: usize, len: usize) -> &[u8] { + unsafe { + let ptr = (self.mem_map_address.Value as *const u8).add(offset); + std::slice::from_raw_parts(ptr, len) + } + } + + fn next_raw_header(&self) -> Result { + // wait for event signal + unsafe { WaitForSingleObject(self.event_handle, TIMEOUT_MS) }; + + // read the header + let ptr = self.mem_map_address.Value as *const raw::Header; + let raw_header = unsafe { raw::Header::from_raw_ptr(ptr)? }; + + if raw_header.status != 1 { + return Err(IRacingClientError::Disconnected(None)); + } + + Ok(raw_header) + } + + pub fn next_header(&self) -> Result { + let raw_header = self.next_raw_header()?; + let header = Header::from_raw(&raw_header)?; + + Ok(header) + } + + pub fn next_sample(&self) -> Result, IRacingClientError> { + let raw_header = self.next_raw_header()?; + let header = Header::from_raw(&raw_header)?; + + let newest_var_buf = raw_header + .var_bufs + .iter() + .map(VarBufInfo::from_raw) + .process_results(|a| a.max_by_key(|vb| vb.tick_count))? + .expect("there are always four var bufs"); + + let sample_slice = unsafe { self.slice(newest_var_buf.buf_offset, header.buf_len) }; + Ok(Sample::new_as_owned(sample_slice)) + } + + pub fn next_sample_into_buf<'buf>( + &self, + buf: &'buf mut [u8], + ) -> Result, IRacingClientError> { + let raw_header = self.next_raw_header()?; + let header = Header::from_raw(&raw_header)?; + + let newest_var_buf = raw_header + .var_bufs + .iter() + .map(VarBufInfo::from_raw) + .process_results(|a| a.max_by_key(|vb| vb.tick_count))? + .expect("there are always four var bufs"); + + let sample_slice = unsafe { self.slice(newest_var_buf.buf_offset, header.buf_len) }; + + // copy the slice into the buffer + buf.clone_from_slice(sample_slice); + Ok(Sample::new(buf)) + } + + pub fn vars(&self) -> &VarSet { + &self.vars + } + + pub fn buf_len(&self) -> usize { + self.buf_len + } +} + +impl Drop for IRacingClient { + fn drop(&mut self) { + unsafe { + // TODO: verify safety. if one of these fails, can we still close the others? + let _ = CloseHandle(self.event_handle); + let _ = UnmapViewOfFile(self.mem_map_address); + let _ = CloseHandle(self.file_mapping_handle); + } + } +} diff --git a/crates/irsdk/src/lib.rs b/crates/irsdk/src/lib.rs index d02dc8b..2bd8e79 100644 --- a/crates/irsdk/src/lib.rs +++ b/crates/irsdk/src/lib.rs @@ -1,170 +1,3 @@ -use ibt::raw; -use ibt::telemetry::{Header, Sample, VarBufInfo, VarHeader, VarSet}; -use itertools::Itertools; -use windows::Win32::Foundation::{CloseHandle, HANDLE}; -use windows::Win32::System::Memory::{ - FILE_MAP_READ, MEMORY_MAPPED_VIEW_ADDRESS, MapViewOfFile, OpenFileMappingW, UnmapViewOfFile, -}; -use windows::Win32::System::Threading::{ - OpenEventW, SYNCHRONIZATION_SYNCHRONIZE, WaitForSingleObject, -}; -use windows::core::{PCWSTR, w}; +mod client; -const MEM_MAP_FILE_NAME: PCWSTR = w!(r"Local\IRSDKMemMapFileName"); -const DATA_VALID_EVENT_NAME: PCWSTR = w!(r"Local\IRSDKDataValidEvent"); -const TIMEOUT_MS: u32 = 1000; -const FILE_NOT_FOUND_CODE: i32 = 0x80070002u32 as i32; - -#[derive(Clone, Debug, thiserror::Error)] -pub enum IRacingClientError { - #[error("Unknown windows error")] - Windows(#[source] windows::core::Error), - - #[error("iRacing is not running")] - Disconnected(#[source] Option), - - #[error(transparent)] - RawTelemError(#[from] ibt::RawTelemError), - - #[error(transparent)] - RawConversionError(#[from] ibt::telemetry::RawConversionError), -} - -impl From for IRacingClientError { - fn from(err: windows::core::Error) -> Self { - if err.code().0 == FILE_NOT_FOUND_CODE { - Self::Disconnected(Some(err)) - } else { - Self::Windows(err) - } - } -} - -#[derive(Debug)] -pub struct IRacingClient { - file_mapping_handle: HANDLE, - mem_map_address: MEMORY_MAPPED_VIEW_ADDRESS, - event_handle: HANDLE, - - vars: VarSet, - buf_len: usize, -} - -impl IRacingClient { - pub fn connect() -> Result { - let file_mapping_handle = - unsafe { OpenFileMappingW(FILE_MAP_READ.0, false, MEM_MAP_FILE_NAME)? }; - let mem_map_address = unsafe { MapViewOfFile(file_mapping_handle, FILE_MAP_READ, 0, 0, 0) }; - let event_handle = - unsafe { OpenEventW(SYNCHRONIZATION_SYNCHRONIZE, false, DATA_VALID_EVENT_NAME)? }; - - let mut client = Self { - file_mapping_handle, - mem_map_address, - event_handle, - vars: VarSet::new(vec![]), - buf_len: 0, - }; - - let raw_header = client.next_raw_header()?; - let header = Header::from_raw(&raw_header)?; - client.buf_len = header.buf_len; - - // Read the var headers once - let vh_offset = raw_header.var_header_offset as usize; - let vh_len = raw::VAR_HEADER_SIZE * raw_header.num_vars as usize; - let vh_slice = unsafe { client.slice(vh_offset, vh_len) }; - - let var_headers = raw::VarHeader::slice_from_fraw_bytes(vh_slice) - .iter() - .map(VarHeader::from_raw) - .collect(); - client.vars = VarSet::new(var_headers); - - Ok(client) - } - - unsafe fn slice(&self, offset: usize, len: usize) -> &[u8] { - unsafe { - let ptr = (self.mem_map_address.Value as *const u8).add(offset); - std::slice::from_raw_parts(ptr, len) - } - } - - fn next_raw_header(&self) -> Result { - // wait for event signal - unsafe { WaitForSingleObject(self.event_handle, TIMEOUT_MS) }; - - // read the header - let ptr = self.mem_map_address.Value as *const raw::Header; - let raw_header = unsafe { raw::Header::from_raw_ptr(ptr)? }; - - if raw_header.status != 1 { - return Err(IRacingClientError::Disconnected(None)); - } - - Ok(raw_header) - } - - pub fn next_header(&self) -> Result { - let raw_header = self.next_raw_header()?; - let header = Header::from_raw(&raw_header)?; - - Ok(header) - } - - pub fn next_sample(&self) -> Result, IRacingClientError> { - let raw_header = self.next_raw_header()?; - let header = Header::from_raw(&raw_header)?; - - let newest_var_buf = raw_header - .var_bufs - .iter() - .map(VarBufInfo::from_raw) - .process_results(|a| a.max_by_key(|vb| vb.tick_count))? - .expect("there are always four var bufs"); - - let sample_slice = unsafe { self.slice(newest_var_buf.buf_offset, header.buf_len) }; - Ok(Sample::new_as_owned(sample_slice)) - } - - pub fn next_sample_into_buf<'buf>( - &self, - buf: &'buf mut [u8], - ) -> Result, IRacingClientError> { - let raw_header = self.next_raw_header()?; - let header = Header::from_raw(&raw_header)?; - - let newest_var_buf = raw_header - .var_bufs - .iter() - .map(VarBufInfo::from_raw) - .process_results(|a| a.max_by_key(|vb| vb.tick_count))? - .expect("there are always four var bufs"); - - let sample_slice = unsafe { self.slice(newest_var_buf.buf_offset, header.buf_len) }; - - // copy the slice into the buffer - buf.clone_from_slice(sample_slice); - Ok(Sample::new(buf)) - } - - pub fn vars(&self) -> &VarSet { - &self.vars - } - - pub fn buf_len(&self) -> usize { - self.buf_len - } -} - -impl Drop for IRacingClient { - fn drop(&mut self) { - unsafe { - // TODO: verify safety. if one of these fails, can we still close the others? - let _ = CloseHandle(self.event_handle); - let _ = UnmapViewOfFile(self.mem_map_address); - let _ = CloseHandle(self.file_mapping_handle); - } - } -} +pub use client::{IRacingClient, IRacingClientError}; -- 2.51.2