Add the derive proc macro
This commit is contained in:
59
Cargo.lock
generated
59
Cargo.lock
generated
@@ -107,6 +107,12 @@ dependencies = [
|
|||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dissimilar"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.8.0"
|
version = "1.8.0"
|
||||||
@@ -163,6 +169,12 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@@ -353,12 +365,40 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.145"
|
version = "1.0.145"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.145"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.85"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@@ -389,9 +429,12 @@ dependencies = [
|
|||||||
name = "simconnect-sdk-derive"
|
name = "simconnect-sdk-derive"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"simconnect-sdk",
|
||||||
"syn",
|
"syn",
|
||||||
|
"trybuild",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -541,6 +584,22 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trybuild"
|
||||||
|
version = "1.0.71"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ea496675d71016e9bc76aa42d87f16aefd95447cc5818e671e12b2d7e269075d"
|
||||||
|
dependencies = [
|
||||||
|
"dissimilar",
|
||||||
|
"glob",
|
||||||
|
"once_cell",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"termcolor",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@@ -6,4 +6,5 @@
|
|||||||
git clone git@github.com:mihai-dinculescu/simconnect-sdk.git
|
git clone git@github.com:mihai-dinculescu/simconnect-sdk.git
|
||||||
cd simconnect-sdk
|
cd simconnect-sdk
|
||||||
RUST_LOG=info cargo run --bin basic
|
RUST_LOG=info cargo run --bin basic
|
||||||
|
RUST_LOG=info cargo run --bin basic_no_macros
|
||||||
```
|
```
|
||||||
|
@@ -5,13 +5,18 @@ authors = ["Mihai Dinculescu <mihai.dinculescu@outlook.com>"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "SimConnect SDK Examples"
|
description = "SimConnect SDK Examples"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
publish = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "basic"
|
name = "basic"
|
||||||
path = "src/basic.rs"
|
path = "src/basic.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "basic_no_macros"
|
||||||
|
path = "src/basic_no_macros.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = [ "env-filter" ] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
|
||||||
simconnect-sdk = { path = "../simconnect-sdk" }
|
simconnect-sdk = { path = "../simconnect-sdk", features = ["derive"] }
|
||||||
|
@@ -1,66 +1,29 @@
|
|||||||
use simconnect_sdk::{
|
#![allow(dead_code)]
|
||||||
ConditionEnum, DataType, Notification, NotificationData, PeriodEnum, SimConnect,
|
|
||||||
SimConnectError,
|
use simconnect_sdk::{Notification, SimConnect, SimConnectObject};
|
||||||
};
|
use simconnect_sdk_examples::setup_logging;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use simconnect_sdk_examples::setup_logging;
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second")]
|
||||||
#[derive(Debug, Clone)]
|
struct GpsData {
|
||||||
pub struct GpsData {
|
#[simconnect(name = "PLANE LATITUDE", unit = "degrees")]
|
||||||
pub lat: f64,
|
lat: f64,
|
||||||
pub lon: f64,
|
#[simconnect(name = "PLANE LONGITUDE", unit = "degrees")]
|
||||||
pub alt: f64,
|
lon: f64,
|
||||||
pub gps_ground_magnetic_track: f64,
|
#[simconnect(name = "PLANE LONGITUDE", unit = "degrees")]
|
||||||
pub gps_ground_speed: f64,
|
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,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl simconnect_sdk::SimConnectObjectExt for GpsData {
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError> {
|
#[simconnect(period = "second", condition = "changed")]
|
||||||
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<Self, Self::Error> {
|
|
||||||
value.try_into::<GpsData>().ok_or(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct OnGround {
|
pub struct OnGround {
|
||||||
pub sim_on_ground: bool,
|
#[simconnect(name = "SIM ON GROUND", unit = "bool")]
|
||||||
}
|
sim_on_ground: bool,
|
||||||
|
|
||||||
impl simconnect_sdk::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, PeriodEnum::Second, ConditionEnum::None)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&NotificationData> for OnGround {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: &NotificationData) -> Result<Self, Self::Error> {
|
|
||||||
value.try_into::<OnGround>().ok_or(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
@@ -81,11 +44,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
Some(Notification::Data(data)) => {
|
Some(Notification::Data(data)) => {
|
||||||
if let Ok(gps_data) = GpsData::try_from(&data) {
|
if let Ok(gps_data) = GpsData::try_from(&data) {
|
||||||
info!("GPS Data: {:?}", gps_data);
|
info!("GPS Data: {gps_data:?}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Ok(on_ground) = OnGround::try_from(&data) {
|
if let Ok(on_ground) = OnGround::try_from(&data) {
|
||||||
info!("On Ground data: {:?}", on_ground);
|
info!("On Ground data: {on_ground:?}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
108
examples/src/basic_no_macros.rs
Normal file
108
examples/src/basic_no_macros.rs
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use simconnect_sdk::{
|
||||||
|
Condition, DataType, Notification, NotificationData, Period, SimConnect, SimConnectError,
|
||||||
|
SimConnectObjectExt,
|
||||||
|
};
|
||||||
|
use tracing::{error, info};
|
||||||
|
|
||||||
|
use simconnect_sdk_examples::setup_logging;
|
||||||
|
|
||||||
|
#[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 {
|
||||||
|
fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&NotificationData> for GpsData {
|
||||||
|
type Error = SimConnectError;
|
||||||
|
|
||||||
|
fn try_from(value: &NotificationData) -> Result<Self, Self::Error> {
|
||||||
|
value.try_transmute::<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<Self, Self::Error> {
|
||||||
|
value.try_transmute::<OnGround>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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::<GpsData>()?;
|
||||||
|
client.register_object::<OnGround>()?;
|
||||||
|
}
|
||||||
|
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(())
|
||||||
|
}
|
@@ -5,11 +5,22 @@ authors = ["Mihai Dinculescu <mihai.dinculescu@outlook.com>"]
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Macros of SimConnect SDK for Rust"
|
description = "Macros of SimConnect SDK for Rust"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
autotests = false
|
||||||
|
publish = false
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "tests"
|
||||||
|
path = "tests/run.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
syn = "1.0"
|
syn = "1.0"
|
||||||
|
once_cell = "1.7"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
simconnect-sdk = { path = "../simconnect-sdk" }
|
||||||
|
trybuild = { version = "1.0", features = ["diff"] }
|
||||||
|
@@ -1,7 +1,341 @@
|
|||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
use proc_macro::TokenStream;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[proc_macro_derive(SimConnectObject, attributes(field))]
|
use once_cell::sync::Lazy;
|
||||||
pub fn derive(_: TokenStream) -> TokenStream {
|
use proc_macro::TokenStream;
|
||||||
TokenStream::new()
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
|
|
||||||
|
struct FieldInfo {
|
||||||
|
required: bool,
|
||||||
|
accepted_values: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
static ALLOWED_CLASS_ATTRIBUTES: Lazy<HashMap<String, FieldInfo>> = Lazy::new(|| {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"period".to_string(),
|
||||||
|
FieldInfo {
|
||||||
|
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 {
|
||||||
|
required: false,
|
||||||
|
accepted_values: vec!["none".to_string(), "changed".to_string()],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map
|
||||||
|
});
|
||||||
|
|
||||||
|
static ALLOWED_FIELD_ATTRIBUTES: Lazy<HashMap<String, FieldInfo>> = Lazy::new(|| {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
|
||||||
|
map.insert(
|
||||||
|
"name".to_string(),
|
||||||
|
FieldInfo {
|
||||||
|
required: true,
|
||||||
|
accepted_values: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
map.insert(
|
||||||
|
"unit".to_string(),
|
||||||
|
FieldInfo {
|
||||||
|
required: true,
|
||||||
|
accepted_values: vec![],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
map
|
||||||
|
});
|
||||||
|
const SUPPORTED_FIELD_TYPES: [&str; 2] = ["f64", "bool"];
|
||||||
|
|
||||||
|
/// SimConnectObject derive macro.
|
||||||
|
///
|
||||||
|
/// # 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`.
|
||||||
|
///
|
||||||
|
/// # Field Arguments
|
||||||
|
/// * `name` - Required. The name of the field. One from <http://www.prepar3d.com/SDKv3/LearningCenter/utilities/variables/simulation_variables.html#Simulation%20Variables>.
|
||||||
|
/// * `unit` - Required. The unit of the field.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// use simconnect_sdk::SimConnectObject;
|
||||||
|
///
|
||||||
|
/// #[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,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[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 fields = if let syn::Data::Struct(syn::DataStruct {
|
||||||
|
fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
|
||||||
|
..
|
||||||
|
}) = ast.data
|
||||||
|
{
|
||||||
|
named
|
||||||
|
} else {
|
||||||
|
return mk_err(
|
||||||
|
ast,
|
||||||
|
"Unsupported field type. Only named fields are supported.",
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
};
|
||||||
|
|
||||||
|
let build_fields = fields.iter().map(parse_field);
|
||||||
|
let request_data = request_data(&ast);
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl simconnect_sdk::SimConnectObjectExt for #name {
|
||||||
|
fn register(client: &mut simconnect_sdk::SimConnect, id: u32) -> Result<(), simconnect_sdk::SimConnectError> {
|
||||||
|
#(#build_fields)*
|
||||||
|
|
||||||
|
#request_data
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<&simconnect_sdk::NotificationData> for #name {
|
||||||
|
type Error = simconnect_sdk::SimConnectError;
|
||||||
|
|
||||||
|
fn try_from(value: &simconnect_sdk::NotificationData) -> Result<Self, Self::Error> {
|
||||||
|
value.try_transmute::<#name>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_field(f: &syn::Field) -> proc_macro2::TokenStream {
|
||||||
|
let error_message = "expected attribute `#[simconnect(name = \"...\", unit = \"...\")]`";
|
||||||
|
|
||||||
|
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!(
|
||||||
|
"Field type must be one of ['{}']",
|
||||||
|
SUPPORTED_FIELD_TYPES.join("', '")
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => mk_err(f, error_message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_data(ast: &DeriveInput) -> proc_macro2::TokenStream {
|
||||||
|
let attr = get_attribute(&ast.attrs);
|
||||||
|
let error_message = "expected attribute `#[simconnect(period = \"...\", condition = \"...\")]`";
|
||||||
|
|
||||||
|
match attr {
|
||||||
|
Some(attr) => {
|
||||||
|
let properties =
|
||||||
|
extract_attribute_string_properties(attr, &ALLOWED_CLASS_ATTRIBUTES, error_message);
|
||||||
|
|
||||||
|
match properties {
|
||||||
|
Ok(properties) => {
|
||||||
|
let period = match properties.get("period") {
|
||||||
|
Some(p) if p == "once" => {
|
||||||
|
quote! {
|
||||||
|
simconnect_sdk::Period::Once
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(p) if p == "visual-frame" => {
|
||||||
|
quote! {
|
||||||
|
simconnect_sdk::Period::VisualFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(p) if p == "sim-frame" => {
|
||||||
|
quote! {
|
||||||
|
simconnect_sdk::Period::SimFrame
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
quote! {
|
||||||
|
simconnect_sdk::Period::Second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let condition = match properties.get("condition") {
|
||||||
|
Some(c) if c == "changed" => {
|
||||||
|
quote! {
|
||||||
|
simconnect_sdk::Condition::Changed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
quote! {
|
||||||
|
simconnect_sdk::Condition::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
client.request_data_on_sim_object(id, #period, #condition, 0)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<String, FieldInfo>,
|
||||||
|
error_message: &str,
|
||||||
|
) -> Result<HashMap<String, String>, 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(s) => {
|
||||||
|
let value = s.value();
|
||||||
|
|
||||||
|
if !property.accepted_values.is_empty()
|
||||||
|
&& !property.accepted_values.contains(&value)
|
||||||
|
{
|
||||||
|
// found an invalid value
|
||||||
|
return Err(mk_err(
|
||||||
|
nv,
|
||||||
|
&format!(
|
||||||
|
"`{ident_string}` must be one of ['{}']",
|
||||||
|
property.accepted_values.join("', '")
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
results.insert(ident_string, value);
|
||||||
|
}
|
||||||
|
lit => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv,
|
||||||
|
format!("expected string, found {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: quote::ToTokens>(t: T, message: &str) -> proc_macro2::TokenStream {
|
||||||
|
syn::Error::new_spanned(t, message).to_compile_error()
|
||||||
}
|
}
|
||||||
|
31
simconnect-sdk-derive/tests/01-parse.rs
Normal file
31
simconnect-sdk-derive/tests/01-parse.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
#![allow(unused_variables, dead_code)]
|
||||||
|
|
||||||
|
use simconnect_sdk_derive::SimConnectObject;
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second")]
|
||||||
|
struct GpsData1 {
|
||||||
|
#[simconnect(name = "PLANE LATITUDE", unit = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
#[simconnect(name = "PLANE LONGITUDE", unit = "degrees")]
|
||||||
|
pub lon: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData2 {
|
||||||
|
#[simconnect(name = "PLANE LATITUDE", unit = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
#[simconnect(name = "PLANE LONGITUDE", unit = "degrees")]
|
||||||
|
pub lon: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "visual-frame", condition = "changed")]
|
||||||
|
struct GpsData3 {
|
||||||
|
#[simconnect(name = "PLANE LATITUDE", unit = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
#[simconnect(name = "PLANE LONGITUDE", unit = "degrees")]
|
||||||
|
pub lon: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
39
simconnect-sdk-derive/tests/02-struct-attr-errors.rs
Normal file
39
simconnect-sdk-derive/tests/02-struct-attr-errors.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#![allow(unused_variables, dead_code)]
|
||||||
|
use simconnect_sdk_derive::SimConnectObject;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "visual-frame", condition = "changed")]
|
||||||
|
struct GpsData1(f64);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
struct GpsData2 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect]
|
||||||
|
struct GpsData3 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect()]
|
||||||
|
struct GpsData4 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", period = "second")]
|
||||||
|
struct GpsData5 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(condition = "none", condition = "none")]
|
||||||
|
struct GpsData6 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none", test = "test")]
|
||||||
|
struct GpsData7 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(periodX = "second", condition = "none")]
|
||||||
|
struct GpsData8 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", conditionX = "none")]
|
||||||
|
struct GpsData9 {}
|
||||||
|
|
||||||
|
fn main() {}
|
54
simconnect-sdk-derive/tests/02-struct-attr-errors.stderr
Normal file
54
simconnect-sdk-derive/tests/02-struct-attr-errors.stderr
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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);
|
||||||
|
| |_____________________^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:9:1
|
||||||
|
|
|
||||||
|
9 | struct GpsData2 {}
|
||||||
|
| ^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:12:3
|
||||||
|
|
|
||||||
|
12 | #[simconnect]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:16:3
|
||||||
|
|
|
||||||
|
16 | #[simconnect()]
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:20:3
|
||||||
|
|
|
||||||
|
20 | #[simconnect(period = "second", period = "second")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:24:3
|
||||||
|
|
|
||||||
|
24 | #[simconnect(condition = "none", condition = "none")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:28:3
|
||||||
|
|
|
||||||
|
28 | #[simconnect(period = "second", condition = "none", test = "test")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:32:3
|
||||||
|
|
|
||||||
|
32 | #[simconnect(periodX = "second", condition = "none")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(period = "...", condition = "...")]`
|
||||||
|
--> tests/02-struct-attr-errors.rs:36:3
|
||||||
|
|
|
||||||
|
36 | #[simconnect(period = "second", conditionX = "none")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
73
simconnect-sdk-derive/tests/03-field-attr-errors.rs
Normal file
73
simconnect-sdk-derive/tests/03-field-attr-errors.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#![allow(unused_variables, dead_code)]
|
||||||
|
use simconnect_sdk_derive::SimConnectObject;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData1 {
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData2 {
|
||||||
|
#[simconnect]
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData3 {
|
||||||
|
#[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 {
|
||||||
|
#[simconnect(unit = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData6 {
|
||||||
|
#[simconnect(name = "PLANE LATITUDE", name = "PLANE LATITUDE")]
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData7 {
|
||||||
|
#[simconnect(unit = "degrees", unit = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData8 {
|
||||||
|
#[simconnect(name = "PLANE LATITUDE", unit = "degrees", unit = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData9 {
|
||||||
|
#[simconnect(nameX = "PLANE LATITUDE", unit = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData10 {
|
||||||
|
#[simconnect(name = "PLANE LATITUDE", unitX = "degrees")]
|
||||||
|
pub lat: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
59
simconnect-sdk-derive/tests/03-field-attr-errors.stderr
Normal file
59
simconnect-sdk-derive/tests/03-field-attr-errors.stderr
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:7:5
|
||||||
|
|
|
||||||
|
7 | pub lat: f64,
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:13:7
|
||||||
|
|
|
||||||
|
13 | #[simconnect]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:20:7
|
||||||
|
|
|
||||||
|
20 | #[simconnect()]
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> 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")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:41:7
|
||||||
|
|
|
||||||
|
41 | #[simconnect(name = "PLANE LATITUDE", name = "PLANE LATITUDE")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:48:7
|
||||||
|
|
|
||||||
|
48 | #[simconnect(unit = "degrees", unit = "degrees")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:55:7
|
||||||
|
|
|
||||||
|
55 | #[simconnect(name = "PLANE LATITUDE", unit = "degrees", unit = "degrees")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:62:7
|
||||||
|
|
|
||||||
|
62 | #[simconnect(nameX = "PLANE LATITUDE", unit = "degrees")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: expected attribute `#[simconnect(name = "...", unit = "...")]`
|
||||||
|
--> tests/03-field-attr-errors.rs:69:7
|
||||||
|
|
|
||||||
|
69 | #[simconnect(name = "PLANE LATITUDE", unitX = "degrees")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
27
simconnect-sdk-derive/tests/04-invalid-values.rs
Normal file
27
simconnect-sdk-derive/tests/04-invalid-values.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#![allow(unused_variables, dead_code)]
|
||||||
|
use simconnect_sdk_derive::SimConnectObject;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = 123, condition = "none")]
|
||||||
|
struct GpsData1 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = 123)]
|
||||||
|
struct GpsData2 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "X")]
|
||||||
|
struct GpsData3 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "X")]
|
||||||
|
struct GpsData4 {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, SimConnectObject)]
|
||||||
|
#[simconnect(period = "second", condition = "none")]
|
||||||
|
struct GpsData5 {
|
||||||
|
#[simconnect(name = "PLANE LATITUDE", unit = "degrees")]
|
||||||
|
pub lat: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
30
simconnect-sdk-derive/tests/04-invalid-values.stderr
Normal file
30
simconnect-sdk-derive/tests/04-invalid-values.stderr
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
error: expected string, 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 })
|
||||||
|
--> 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']
|
||||||
|
--> tests/04-invalid-values.rs:13:14
|
||||||
|
|
|
||||||
|
13 | #[simconnect(period = "X")]
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
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
|
||||||
|
|
|
||||||
|
23 | / #[simconnect(name = "PLANE LATITUDE", unit = "degrees")]
|
||||||
|
24 | | pub lat: String,
|
||||||
|
| |___________________^
|
8
simconnect-sdk-derive/tests/run.rs
Normal file
8
simconnect-sdk-derive/tests/run.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#[test]
|
||||||
|
fn tests() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
t.pass("tests/01-parse.rs");
|
||||||
|
t.compile_fail("tests/02-struct-attr-errors.rs");
|
||||||
|
t.compile_fail("tests/03-field-attr-errors.rs");
|
||||||
|
t.compile_fail("tests/04-invalid-values.rs");
|
||||||
|
}
|
@@ -6,6 +6,10 @@ edition = "2021"
|
|||||||
description = "SimConnect SDK for Rust"
|
description = "SimConnect SDK for Rust"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
derive = ["simconnect-sdk-derive"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.60"
|
bindgen = "0.60"
|
||||||
|
|
||||||
@@ -13,4 +17,4 @@ bindgen = "0.60"
|
|||||||
num_enum = "0.5"
|
num_enum = "0.5"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
simconnect-sdk-derive = { path = "../simconnect-sdk-derive" }
|
simconnect-sdk-derive = { path = "../simconnect-sdk-derive", optional = true }
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/// Airport data.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AirportData {
|
pub struct AirportData {
|
||||||
pub icao: String,
|
pub icao: String,
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
|
/// Specifies under which conditions the data is to be sent by the server and received by the client.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ConditionEnum {
|
pub enum Condition {
|
||||||
|
/// The default, data will be sent strictly according to the defined period.
|
||||||
None,
|
None,
|
||||||
|
/// Data will only be sent to the client when one or more values have changed.
|
||||||
Changed,
|
Changed,
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
|
/// SimConnect object property data type.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum DataType {
|
pub enum DataType {
|
||||||
F64,
|
Float64,
|
||||||
Bool,
|
Bool,
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
use std::os::raw::c_char;
|
use std::os::raw::c_char;
|
||||||
|
|
||||||
|
/// SimConnect event.
|
||||||
|
/// As defined at <https://www.prepar3d.com/SDKv3/LearningCenter/utilities/variables/event_ids.html>
|
||||||
#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)]
|
#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
@@ -1,5 +0,0 @@
|
|||||||
#[derive(Copy, Clone)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum Group {
|
|
||||||
Group0,
|
|
||||||
}
|
|
@@ -2,14 +2,14 @@ mod airport_data;
|
|||||||
mod condition;
|
mod condition;
|
||||||
mod data_type;
|
mod data_type;
|
||||||
mod event;
|
mod event;
|
||||||
mod group;
|
|
||||||
mod notification;
|
mod notification;
|
||||||
|
mod notification_group;
|
||||||
mod period;
|
mod period;
|
||||||
|
|
||||||
pub use airport_data::*;
|
pub use airport_data::*;
|
||||||
pub use condition::*;
|
pub use condition::*;
|
||||||
pub use data_type::*;
|
pub use data_type::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
pub use group::*;
|
|
||||||
pub use notification::*;
|
pub use notification::*;
|
||||||
|
pub use notification_group::*;
|
||||||
pub use period::*;
|
pub use period::*;
|
||||||
|
@@ -1,28 +1,39 @@
|
|||||||
use crate::{AirportData, Event, SimConnectObjectExt};
|
use crate::{AirportData, Event, SimConnectError, SimConnectObjectExt};
|
||||||
|
|
||||||
|
/// Notification received from SimConnect.
|
||||||
pub enum Notification {
|
pub enum Notification {
|
||||||
|
/// SimConnect open
|
||||||
Open,
|
Open,
|
||||||
|
/// SimConnect event
|
||||||
Event(Event),
|
Event(Event),
|
||||||
|
/// SimConnect object
|
||||||
Data(NotificationData),
|
Data(NotificationData),
|
||||||
|
/// SimConnect airport list
|
||||||
AirportList(Vec<AirportData>),
|
AirportList(Vec<AirportData>),
|
||||||
|
/// SimConnect quit
|
||||||
Quit,
|
Quit,
|
||||||
|
/// SimConnect exception
|
||||||
Exception(u32),
|
Exception(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Notification data object.
|
||||||
pub struct NotificationData {
|
pub struct NotificationData {
|
||||||
pub(crate) type_id: String,
|
pub(crate) type_id: String,
|
||||||
pub(crate) data_addr: *const u32,
|
pub(crate) data_addr: *const u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NotificationData {
|
impl NotificationData {
|
||||||
pub fn try_into<T: SimConnectObjectExt>(&self) -> Option<T> {
|
pub fn try_transmute<T: SimConnectObjectExt>(&self) -> Result<T, SimConnectError> {
|
||||||
let type_id: String = std::any::type_name::<T>().into();
|
let type_id: String = std::any::type_name::<T>().into();
|
||||||
|
|
||||||
if self.type_id == type_id {
|
if self.type_id == type_id {
|
||||||
let data: &T = unsafe { std::mem::transmute_copy(&self.data_addr) };
|
let data: &T = unsafe { std::mem::transmute_copy(&self.data_addr) };
|
||||||
Some(data.clone())
|
Ok(data.clone())
|
||||||
} else {
|
} else {
|
||||||
None
|
Err(SimConnectError::ObjectMismatch {
|
||||||
|
actual: self.type_id.clone(),
|
||||||
|
expected: type_id,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
simconnect-sdk/src/domain/notification_group.rs
Normal file
6
simconnect-sdk/src/domain/notification_group.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// SimConnect event notification group.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum NotificationGroup {
|
||||||
|
Group0,
|
||||||
|
}
|
@@ -1,5 +1,16 @@
|
|||||||
|
/// Specifies how often the data is to be sent by the server and received by the client.
|
||||||
|
/// 0 - every interval.
|
||||||
|
/// 1 - every other interval.
|
||||||
|
/// 2 - every third interval.
|
||||||
|
/// etc.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum PeriodEnum {
|
pub enum Period {
|
||||||
VisualFrame { interval: u32 },
|
/// Specifies that the data should be sent once only. Note that this is not an efficient way of receiving data frequently, use one of the other periods if there is a regular frequency to the data request.
|
||||||
|
Once,
|
||||||
|
/// Specifies that the data should be sent every visual (rendered) frame.
|
||||||
|
VisualFrame,
|
||||||
|
/// Specifies that the data should be sent every simulated frame, whether that frame is rendered or not.
|
||||||
|
SimFrame,
|
||||||
|
/// Specifies that the data should be sent once every second.
|
||||||
Second,
|
Second,
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +1,24 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// SimConnect SDK error.
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum SimConnectError {
|
pub enum SimConnectError {
|
||||||
|
/// SimConnect error.
|
||||||
#[error("SimConnect error: {0}")]
|
#[error("SimConnect error: {0}")]
|
||||||
SimConnectError(i32),
|
SimConnectError(i32),
|
||||||
#[error("SimConnect unrecognized: {0}")]
|
#[error("SimConnect unrecognized: {0}")]
|
||||||
|
/// SimConnect unrecognized error. Occurs when an unimplemented event is received by the SDK.
|
||||||
SimConnectUnrecognizedEvent(u32),
|
SimConnectUnrecognizedEvent(u32),
|
||||||
|
/// Object already registered with the client instance.
|
||||||
#[error("Object `{0}` has already been registered")]
|
#[error("Object `{0}` has already been registered")]
|
||||||
ObjectAlreadyRegistered(String),
|
ObjectAlreadyRegistered(String),
|
||||||
|
/// Object mismatch.
|
||||||
|
#[error("Tried to convert object of type {actual} to {expected}")]
|
||||||
|
ObjectMismatch { actual: String, expected: String },
|
||||||
|
/// Conversation error.
|
||||||
#[error("Conversion error: {0}")]
|
#[error("Conversion error: {0}")]
|
||||||
ConversionError(#[from] std::num::TryFromIntError),
|
ConversionError(#[from] std::num::TryFromIntError),
|
||||||
|
/// Unexpected error.
|
||||||
#[error("Unexpected error: {0}")]
|
#[error("Unexpected error: {0}")]
|
||||||
UnexpectedError(String),
|
UnexpectedError(String),
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,8 @@
|
|||||||
|
//! # SimConnect SDK
|
||||||
|
//! SimConnect SDK in Rust.
|
||||||
|
//!
|
||||||
|
//! See [examples](https://github.com/mihai-dinculescu/simconnect-sdk/tree/main/examples/src).
|
||||||
|
|
||||||
mod bindings;
|
mod bindings;
|
||||||
mod domain;
|
mod domain;
|
||||||
mod errors;
|
mod errors;
|
||||||
@@ -13,5 +18,7 @@ pub use errors::SimConnectError;
|
|||||||
pub use simconnect::SimConnect;
|
pub use simconnect::SimConnect;
|
||||||
pub use simconnect_object_ext::SimConnectObjectExt;
|
pub use simconnect_object_ext::SimConnectObjectExt;
|
||||||
|
|
||||||
|
#[cfg(feature = "simconnect-sdk-derive")]
|
||||||
extern crate simconnect_sdk_derive;
|
extern crate simconnect_sdk_derive;
|
||||||
|
#[cfg(feature = "simconnect-sdk-derive")]
|
||||||
pub use simconnect_sdk_derive::*;
|
pub use simconnect_sdk_derive::*;
|
||||||
|
@@ -2,10 +2,11 @@ use std::ffi::c_void;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, AirportData,
|
as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, AirportData,
|
||||||
ConditionEnum, DataType, Event, Group, Notification, NotificationData, PeriodEnum,
|
Condition, DataType, Event, Notification, NotificationData, NotificationGroup, Period,
|
||||||
SimConnectError, SimConnectObjectExt,
|
SimConnectError, SimConnectObjectExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// SimConnect SDK Client.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SimConnect {
|
pub struct SimConnect {
|
||||||
pub handle: std::ptr::NonNull<c_void>,
|
pub handle: std::ptr::NonNull<c_void>,
|
||||||
@@ -71,7 +72,7 @@ impl SimConnect {
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let group = Group::Group0;
|
let group = NotificationGroup::Group0;
|
||||||
|
|
||||||
success!(unsafe {
|
success!(unsafe {
|
||||||
bindings::SimConnect_AddClientEventToNotificationGroup(
|
bindings::SimConnect_AddClientEventToNotificationGroup(
|
||||||
@@ -98,7 +99,7 @@ impl SimConnect {
|
|||||||
data_type: DataType,
|
data_type: DataType,
|
||||||
) -> Result<(), SimConnectError> {
|
) -> Result<(), SimConnectError> {
|
||||||
let c_type = match data_type {
|
let c_type = match data_type {
|
||||||
DataType::F64 => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64,
|
DataType::Float64 => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64,
|
||||||
DataType::Bool => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_INT32,
|
DataType::Bool => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_INT32,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,21 +122,22 @@ impl SimConnect {
|
|||||||
pub fn request_data_on_sim_object(
|
pub fn request_data_on_sim_object(
|
||||||
&self,
|
&self,
|
||||||
request_id: u32,
|
request_id: u32,
|
||||||
period: PeriodEnum,
|
period: Period,
|
||||||
condition: ConditionEnum,
|
condition: Condition,
|
||||||
|
interval: u32,
|
||||||
) -> Result<(), SimConnectError> {
|
) -> Result<(), SimConnectError> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let (simconnect_period, simconnect_interval) = match period {
|
let simconnect_period = match period {
|
||||||
PeriodEnum::VisualFrame { interval } => (
|
Period::Once => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_ONCE,
|
||||||
bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME,
|
Period::VisualFrame => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME,
|
||||||
interval,
|
Period::SimFrame => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SIM_FRAME,
|
||||||
),
|
|
||||||
PeriodEnum::Second => (bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND, 0),
|
Period::Second => bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND,
|
||||||
};
|
};
|
||||||
|
|
||||||
let simconnect_flags: u32 = match condition {
|
let simconnect_flags: u32 = match condition {
|
||||||
ConditionEnum::None => 0,
|
Condition::None => 0,
|
||||||
ConditionEnum::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED,
|
Condition::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED,
|
||||||
};
|
};
|
||||||
|
|
||||||
success!(bindings::SimConnect_RequestDataOnSimObject(
|
success!(bindings::SimConnect_RequestDataOnSimObject(
|
||||||
@@ -146,7 +148,7 @@ impl SimConnect {
|
|||||||
simconnect_period,
|
simconnect_period,
|
||||||
simconnect_flags,
|
simconnect_flags,
|
||||||
0,
|
0,
|
||||||
simconnect_interval,
|
interval,
|
||||||
0,
|
0,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
use crate::{NotificationData, SimConnect, SimConnectError};
|
use crate::{NotificationData, SimConnect, SimConnectError};
|
||||||
|
|
||||||
|
/// Trait to be implemented by objects that can be registered with SimConnect.
|
||||||
pub trait SimConnectObjectExt: Clone + for<'a> TryFrom<&'a NotificationData> {
|
pub trait SimConnectObjectExt: Clone + for<'a> TryFrom<&'a NotificationData> {
|
||||||
fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError>;
|
fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError>;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user