diff --git a/Cargo.lock b/Cargo.lock index 1acc99c..d85bef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -200,6 +200,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.5.0" @@ -222,6 +231,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num_enum" version = "0.5.7" @@ -255,6 +274,12 @@ version = "6.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -307,6 +332,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.27" @@ -325,6 +359,15 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.1.0" @@ -337,9 +380,17 @@ version = "0.1.0" dependencies = [ "bindgen", "num_enum", + "thiserror", "tracing", + "tracing-subscriber", ] +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "strsim" version = "0.10.0" @@ -392,6 +443,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "toml" version = "0.5.9" @@ -431,6 +491,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -439,6 +529,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "which" version = "4.3.0" diff --git a/Cargo.toml b/Cargo.toml index 2ef7fb0..702575c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,7 @@ bindgen = "0.60" [dependencies] num_enum = "0.5" tracing = "0.1" +thiserror = "1.0" + +[dev-dependencies] +tracing-subscriber = { version = "0.3", features = [ "env-filter" ] } diff --git a/README.md b/README.md index 8f5eb0c..6f5f3b9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ # SimConnect SDK in Rust -WIP +## Running the examples + +```bash +git clone git@github.com:mihai-dinculescu/simconnect-sdk.git +cd simconnect-sdk +RUST_LOG=info cargo run --example basic +``` diff --git a/SimConnect.dll b/SimConnect.dll new file mode 100644 index 0000000..6b25958 Binary files /dev/null and b/SimConnect.dll differ diff --git a/examples/basic.rs b/examples/basic.rs new file mode 100644 index 0000000..293b328 --- /dev/null +++ b/examples/basic.rs @@ -0,0 +1,102 @@ +use logging::setup_logging; +use simconnect_sdk::{ + ConditionEnum, DataType, Notification, NotificationData, PeriodEnum, SimConnect, + SimConnectError, +}; +use tracing::{error, info}; + +mod logging; + +#[derive(Debug, Clone)] +pub struct GpsData { + pub lat: f64, + pub lon: f64, + pub alt: f64, + pub gps_ground_magnetic_track: f64, + pub gps_ground_speed: f64, +} + +impl simconnect_sdk::SimConnectObject for GpsData { + fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError> { + client.add_to_data_definition(id, "PLANE LATITUDE", "degrees", DataType::F64)?; + client.add_to_data_definition(id, "PLANE LONGITUDE", "degrees", DataType::F64)?; + client.add_to_data_definition(id, "PLANE ALTITUDE", "meters", DataType::F64)?; + client.add_to_data_definition(id, "GPS GROUND MAGNETIC TRACK", "degrees", DataType::F64)?; + client.add_to_data_definition( + id, + "GPS GROUND SPEED", + "Meters per second", + DataType::F64, + )?; + client.request_data_on_sim_object(id, PeriodEnum::Second, ConditionEnum::None)?; + + Ok(()) + } +} + +impl TryFrom<&NotificationData> for GpsData { + type Error = (); + + fn try_from(value: &NotificationData) -> Result { + value.try_into::().ok_or(()) + } +} + +#[derive(Debug, Clone)] +pub struct OnGround { + pub sim_on_ground: bool, +} + +impl simconnect_sdk::SimConnectObject for OnGround { + fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError> { + client.add_to_data_definition(id, "SIM ON GROUND", "bool", DataType::Bool)?; + client.request_data_on_sim_object(id, PeriodEnum::Second, ConditionEnum::None)?; + + Ok(()) + } +} + +impl TryFrom<&NotificationData> for OnGround { + type Error = (); + + fn try_from(value: &NotificationData) -> Result { + value.try_into::().ok_or(()) + } +} + +fn main() -> Result<(), Box> { + setup_logging()?; + + let client = SimConnect::new("Simple Program"); + + match client { + Ok(mut client) => loop { + let notification = client.get_next_dispatch()?; + + match notification { + Some(Notification::Open) => { + info!("Open"); + + client.register_object::()?; + client.register_object::()?; + } + Some(Notification::Data(data)) => { + if let Ok(gps_data) = GpsData::try_from(&data) { + info!("GPS Data: {:?}", gps_data); + continue; + } + if let Ok(on_ground) = OnGround::try_from(&data) { + info!("On Ground data: {:?}", on_ground); + continue; + } + } + _ => (), + } + }, + Err(e) => { + error!("{e:?}") + } + } + + Ok(()) +} diff --git a/examples/logging.rs b/examples/logging.rs new file mode 100644 index 0000000..36b7028 --- /dev/null +++ b/examples/logging.rs @@ -0,0 +1,18 @@ +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + +pub fn setup_logging() -> Result<(), Box> { + let filter_layer = EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("info"))?; + let fmt_layer = fmt::layer() + .with_target(false) + .with_span_events(fmt::format::FmtSpan::FULL); + + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .init(); + + Ok(()) +} + +#[allow(dead_code)] +fn main() {} diff --git a/src/domain/airport_data.rs b/src/domain/airport_data.rs new file mode 100644 index 0000000..4f9498e --- /dev/null +++ b/src/domain/airport_data.rs @@ -0,0 +1,7 @@ +#[derive(Debug, Clone)] +pub struct AirportData { + pub icao: String, + pub lat: f64, + pub lon: f64, + pub alt: f64, +} diff --git a/src/domain/condition.rs b/src/domain/condition.rs new file mode 100644 index 0000000..f69b3fd --- /dev/null +++ b/src/domain/condition.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone)] +pub enum ConditionEnum { + None, + Changed, +} diff --git a/src/domain/data_type.rs b/src/domain/data_type.rs new file mode 100644 index 0000000..a3db80f --- /dev/null +++ b/src/domain/data_type.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone)] +pub enum DataType { + F64, + Bool, +} diff --git a/src/domain/event.rs b/src/domain/event.rs new file mode 100644 index 0000000..ad30f3e --- /dev/null +++ b/src/domain/event.rs @@ -0,0 +1,19 @@ +use std::os::raw::c_char; + +#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)] +#[repr(u32)] +pub enum Event { + Brakes, + BrakesLeft, + AxisLeftBrakeSet, +} + +impl Event { + pub(crate) fn into_c_char(self) -> *const c_char { + match self { + Event::Brakes => "BRAKES\0".as_ptr() as *const c_char, + Event::BrakesLeft => "BRAKES_LEFT\0".as_ptr() as *const c_char, + Event::AxisLeftBrakeSet => "AXIS_LEFT_BRAKE_SET\0".as_ptr() as *const c_char, + } + } +} diff --git a/src/domain/group.rs b/src/domain/group.rs new file mode 100644 index 0000000..b246c9f --- /dev/null +++ b/src/domain/group.rs @@ -0,0 +1,5 @@ +#[derive(Copy, Clone)] +#[repr(u32)] +pub enum Group { + Group0, +} diff --git a/src/domain/mod.rs b/src/domain/mod.rs new file mode 100644 index 0000000..79096b6 --- /dev/null +++ b/src/domain/mod.rs @@ -0,0 +1,15 @@ +mod airport_data; +mod condition; +mod data_type; +mod event; +mod group; +mod notification; +mod period; + +pub use airport_data::*; +pub use condition::*; +pub use data_type::*; +pub use event::*; +pub use group::*; +pub use notification::*; +pub use period::*; diff --git a/src/domain/notification.rs b/src/domain/notification.rs new file mode 100644 index 0000000..ff3eb59 --- /dev/null +++ b/src/domain/notification.rs @@ -0,0 +1,28 @@ +use crate::{AirportData, Event, SimConnectObject}; + +pub enum Notification { + Open, + Event(Event), + Data(NotificationData), + AirportList(Vec), + Quit, + Exception(u32), +} + +pub struct NotificationData { + pub(crate) type_id: String, + pub(crate) data_addr: *const u32, +} + +impl NotificationData { + pub fn try_into(&self) -> Option { + let type_id: String = std::any::type_name::().into(); + + if self.type_id == type_id { + let data: &T = unsafe { std::mem::transmute_copy(&self.data_addr) }; + Some(data.clone()) + } else { + None + } + } +} diff --git a/src/domain/period.rs b/src/domain/period.rs new file mode 100644 index 0000000..1f6fbff --- /dev/null +++ b/src/domain/period.rs @@ -0,0 +1,5 @@ +#[derive(Debug, Clone)] +pub enum PeriodEnum { + VisualFrame { interval: u32 }, + Second, +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..5f61754 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,15 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum SimConnectError { + #[error("SimConnect error: {0}")] + SimConnectError(i32), + #[error("SimConnect unrecognized: {0}")] + SimConnectUnrecognizedEvent(u32), + #[error("Object `{0}` has already been registered")] + ObjectAlreadyRegistered(String), + #[error("Conversion error: {0}")] + ConversionError(#[from] std::num::TryFromIntError), + #[error("Unexpected error: {0}")] + UnexpectedError(String), +} diff --git a/src/lib.rs b/src/lib.rs index 14e43bd..2d51cce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,303 +1,14 @@ -use helpers::fixed_c_str_to_string; -use std::ffi::c_void; -use std::{convert::TryFrom, fmt::Debug}; - mod bindings; +mod domain; +mod errors; mod helpers; +mod macros; +mod simconnect; +mod simconnect_object; -macro_rules! success { - ($hr:expr) => {{ - let hr = $hr; - if hr != 0 { - return Err(hr); - } - }}; -} +pub(crate) use macros::{as_c_string, ok_if_fail, success}; -macro_rules! ok_if_fail { - ($hr:expr, $ret:expr) => {{ - let hr = $hr; - let ret = $ret; - if hr != 0 { - return Ok(ret); - } - }}; -} - -macro_rules! as_c_string { - ($target:expr) => { - std::ffi::CString::new($target) - .expect("failed to create CString") - .as_ptr() - }; -} - -#[derive(Debug)] -pub struct SimConnect { - pub handle: std::ptr::NonNull, -} - -impl SimConnect { - #[tracing::instrument(name = "SimConnect::new")] - pub fn new(name: &str) -> Result { - let mut handle = std::ptr::null_mut(); - - success!(unsafe { - bindings::SimConnect_Open( - &mut handle, - as_c_string!(name), - std::ptr::null_mut(), - 0, - std::ptr::null_mut(), - 0, - ) - }); - - Ok(Self { - handle: std::ptr::NonNull::new(handle) - .expect("ERROR: SimConnect_Open returned null pointer on success"), - }) - } - - #[tracing::instrument(name = "SimConnect::register_event")] - pub fn register_event(&self, event: Event) -> Result<(), i32> { - success!(unsafe { - bindings::SimConnect_MapClientEventToSimEvent( - self.handle.as_ptr(), - event as u32, - event.into_c_char(), - ) - }); - - let group = Group::Group0; - - success!(unsafe { - bindings::SimConnect_AddClientEventToNotificationGroup( - self.handle.as_ptr(), - group as u32, - event as u32, - 0, - ) - }); - - success!(unsafe { - bindings::SimConnect_SetNotificationGroupPriority(self.handle.as_ptr(), group as u32, 1) - }); - - Ok(()) - } - - #[tracing::instrument(name = "SimConnect::add_to_data_definition")] - pub fn add_to_data_definition( - &self, - request_id: u32, - datum_name: &str, - units_name: &str, - ) -> Result<(), i32> { - unsafe { - success!(bindings::SimConnect_AddToDataDefinition( - self.handle.as_ptr(), - request_id, - as_c_string!(datum_name), - as_c_string!(units_name), - bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64, - 0.0, - u32::MAX, - )); - } - - Ok(()) - } - - #[tracing::instrument(name = "SimConnect::request_data_on_sim_object")] - pub fn request_data_on_sim_object( - &self, - request_id: u32, - period: PeriodEnum, - condition: ConditionEnum, - ) -> Result<(), i32> { - unsafe { - let (simconnect_period, simconnect_interval) = match period { - PeriodEnum::VisualFrame { interval } => ( - bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME, - interval, - ), - PeriodEnum::Second => (bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND, 0), - }; - - let simconnect_flags: u32 = match condition { - ConditionEnum::None => 0, - ConditionEnum::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED, - }; - - success!(bindings::SimConnect_RequestDataOnSimObject( - self.handle.as_ptr(), - request_id, - request_id, - request_id, - simconnect_period, - simconnect_flags, - 0, - simconnect_interval, - 0, - )); - } - - Ok(()) - } - - #[tracing::instrument(name = "SimConnect::subscribe_to_airport_list")] - pub fn subscribe_to_airport_list(&self, request_id: u32) -> Result<(), i32> { - unsafe { - success!(bindings::SimConnect_SubscribeToFacilities( - self.handle.as_ptr(), - bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, - request_id, - )); - } - - Ok(()) - } - - #[tracing::instrument(name = "SimConnect::request_airport_list")] - pub fn request_airport_list(&self, request_id: u32) -> Result<(), i32> { - unsafe { - success!(bindings::SimConnect_RequestFacilitiesList( - self.handle.as_ptr(), - bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, - request_id, - )); - } - - Ok(()) - } - - pub fn get_next_dispatch(&self) -> Result, i32> { - let mut data_buf: *mut bindings::SIMCONNECT_RECV = std::ptr::null_mut(); - let mut size_buf: bindings::DWORD = 32; - let size_buf_pointer: *mut bindings::DWORD = &mut size_buf; - - unsafe { - ok_if_fail!( - bindings::SimConnect_GetNextDispatch( - self.handle.as_ptr(), - &mut data_buf, - size_buf_pointer - ), - None - ); - }; - - let result = match unsafe { (*data_buf).dwID as i32 } { - bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_OPEN => Some(Notification::Open), - bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_QUIT => Some(Notification::Quit), - bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT => { - let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) }; - let event = Event::try_from(event.uEventID).expect("Unrecognized event"); - Some(Notification::Event(event)) - } - bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA => { - let event: &bindings::SIMCONNECT_RECV_SIMOBJECT_DATA = unsafe { - std::mem::transmute_copy( - &(data_buf as *const bindings::SIMCONNECT_RECV_SIMOBJECT_DATA), - ) - }; - - let data_addr = std::ptr::addr_of!(event.dwData); - - Some(Notification::Data(event.dwDefineID, data_addr)) - } - bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_AIRPORT_LIST => { - let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST = unsafe { - std::mem::transmute_copy( - &(data_buf as *const bindings::SIMCONNECT_RECV_AIRPORT_LIST), - ) - }; - - let data = event - .rgData - .iter() - .map(|data| AirportData { - icao: fixed_c_str_to_string(&data.Icao), - lat: data.Latitude, - lon: data.Longitude, - alt: data.Altitude, - }) - .collect::>(); - - Some(Notification::AirportList(data)) - } - bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION => { - let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EXCEPTION) }; - Some(Notification::Exception(event.dwException)) - } - bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => None, - _ => panic!("Got unrecognized notification: {}", unsafe { - (*data_buf).dwID as i32 - }), - }; - - Ok(result) - } -} - -pub enum Notification { - Open, - Event(Event), - Data(u32, *const u32), - AirportList(Vec), - Quit, - Exception(u32), -} - -#[derive(Debug, Clone)] -#[allow(dead_code)] -pub struct AirportData { - icao: String, - lat: f64, - lon: f64, - alt: f64, -} - -#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)] -#[repr(u32)] -pub enum Event { - Brakes, - BrakesLeft, - AxisLeftBrakeSet, -} - -#[derive(Debug, Clone)] -pub enum PeriodEnum { - VisualFrame { interval: u32 }, - Second, -} - -#[derive(Debug, Clone)] -pub enum ConditionEnum { - None, - Changed, -} - -use std::os::raw::c_char; -impl Event { - fn into_c_char(self) -> *const c_char { - match self { - Event::Brakes => "BRAKES\0".as_ptr() as *const c_char, - Event::BrakesLeft => "BRAKES_LEFT\0".as_ptr() as *const c_char, - Event::AxisLeftBrakeSet => "AXIS_LEFT_BRAKE_SET\0".as_ptr() as *const c_char, - } - } -} - -#[derive(Copy, Clone)] -#[repr(u32)] -enum Group { - Group0, -} - -impl Drop for SimConnect { - fn drop(&mut self) { - let _ = unsafe { bindings::SimConnect_Close(self.handle.as_ptr()) }; - } -} +pub use domain::*; +pub use errors::SimConnectError; +pub use simconnect::SimConnect; +pub use simconnect_object::SimConnectObject; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..9d0bfa7 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,29 @@ +macro_rules! success { + ($hr:expr) => {{ + let hr = $hr; + if hr != 0 { + return Err(SimConnectError::SimConnectError(hr)); + } + }}; +} +pub(crate) use success; + +macro_rules! ok_if_fail { + ($hr:expr, $ret:expr) => {{ + let hr = $hr; + let ret = $ret; + if hr != 0 { + return Ok(ret); + } + }}; +} +pub(crate) use ok_if_fail; + +macro_rules! as_c_string { + ($target:expr) => { + std::ffi::CString::new($target) + .map_err(|_| SimConnectError::UnexpectedError("failed to create CString".to_string()))? + .as_ptr() + }; +} +pub(crate) use as_c_string; diff --git a/src/simconnect.rs b/src/simconnect.rs new file mode 100644 index 0000000..2302b77 --- /dev/null +++ b/src/simconnect.rs @@ -0,0 +1,267 @@ +use std::ffi::c_void; + +use crate::{ + as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, AirportData, + ConditionEnum, DataType, Event, Group, Notification, NotificationData, PeriodEnum, + SimConnectError, SimConnectObject, +}; + +#[derive(Debug)] +pub struct SimConnect { + pub handle: std::ptr::NonNull, + pub registered_objects: Vec, +} + +impl SimConnect { + #[tracing::instrument(name = "SimConnect::new")] + pub fn new(name: &str) -> Result { + let mut handle = std::ptr::null_mut(); + + success!(unsafe { + bindings::SimConnect_Open( + &mut handle, + as_c_string!(name), + std::ptr::null_mut(), + 0, + std::ptr::null_mut(), + 0, + ) + }); + + Ok(Self { + handle: std::ptr::NonNull::new(handle).ok_or_else(|| { + SimConnectError::UnexpectedError( + "SimConnect_Open returned null pointer on success".to_string(), + ) + })?, + registered_objects: Vec::new(), + }) + } + + pub fn register_object(&mut self) -> Result { + let type_id: String = std::any::type_name::().into(); + + if self.registered_objects.contains(&type_id) { + return Err(SimConnectError::ObjectAlreadyRegistered(type_id)); + } + + self.registered_objects.push(type_id.clone()); + + let id = self + .registered_objects + .iter() + .position(|p| p == &type_id) + .ok_or_else(|| { + SimConnectError::UnexpectedError("failed to find registered event".to_string()) + })?; + let id = u32::try_from(id)?; + + T::register(self, id)?; + + Ok(id) + } + + #[tracing::instrument(name = "SimConnect::register_event")] + pub fn register_event(&self, event: Event) -> Result<(), SimConnectError> { + success!(unsafe { + bindings::SimConnect_MapClientEventToSimEvent( + self.handle.as_ptr(), + event as u32, + event.into_c_char(), + ) + }); + + let group = Group::Group0; + + success!(unsafe { + bindings::SimConnect_AddClientEventToNotificationGroup( + self.handle.as_ptr(), + group as u32, + event as u32, + 0, + ) + }); + + success!(unsafe { + bindings::SimConnect_SetNotificationGroupPriority(self.handle.as_ptr(), group as u32, 1) + }); + + Ok(()) + } + + #[tracing::instrument(name = "SimConnect::add_to_data_definition")] + pub fn add_to_data_definition( + &self, + request_id: u32, + datum_name: &str, + units_name: &str, + data_type: DataType, + ) -> Result<(), SimConnectError> { + let c_type = match data_type { + DataType::F64 => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64, + DataType::Bool => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_INT32, + }; + + unsafe { + success!(bindings::SimConnect_AddToDataDefinition( + self.handle.as_ptr(), + request_id, + as_c_string!(datum_name), + as_c_string!(units_name), + c_type, + 0.0, + u32::MAX, + )); + } + + Ok(()) + } + + #[tracing::instrument(name = "SimConnect::request_data_on_sim_object")] + pub fn request_data_on_sim_object( + &self, + request_id: u32, + period: PeriodEnum, + condition: ConditionEnum, + ) -> Result<(), SimConnectError> { + unsafe { + let (simconnect_period, simconnect_interval) = match period { + PeriodEnum::VisualFrame { interval } => ( + bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME, + interval, + ), + PeriodEnum::Second => (bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND, 0), + }; + + let simconnect_flags: u32 = match condition { + ConditionEnum::None => 0, + ConditionEnum::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED, + }; + + success!(bindings::SimConnect_RequestDataOnSimObject( + self.handle.as_ptr(), + request_id, + request_id, + request_id, + simconnect_period, + simconnect_flags, + 0, + simconnect_interval, + 0, + )); + } + + Ok(()) + } + + #[tracing::instrument(name = "SimConnect::subscribe_to_airport_list")] + pub fn subscribe_to_airport_list(&self, request_id: u32) -> Result<(), SimConnectError> { + unsafe { + success!(bindings::SimConnect_SubscribeToFacilities( + self.handle.as_ptr(), + bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, + request_id, + )); + } + + Ok(()) + } + + #[tracing::instrument(name = "SimConnect::request_airport_list")] + pub fn request_airport_list(&self, request_id: u32) -> Result<(), SimConnectError> { + unsafe { + success!(bindings::SimConnect_RequestFacilitiesList( + self.handle.as_ptr(), + bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT, + request_id, + )); + } + + Ok(()) + } + + pub fn get_next_dispatch(&self) -> Result, SimConnectError> { + let mut data_buf: *mut bindings::SIMCONNECT_RECV = std::ptr::null_mut(); + let mut size_buf: bindings::DWORD = 32; + let size_buf_pointer: *mut bindings::DWORD = &mut size_buf; + + unsafe { + ok_if_fail!( + bindings::SimConnect_GetNextDispatch( + self.handle.as_ptr(), + &mut data_buf, + size_buf_pointer + ), + None + ); + }; + + let result = match unsafe { (*data_buf).dwID as i32 } { + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_OPEN => Some(Notification::Open), + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_QUIT => Some(Notification::Quit), + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT => { + let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) }; + let event = Event::try_from(event.uEventID) + .map_err(|_| SimConnectError::SimConnectUnrecognizedEvent(event.uEventID))?; + Some(Notification::Event(event)) + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA => { + let event: &bindings::SIMCONNECT_RECV_SIMOBJECT_DATA = unsafe { + std::mem::transmute_copy( + &(data_buf as *const bindings::SIMCONNECT_RECV_SIMOBJECT_DATA), + ) + }; + + let object_type = self.registered_objects.get(event.dwDefineID as usize); + + match object_type { + Some(object_type) => { + let data = NotificationData { + type_id: object_type.clone(), + data_addr: std::ptr::addr_of!(event.dwData), + }; + + Some(Notification::Data(data)) + } + _ => None, + } + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_AIRPORT_LIST => { + let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST = unsafe { + std::mem::transmute_copy( + &(data_buf as *const bindings::SIMCONNECT_RECV_AIRPORT_LIST), + ) + }; + + let data = event + .rgData + .iter() + .map(|data| AirportData { + icao: fixed_c_str_to_string(&data.Icao), + lat: data.Latitude, + lon: data.Longitude, + alt: data.Altitude, + }) + .collect::>(); + + Some(Notification::AirportList(data)) + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION => { + let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EXCEPTION) }; + Some(Notification::Exception(event.dwException)) + } + bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => None, + _ => panic!("Got unrecognized notification: {}", unsafe { + (*data_buf).dwID as i32 + }), + }; + + Ok(result) + } +} + +impl Drop for SimConnect { + fn drop(&mut self) { + let _ = unsafe { bindings::SimConnect_Close(self.handle.as_ptr()) }; + } +} diff --git a/src/simconnect_object.rs b/src/simconnect_object.rs new file mode 100644 index 0000000..6990dc3 --- /dev/null +++ b/src/simconnect_object.rs @@ -0,0 +1,5 @@ +use crate::{NotificationData, SimConnect, SimConnectError}; + +pub trait SimConnectObject: Clone + for<'a> TryFrom<&'a NotificationData> { + fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError>; +}