Proc macro preparation
This commit is contained in:
7
simconnect-sdk/src/bindings.rs
Normal file
7
simconnect-sdk/src/bindings.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(dead_code)]
|
||||
#![allow(clippy::upper_case_acronyms)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
7
simconnect-sdk/src/domain/airport_data.rs
Normal file
7
simconnect-sdk/src/domain/airport_data.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AirportData {
|
||||
pub icao: String,
|
||||
pub lat: f64,
|
||||
pub lon: f64,
|
||||
pub alt: f64,
|
||||
}
|
5
simconnect-sdk/src/domain/condition.rs
Normal file
5
simconnect-sdk/src/domain/condition.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConditionEnum {
|
||||
None,
|
||||
Changed,
|
||||
}
|
5
simconnect-sdk/src/domain/data_type.rs
Normal file
5
simconnect-sdk/src/domain/data_type.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DataType {
|
||||
F64,
|
||||
Bool,
|
||||
}
|
19
simconnect-sdk/src/domain/event.rs
Normal file
19
simconnect-sdk/src/domain/event.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use std::os::raw::c_char;
|
||||
|
||||
#[derive(Debug, Copy, Clone, num_enum::TryFromPrimitive)]
|
||||
#[repr(u32)]
|
||||
pub enum Event {
|
||||
Brakes,
|
||||
BrakesLeft,
|
||||
AxisLeftBrakeSet,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
pub(crate) fn into_c_char(self) -> *const c_char {
|
||||
match self {
|
||||
Event::Brakes => "BRAKES\0".as_ptr() as *const c_char,
|
||||
Event::BrakesLeft => "BRAKES_LEFT\0".as_ptr() as *const c_char,
|
||||
Event::AxisLeftBrakeSet => "AXIS_LEFT_BRAKE_SET\0".as_ptr() as *const c_char,
|
||||
}
|
||||
}
|
||||
}
|
5
simconnect-sdk/src/domain/group.rs
Normal file
5
simconnect-sdk/src/domain/group.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(u32)]
|
||||
pub enum Group {
|
||||
Group0,
|
||||
}
|
15
simconnect-sdk/src/domain/mod.rs
Normal file
15
simconnect-sdk/src/domain/mod.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
mod airport_data;
|
||||
mod condition;
|
||||
mod data_type;
|
||||
mod event;
|
||||
mod group;
|
||||
mod notification;
|
||||
mod period;
|
||||
|
||||
pub use airport_data::*;
|
||||
pub use condition::*;
|
||||
pub use data_type::*;
|
||||
pub use event::*;
|
||||
pub use group::*;
|
||||
pub use notification::*;
|
||||
pub use period::*;
|
28
simconnect-sdk/src/domain/notification.rs
Normal file
28
simconnect-sdk/src/domain/notification.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use crate::{AirportData, Event, SimConnectObjectExt};
|
||||
|
||||
pub enum Notification {
|
||||
Open,
|
||||
Event(Event),
|
||||
Data(NotificationData),
|
||||
AirportList(Vec<AirportData>),
|
||||
Quit,
|
||||
Exception(u32),
|
||||
}
|
||||
|
||||
pub struct NotificationData {
|
||||
pub(crate) type_id: String,
|
||||
pub(crate) data_addr: *const u32,
|
||||
}
|
||||
|
||||
impl NotificationData {
|
||||
pub fn try_into<T: SimConnectObjectExt>(&self) -> Option<T> {
|
||||
let type_id: String = std::any::type_name::<T>().into();
|
||||
|
||||
if self.type_id == type_id {
|
||||
let data: &T = unsafe { std::mem::transmute_copy(&self.data_addr) };
|
||||
Some(data.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
5
simconnect-sdk/src/domain/period.rs
Normal file
5
simconnect-sdk/src/domain/period.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PeriodEnum {
|
||||
VisualFrame { interval: u32 },
|
||||
Second,
|
||||
}
|
15
simconnect-sdk/src/errors.rs
Normal file
15
simconnect-sdk/src/errors.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SimConnectError {
|
||||
#[error("SimConnect error: {0}")]
|
||||
SimConnectError(i32),
|
||||
#[error("SimConnect unrecognized: {0}")]
|
||||
SimConnectUnrecognizedEvent(u32),
|
||||
#[error("Object `{0}` has already been registered")]
|
||||
ObjectAlreadyRegistered(String),
|
||||
#[error("Conversion error: {0}")]
|
||||
ConversionError(#[from] std::num::TryFromIntError),
|
||||
#[error("Unexpected error: {0}")]
|
||||
UnexpectedError(String),
|
||||
}
|
14
simconnect-sdk/src/helpers.rs
Normal file
14
simconnect-sdk/src/helpers.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use std::ffi::CString;
|
||||
|
||||
pub fn fixed_c_str_to_string(data: &[i8]) -> String {
|
||||
let u8slice = unsafe { &*(data as *const _ as *const [u8]) };
|
||||
|
||||
let mut value = u8slice.to_vec();
|
||||
|
||||
let pos = value.iter().position(|c| *c == 0).unwrap_or(value.len());
|
||||
|
||||
value.truncate(pos);
|
||||
let icao = unsafe { CString::from_vec_unchecked(value) };
|
||||
|
||||
icao.to_str().unwrap().to_string()
|
||||
}
|
17
simconnect-sdk/src/lib.rs
Normal file
17
simconnect-sdk/src/lib.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
mod bindings;
|
||||
mod domain;
|
||||
mod errors;
|
||||
mod helpers;
|
||||
mod macros;
|
||||
mod simconnect;
|
||||
mod simconnect_object_ext;
|
||||
|
||||
pub(crate) use macros::{as_c_string, ok_if_fail, success};
|
||||
|
||||
pub use domain::*;
|
||||
pub use errors::SimConnectError;
|
||||
pub use simconnect::SimConnect;
|
||||
pub use simconnect_object_ext::SimConnectObjectExt;
|
||||
|
||||
extern crate simconnect_sdk_derive;
|
||||
pub use simconnect_sdk_derive::*;
|
29
simconnect-sdk/src/macros.rs
Normal file
29
simconnect-sdk/src/macros.rs
Normal file
@@ -0,0 +1,29 @@
|
||||
macro_rules! success {
|
||||
($hr:expr) => {{
|
||||
let hr = $hr;
|
||||
if hr != 0 {
|
||||
return Err(SimConnectError::SimConnectError(hr));
|
||||
}
|
||||
}};
|
||||
}
|
||||
pub(crate) use success;
|
||||
|
||||
macro_rules! ok_if_fail {
|
||||
($hr:expr, $ret:expr) => {{
|
||||
let hr = $hr;
|
||||
let ret = $ret;
|
||||
if hr != 0 {
|
||||
return Ok(ret);
|
||||
}
|
||||
}};
|
||||
}
|
||||
pub(crate) use ok_if_fail;
|
||||
|
||||
macro_rules! as_c_string {
|
||||
($target:expr) => {
|
||||
std::ffi::CString::new($target)
|
||||
.map_err(|_| SimConnectError::UnexpectedError("failed to create CString".to_string()))?
|
||||
.as_ptr()
|
||||
};
|
||||
}
|
||||
pub(crate) use as_c_string;
|
267
simconnect-sdk/src/simconnect.rs
Normal file
267
simconnect-sdk/src/simconnect.rs
Normal file
@@ -0,0 +1,267 @@
|
||||
use std::ffi::c_void;
|
||||
|
||||
use crate::{
|
||||
as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, AirportData,
|
||||
ConditionEnum, DataType, Event, Group, Notification, NotificationData, PeriodEnum,
|
||||
SimConnectError, SimConnectObjectExt,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimConnect {
|
||||
pub handle: std::ptr::NonNull<c_void>,
|
||||
pub registered_objects: Vec<String>,
|
||||
}
|
||||
|
||||
impl SimConnect {
|
||||
#[tracing::instrument(name = "SimConnect::new")]
|
||||
pub fn new(name: &str) -> Result<Self, SimConnectError> {
|
||||
let mut handle = std::ptr::null_mut();
|
||||
|
||||
success!(unsafe {
|
||||
bindings::SimConnect_Open(
|
||||
&mut handle,
|
||||
as_c_string!(name),
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
});
|
||||
|
||||
Ok(Self {
|
||||
handle: std::ptr::NonNull::new(handle).ok_or_else(|| {
|
||||
SimConnectError::UnexpectedError(
|
||||
"SimConnect_Open returned null pointer on success".to_string(),
|
||||
)
|
||||
})?,
|
||||
registered_objects: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn register_object<T: SimConnectObjectExt>(&mut self) -> Result<u32, SimConnectError> {
|
||||
let type_id: String = std::any::type_name::<T>().into();
|
||||
|
||||
if self.registered_objects.contains(&type_id) {
|
||||
return Err(SimConnectError::ObjectAlreadyRegistered(type_id));
|
||||
}
|
||||
|
||||
self.registered_objects.push(type_id.clone());
|
||||
|
||||
let id = self
|
||||
.registered_objects
|
||||
.iter()
|
||||
.position(|p| p == &type_id)
|
||||
.ok_or_else(|| {
|
||||
SimConnectError::UnexpectedError("failed to find registered event".to_string())
|
||||
})?;
|
||||
let id = u32::try_from(id)?;
|
||||
|
||||
T::register(self, id)?;
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "SimConnect::register_event")]
|
||||
pub fn register_event(&self, event: Event) -> Result<(), SimConnectError> {
|
||||
success!(unsafe {
|
||||
bindings::SimConnect_MapClientEventToSimEvent(
|
||||
self.handle.as_ptr(),
|
||||
event as u32,
|
||||
event.into_c_char(),
|
||||
)
|
||||
});
|
||||
|
||||
let group = Group::Group0;
|
||||
|
||||
success!(unsafe {
|
||||
bindings::SimConnect_AddClientEventToNotificationGroup(
|
||||
self.handle.as_ptr(),
|
||||
group as u32,
|
||||
event as u32,
|
||||
0,
|
||||
)
|
||||
});
|
||||
|
||||
success!(unsafe {
|
||||
bindings::SimConnect_SetNotificationGroupPriority(self.handle.as_ptr(), group as u32, 1)
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "SimConnect::add_to_data_definition")]
|
||||
pub fn add_to_data_definition(
|
||||
&self,
|
||||
request_id: u32,
|
||||
datum_name: &str,
|
||||
units_name: &str,
|
||||
data_type: DataType,
|
||||
) -> Result<(), SimConnectError> {
|
||||
let c_type = match data_type {
|
||||
DataType::F64 => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_FLOAT64,
|
||||
DataType::Bool => bindings::SIMCONNECT_DATATYPE_SIMCONNECT_DATATYPE_INT32,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
success!(bindings::SimConnect_AddToDataDefinition(
|
||||
self.handle.as_ptr(),
|
||||
request_id,
|
||||
as_c_string!(datum_name),
|
||||
as_c_string!(units_name),
|
||||
c_type,
|
||||
0.0,
|
||||
u32::MAX,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "SimConnect::request_data_on_sim_object")]
|
||||
pub fn request_data_on_sim_object(
|
||||
&self,
|
||||
request_id: u32,
|
||||
period: PeriodEnum,
|
||||
condition: ConditionEnum,
|
||||
) -> Result<(), SimConnectError> {
|
||||
unsafe {
|
||||
let (simconnect_period, simconnect_interval) = match period {
|
||||
PeriodEnum::VisualFrame { interval } => (
|
||||
bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_VISUAL_FRAME,
|
||||
interval,
|
||||
),
|
||||
PeriodEnum::Second => (bindings::SIMCONNECT_PERIOD_SIMCONNECT_PERIOD_SECOND, 0),
|
||||
};
|
||||
|
||||
let simconnect_flags: u32 = match condition {
|
||||
ConditionEnum::None => 0,
|
||||
ConditionEnum::Changed => bindings::SIMCONNECT_DATA_REQUEST_FLAG_CHANGED,
|
||||
};
|
||||
|
||||
success!(bindings::SimConnect_RequestDataOnSimObject(
|
||||
self.handle.as_ptr(),
|
||||
request_id,
|
||||
request_id,
|
||||
request_id,
|
||||
simconnect_period,
|
||||
simconnect_flags,
|
||||
0,
|
||||
simconnect_interval,
|
||||
0,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "SimConnect::subscribe_to_airport_list")]
|
||||
pub fn subscribe_to_airport_list(&self, request_id: u32) -> Result<(), SimConnectError> {
|
||||
unsafe {
|
||||
success!(bindings::SimConnect_SubscribeToFacilities(
|
||||
self.handle.as_ptr(),
|
||||
bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT,
|
||||
request_id,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "SimConnect::request_airport_list")]
|
||||
pub fn request_airport_list(&self, request_id: u32) -> Result<(), SimConnectError> {
|
||||
unsafe {
|
||||
success!(bindings::SimConnect_RequestFacilitiesList(
|
||||
self.handle.as_ptr(),
|
||||
bindings::SIMCONNECT_FACILITY_LIST_TYPE_SIMCONNECT_FACILITY_LIST_TYPE_AIRPORT,
|
||||
request_id,
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_next_dispatch(&self) -> Result<Option<Notification>, SimConnectError> {
|
||||
let mut data_buf: *mut bindings::SIMCONNECT_RECV = std::ptr::null_mut();
|
||||
let mut size_buf: bindings::DWORD = 32;
|
||||
let size_buf_pointer: *mut bindings::DWORD = &mut size_buf;
|
||||
|
||||
unsafe {
|
||||
ok_if_fail!(
|
||||
bindings::SimConnect_GetNextDispatch(
|
||||
self.handle.as_ptr(),
|
||||
&mut data_buf,
|
||||
size_buf_pointer
|
||||
),
|
||||
None
|
||||
);
|
||||
};
|
||||
|
||||
let result = match unsafe { (*data_buf).dwID as i32 } {
|
||||
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_OPEN => Some(Notification::Open),
|
||||
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_QUIT => Some(Notification::Quit),
|
||||
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT => {
|
||||
let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) };
|
||||
let event = Event::try_from(event.uEventID)
|
||||
.map_err(|_| SimConnectError::SimConnectUnrecognizedEvent(event.uEventID))?;
|
||||
Some(Notification::Event(event))
|
||||
}
|
||||
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA => {
|
||||
let event: &bindings::SIMCONNECT_RECV_SIMOBJECT_DATA = unsafe {
|
||||
std::mem::transmute_copy(
|
||||
&(data_buf as *const bindings::SIMCONNECT_RECV_SIMOBJECT_DATA),
|
||||
)
|
||||
};
|
||||
|
||||
let object_type = self.registered_objects.get(event.dwDefineID as usize);
|
||||
|
||||
match object_type {
|
||||
Some(object_type) => {
|
||||
let data = NotificationData {
|
||||
type_id: object_type.clone(),
|
||||
data_addr: std::ptr::addr_of!(event.dwData),
|
||||
};
|
||||
|
||||
Some(Notification::Data(data))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_AIRPORT_LIST => {
|
||||
let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST = unsafe {
|
||||
std::mem::transmute_copy(
|
||||
&(data_buf as *const bindings::SIMCONNECT_RECV_AIRPORT_LIST),
|
||||
)
|
||||
};
|
||||
|
||||
let data = event
|
||||
.rgData
|
||||
.iter()
|
||||
.map(|data| AirportData {
|
||||
icao: fixed_c_str_to_string(&data.Icao),
|
||||
lat: data.Latitude,
|
||||
lon: data.Longitude,
|
||||
alt: data.Altitude,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Some(Notification::AirportList(data))
|
||||
}
|
||||
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION => {
|
||||
let event = unsafe { *(data_buf as *const bindings::SIMCONNECT_RECV_EXCEPTION) };
|
||||
Some(Notification::Exception(event.dwException))
|
||||
}
|
||||
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => None,
|
||||
_ => panic!("Got unrecognized notification: {}", unsafe {
|
||||
(*data_buf).dwID as i32
|
||||
}),
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SimConnect {
|
||||
fn drop(&mut self) {
|
||||
let _ = unsafe { bindings::SimConnect_Close(self.handle.as_ptr()) };
|
||||
}
|
||||
}
|
5
simconnect-sdk/src/simconnect_object_ext.rs
Normal file
5
simconnect-sdk/src/simconnect_object_ext.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use crate::{NotificationData, SimConnect, SimConnectError};
|
||||
|
||||
pub trait SimConnectObjectExt: Clone + for<'a> TryFrom<&'a NotificationData> {
|
||||
fn register(client: &mut SimConnect, id: u32) -> Result<(), SimConnectError>;
|
||||
}
|
Reference in New Issue
Block a user