diff --git a/README.md b/README.md index 7714be2..5949d7d 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,19 @@ use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second")] #[allow(dead_code)] -struct GpsData { +struct AirplaneData { + #[simconnect(name = "TITLE")] + title: String, + #[simconnect(name = "CATEGORY")] + category: String, #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] lat: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] lon: f64, #[simconnect(name = "PLANE ALTITUDE", unit = "feet")] alt: f64, + #[simconnect(name = "SIM ON GROUND")] + sim_on_ground: bool, } fn main() -> Result<(), Box> { @@ -43,17 +49,17 @@ fn main() -> Result<(), Box> { println!("Connection opened."); // After the connection is successfully open, we register the struct - client.register_object::()?; + client.register_object::()?; } Some(Notification::Object(data)) => { - if let Ok(gps_data) = GpsData::try_from(&data) { - println!("{gps_data:?}"); + if let Ok(airplane_data) = AirplaneData::try_from(&data) { + println!("{airplane_data:?}"); notifications_received += 1; // After we have received 10 notifications, we unregister the struct if notifications_received > 10 { - client.unregister_object::()?; + client.unregister_object::()?; println!("Subscription stopped."); break; } diff --git a/examples/src/data.rs b/examples/src/data.rs index f174c7f..7c5539c 100644 --- a/examples/src/data.rs +++ b/examples/src/data.rs @@ -5,13 +5,19 @@ use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second")] #[allow(dead_code)] -struct GpsData { +struct AirplaneData { + #[simconnect(name = "TITLE")] + title: String, + #[simconnect(name = "CATEGORY")] + category: String, #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] lat: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] lon: f64, #[simconnect(name = "PLANE ALTITUDE", unit = "feet")] alt: f64, + #[simconnect(name = "SIM ON GROUND")] + sim_on_ground: bool, } fn main() -> Result<(), Box> { @@ -29,17 +35,17 @@ fn main() -> Result<(), Box> { println!("Connection opened."); // After the connection is successfully open, we register the struct - client.register_object::()?; + client.register_object::()?; } Some(Notification::Object(data)) => { - if let Ok(gps_data) = GpsData::try_from(&data) { - println!("{gps_data:?}"); + if let Ok(airplane_data) = AirplaneData::try_from(&data) { + println!("{airplane_data:?}"); notifications_received += 1; // After we have received 10 notifications, we unregister the struct if notifications_received > 10 { - client.unregister_object::()?; + client.unregister_object::()?; println!("Subscription stopped."); break; } diff --git a/examples/src/data_multiple_objects.rs b/examples/src/data_multiple_objects.rs index 6249959..50fab3d 100644 --- a/examples/src/data_multiple_objects.rs +++ b/examples/src/data_multiple_objects.rs @@ -19,8 +19,20 @@ struct GpsData { #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "changed")] #[allow(dead_code)] +struct AirplaneData { + #[simconnect(name = "TITLE")] + title: String, + #[simconnect(name = "CATEGORY")] + category: String, +} + +/// A third data structure that will be used to receive data from SimConnect. +/// See the documentation of `SimConnectObject` for more information on the arguments of the `simconnect` attribute. +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second", condition = "changed")] +#[allow(dead_code)] pub struct OnGround { - #[simconnect(name = "SIM ON GROUND", unit = "bool")] + #[simconnect(name = "SIM ON GROUND")] sim_on_ground: bool, } @@ -37,6 +49,7 @@ fn main() -> Result<(), Box> { // After the connection is successfully open, we register the structs client.register_object::()?; + client.register_object::()?; client.register_object::()?; } Some(Notification::Object(data)) => { @@ -45,6 +58,11 @@ fn main() -> Result<(), Box> { // We've already got our data, there's no point in trying another in this iteration continue; } + if let Ok(airplane_data) = AirplaneData::try_from(&data) { + println!("{airplane_data:?}"); + // We've already got our data, there's no point in trying another in this iteration + continue; + } if let Ok(on_ground) = OnGround::try_from(&data) { println!("{on_ground:?}"); // We've already got our data, there's no point in trying another in this iteration diff --git a/examples/src/data_with_tracing.rs b/examples/src/data_with_tracing.rs index 88f65c0..f617e66 100644 --- a/examples/src/data_with_tracing.rs +++ b/examples/src/data_with_tracing.rs @@ -7,13 +7,19 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter}; #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second")] #[allow(dead_code)] -struct GpsData { +struct AirplaneData { + #[simconnect(name = "TITLE")] + title: String, + #[simconnect(name = "CATEGORY")] + category: String, #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] lat: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] lon: f64, #[simconnect(name = "PLANE ALTITUDE", unit = "feet")] alt: f64, + #[simconnect(name = "SIM ON GROUND")] + sim_on_ground: bool, } fn main() -> Result<(), Box> { @@ -33,17 +39,17 @@ fn main() -> Result<(), Box> { info!("Open"); // After the connection is successfully open, we register the struct - client.register_object::()?; + client.register_object::()?; } Some(Notification::Object(data)) => { - if let Ok(gps_data) = GpsData::try_from(&data) { - info!("{gps_data:?}"); + if let Ok(airplane_data) = AirplaneData::try_from(&data) { + info!("{airplane_data:?}"); notifications_received += 1; // After we have received 10 notifications, we unregister the struct if notifications_received > 10 { - client.unregister_object::()?; + client.unregister_object::()?; println!("Subscription stopped."); break; } diff --git a/examples/src/data_without_macro.rs b/examples/src/data_without_macro.rs index 9c2942b..feb8b60 100644 --- a/examples/src/data_without_macro.rs +++ b/examples/src/data_without_macro.rs @@ -1,22 +1,41 @@ +/// This example shows all the work that the [`simconnect_sdk::SimConnectObject`] macro is doing behind the scenes. +/// You're probably better off using the macro in a real-life use-case. use simconnect_sdk::{ - Condition, DataType, Notification, Object, Period, SimConnect, SimConnectError, - SimConnectObjectExt, + fixed_c_str_to_string, Condition, DataType, Notification, Object, Period, SimConnect, + SimConnectError, SimConnectObjectExt, }; /// A data structure that will be used to receive data from SimConnect. #[derive(Debug, Clone)] #[allow(dead_code)] -pub struct GpsData { +pub struct AirplaneData { + title: String, + category: String, lat: f64, lon: f64, alt: f64, + sim_on_ground: bool, } -impl SimConnectObjectExt for GpsData { +/// An intermediate data structure that will map 1:1 to the object received from SimConnect. +#[repr(C, packed)] +struct AirplaneDataCPacked { + title: [i8; 256], + category: [i8; 256], + lat: f64, + lon: f64, + alt: f64, + sim_on_ground: bool, +} + +impl SimConnectObjectExt for AirplaneData { fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError> { + client.add_to_data_definition(id, "TITLE", "", DataType::String)?; + client.add_to_data_definition(id, "CATEGORY", "", DataType::String)?; 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", "feet", DataType::Float64)?; + client.add_to_data_definition(id, "SIM ON GROUND", "", DataType::Float64)?; client.request_data_on_sim_object(id, Period::Second, Condition::None, 0)?; @@ -24,11 +43,20 @@ impl SimConnectObjectExt for GpsData { } } -impl TryFrom<&Object> for GpsData { +impl TryFrom<&Object> for AirplaneData { type Error = SimConnectError; fn try_from(value: &Object) -> Result { - value.try_transmute::() + let raw = value.try_transmute::()?; + + Ok(AirplaneData { + title: fixed_c_str_to_string(&raw.title), + category: fixed_c_str_to_string(&raw.category), + lat: raw.lat, + lon: raw.lon, + alt: raw.alt, + sim_on_ground: raw.sim_on_ground, + }) } } @@ -47,17 +75,17 @@ fn main() -> Result<(), Box> { println!("Connection opened."); // After the connection is successfully open, we register the struct - client.register_object::()?; + client.register_object::()?; } Some(Notification::Object(data)) => { - if let Ok(gps_data) = GpsData::try_from(&data) { + if let Ok(gps_data) = AirplaneData::try_from(&data) { println!("{gps_data:?}"); notifications_received += 1; // After we have received 10 notifications, we unregister the struct if notifications_received > 10 { - client.unregister_object::()?; + client.unregister_object::()?; println!("Subscription stopped."); break; } diff --git a/simconnect-sdk-derive/src/fields.rs b/simconnect-sdk-derive/src/fields.rs new file mode 100644 index 0000000..85ef79a --- /dev/null +++ b/simconnect-sdk-derive/src/fields.rs @@ -0,0 +1,248 @@ +use std::collections::HashMap; + +use once_cell::sync::Lazy; + +use crate::helpers::{get_attribute, mk_err}; + +#[derive(Debug, PartialEq, Eq)] +pub enum FieldType { + Str, + Int, +} + +pub struct FieldInfo { + pub field_type: FieldType, + pub required: bool, + pub accepted_values: Vec, +} + +pub static ALLOWED_CLASS_ATTRIBUTES: Lazy> = Lazy::new(|| { + let mut map = HashMap::new(); + + map.insert( + "period".to_string(), + FieldInfo { + field_type: FieldType::Str, + required: true, + accepted_values: vec![ + "once".to_string(), + "visual-frame".to_string(), + "sim-frame".to_string(), + "second".to_string(), + ], + }, + ); + map.insert( + "condition".to_string(), + FieldInfo { + field_type: FieldType::Str, + required: false, + accepted_values: vec!["none".to_string(), "changed".to_string()], + }, + ); + map.insert( + "interval".to_string(), + FieldInfo { + field_type: FieldType::Int, + required: false, + accepted_values: vec![], + }, + ); + + map +}); + +pub static ALLOWED_FIELD_ATTRIBUTES: Lazy> = Lazy::new(|| { + let mut map = HashMap::new(); + + map.insert( + "name".to_string(), + FieldInfo { + field_type: FieldType::Str, + required: true, + accepted_values: vec![], + }, + ); + map.insert( + "unit".to_string(), + FieldInfo { + field_type: FieldType::Str, + required: false, + accepted_values: vec![], + }, + ); + + map +}); +pub const SUPPORTED_FIELD_TYPES: [&str; 3] = ["f64", "bool", "String"]; + +pub fn extract_attribute_properties( + attr: &syn::Attribute, + allowed_properties: &HashMap, + error_message: &str, +) -> Result, proc_macro2::TokenStream> { + let mut results = HashMap::new(); + + match attr.parse_meta() { + Ok(syn::Meta::List(nvs)) => { + for item in nvs.nested.iter() { + match &item { + syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) => { + match nv.path.get_ident() { + Some(ident) => { + let ident_string = ident.to_string(); + + let allowed_property = allowed_properties.get(&ident_string); + + match allowed_property { + Some(property) => { + if results.contains_key(&ident_string) { + // found a duplicate property name + return Err(mk_err(nvs.clone(), error_message)); + } + + match &nv.lit { + syn::Lit::Str(lit) + if property.field_type == FieldType::Str => + { + let value = lit.value(); + + if !property.accepted_values.is_empty() + && !property.accepted_values.contains(&value) + { + // found an invalid value + return Err(mk_err( + nv, + &format!( + r#"`{ident_string}` must be one of ["{}"]."#, + property + .accepted_values + .join(r#"", ""#) + ), + )); + } + + results.insert(ident_string, value); + } + syn::Lit::Int(lit) + if property.field_type == FieldType::Int => + { + let value = lit.to_string(); + + if !property.accepted_values.is_empty() + && !property.accepted_values.contains(&value) + { + // found an invalid value + return Err(mk_err( + nv, + &format!( + r#"`{ident_string}` must be one of ["{}"]."#, + property + .accepted_values + .join(r#"", ""#) + ), + )); + } + + results.insert(ident_string, value); + } + lit => { + return Err(syn::Error::new_spanned( + nv, + format!( + "Expected {:?}, found {:?}", + property.field_type, lit + ), + ) + .to_compile_error()) + } + } + } + None => { + // found an unexpected property name + return Err(mk_err(nvs.clone(), error_message)); + } + } + } + None => { + // no ident found + return Err(mk_err(nvs.clone(), error_message)); + } + } + } + meta => { + // nvc.nested[] was not k = v + return Err(mk_err(meta, error_message)); + } + } + } + + // check that all required properties are specified + for (field, _) in allowed_properties.iter().filter(|(_, fi)| fi.required) { + if !results.contains_key(field) { + return Err(mk_err(nvs, error_message)); + } + } + } + Ok(meta) => { + // inside of #[] there was just an identifier (`#[simconnect]`) + // or a key-value mapping (`#[simconnect = "foo"]`), neither of which are okay. + + return Err(mk_err(meta, error_message)); + } + Err(e) => { + return Err(e.to_compile_error()); + } + }; + + Ok(results) +} + +pub fn parse_field_attributes( + field: &syn::Field, +) -> Result<(&proc_macro2::Ident, &syn::Path, HashMap), proc_macro2::TokenStream> { + let attr = get_attribute(&field.attrs); + + let error_message = + "expected attribute `#[simconnect(name = \"...\", unit = \"...\")]`. `unit` is optional."; + + let name = field.ident.as_ref().expect("this should not happen"); + let ty = &field.ty; + + match attr { + Some(attr) => { + let properties = + extract_attribute_properties(attr, &ALLOWED_FIELD_ATTRIBUTES, error_message); + + match properties { + Ok(properties) => { + let error_message_supported_types = &format!( + r#"Field type must be one of ["{}"]."#, + SUPPORTED_FIELD_TYPES.join(r#"", ""#) + ); + + match ty { + syn::Type::Path(syn::TypePath { path, .. }) => { + let path_segments = &path.segments; + let path_idents = path_segments.iter().map(|s| &s.ident); + + match path_idents.last() { + Some(value) + if SUPPORTED_FIELD_TYPES + .contains(&value.to_string().as_str()) => + { + Ok((name, path, properties)) + } + + _ => Err(mk_err(ty, error_message_supported_types)), + } + } + _ => Err(mk_err(ty, error_message_supported_types)), + } + } + Err(e) => Err(e), + } + } + None => Err(mk_err(field, error_message)), + } +} diff --git a/simconnect-sdk-derive/src/helpers.rs b/simconnect-sdk-derive/src/helpers.rs new file mode 100644 index 0000000..1ca7cd4 --- /dev/null +++ b/simconnect-sdk-derive/src/helpers.rs @@ -0,0 +1,9 @@ +pub fn get_attribute(attrs: &[syn::Attribute]) -> Option<&syn::Attribute> { + attrs + .iter() + .find(|&attr| attr.path.segments.len() == 1 && attr.path.segments[0].ident == "simconnect") +} + +pub fn mk_err(t: T, message: &str) -> proc_macro2::TokenStream { + syn::Error::new_spanned(t, message).to_compile_error() +} diff --git a/simconnect-sdk-derive/src/lib.rs b/simconnect-sdk-derive/src/lib.rs index 420b157..42dc0d4 100644 --- a/simconnect-sdk-derive/src/lib.rs +++ b/simconnect-sdk-derive/src/lib.rs @@ -1,82 +1,15 @@ extern crate proc_macro; + use std::collections::HashMap; -use once_cell::sync::Lazy; +use fields::{extract_attribute_properties, parse_field_attributes, ALLOWED_CLASS_ATTRIBUTES}; +use helpers::{get_attribute, mk_err}; use proc_macro::TokenStream; use quote::quote; use syn::{parse_macro_input, DeriveInput}; -#[derive(Debug, PartialEq, Eq)] -enum FieldType { - Str, - Int, -} - -struct FieldInfo { - field_type: FieldType, - required: bool, - accepted_values: Vec, -} - -static ALLOWED_CLASS_ATTRIBUTES: Lazy> = Lazy::new(|| { - let mut map = HashMap::new(); - - map.insert( - "period".to_string(), - FieldInfo { - field_type: FieldType::Str, - required: true, - accepted_values: vec![ - "once".to_string(), - "visual-frame".to_string(), - "sim-frame".to_string(), - "second".to_string(), - ], - }, - ); - map.insert( - "condition".to_string(), - FieldInfo { - field_type: FieldType::Str, - required: false, - accepted_values: vec!["none".to_string(), "changed".to_string()], - }, - ); - map.insert( - "interval".to_string(), - FieldInfo { - field_type: FieldType::Int, - required: false, - accepted_values: vec![], - }, - ); - - map -}); - -static ALLOWED_FIELD_ATTRIBUTES: Lazy> = Lazy::new(|| { - let mut map = HashMap::new(); - - map.insert( - "name".to_string(), - FieldInfo { - field_type: FieldType::Str, - required: true, - accepted_values: vec![], - }, - ); - map.insert( - "unit".to_string(), - FieldInfo { - field_type: FieldType::Str, - required: true, - accepted_values: vec![], - }, - ); - - map -}); -const SUPPORTED_FIELD_TYPES: [&str; 2] = ["f64", "bool"]; +mod fields; +mod helpers; /// SimConnectObject derive macro. /// @@ -87,7 +20,7 @@ const SUPPORTED_FIELD_TYPES: [&str; 2] = ["f64", "bool"]; /// /// # Field Arguments /// * `name` - Required. The name of the field. One from . -/// * `unit` - Required. The unit of the field. +/// * `unit` - Optional. The unit of the field. For `string`s and `bool`s it should be left out or be empty string. For numeric fields it should be one from . /// /// # Example /// @@ -96,20 +29,27 @@ const SUPPORTED_FIELD_TYPES: [&str; 2] = ["f64", "bool"]; /// /// #[derive(Debug, Clone, SimConnectObject)] /// #[simconnect(period = "second")] -/// struct GpsData { +/// struct AirplaneData { +/// #[simconnect(name = "TITLE")] +/// title: String, +/// #[simconnect(name = "CATEGORY")] +/// category: String, /// #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] /// lat: f64, /// #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] /// lon: f64, -/// #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] +/// #[simconnect(name = "PLANE ALTITUDE", unit = "feet")] /// alt: f64, +/// #[simconnect(name = "SIM ON GROUND", unit = "bool")] +/// sim_on_ground: bool, /// } /// ``` #[proc_macro_derive(SimConnectObject, attributes(simconnect))] pub fn derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); - let name = &ast.ident; + let name_ident = &ast.ident; + let packed_ident = syn::Ident::new(&format!("{}CPacked", name_ident), name_ident.span()); let fields = if let syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }), @@ -125,24 +65,53 @@ pub fn derive(input: TokenStream) -> TokenStream { .into(); }; - let build_fields = fields.iter().map(parse_field); - let request_data = request_data(&ast); + // parse the fields and their attributes + let mut parsed_fields = Vec::with_capacity(fields.len()); + for field in fields { + let result = parse_field_attributes(field); + match result { + Ok(field) => { + parsed_fields.push(field); + } + Err(e) => return e.into(), + } + } + + // packed struct fields + let packed_fields = parsed_fields + .iter() + .map(|(ident, path, _)| build_packed_field(ident, path)); + let packed_fields_assignments = parsed_fields + .iter() + .map(|(ident, path, _)| build_packed_field_assignment(ident, path)); + + // SC fields + let sc_definition = parsed_fields + .iter() + .map(|(_, path, properties)| build_sc_definition(path, properties)); + let sc_request = build_sc_request(&ast); + + // put everything together let expanded = quote! { - impl simconnect_sdk::SimConnectObjectExt for #name { + #[repr(C, packed)] + struct #packed_ident { + #(#packed_fields,)* + } + impl simconnect_sdk::SimConnectObjectExt for #name_ident { fn register(client: &mut simconnect_sdk::SimConnect, id: u32) -> Result<(), simconnect_sdk::SimConnectError> { - #(#build_fields)* - - #request_data - + #(#sc_definition)* + #sc_request Ok(()) } } - impl TryFrom<&simconnect_sdk::Object> for #name { + impl TryFrom<&simconnect_sdk::Object> for #name_ident { type Error = simconnect_sdk::SimConnectError; - fn try_from(value: &simconnect_sdk::Object) -> Result { - value.try_transmute::<#name>() + let raw = value.try_transmute::<#name_ident, #packed_ident>()?; + Ok(#name_ident { + #(#packed_fields_assignments,)* + }) } } }; @@ -150,64 +119,92 @@ pub fn derive(input: TokenStream) -> TokenStream { expanded.into() } -fn parse_field(f: &syn::Field) -> proc_macro2::TokenStream { - let error_message = "expected attribute `#[simconnect(name = \"...\", unit = \"...\")]`"; +fn build_packed_field(ident: &proc_macro2::Ident, path: &syn::Path) -> proc_macro2::TokenStream { + let path_segments = &path.segments; + let path_idents = path_segments.iter().map(|s| &s.ident); - let attr = get_attribute(&f.attrs); - - match attr { - Some(attr) => { - let ty = &f.ty; - let properties = - extract_attribute_string_properties(attr, &ALLOWED_FIELD_ATTRIBUTES, error_message); - - match properties { - Ok(properties) => { - let error_message_supported_types = &format!( - r#"Field type must be one of ["{}"]"#, - SUPPORTED_FIELD_TYPES.join(r#"", ""#) - ); - - match ty { - syn::Type::Path(syn::TypePath { path, .. }) => { - let path = &path.segments; - let path = path.iter().map(|s| &s.ident); - - let name = properties.get("name").expect("this should never happen"); - let unit = properties.get("unit").expect("this should never happen"); - - match path.last() { - Some(value) if value == "f64" => { - quote! { - client.add_to_data_definition(id, #name, #unit, simconnect_sdk::DataType::Float64)?; - } - } - Some(value) if value == "bool" => { - quote! { - client.add_to_data_definition(id, #name, #unit, simconnect_sdk::DataType::Bool)?; - } - } - _ => mk_err(f, error_message_supported_types), - } - } - _ => mk_err(f, error_message_supported_types), - } - } - Err(e) => e, + match path_idents.last() { + Some(value) if value == "String" => { + quote! { + #ident: [std::primitive::i8; 256] + } + } + _ => { + quote! { + #ident: #path } } - None => mk_err(f, error_message), } } -fn request_data(ast: &DeriveInput) -> proc_macro2::TokenStream { +fn build_packed_field_assignment( + ident: &proc_macro2::Ident, + path: &syn::Path, +) -> proc_macro2::TokenStream { + let path_segments = &path.segments; + let path_idents = path_segments.iter().map(|s| &s.ident); + + match path_idents.last() { + Some(value) if value == "String" => { + quote! { + #ident: simconnect_sdk::fixed_c_str_to_string(&raw.#ident) + } + } + _ => { + quote! { + #ident: raw.#ident + } + } + } +} + +fn build_sc_definition( + path: &syn::Path, + properties: &HashMap, +) -> proc_macro2::TokenStream { + let error_message = + "expected attribute `#[simconnect(name = \"...\", unit = \"...\")]`. `unit` is optional."; + + let path_segments = &path.segments; + let path_idents = path_segments.iter().map(|s| &s.ident); + + let name = properties.get("name").expect("this should never happen"); + let unit = match properties.get("unit") { + Some(unit) => unit, + None => "", + }; + + match path_idents.last() { + Some(value) if value == "f64" => { + quote! { + client.add_to_data_definition(id, #name, #unit, simconnect_sdk::DataType::Float64)?; + } + } + Some(value) if value == "bool" => { + quote! { + client.add_to_data_definition(id, #name, #unit, simconnect_sdk::DataType::Bool)?; + } + } + Some(value) if value == "String" => { + quote! { + client.add_to_data_definition(id, #name, #unit, simconnect_sdk::DataType::String)?; + } + } + _ => { + // this error is already caught in `parse_field_attributes` + mk_err(path, error_message) + } + } +} + +fn build_sc_request(ast: &DeriveInput) -> proc_macro2::TokenStream { let attr = get_attribute(&ast.attrs); let error_message = "expected attribute `#[simconnect(period = \"...\", condition = \"...\", interval = ...)]`. `condition` and `interval` are optional."; match attr { Some(attr) => { let properties = - extract_attribute_string_properties(attr, &ALLOWED_CLASS_ATTRIBUTES, error_message); + extract_attribute_properties(attr, &ALLOWED_CLASS_ATTRIBUTES, error_message); match properties { Ok(properties) => { @@ -262,135 +259,3 @@ fn request_data(ast: &DeriveInput) -> proc_macro2::TokenStream { None => mk_err(ast, error_message), } } - -fn get_attribute(attrs: &[syn::Attribute]) -> Option<&syn::Attribute> { - attrs - .iter() - .find(|&attr| attr.path.segments.len() == 1 && attr.path.segments[0].ident == "simconnect") -} - -fn extract_attribute_string_properties( - attr: &syn::Attribute, - allowed_properties: &HashMap, - error_message: &str, -) -> Result, proc_macro2::TokenStream> { - let mut results = HashMap::new(); - - match attr.parse_meta() { - Ok(syn::Meta::List(nvs)) => { - for item in nvs.nested.iter() { - match &item { - syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) => { - match nv.path.get_ident() { - Some(ident) => { - let ident_string = ident.to_string(); - - let allowed_property = allowed_properties.get(&ident_string); - - match allowed_property { - Some(property) => { - if results.contains_key(&ident_string) { - // found a duplicate property name - return Err(mk_err(nvs.clone(), error_message)); - } - - match &nv.lit { - syn::Lit::Str(lit) - if property.field_type == FieldType::Str => - { - let value = lit.value(); - - if !property.accepted_values.is_empty() - && !property.accepted_values.contains(&value) - { - // found an invalid value - return Err(mk_err( - nv, - &format!( - r#"`{ident_string}` must be one of ["{}"]"#, - property - .accepted_values - .join(r#"", ""#) - ), - )); - } - - results.insert(ident_string, value); - } - syn::Lit::Int(lit) - if property.field_type == FieldType::Int => - { - let value = lit.to_string(); - - if !property.accepted_values.is_empty() - && !property.accepted_values.contains(&value) - { - // found an invalid value - return Err(mk_err( - nv, - &format!( - r#"`{ident_string}` must be one of ["{}""]"#, - property - .accepted_values - .join(r#"", ""#) - ), - )); - } - - results.insert(ident_string, value); - } - lit => { - return Err(syn::Error::new_spanned( - nv, - format!( - "Expected {:?}, found {:?}", - property.field_type, lit - ), - ) - .to_compile_error()) - } - } - } - None => { - // found an unexpected property name - return Err(mk_err(nvs.clone(), error_message)); - } - } - } - None => { - // no ident found - return Err(mk_err(nvs.clone(), error_message)); - } - } - } - meta => { - // nvc.nested[] was not k = v - return Err(mk_err(meta, error_message)); - } - } - } - - // check that all required properties are specified - for (field, _) in allowed_properties.iter().filter(|(_, fi)| fi.required) { - if !results.contains_key(field) { - return Err(mk_err(nvs, error_message)); - } - } - } - Ok(meta) => { - // inside of #[] there was just an identifier (`#[simconnect]`) - // or a key-value mapping (`#[simconnect = "foo"]`), neither of which are okay. - - return Err(mk_err(meta, error_message)); - } - Err(e) => { - return Err(e.to_compile_error()); - } - }; - - Ok(results) -} - -fn mk_err(t: T, message: &str) -> proc_macro2::TokenStream { - syn::Error::new_spanned(t, message).to_compile_error() -} diff --git a/simconnect-sdk-derive/tests/01-parse.rs b/simconnect-sdk-derive/tests/01-parse.rs index c8f4f25..bc6f7a6 100644 --- a/simconnect-sdk-derive/tests/01-parse.rs +++ b/simconnect-sdk-derive/tests/01-parse.rs @@ -3,7 +3,7 @@ use simconnect_sdk_derive::SimConnectObject; #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second")] -struct GpsData1 { +struct Data1 { #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] pub lat: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] @@ -12,7 +12,7 @@ struct GpsData1 { #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData2 { +struct Data2 { #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] pub lat: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] @@ -21,7 +21,7 @@ struct GpsData2 { #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "visual-frame", condition = "changed")] -struct GpsData3 { +struct Data3 { #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] pub lat: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] @@ -30,7 +30,7 @@ struct GpsData3 { #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "visual-frame", condition = "changed", interval = 0)] -struct GpsData4 { +struct Data4 { #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] pub lat: f64, #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] diff --git a/simconnect-sdk-derive/tests/02-struct-attr-errors.rs b/simconnect-sdk-derive/tests/02-struct-attr-errors.rs index e6ddf1a..e838148 100644 --- a/simconnect-sdk-derive/tests/02-struct-attr-errors.rs +++ b/simconnect-sdk-derive/tests/02-struct-attr-errors.rs @@ -3,45 +3,45 @@ use simconnect_sdk_derive::SimConnectObject; #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "visual-frame", condition = "changed")] -struct GpsData1(f64); +struct Data1(f64); #[derive(Debug, Clone, SimConnectObject)] -struct GpsData2 {} +struct Data2 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect] -struct GpsData3 {} +struct Data3 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect()] -struct GpsData4 {} +struct Data4 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", period = "second")] -struct GpsData5 {} +struct Data5 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none", condition = "none")] -struct GpsData6 {} +struct Data6 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", interval = 0, interval = 0)] -struct GpsData7 {} +struct Data7 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", test = "test")] -struct GpsData8 {} +struct Data8 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(periodX = "second", condition = "none")] -struct GpsData9 {} +struct Data9 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", conditionX = "none")] -struct GpsData10 {} +struct Data10 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", intervalX = 0)] -struct GpsData11 {} +struct Data11 {} fn main() {} diff --git a/simconnect-sdk-derive/tests/02-struct-attr-errors.stderr b/simconnect-sdk-derive/tests/02-struct-attr-errors.stderr index 947f784..f091003 100644 --- a/simconnect-sdk-derive/tests/02-struct-attr-errors.stderr +++ b/simconnect-sdk-derive/tests/02-struct-attr-errors.stderr @@ -2,14 +2,14 @@ error: Unsupported field type. Only named fields are supported. --> tests/02-struct-attr-errors.rs:5:1 | 5 | / #[simconnect(period = "visual-frame", condition = "changed")] -6 | | struct GpsData1(f64); - | |_____________________^ +6 | | struct Data1(f64); + | |__________________^ error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:9:1 | -9 | struct GpsData2 {} - | ^^^^^^^^^^^^^^^^^^ +9 | struct Data2 {} + | ^^^^^^^^^^^^^^^ error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:12:3 diff --git a/simconnect-sdk-derive/tests/03-field-attr-errors.rs b/simconnect-sdk-derive/tests/03-field-attr-errors.rs index a7caba9..8521531 100644 --- a/simconnect-sdk-derive/tests/03-field-attr-errors.rs +++ b/simconnect-sdk-derive/tests/03-field-attr-errors.rs @@ -3,69 +3,62 @@ use simconnect_sdk_derive::SimConnectObject; #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData1 { +struct Data1 { pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData2 { +struct Data2 { #[simconnect] pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData3 { +struct Data3 { #[simconnect()] pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData4 { - #[simconnect(name = "PLANE LATITUDE")] - pub lat: f64, -} - -#[derive(Debug, Clone, SimConnectObject)] -#[simconnect(period = "second", condition = "none")] -struct GpsData5 { +struct Data4 { #[simconnect(unit = "degrees")] pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData6 { +struct Data5 { #[simconnect(name = "PLANE LATITUDE", name = "PLANE LATITUDE")] pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData7 { +struct Data6 { #[simconnect(unit = "degrees", unit = "degrees")] pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData8 { +struct Data7 { #[simconnect(name = "PLANE LATITUDE", unit = "degrees", unit = "degrees")] pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData9 { +struct Data8 { #[simconnect(nameX = "PLANE LATITUDE", unit = "degrees")] pub lat: f64, } #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData10 { +struct Data9 { #[simconnect(name = "PLANE LATITUDE", unitX = "degrees")] pub lat: f64, } diff --git a/simconnect-sdk-derive/tests/03-field-attr-errors.stderr b/simconnect-sdk-derive/tests/03-field-attr-errors.stderr index d14d28f..869fb0b 100644 --- a/simconnect-sdk-derive/tests/03-field-attr-errors.stderr +++ b/simconnect-sdk-derive/tests/03-field-attr-errors.stderr @@ -1,59 +1,53 @@ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. --> tests/03-field-attr-errors.rs:7:5 | 7 | pub lat: f64, | ^^^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. --> tests/03-field-attr-errors.rs:13:7 | 13 | #[simconnect] | ^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. --> tests/03-field-attr-errors.rs:20:7 | 20 | #[simconnect()] | ^^^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. --> tests/03-field-attr-errors.rs:27:7 | -27 | #[simconnect(name = "PLANE LATITUDE")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: expected attribute `#[simconnect(name = "...", unit = "...")]` - --> tests/03-field-attr-errors.rs:34:7 - | -34 | #[simconnect(unit = "degrees")] +27 | #[simconnect(unit = "degrees")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` - --> tests/03-field-attr-errors.rs:41:7 +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. + --> tests/03-field-attr-errors.rs:34:7 | -41 | #[simconnect(name = "PLANE LATITUDE", name = "PLANE LATITUDE")] +34 | #[simconnect(name = "PLANE LATITUDE", name = "PLANE LATITUDE")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` - --> tests/03-field-attr-errors.rs:48:7 +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. + --> tests/03-field-attr-errors.rs:41:7 | -48 | #[simconnect(unit = "degrees", unit = "degrees")] +41 | #[simconnect(unit = "degrees", unit = "degrees")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` - --> tests/03-field-attr-errors.rs:55:7 +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. + --> tests/03-field-attr-errors.rs:48:7 | -55 | #[simconnect(name = "PLANE LATITUDE", unit = "degrees", unit = "degrees")] +48 | #[simconnect(name = "PLANE LATITUDE", unit = "degrees", unit = "degrees")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` - --> tests/03-field-attr-errors.rs:62:7 +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. + --> tests/03-field-attr-errors.rs:55:7 | -62 | #[simconnect(nameX = "PLANE LATITUDE", unit = "degrees")] +55 | #[simconnect(nameX = "PLANE LATITUDE", unit = "degrees")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(name = "...", unit = "...")]` - --> tests/03-field-attr-errors.rs:69:7 +error: expected attribute `#[simconnect(name = "...", unit = "...")]`. `unit` is optional. + --> tests/03-field-attr-errors.rs:62:7 | -69 | #[simconnect(name = "PLANE LATITUDE", unitX = "degrees")] +62 | #[simconnect(name = "PLANE LATITUDE", unitX = "degrees")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/simconnect-sdk-derive/tests/04-invalid-values.rs b/simconnect-sdk-derive/tests/04-invalid-values.rs index 39c9bce..1381dfa 100644 --- a/simconnect-sdk-derive/tests/04-invalid-values.rs +++ b/simconnect-sdk-derive/tests/04-invalid-values.rs @@ -3,33 +3,33 @@ use simconnect_sdk_derive::SimConnectObject; #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = 123, condition = "none")] -struct GpsData1 {} +struct Data1 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = 123)] -struct GpsData2 {} +struct Data2 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "X")] -struct GpsData3 {} +struct Data3 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "X")] -struct GpsData4 {} +struct Data4 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", interval = "X")] -struct GpsData5 {} +struct Data5 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", interval = 0.0)] -struct GpsData6 {} +struct Data6 {} #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData7 { +struct Data7 { #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] - pub lat: String, + pub lat: u64, } fn main() {} diff --git a/simconnect-sdk-derive/tests/04-invalid-values.stderr b/simconnect-sdk-derive/tests/04-invalid-values.stderr index 61250f5..5260d1a 100644 --- a/simconnect-sdk-derive/tests/04-invalid-values.stderr +++ b/simconnect-sdk-derive/tests/04-invalid-values.stderr @@ -10,13 +10,13 @@ error: Expected Str, found Int(LitInt { token: 123 }) 9 | #[simconnect(period = "second", condition = 123)] | ^^^^^^^^^^^^^^^ -error: `period` must be one of ["once", "visual-frame", "sim-frame", "second"] +error: `period` must be one of ["once", "visual-frame", "sim-frame", "second"]. --> tests/04-invalid-values.rs:13:14 | 13 | #[simconnect(period = "X")] | ^^^^^^^^^^^^ -error: `condition` must be one of ["none", "changed"] +error: `condition` must be one of ["none", "changed"]. --> tests/04-invalid-values.rs:17:33 | 17 | #[simconnect(period = "second", condition = "X")] @@ -34,9 +34,8 @@ error: Expected Int, found Float(LitFloat { token: 0.0 }) 25 | #[simconnect(period = "second", interval = 0.0)] | ^^^^^^^^^^^^^^ -error: Field type must be one of ["f64", "bool"] - --> tests/04-invalid-values.rs:31:5 +error: Field type must be one of ["f64", "bool", "String"]. + --> tests/04-invalid-values.rs:32:14 | -31 | / #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] -32 | | pub lat: String, - | |___________________^ +32 | pub lat: u64, + | ^^^ diff --git a/simconnect-sdk/build.rs b/simconnect-sdk/build.rs index a904328..1a57582 100644 --- a/simconnect-sdk/build.rs +++ b/simconnect-sdk/build.rs @@ -44,6 +44,7 @@ fn main() { .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER") .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE") .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME") + .allowlist_var("SIMCONNECT_OBJECT_ID_USER") .generate() .expect("Unable to generate bindings"); diff --git a/simconnect-sdk/src/domain/data_type.rs b/simconnect-sdk/src/domain/data_type.rs index 9677d93..13c123e 100644 --- a/simconnect-sdk/src/domain/data_type.rs +++ b/simconnect-sdk/src/domain/data_type.rs @@ -3,4 +3,5 @@ pub enum DataType { Float64, Bool, + String, } diff --git a/simconnect-sdk/src/domain/notification.rs b/simconnect-sdk/src/domain/notification.rs index 3d42094..6bc8589 100644 --- a/simconnect-sdk/src/domain/notification.rs +++ b/simconnect-sdk/src/domain/notification.rs @@ -35,12 +35,12 @@ impl Object { /// /// # Errors /// - [`crate::SimConnectError::ObjectMismatch`] -- The type of this SimConnect object is different from `T`. - pub fn try_transmute(&self) -> Result { + pub fn try_transmute(&self) -> Result { let type_name: String = std::any::type_name::().into(); if self.type_name == type_name { - let data: &T = unsafe { std::mem::transmute_copy(&self.data_addr) }; - Ok(data.clone()) + let data: I = unsafe { std::ptr::read_unaligned(self.data_addr as *const I) }; + Ok(data) } else { Err(SimConnectError::ObjectMismatch { actual: self.type_name.clone(), diff --git a/simconnect-sdk/src/lib.rs b/simconnect-sdk/src/lib.rs index 85af99e..5eb1fb1 100644 --- a/simconnect-sdk/src/lib.rs +++ b/simconnect-sdk/src/lib.rs @@ -26,6 +26,7 @@ pub(crate) use macros::{as_c_string, ok_if_fail, success}; pub use domain::*; pub use errors::SimConnectError; +pub use helpers::fixed_c_str_to_string; pub use simconnect::SimConnect; pub use simconnect_object_ext::SimConnectObjectExt; diff --git a/simconnect-sdk/src/simconnect/base.rs b/simconnect-sdk/src/simconnect/base.rs index 4e71ffb..3417979 100644 --- a/simconnect-sdk/src/simconnect/base.rs +++ b/simconnect-sdk/src/simconnect/base.rs @@ -68,21 +68,23 @@ impl SimConnect { ); }; - let result = match unsafe { (*data_buf).dwID as i32 } { + let recv_id = unsafe { (*data_buf).dwID as i32 }; + + let result = match recv_id { 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: &bindings::SIMCONNECT_RECV_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 event: &bindings::SIMCONNECT_RECV_SIMOBJECT_DATA = + unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_SIMOBJECT_DATA) }; let type_name = self.get_type_name_by_request_id(event.dwDefineID); @@ -99,11 +101,8 @@ impl SimConnect { } } 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 event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST = + unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_AIRPORT_LIST) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { @@ -122,11 +121,8 @@ impl SimConnect { Some(Notification::AirportList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_WAYPOINT_LIST => { - let event: &bindings::SIMCONNECT_RECV_WAYPOINT_LIST = unsafe { - std::mem::transmute_copy( - &(data_buf as *const bindings::SIMCONNECT_RECV_WAYPOINT_LIST), - ) - }; + let event: &bindings::SIMCONNECT_RECV_WAYPOINT_LIST = + unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_WAYPOINT_LIST) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { @@ -146,11 +142,8 @@ impl SimConnect { Some(Notification::WaypointList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NDB_LIST => { - let event: &bindings::SIMCONNECT_RECV_NDB_LIST = unsafe { - std::mem::transmute_copy( - &(data_buf as *const bindings::SIMCONNECT_RECV_NDB_LIST), - ) - }; + let event: &bindings::SIMCONNECT_RECV_NDB_LIST = + unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_NDB_LIST) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { @@ -171,11 +164,8 @@ impl SimConnect { Some(Notification::NdbList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_VOR_LIST => { - let event: &bindings::SIMCONNECT_RECV_VOR_LIST = unsafe { - std::mem::transmute_copy( - &(data_buf as *const bindings::SIMCONNECT_RECV_VOR_LIST), - ) - }; + let event: &bindings::SIMCONNECT_RECV_VOR_LIST = + unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_VOR_LIST) }; let data = (0..event._base.dwArraySize as usize) .map(|i| { @@ -241,13 +231,12 @@ impl SimConnect { Some(Notification::VorList(data)) } bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION => { - let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EXCEPTION) }; + let event: &bindings::SIMCONNECT_RECV_EXCEPTION = + 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 - }), + id => panic!("Got unrecognized notification: {id}"), }; Ok(result) diff --git a/simconnect-sdk/src/simconnect/objects.rs b/simconnect-sdk/src/simconnect/objects.rs index b4ed7a8..fe03395 100644 --- a/simconnect-sdk/src/simconnect/objects.rs +++ b/simconnect-sdk/src/simconnect/objects.rs @@ -45,21 +45,22 @@ impl SimConnect { pub fn add_to_data_definition( &self, request_id: u32, - datum_name: &str, - units_name: &str, + name: &str, + unit: &str, data_type: DataType, ) -> Result<(), SimConnectError> { let c_type = match data_type { DataType::Float64 => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64, DataType::Bool => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_INT32, + DataType::String => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_STRING256, }; unsafe { success!(bindings::SimConnect_AddToDataDefinition( self.handle.as_ptr(), request_id, - as_c_string!(datum_name), - as_c_string!(units_name), + as_c_string!(name), + as_c_string!(unit), c_type, 0.0, u32::MAX, @@ -71,7 +72,12 @@ impl SimConnect { /// Request when the SimConnect client is to receive data values for a specific object. /// + /// # Current limitation + /// All objects are requested from the local user's aircraft POV. + /// This comes with the side-effect that currently there is no way to request data for other aircraft in multiplayer. + /// /// # Arguments + /// * `request_id` - The request ID of the object. /// * `period` - [`crate::Period`] /// * `condition` - [`crate::Condition`] /// * `interval` - The number of period events that should elapse between transmissions of the data. `0` means the data is transmitted every Period, `1` means that the data is transmitted every other Period, etc. @@ -91,7 +97,7 @@ impl SimConnect { self.handle.as_ptr(), request_id, request_id, - request_id, + bindings::SIMCONNECT_OBJECT_ID_USER, period.into(), condition.into(), 0,