diff --git a/README.md b/README.md index 7a11f18..cb7d809 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,59 @@ # SimConnect SDK in Rust -## Running the examples +## Usage -```bash -git clone git@github.com:mihai-dinculescu/simconnect-sdk.git -cd simconnect-sdk -RUST_LOG=info cargo run --bin basic -RUST_LOG=info cargo run --bin basic_no_macros +```toml +[dependencies] +simconnect-sdk = { version = "0.1", features = ["derive"] } ``` + +```rust +use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; + +/// A data structure that will be used to receive data from SimConnect. +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second")] +struct GpsData { + #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] + lat: f64, + #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] + lon: f64, + #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] + alt: f64, +} + +fn main() -> Result<(), Box> { + let client = SimConnect::new("Simple Program"); + + match client { + Ok(mut client) => loop { + let notification = client.get_next_dispatch()?; + + match notification { + Some(Notification::Open) => { + println!("Open"); + + // The struct must be registered after the connection is successfully open + client.register_object::()?; + } + Some(Notification::Data(data)) => { + if let Ok(gps_data) = GpsData::try_from(&data) { + println!("GPS Data: {gps_data:?}"); + } + } + _ => (), + } + + // sleep for about a frame to reduce CPU usage + std::thread::sleep(std::time::Duration::from_millis(16)); + }, + Err(e) => { + println!("Error: {e:?}") + } + } + + Ok(()) +} +``` + +See [more examples](https://github.com/mihai-dinculescu/simconnect-sdk/tree/main/examples). diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 5257977..3fb73b3 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -12,8 +12,16 @@ name = "basic" path = "src/basic.rs" [[bin]] -name = "basic_no_macros" -path = "src/basic_no_macros.rs" +name = "basic_with_tracing" +path = "src/basic_with_tracing.rs" + +[[bin]] +name = "basic_without_macro" +path = "src/basic_without_macro.rs" + +[[bin]] +name = "multiple_objects" +path = "src/multiple_objects.rs" [dependencies] tracing = "0.1" diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..58d7cef --- /dev/null +++ b/examples/README.md @@ -0,0 +1,32 @@ +# SimConnect SDK Examples + +## Checkout the repository + +```bash +git clone git@github.com:mihai-dinculescu/simconnect-sdk.git +cd simconnect-sdk +``` + +## Receiving data + +```bash +cargo run --bin basic +``` + +## Using tracing + +```bash +RUST_LOG=info cargo run --bin basic_with_tracing +``` + +## Receiving data without the derive macro + +```bash +cargo run --bin basic_without_macro +``` + +## Multiple objects + +```bash +cargo run --bin multiple_objects +``` diff --git a/examples/src/basic.rs b/examples/src/basic.rs index 29e68f0..79a022f 100644 --- a/examples/src/basic.rs +++ b/examples/src/basic.rs @@ -1,9 +1,8 @@ #![allow(dead_code)] use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; -use simconnect_sdk_examples::setup_logging; -use tracing::{error, info}; +/// A data structure that will be used to receive data from SimConnect. #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second")] struct GpsData { @@ -13,22 +12,9 @@ struct GpsData { lon: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] alt: f64, - #[simconnect(name = "GPS GROUND MAGNETIC TRACK", unit = "degrees")] - gps_ground_magnetic_track: f64, - #[simconnect(name = "GPS GROUND SPEED", unit = "Meters per second")] - gps_ground_speed: f64, -} - -#[derive(Debug, Clone, SimConnectObject)] -#[simconnect(period = "second", condition = "changed")] -pub struct OnGround { - #[simconnect(name = "SIM ON GROUND", unit = "bool")] - sim_on_ground: bool, } fn main() -> Result<(), Box> { - setup_logging()?; - let client = SimConnect::new("Simple Program"); match client { @@ -37,26 +23,24 @@ fn main() -> Result<(), Box> { match notification { Some(Notification::Open) => { - info!("Open"); + println!("Open"); + // The struct must be registered after the connection is successfully 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; + println!("GPS Data: {gps_data:?}"); } } _ => (), } + + // sleep for about a frame to reduce CPU usage + std::thread::sleep(std::time::Duration::from_millis(16)); }, Err(e) => { - error!("{e:?}") + println!("Error: {e:?}") } } diff --git a/examples/src/basic_with_tracing.rs b/examples/src/basic_with_tracing.rs new file mode 100644 index 0000000..32f9011 --- /dev/null +++ b/examples/src/basic_with_tracing.rs @@ -0,0 +1,66 @@ +#![allow(dead_code)] + +use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; +use tracing::{error, info}; +use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + +/// A data structure that will be used to receive data from SimConnect. +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second")] +struct GpsData { + #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] + lat: f64, + #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] + lon: f64, + #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] + alt: f64, +} + +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"); + + // The struct must be registered after the connection is successfully open + client.register_object::()?; + } + Some(Notification::Data(data)) => { + if let Ok(gps_data) = GpsData::try_from(&data) { + info!("GPS Data: {gps_data:?}"); + } + } + _ => (), + } + + // sleep for about a frame to reduce CPU usage + std::thread::sleep(std::time::Duration::from_millis(16)); + }, + Err(e) => { + error!("{e:?}") + } + } + + Ok(()) +} + +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(()) +} diff --git a/examples/src/basic_no_macros.rs b/examples/src/basic_without_macro.rs similarity index 51% rename from examples/src/basic_no_macros.rs rename to examples/src/basic_without_macro.rs index 6aa5103..3bb7bdf 100644 --- a/examples/src/basic_no_macros.rs +++ b/examples/src/basic_without_macro.rs @@ -4,17 +4,13 @@ use simconnect_sdk::{ Condition, DataType, Notification, NotificationData, Period, SimConnect, SimConnectError, SimConnectObjectExt, }; -use tracing::{error, info}; - -use simconnect_sdk_examples::setup_logging; +/// A data structure that will be used to receive data from SimConnect. #[derive(Debug, Clone)] pub struct GpsData { lat: f64, lon: f64, alt: f64, - gps_ground_magnetic_track: f64, - gps_ground_speed: f64, } impl SimConnectObjectExt for GpsData { @@ -22,18 +18,7 @@ impl SimConnectObjectExt for GpsData { client.add_to_data_definition(id, "PLANE LATITUDE", "degrees", DataType::Float64)?; client.add_to_data_definition(id, "PLANE LONGITUDE", "degrees", DataType::Float64)?; client.add_to_data_definition(id, "PLANE ALTITUDE", "meters", DataType::Float64)?; - client.add_to_data_definition( - id, - "GPS GROUND MAGNETIC TRACK", - "degrees", - DataType::Float64, - )?; - client.add_to_data_definition( - id, - "GPS GROUND SPEED", - "Meters per second", - DataType::Float64, - )?; + client.request_data_on_sim_object(id, Period::Second, Condition::None, 0)?; Ok(()) @@ -48,31 +33,7 @@ impl TryFrom<&NotificationData> for GpsData { } } -#[derive(Debug, Clone)] -pub struct OnGround { - sim_on_ground: bool, -} - -impl SimConnectObjectExt 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, Period::Second, Condition::Changed, 0)?; - - Ok(()) - } -} - -impl TryFrom<&NotificationData> for OnGround { - type Error = SimConnectError; - - fn try_from(value: &NotificationData) -> Result { - value.try_transmute::() - } -} - fn main() -> Result<(), Box> { - setup_logging()?; - let client = SimConnect::new("Simple Program"); match client { @@ -81,26 +42,24 @@ fn main() -> Result<(), Box> { match notification { Some(Notification::Open) => { - info!("Open"); + println!("Open"); + // The struct must be registered after the connection is successfully 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; + println!("GPS Data: {gps_data:?}"); } } _ => (), } + + // sleep for about a frame to reduce CPU usage + std::thread::sleep(std::time::Duration::from_millis(16)); }, Err(e) => { - error!("{e:?}") + println!("Error: {e:?}") } } diff --git a/examples/src/lib.rs b/examples/src/lib.rs deleted file mode 100644 index dee1f94..0000000 --- a/examples/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -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(()) -} diff --git a/examples/src/multiple_objects.rs b/examples/src/multiple_objects.rs new file mode 100644 index 0000000..2b783dd --- /dev/null +++ b/examples/src/multiple_objects.rs @@ -0,0 +1,64 @@ +#![allow(dead_code)] + +use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; + +/// A data structure that will be used to receive data from SimConnect. +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second")] +struct GpsData { + #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] + lat: f64, + #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] + lon: f64, + #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] + alt: f64, +} + +/// A second data structure that will be used to receive data from SimConnect. +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second", condition = "changed")] +pub struct OnGround { + #[simconnect(name = "SIM ON GROUND", unit = "bool")] + sim_on_ground: bool, +} + +fn main() -> Result<(), Box> { + let client = SimConnect::new("Simple Program"); + + match client { + Ok(mut client) => loop { + let notification = client.get_next_dispatch()?; + + match notification { + Some(Notification::Open) => { + println!("Open"); + + // The structs must be registered after the connection is successfully open + client.register_object::()?; + client.register_object::()?; + } + Some(Notification::Data(data)) => { + if let Ok(gps_data) = GpsData::try_from(&data) { + println!("GPS Data: {gps_data:?}"); + // We've already got our object, there's no point in trying another in this iteration + continue; + } + if let Ok(on_ground) = OnGround::try_from(&data) { + println!("On Ground data: {on_ground:?}"); + // We've already got our object, there's no point in trying another in this iteration + continue; + } + } + _ => (), + } + + // sleep for about a frame to reduce CPU usage + std::thread::sleep(std::time::Duration::from_millis(16)); + }, + Err(e) => { + println!("Error: {e:?}") + } + } + + Ok(()) +} diff --git a/simconnect-sdk/Cargo.toml b/simconnect-sdk/Cargo.toml index 11f874d..8bfbce4 100644 --- a/simconnect-sdk/Cargo.toml +++ b/simconnect-sdk/Cargo.toml @@ -18,3 +18,10 @@ num_enum = "0.5" tracing = "0.1" thiserror = "1.0" simconnect-sdk-derive = { path = "../simconnect-sdk-derive", optional = true } + +# docs.rs-specific configuration +[package.metadata.docs.rs] +# document all features +all-features = true +# defines the configuration attribute `docsrs` +rustdoc-args = ["--cfg", "docsrs"] diff --git a/simconnect-sdk/src/lib.rs b/simconnect-sdk/src/lib.rs index 8bcaf3d..bd18393 100644 --- a/simconnect-sdk/src/lib.rs +++ b/simconnect-sdk/src/lib.rs @@ -1,7 +1,63 @@ //! # SimConnect SDK //! SimConnect SDK in Rust. //! -//! See [examples](https://github.com/mihai-dinculescu/simconnect-sdk/tree/main/examples/src). +//! ## Usage + +//! ```toml +//! [dependencies] +//! simconnect-sdk = { version = "0.1", features = ["derive"] } +//! ``` +//! +//! ```no_run +//! use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; +//! +//! /// A data structure that will be used to receive data from SimConnect. +//! #[derive(Debug, Clone, SimConnectObject)] +//! #[simconnect(period = "second")] +//! struct GpsData { +//! #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] +//! lat: f64, +//! #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] +//! lon: f64, +//! #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] +//! alt: f64, +//! } +//! +//! fn main() -> Result<(), Box> { +//! let client = SimConnect::new("Simple Program"); +//! +//! match client { +//! Ok(mut client) => loop { +//! let notification = client.get_next_dispatch()?; +//! +//! match notification { +//! Some(Notification::Open) => { +//! println!("Open"); +//! +//! // The struct must be registered after the connection is successfully open +//! client.register_object::()?; +//! } +//! Some(Notification::Data(data)) => { +//! if let Ok(gps_data) = GpsData::try_from(&data) { +//! println!("GPS Data: {gps_data:?}"); +//! } +//! } +//! _ => (), +//! } +//! +//! // sleep for about a frame to reduce CPU usage +//! std::thread::sleep(std::time::Duration::from_millis(16)); +//! }, +//! Err(e) => { +//! println!("Error: {e:?}") +//! } +//! } +//! +//! Ok(()) +//! } +//! ``` +//! +//! See [more examples](https://github.com/mihai-dinculescu/simconnect-sdk/tree/main/examples). mod bindings; mod domain; @@ -19,6 +75,8 @@ pub use simconnect::SimConnect; pub use simconnect_object_ext::SimConnectObjectExt; #[cfg(feature = "simconnect-sdk-derive")] +#[cfg_attr(docsrs, doc(cfg(feature = "simconnect-sdk-derive")))] extern crate simconnect_sdk_derive; #[cfg(feature = "simconnect-sdk-derive")] +#[cfg_attr(docsrs, doc(cfg(feature = "simconnect-sdk-derive")))] pub use simconnect_sdk_derive::*; diff --git a/simconnect-sdk/src/simconnect.rs b/simconnect-sdk/src/simconnect.rs index 7842cec..1364930 100644 --- a/simconnect-sdk/src/simconnect.rs +++ b/simconnect-sdk/src/simconnect.rs @@ -7,13 +7,65 @@ use crate::{ }; /// SimConnect SDK Client. +/// +/// # Example +/// +/// ```no_run +/// use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; +/// +/// /// A data structure that will be used to receive data from SimConnect. +/// #[derive(Debug, Clone, SimConnectObject)] +/// #[simconnect(period = "second")] +/// struct GpsData { +/// #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] +/// lat: f64, +/// #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] +/// lon: f64, +/// #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] +/// alt: f64, +/// } +/// +/// fn main() -> Result<(), Box> { +/// let client = SimConnect::new("Simple Program"); +/// +/// match client { +/// Ok(mut client) => loop { +/// let notification = client.get_next_dispatch()?; +/// +/// match notification { +/// Some(Notification::Open) => { +/// println!("Open"); +/// +/// // The struct must be registered after the connection is successfully open +/// client.register_object::()?; +/// } +/// Some(Notification::Data(data)) => { +/// if let Ok(gps_data) = GpsData::try_from(&data) { +/// println!("GPS Data: {gps_data:?}"); +/// } +/// } +/// _ => (), +/// } +/// +/// // sleep for about a frame to reduce CPU usage +/// std::thread::sleep(std::time::Duration::from_millis(16)); +/// }, +/// Err(e) => { +/// println!("Error: {e:?}") +/// } +/// } +/// +/// Ok(()) +/// } +/// ``` #[derive(Debug)] pub struct SimConnect { - pub handle: std::ptr::NonNull, - pub registered_objects: Vec, + handle: std::ptr::NonNull, + registered_objects: Vec, } impl SimConnect { + /// Create a new SimConnect SDK client. #[tracing::instrument(name = "SimConnect::new")] pub fn new(name: &str) -> Result { let mut handle = std::ptr::null_mut(); @@ -182,6 +234,9 @@ impl SimConnect { Ok(()) } + /// Receive the next SimConnect message. + /// This is a non-blocking function. If there are no messages to receive, it will return None immediately. + /// When called in a loop, it is recommended to use a short sleep time. 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;