From 3a7237addf73e6b70b6b0c556e5130e44e2cf2f9 Mon Sep 17 00:00:00 2001 From: Mihai Dinculescu Date: Wed, 19 Oct 2022 13:18:26 +0100 Subject: [PATCH] Add support for to the macro --- README.md | 1 + examples/src/data.rs | 1 + examples/src/data_multiple_objects.rs | 2 + examples/src/data_with_tracing.rs | 1 + simconnect-sdk-derive/src/lib.rs | 74 ++++++++++++++++--- simconnect-sdk-derive/tests/01-parse.rs | 9 +++ .../tests/02-struct-attr-errors.rs | 16 +++- .../tests/02-struct-attr-errors.stderr | 42 +++++++---- .../tests/04-invalid-values.rs | 10 ++- .../tests/04-invalid-values.stderr | 28 +++++-- simconnect-sdk/src/domain/condition.rs | 2 +- .../src/domain/notification_group.rs | 2 +- simconnect-sdk/src/simconnect/objects.rs | 12 +-- 13 files changed, 155 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index bb86434..cf32e4e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ simconnect-sdk = { version = "0.1", features = ["derive"] } use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; /// A 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")] #[allow(dead_code)] diff --git a/examples/src/data.rs b/examples/src/data.rs index 9472247..15d8883 100644 --- a/examples/src/data.rs +++ b/examples/src/data.rs @@ -1,6 +1,7 @@ use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; /// A 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")] #[allow(dead_code)] diff --git a/examples/src/data_multiple_objects.rs b/examples/src/data_multiple_objects.rs index e26a565..fb1155e 100644 --- a/examples/src/data_multiple_objects.rs +++ b/examples/src/data_multiple_objects.rs @@ -1,6 +1,7 @@ use simconnect_sdk::{Notification, SimConnect, SimConnectObject}; /// A 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")] #[allow(dead_code)] @@ -14,6 +15,7 @@ struct GpsData { } /// A second 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)] diff --git a/examples/src/data_with_tracing.rs b/examples/src/data_with_tracing.rs index dd9d295..0f4b721 100644 --- a/examples/src/data_with_tracing.rs +++ b/examples/src/data_with_tracing.rs @@ -3,6 +3,7 @@ use tracing::{error, info}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; /// A 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")] #[allow(dead_code)] diff --git a/simconnect-sdk-derive/src/lib.rs b/simconnect-sdk-derive/src/lib.rs index ac072a2..420b157 100644 --- a/simconnect-sdk-derive/src/lib.rs +++ b/simconnect-sdk-derive/src/lib.rs @@ -6,7 +6,14 @@ 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, } @@ -17,6 +24,7 @@ static ALLOWED_CLASS_ATTRIBUTES: Lazy> = Lazy::new(|| map.insert( "period".to_string(), FieldInfo { + field_type: FieldType::Str, required: true, accepted_values: vec![ "once".to_string(), @@ -29,10 +37,19 @@ static ALLOWED_CLASS_ATTRIBUTES: Lazy> = Lazy::new(|| 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 }); @@ -43,6 +60,7 @@ static ALLOWED_FIELD_ATTRIBUTES: Lazy> = Lazy::new(|| map.insert( "name".to_string(), FieldInfo { + field_type: FieldType::Str, required: true, accepted_values: vec![], }, @@ -50,6 +68,7 @@ static ALLOWED_FIELD_ATTRIBUTES: Lazy> = Lazy::new(|| map.insert( "unit".to_string(), FieldInfo { + field_type: FieldType::Str, required: true, accepted_values: vec![], }, @@ -63,7 +82,8 @@ const SUPPORTED_FIELD_TYPES: [&str; 2] = ["f64", "bool"]; /// /// # Struct Arguments /// * `period` - Required. One of `once`, `visual-frame`, `sim-frame`, `second`. -/// * `condition` - Optional. The condition of the data. Must be either `none` or `changed`. Defaults to `none`. +/// * `condition` - Optional. Defaults to `none`. The condition of the data. Must be either `none` or `changed`. `changed` = Data will only be sent to the client when one or more values have changed. All the variables in a data definition will be returned if just one of the values changes. +/// * `interval` - Optional. Defaults to `0`. 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. /// /// # Field Arguments /// * `name` - Required. The name of the field. One from . @@ -144,8 +164,8 @@ fn parse_field(f: &syn::Field) -> proc_macro2::TokenStream { match properties { Ok(properties) => { let error_message_supported_types = &format!( - "Field type must be one of ['{}']", - SUPPORTED_FIELD_TYPES.join("', '") + r#"Field type must be one of ["{}"]"#, + SUPPORTED_FIELD_TYPES.join(r#"", ""#) ); match ty { @@ -182,7 +202,7 @@ fn parse_field(f: &syn::Field) -> proc_macro2::TokenStream { fn request_data(ast: &DeriveInput) -> proc_macro2::TokenStream { let attr = get_attribute(&ast.attrs); - let error_message = "expected attribute `#[simconnect(period = \"...\", condition = \"...\")]`"; + let error_message = "expected attribute `#[simconnect(period = \"...\", condition = \"...\", interval = ...)]`. `condition` and `interval` are optional."; match attr { Some(attr) => { @@ -227,8 +247,13 @@ fn request_data(ast: &DeriveInput) -> proc_macro2::TokenStream { } }; + let interval = match properties.get("interval") { + Some(i) => i.parse::().unwrap_or_default(), + None => 0, + }; + quote! { - client.request_data_on_sim_object(id, #period, #condition, 0)?; + client.request_data_on_sim_object(id, #period, #condition, #interval)?; } } Err(e) => e, @@ -270,8 +295,10 @@ fn extract_attribute_string_properties( } match &nv.lit { - syn::Lit::Str(s) => { - let value = s.value(); + 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) @@ -280,8 +307,32 @@ fn extract_attribute_string_properties( return Err(mk_err( nv, &format!( - "`{ident_string}` must be one of ['{}']", - property.accepted_values.join("', '") + 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#"", ""#) ), )); } @@ -291,7 +342,10 @@ fn extract_attribute_string_properties( lit => { return Err(syn::Error::new_spanned( nv, - format!("expected string, found {lit:?}"), + format!( + "Expected {:?}, found {:?}", + property.field_type, lit + ), ) .to_compile_error()) } diff --git a/simconnect-sdk-derive/tests/01-parse.rs b/simconnect-sdk-derive/tests/01-parse.rs index e982ab0..c8f4f25 100644 --- a/simconnect-sdk-derive/tests/01-parse.rs +++ b/simconnect-sdk-derive/tests/01-parse.rs @@ -28,4 +28,13 @@ struct GpsData3 { pub lon: f64, } +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "visual-frame", condition = "changed", interval = 0)] +struct GpsData4 { + #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] + pub lat: f64, + #[simconnect(name = "PLANE LONGITUDE", unit = "degrees")] + pub lon: f64, +} + fn main() {} diff --git a/simconnect-sdk-derive/tests/02-struct-attr-errors.rs b/simconnect-sdk-derive/tests/02-struct-attr-errors.rs index b63353e..e6ddf1a 100644 --- a/simconnect-sdk-derive/tests/02-struct-attr-errors.rs +++ b/simconnect-sdk-derive/tests/02-struct-attr-errors.rs @@ -21,19 +21,27 @@ struct GpsData4 {} struct GpsData5 {} #[derive(Debug, Clone, SimConnectObject)] -#[simconnect(condition = "none", condition = "none")] +#[simconnect(period = "second", condition = "none", condition = "none")] struct GpsData6 {} #[derive(Debug, Clone, SimConnectObject)] -#[simconnect(period = "second", condition = "none", test = "test")] +#[simconnect(period = "second", interval = 0, interval = 0)] struct GpsData7 {} #[derive(Debug, Clone, SimConnectObject)] -#[simconnect(periodX = "second", condition = "none")] +#[simconnect(period = "second", test = "test")] struct GpsData8 {} #[derive(Debug, Clone, SimConnectObject)] -#[simconnect(period = "second", conditionX = "none")] +#[simconnect(periodX = "second", condition = "none")] struct GpsData9 {} +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second", conditionX = "none")] +struct GpsData10 {} + +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second", intervalX = 0)] +struct GpsData11 {} + 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 6ca1b01..947f784 100644 --- a/simconnect-sdk-derive/tests/02-struct-attr-errors.stderr +++ b/simconnect-sdk-derive/tests/02-struct-attr-errors.stderr @@ -5,50 +5,62 @@ error: Unsupported field type. Only named fields are supported. 6 | | struct GpsData1(f64); | |_____________________^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:9:1 | 9 | struct GpsData2 {} | ^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:12:3 | 12 | #[simconnect] | ^^^^^^^^^^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:16:3 | 16 | #[simconnect()] | ^^^^^^^^^^^^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:20:3 | 20 | #[simconnect(period = "second", period = "second")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:24:3 | -24 | #[simconnect(condition = "none", condition = "none")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +24 | #[simconnect(period = "second", condition = "none", condition = "none")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:28:3 | -28 | #[simconnect(period = "second", condition = "none", test = "test")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +28 | #[simconnect(period = "second", interval = 0, interval = 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:32:3 | -32 | #[simconnect(periodX = "second", condition = "none")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +32 | #[simconnect(period = "second", test = "test")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: expected attribute `#[simconnect(period = "...", condition = "...")]` +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. --> tests/02-struct-attr-errors.rs:36:3 | -36 | #[simconnect(period = "second", conditionX = "none")] +36 | #[simconnect(periodX = "second", condition = "none")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. + --> tests/02-struct-attr-errors.rs:40:3 + | +40 | #[simconnect(period = "second", conditionX = "none")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: expected attribute `#[simconnect(period = "...", condition = "...", interval = ...)]`. `condition` and `interval` are optional. + --> tests/02-struct-attr-errors.rs:44:3 + | +44 | #[simconnect(period = "second", intervalX = 0)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/simconnect-sdk-derive/tests/04-invalid-values.rs b/simconnect-sdk-derive/tests/04-invalid-values.rs index 00c2bb8..39c9bce 100644 --- a/simconnect-sdk-derive/tests/04-invalid-values.rs +++ b/simconnect-sdk-derive/tests/04-invalid-values.rs @@ -17,9 +17,17 @@ struct GpsData3 {} #[simconnect(period = "second", condition = "X")] struct GpsData4 {} +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second", interval = "X")] +struct GpsData5 {} + +#[derive(Debug, Clone, SimConnectObject)] +#[simconnect(period = "second", interval = 0.0)] +struct GpsData6 {} + #[derive(Debug, Clone, SimConnectObject)] #[simconnect(period = "second", condition = "none")] -struct GpsData5 { +struct GpsData7 { #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] pub lat: String, } diff --git a/simconnect-sdk-derive/tests/04-invalid-values.stderr b/simconnect-sdk-derive/tests/04-invalid-values.stderr index c8a38b9..61250f5 100644 --- a/simconnect-sdk-derive/tests/04-invalid-values.stderr +++ b/simconnect-sdk-derive/tests/04-invalid-values.stderr @@ -1,30 +1,42 @@ -error: expected string, found Int(LitInt { token: 123 }) +error: Expected Str, found Int(LitInt { token: 123 }) --> tests/04-invalid-values.rs:5:14 | 5 | #[simconnect(period = 123, condition = "none")] | ^^^^^^^^^^^^ -error: expected string, found Int(LitInt { token: 123 }) +error: Expected Str, found Int(LitInt { token: 123 }) --> tests/04-invalid-values.rs:9:33 | 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")] | ^^^^^^^^^^^^^^^ -error: Field type must be one of ['f64', 'bool'] - --> tests/04-invalid-values.rs:23:5 +error: Expected Int, found Str(LitStr { token: "X" }) + --> tests/04-invalid-values.rs:21:33 | -23 | / #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] -24 | | pub lat: String, +21 | #[simconnect(period = "second", interval = "X")] + | ^^^^^^^^^^^^^^ + +error: Expected Int, found Float(LitFloat { token: 0.0 }) + --> tests/04-invalid-values.rs:25:33 + | +25 | #[simconnect(period = "second", interval = 0.0)] + | ^^^^^^^^^^^^^^ + +error: Field type must be one of ["f64", "bool"] + --> tests/04-invalid-values.rs:31:5 + | +31 | / #[simconnect(name = "PLANE LATITUDE", unit = "degrees")] +32 | | pub lat: String, | |___________________^ diff --git a/simconnect-sdk/src/domain/condition.rs b/simconnect-sdk/src/domain/condition.rs index e45831c..d329437 100644 --- a/simconnect-sdk/src/domain/condition.rs +++ b/simconnect-sdk/src/domain/condition.rs @@ -5,7 +5,7 @@ use crate::bindings; pub enum Condition { /// The default, data will be sent strictly according to the defined period. None, - /// Data will only be sent to the client when one or more values have changed. + /// Data will only be sent to the client when one or more values have changed. All the variables in a data definition will be returned if just one of the values changes. Changed, } diff --git a/simconnect-sdk/src/domain/notification_group.rs b/simconnect-sdk/src/domain/notification_group.rs index 4b33eb2..8e56c95 100644 --- a/simconnect-sdk/src/domain/notification_group.rs +++ b/simconnect-sdk/src/domain/notification_group.rs @@ -1,5 +1,5 @@ /// SimConnect event notification group. -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] #[repr(u32)] pub enum NotificationGroup { Group0, diff --git a/simconnect-sdk/src/simconnect/objects.rs b/simconnect-sdk/src/simconnect/objects.rs index e4bbef5..b4ed7a8 100644 --- a/simconnect-sdk/src/simconnect/objects.rs +++ b/simconnect-sdk/src/simconnect/objects.rs @@ -40,7 +40,7 @@ impl SimConnect { /// Add a Microsoft Flight Simulator simulation variable name to a client defined object definition. /// /// # Remarks - /// The [`crate::SimConnectObject`] macro will automatically call this method for you. + /// The [`crate::SimConnectObject`] macro will automatically call this method for the struct. #[tracing::instrument(name = "SimConnect::add_to_data_definition")] pub fn add_to_data_definition( &self, @@ -71,11 +71,13 @@ impl SimConnect { /// Request when the SimConnect client is to receive data values for a specific object. /// - /// # Remarks - /// The [`crate::SimConnectObject`] macro will automatically call this method for you. + /// # Arguments + /// * `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. /// - /// It is possible to change the period of a request, by re-sending the [`crate::SimConnect::request_data_on_sim_object`] call with the same `request_id` parameters, but with a new `period`. - /// The one exception to this is the new period cannot be [`crate::Period::Once`], in this case a request with a new `request_id` should be sent. + /// # Remarks + /// The [`crate::SimConnectObject`] macro will automatically call this method for the struct. #[tracing::instrument(name = "SimConnect::request_data_on_sim_object")] pub fn request_data_on_sim_object( &self,