Rework events and add proper support for client events

Added

- Client events are now implemented through `SimConnect::subscribe_to_client_event`, `SimConnect::unsubscribe_from_client_event` and `SimConnect::unsubscribe_from_all_client_events`.
- `subscribe_to_client_events.rs` example has been added.
- `SimConnectError::EventAlreadySubscribedTo` and `SimConnectError::EventNotSubscribedTo` error variants have been added.

Changed

- A second call to `SimConnect::subscribe_to_system_event` for the same event will now return an error of type `SimConnectError::EventAlreadySubscribedTo` instead of `SimConnectError::SimConnectException`.
- The call to `SimConnect::unsubscribe_from_system_event` is now a NOOP when the system event is not subscribed to.
- `SimConnectError::UnimplementedMessageType` has been renamed to `SimConnectError::UnimplementedNotification`.

Removed

- `SimConnect::register_event` has been replaced by the new client event functions.
- `NotificationGroup` has been removed in favor of an internally managed notification group.
This commit is contained in:
Mihai Dinculescu
2023-05-01 19:04:55 +01:00
parent 85e5341599
commit d89fa524c2
16 changed files with 510 additions and 141 deletions

View File

@@ -6,6 +6,23 @@ file. This change log follows the conventions of
## [Unreleased] ## [Unreleased]
### Added
- Client events are now implemented through `SimConnect::subscribe_to_client_event`, `SimConnect::unsubscribe_from_client_event` and `SimConnect::unsubscribe_from_all_client_events`.
- `subscribe_to_client_events.rs` example has been added.
- `SimConnectError::EventAlreadySubscribedTo` and `SimConnectError::EventNotSubscribedTo` error variants have been added.
### Changed
- A second call to `SimConnect::subscribe_to_system_event` for the same event will now return an error of type `SimConnectError::EventAlreadySubscribedTo` instead of `SimConnectError::SimConnectException`.
- The call to `SimConnect::unsubscribe_from_system_event` is now a NOOP when the system event is not subscribed to.
- `SimConnectError::UnimplementedMessageType` has been renamed to `SimConnectError::UnimplementedNotification`.
### Removed
- `SimConnect::register_event` has been replaced by the new client event functions.
- `NotificationGroup` has been removed in favor of an internally managed notification group.
## [v0.2.2] - 2023-02-22 ## [v0.2.2] - 2023-02-22
### Changed ### Changed

View File

@@ -3,34 +3,34 @@
## General ## General
| Feature | Status | Comment | | Feature | Status | Comment |
| --------------------------------------- | ------- | ------- | | --------------------------------------- | ------- | -------------------------------------------- |
| DispatchProc | | | | DispatchProc | | |
| SimConnect_Open | ✓ | | | SimConnect_Open | ✓ | |
| SimConnect_Close | ✓ | | | SimConnect_Close | ✓ | |
| SimConnect_CallDispatch | | | | SimConnect_CallDispatch | | |
| SimConnect_GetNextDispatch | ✓ | | | SimConnect_GetNextDispatch | ✓ | |
| SimConnect_RequestSystemState | | | | SimConnect_RequestSystemState | | |
| SimConnect_MapClientEventToSimEvent | - | WIP | | SimConnect_MapClientEventToSimEvent | ✓ | Encapsulated by `subscribe_to_client_event`. |
| SimConnect_SubscribeToSystemEvent | ✓ | | | SimConnect_SubscribeToSystemEvent | ✓ | |
| SimConnect_SetSystemEventState | | | | SimConnect_SetSystemEventState | | |
| SimConnect_UnsubscribeFromSystemEvent | ✓ | | | SimConnect_UnsubscribeFromSystemEvent | ✓ | |
| SimConnect_SetNotificationGroupPriority | - | WIP | | SimConnect_SetNotificationGroupPriority | ✓ | Encapsulated by `subscribe_to_client_event`. |
## Events And Data ## Events And Data
| Feature | Status | Comment | | Feature | Status | Comment |
| -------------------------------------------- | ------- | ----------------------------------- | | -------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------- |
| SimConnect_RequestDataOnSimObject | ✓ | Only for SIMCONNECT_OBJECT_ID_USER | | SimConnect_RequestDataOnSimObject | ✓ | Only for `SIMCONNECT_OBJECT_ID_USER`. |
| SimConnect_RequestDataOnSimObjectType | | | | SimConnect_RequestDataOnSimObjectType | | |
| SimConnect_AddClientEventToNotificationGroup | - | WIP | | SimConnect_AddClientEventToNotificationGroup | ✓ | Encapsulated by `subscribe_to_client_event`. |
| SimConnect_RemoveClientEvent | | | | SimConnect_RemoveClientEvent | ✓ | |
| SimConnect_TransmitClientEvent | | | | SimConnect_TransmitClientEvent | | |
| SimConnect_TransmitClientEvent_EX1 | | | | SimConnect_TransmitClientEvent_EX1 | | |
| SimConnect_MapClientDataNameToID | | | | SimConnect_MapClientDataNameToID | | |
| SimConnect_RequestClientData | | | | SimConnect_RequestClientData | | |
| SimConnect_CreateClientData | | | | SimConnect_CreateClientData | | |
| SimConnect_AddToClientDataDefinition | | | | SimConnect_AddToClientDataDefinition | | |
| SimConnect_AddToDataDefinition | ✓ | Supports `f64`, `bool` and `String` | | SimConnect_AddToDataDefinition | ✓ | Encapsulated by `register_object` and the `simconnect` macro. Supports `f64`, `bool` and `String`. |
| SimConnect_SetClientData | | | | SimConnect_SetClientData | | |
| SimConnect_SetDataOnSimObject | | | | SimConnect_SetDataOnSimObject | | |
| SimConnect_ClearClientDataDefinition | | | | SimConnect_ClearClientDataDefinition | | |
@@ -38,7 +38,7 @@
| SimConnect_MapInputEventToClientEvent | | | | SimConnect_MapInputEventToClientEvent | | |
| SimConnect_RequestNotificationGroup | | | | SimConnect_RequestNotificationGroup | | |
| SimConnect_ClearInputGroup | | | | SimConnect_ClearInputGroup | | |
| SimConnect_ClearNotificationGroup | | | | SimConnect_ClearNotificationGroup | ✓ | Implemented by `unsubscribe_from_all_client_events`. |
| SimConnect_RequestReservedKey | | | | SimConnect_RequestReservedKey | | |
| SimConnect_SetInputGroupPriority | | | | SimConnect_SetInputGroupPriority | | |
| SimConnect_SetInputGroupState | | | | SimConnect_SetInputGroupState | | |

View File

@@ -30,8 +30,12 @@ name = "facilities"
path = "src/facilities.rs" path = "src/facilities.rs"
[[bin]] [[bin]]
name = "system_events" name = "subscribe_to_client_events"
path = "src/system_events.rs" path = "src/subscribe_to_client_events.rs"
[[bin]]
name = "subscribe_to_system_events"
path = "src/subscribe_to_system_events.rs"
[dependencies] [dependencies]
tracing = "0.1" tracing = "0.1"

View File

@@ -45,8 +45,14 @@ cargo run --bin data_multiple_objects
cargo run --bin facilities cargo run --bin facilities
``` ```
## Receiving system events ## Subscribe to client events
```bash ```bash
cargo run --bin system_events cargo run --bin subscribe_to_client_events
```
## Subscribe to system events
```bash
cargo run --bin subscribe_to_system_events
``` ```

View File

@@ -0,0 +1,62 @@
use simconnect_sdk::{ClientEvent, ClientEventRequest, Notification, SimConnect};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = SimConnect::new("Client Events example");
let mut throttle_events_received = 0;
let mut elevator_events_received = 0;
match client {
Ok(mut client) => loop {
let notification = client.get_next_dispatch()?;
match notification {
Some(Notification::Open) => {
println!("Connection opened.");
// After the connection is successfully open
// We subscribe to the client events we're interested in
client.subscribe_to_client_event(ClientEventRequest::Throttle1Set)?;
client.subscribe_to_client_event(ClientEventRequest::AxisElevatorSet)?;
}
Some(Notification::ClientEvent(event)) => match event {
ClientEvent::Throttle1Set { value } => {
println!("Throttle1Set: {value}");
throttle_events_received += 1;
if throttle_events_received >= 9 {
// We unsubscribe from the client event after we receive 10 of them
// This might run multiple times if there are more events queued up
println!("Unsubscribing from Throttle1Set...");
client
.unsubscribe_from_client_event(ClientEventRequest::Throttle1Set)?;
}
}
ClientEvent::AxisElevatorSet { value } => {
println!("AxisElevatorSet: {value}");
elevator_events_received += 1;
if elevator_events_received >= 9 {
// We unsubscribe from the client event after we receive 10 of them
// This might run multiple times if there are more events queued up
println!("Unsubscribing from AxisElevatorSet...");
client.unsubscribe_from_client_event(
ClientEventRequest::AxisElevatorSet,
)?;
}
}
_ => {}
},
_ => (),
}
// sleep for about a frame to reduce CPU usage
std::thread::sleep(std::time::Duration::from_millis(16));
},
Err(e) => {
println!("Error: {e:?}")
}
}
Ok(())
}

View File

@@ -1,7 +1,7 @@
use simconnect_sdk::{Notification, SimConnect, SystemEvent, SystemEventRequest}; use simconnect_sdk::{Notification, SimConnect, SystemEvent, SystemEventRequest};
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = SimConnect::new("System events example"); let client = SimConnect::new("System Events example");
match client { match client {
Ok(mut client) => loop { Ok(mut client) => loop {

View File

@@ -25,10 +25,12 @@ fn main() {
.allowlist_function("SimConnect_AddToDataDefinition") .allowlist_function("SimConnect_AddToDataDefinition")
.allowlist_function("SimConnect_CallDispatch") .allowlist_function("SimConnect_CallDispatch")
.allowlist_function("SimConnect_ClearDataDefinition") .allowlist_function("SimConnect_ClearDataDefinition")
.allowlist_function("SimConnect_ClearNotificationGroup")
.allowlist_function("SimConnect_Close") .allowlist_function("SimConnect_Close")
.allowlist_function("SimConnect_GetNextDispatch") .allowlist_function("SimConnect_GetNextDispatch")
.allowlist_function("SimConnect_MapClientEventToSimEvent") .allowlist_function("SimConnect_MapClientEventToSimEvent")
.allowlist_function("SimConnect_Open") .allowlist_function("SimConnect_Open")
.allowlist_function("SimConnect_RemoveClientEvent")
.allowlist_function("SimConnect_RequestDataOnSimObject") .allowlist_function("SimConnect_RequestDataOnSimObject")
.allowlist_function("SimConnect_RequestFacilitiesList") .allowlist_function("SimConnect_RequestFacilitiesList")
.allowlist_function("SimConnect_SetNotificationGroupPriority") .allowlist_function("SimConnect_SetNotificationGroupPriority")
@@ -52,6 +54,11 @@ fn main() {
.allowlist_type("SIMCONNECT_RECV_WAYPOINT_LIST") .allowlist_type("SIMCONNECT_RECV_WAYPOINT_LIST")
.allowlist_type("SIMCONNECT_RECV") .allowlist_type("SIMCONNECT_RECV")
.allowlist_var("SIMCONNECT_DATA_REQUEST_FLAG_CHANGED") .allowlist_var("SIMCONNECT_DATA_REQUEST_FLAG_CHANGED")
.allowlist_var("SIMCONNECT_GROUP_PRIORITY_DEFAULT")
.allowlist_var("SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE")
.allowlist_var("SIMCONNECT_GROUP_PRIORITY_HIGHEST")
.allowlist_var("SIMCONNECT_GROUP_PRIORITY_LOWEST")
.allowlist_var("SIMCONNECT_GROUP_PRIORITY_STANDARD")
.allowlist_var("SIMCONNECT_OBJECT_ID_USER") .allowlist_var("SIMCONNECT_OBJECT_ID_USER")
.allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME") .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME")
.allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE") .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE")

View File

@@ -0,0 +1,194 @@
use std::os::raw::c_char;
use crate::{bindings, SimConnectError};
// System Events start from 0 so we have to stagger the values to avoid collisions.
pub(crate) const CLIENT_EVENT_DISCRIMINANT_START: u32 = 1024;
/// SimConnect Client Event Request.
///
/// Defined by <https://www.prepar3d.com/SDKv5/sdk/references/variables/event_ids.html>.
/// Extended by <https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Event_IDs.htm>.
#[derive(Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive)]
#[repr(u32)]
#[non_exhaustive]
pub enum ClientEventRequest {
// ---------------
// Aircraft Engine
// ---------------
/// Set throttle 1 exactly (0 to 16383).
Throttle1Set = CLIENT_EVENT_DISCRIMINANT_START,
/// Set throttle 2 exactly (0 to 16383).
Throttle2Set,
/// Set throttle 3 exactly (0 to 16383).
Throttle3Set,
/// Set throttle 4 exactly (0 to 16383).
Throttle4Set,
// ---------------
// Aircraft Flight Controls
// ---------------
/// Sets elevator position (-16383 - +16383).
AxisElevatorSet,
// ---------------
// Aircraft Miscellaneous Systems
// ---------------
/// Increment brake pressure. Note: These are simulated spring-loaded toe brakes, which will bleed back to zero over time.
Brakes,
/// Increments left brake pressure. Note: This is a simulated spring-loaded toe brake, which will bleed back to zero over time.
BrakesLeft,
/// Increments right brake pressure. Note: This is a simulated spring-loaded toe brake, which will bleed back to zero over time.
BrakesRight,
/// Sets left brake position from axis controller (e.g. joystick). -16383 (0 brakes) to +16383 (max brakes).
AxisLeftBrakeSet,
/// Sets right brake position from axis controller (e.g. joystick). -16383 (0 brakes) to +16383 (max brakes).
AxisRightBrakeSet,
/// Toggles parking brake on/off.
ParkingBrakes,
}
impl ClientEventRequest {
pub(crate) fn into_c_char(self) -> *const c_char {
match self {
// Aircraft Engine
Self::Throttle1Set => "THROTTLE1_SET\0".as_ptr() as *const c_char,
Self::Throttle2Set => "THROTTLE2_SET\0".as_ptr() as *const c_char,
Self::Throttle3Set => "THROTTLE3_SET\0".as_ptr() as *const c_char,
Self::Throttle4Set => "THROTTLE4_SET\0".as_ptr() as *const c_char,
// Aircraft Flight Controls
Self::AxisElevatorSet => "AXIS_ELEVATOR_SET\0".as_ptr() as *const c_char,
// Aircraft Miscellaneous Systems
Self::Brakes => "BRAKES\0".as_ptr() as *const c_char,
Self::BrakesLeft => "BRAKES_LEFT\0".as_ptr() as *const c_char,
Self::BrakesRight => "BRAKES_RIGHT\0".as_ptr() as *const c_char,
Self::AxisLeftBrakeSet => "AXIS_LEFT_BRAKE_SET\0".as_ptr() as *const c_char,
Self::AxisRightBrakeSet => "AXIS_RIGHT_BRAKE_SET\0".as_ptr() as *const c_char,
Self::ParkingBrakes => "PARKING_BRAKES\0".as_ptr() as *const c_char,
}
}
}
/// SimConnect Client Event.
///
/// Defined by <https://www.prepar3d.com/SDKv5/sdk/references/variables/event_ids.html>.
/// Extended by <https://docs.flightsimulator.com/html/Programming_Tools/Event_IDs/Event_IDs.htm>.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ClientEvent {
// ---------------
// Aircraft Engine
// ---------------
/// Set throttle 1 exactly (0 to 16383).
Throttle1Set {
/// -16383 (0 throttle) to +16383 (max throttle).
value: i32,
},
/// Set throttle 2 exactly (0 to 16383).
Throttle2Set {
/// -16383 (0 throttle) to +16383 (max throttle).
value: i32,
},
/// Set throttle 3 exactly (0 to 16383).
Throttle3Set {
/// -16383 (0 throttle) to +16383 (max throttle).
value: i32,
},
/// Set throttle 4 exactly (0 to 16383).
Throttle4Set {
/// -16383 (0 throttle) to +16383 (max throttle).
value: i32,
},
// ---------------
// Aircraft Flight Controls
// ---------------
/// Sets elevator position (-16383 - +16383).
AxisElevatorSet {
/// -16383 (full down) to +16383 (full up).
value: i32,
},
// ---------------
// Aircraft Miscellaneous Systems
// ---------------
/// Increment brake pressure. Note: These are simulated spring-loaded toe brakes, which will bleed back to zero over time.
Brakes,
/// Increments left brake pressure. Note: This is a simulated spring-loaded toe brake, which will bleed back to zero over time.
BrakesLeft,
/// Increments right brake pressure. Note: This is a simulated spring-loaded toe brake, which will bleed back to zero over time.
BrakesRight,
/// Sets left brake position from axis controller (e.g. joystick). -16383 (0 brakes) to +16383 (max brakes).
AxisLeftBrakeSet {
/// -16383 (0 brakes) to +16383 (max brakes).
value: i32,
},
/// Sets right brake position from axis controller (e.g. joystick). -16383 (0 brakes) to +16383 (max brakes).
AxisRightBrakeSet {
/// -16383 (0 brakes) to +16383 (max brakes).
value: i32,
},
/// Toggles parking brake on/off.
ParkingBrakes,
}
impl TryFrom<&bindings::SIMCONNECT_RECV_EVENT> for ClientEvent {
type Error = SimConnectError;
fn try_from(event: &bindings::SIMCONNECT_RECV_EVENT) -> Result<Self, Self::Error> {
let request = ClientEventRequest::try_from(event.uEventID)
.map_err(|_| SimConnectError::UnimplementedEventType(event.uEventID))?;
match request {
// Aircraft Engine
ClientEventRequest::Throttle1Set => Ok(Self::Throttle1Set {
value: event.dwData as i32,
}),
ClientEventRequest::Throttle2Set => Ok(Self::Throttle2Set {
value: event.dwData as i32,
}),
ClientEventRequest::Throttle3Set => Ok(Self::Throttle3Set {
value: event.dwData as i32,
}),
ClientEventRequest::Throttle4Set => Ok(Self::Throttle4Set {
value: event.dwData as i32,
}),
// Aircraft Flight Controls
ClientEventRequest::AxisElevatorSet => Ok(Self::AxisElevatorSet {
value: event.dwData as i32,
}),
// Aircraft Miscellaneous Systems
ClientEventRequest::Brakes => Ok(Self::Brakes),
ClientEventRequest::BrakesLeft => Ok(Self::BrakesLeft),
ClientEventRequest::BrakesRight => Ok(Self::BrakesRight),
ClientEventRequest::AxisLeftBrakeSet => Ok(Self::AxisLeftBrakeSet {
value: event.dwData as i32,
}),
ClientEventRequest::AxisRightBrakeSet => Ok(Self::AxisRightBrakeSet {
value: event.dwData as i32,
}),
ClientEventRequest::ParkingBrakes => Ok(Self::ParkingBrakes),
}
}
}
impl From<ClientEvent> for (ClientEventRequest, i32) {
fn from(event: ClientEvent) -> Self {
match event {
// Aircraft Engine
ClientEvent::Throttle1Set { value } => (ClientEventRequest::Throttle1Set, value),
ClientEvent::Throttle2Set { value } => (ClientEventRequest::Throttle2Set, value),
ClientEvent::Throttle3Set { value } => (ClientEventRequest::Throttle3Set, value),
ClientEvent::Throttle4Set { value } => (ClientEventRequest::Throttle4Set, value),
// Aircraft Flight Controls
ClientEvent::AxisElevatorSet { value } => (ClientEventRequest::AxisElevatorSet, value),
// Aircraft Miscellaneous Systems
ClientEvent::Brakes => (ClientEventRequest::Brakes, 0),
ClientEvent::BrakesLeft => (ClientEventRequest::BrakesLeft, 0),
ClientEvent::BrakesRight => (ClientEventRequest::BrakesRight, 0),
ClientEvent::AxisLeftBrakeSet { value } => {
(ClientEventRequest::AxisLeftBrakeSet, value)
}
ClientEvent::AxisRightBrakeSet { value } => {
(ClientEventRequest::AxisRightBrakeSet, value)
}
ClientEvent::ParkingBrakes => (ClientEventRequest::ParkingBrakes, 0),
}
}
}

View File

@@ -1,15 +1,15 @@
mod client_event;
mod condition; mod condition;
mod data_type; mod data_type;
mod events;
mod facilities; mod facilities;
mod notification; mod notification;
mod notification_group;
mod period; mod period;
mod system_event;
pub use client_event::*;
pub use condition::*; pub use condition::*;
pub use data_type::*; pub use data_type::*;
pub use events::*;
pub use facilities::*; pub use facilities::*;
pub use notification::*; pub use notification::*;
pub use notification_group::*;
pub use period::*; pub use period::*;
pub use system_event::*;

View File

@@ -1,6 +0,0 @@
/// SimConnect event notification group.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u32)]
pub enum NotificationGroup {
Group0,
}

View File

@@ -268,38 +268,3 @@ impl TryFrom<&bindings::SIMCONNECT_RECV_EVENT_FRAME> for SystemEvent {
} }
} }
} }
pub(crate) const CLIENT_EVENT_START: u32 = 128;
/// SimConnect Client Event.
///
/// WIP. As defined by <https://www.prepar3d.com/SDKv5/sdk/references/variables/event_ids.html>.
#[derive(Debug, Copy, Clone, PartialEq, Eq, num_enum::TryFromPrimitive)]
#[repr(u32)]
#[non_exhaustive]
pub enum ClientEvent {
// Aircraft Engine
/// Set throttles max.
ThrottleFull = CLIENT_EVENT_START,
// ---------------
// Aircraft Miscellaneous Systems
/// Increment brake pressure. Note: These are simulated spring-loaded toe brakes, which will bleed back to zero over time.
Brakes,
/// Increments left brake pressure. Note: This is a simulated spring-loaded toe brake, which will bleed back to zero over time.
BrakesLeft,
/// Sets left brake position from axis controller (e.g. joystick). -16383 (0 brakes) to +16383 (max brakes).
AxisLeftBrakeSet,
}
impl ClientEvent {
pub(crate) fn into_c_char(self) -> *const c_char {
match self {
// Aircraft Engine
ClientEvent::ThrottleFull => "THROTTLE_FULL\0".as_ptr() as *const c_char,
// Aircraft Miscellaneous Systems
ClientEvent::Brakes => "BRAKES\0".as_ptr() as *const c_char,
ClientEvent::BrakesLeft => "BRAKES_LEFT\0".as_ptr() as *const c_char,
ClientEvent::AxisLeftBrakeSet => "AXIS_LEFT_BRAKE_SET\0".as_ptr() as *const c_char,
}
}
}

View File

@@ -13,15 +13,19 @@ pub enum SimConnectError {
/// An unimplemented event type has been received by the SDK. /// An unimplemented event type has been received by the SDK.
#[error("Unimplemented event in the SDK: {0}")] #[error("Unimplemented event in the SDK: {0}")]
UnimplementedEventType(u32), UnimplementedEventType(u32),
/// An unimplemented message type has been received by the SDK. /// An unimplemented notification has been received by the SDK.
#[error("Unimplemented notification in the SDK: {0}")] #[error("Unimplemented notification in the SDK: {0}")]
UnimplementedMessageType(i32), UnimplementedNotification(i32),
/// Object already registered with the client instance. /// 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 already registered with the client instance. /// Object already registered with the client instance.
#[error("Object `{0}` has not been registered")] #[error("Object `{0}` has not been registered")]
ObjectNotRegistered(String), ObjectNotRegistered(String),
#[error("Event `{0}` has already been subscribed to")]
EventAlreadySubscribedTo(String),
#[error("Event `{0}` has not been subscribed to")]
EventNotSubscribedTo(String),
/// Object mismatch. /// Object mismatch.
#[error("Tried to convert object of type {actual} to {expected}")] #[error("Tried to convert object of type {actual} to {expected}")]
ObjectMismatch { actual: String, expected: String }, ObjectMismatch { actual: String, expected: String },

View File

@@ -2,11 +2,13 @@ use std::{collections::HashMap, ffi::c_void};
use tracing::{error, span, trace, warn, Level}; use tracing::{error, span, trace, warn, Level};
use crate::{ use crate::domain::{
as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, Airport, Airport, ClientEvent, ClientEventRequest, Notification, Object, SystemEvent,
ClientEvent, Notification, Object, SimConnectError, SystemEvent, Waypoint, CLIENT_EVENT_START, SystemEventRequest, Waypoint, CLIENT_EVENT_DISCRIMINANT_START, NDB, VOR,
NDB, VOR,
}; };
use crate::helpers::fixed_c_str_to_string;
use crate::simconnect::EventRegister;
use crate::{as_c_string, bindings, ok_if_fail, success, SimConnectError};
/// SimConnect SDK Client. /// SimConnect SDK Client.
/// ///
@@ -83,20 +85,22 @@ use crate::{
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub struct SimConnect { pub struct SimConnect {
pub(super) handle: std::ptr::NonNull<c_void>, pub(crate) handle: std::ptr::NonNull<c_void>,
pub(super) next_request_id: u32, pub(crate) next_request_id: u32,
pub(super) registered_objects: HashMap<String, RegisteredObject>, pub(crate) registered_objects: HashMap<String, RegisteredObject>,
pub(crate) system_event_register: EventRegister<SystemEventRequest>,
pub(crate) client_event_register: EventRegister<ClientEventRequest>,
} }
/// A struct that represents a registered object. /// A struct that represents a registered object.
#[derive(Debug)] #[derive(Debug)]
pub(super) struct RegisteredObject { pub(crate) struct RegisteredObject {
pub id: u32, pub id: u32,
pub transient: bool, pub transient: bool,
} }
impl RegisteredObject { impl RegisteredObject {
pub(super) fn new(id: u32, transient: bool) -> Self { pub(crate) fn new(id: u32, transient: bool) -> Self {
Self { id, transient } Self { id, transient }
} }
} }
@@ -126,6 +130,8 @@ impl SimConnect {
})?, })?,
next_request_id: 0, next_request_id: 0,
registered_objects: HashMap::new(), registered_objects: HashMap::new(),
system_event_register: EventRegister::new(),
client_event_register: EventRegister::new(),
}) })
} }
@@ -172,9 +178,8 @@ impl SimConnect {
let event: &bindings::SIMCONNECT_RECV_EVENT = let event: &bindings::SIMCONNECT_RECV_EVENT =
unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) }; unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) };
if event.uEventID >= CLIENT_EVENT_START { if event.uEventID >= CLIENT_EVENT_DISCRIMINANT_START {
let event = ClientEvent::try_from(event.uEventID) let event = ClientEvent::try_from(event)?;
.map_err(|_| SimConnectError::UnimplementedEventType(event.uEventID))?;
Ok(Some(Notification::ClientEvent(event))) Ok(Some(Notification::ClientEvent(event)))
} else { } else {
@@ -393,7 +398,7 @@ impl SimConnect {
bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => Ok(None), bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => Ok(None),
id => { id => {
error!("Received unhandled notification ID: {}", id); error!("Received unhandled notification ID: {}", id);
Err(SimConnectError::UnimplementedMessageType(id)) Err(SimConnectError::UnimplementedNotification(id))
} }
} }
} }
@@ -401,7 +406,7 @@ impl SimConnect {
/// Register a Request ID in the internal state so that the user doesn't have to manually manage Request IDs. /// Register a Request ID in the internal state so that the user doesn't have to manually manage Request IDs.
#[tracing::instrument(name = "SimConnect::new_request_id", level = "trace", skip(self))] #[tracing::instrument(name = "SimConnect::new_request_id", level = "trace", skip(self))]
pub(super) fn new_request_id( pub(crate) fn new_request_id(
&mut self, &mut self,
type_name: String, type_name: String,
transient: bool, transient: bool,
@@ -436,7 +441,7 @@ impl SimConnect {
level = "trace", level = "trace",
skip(self) skip(self)
)] )]
pub(super) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option<u32> { pub(crate) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option<u32> {
self.registered_objects.remove(type_name).map(|obj| obj.id) self.registered_objects.remove(type_name).map(|obj| obj.id)
} }
@@ -446,7 +451,7 @@ impl SimConnect {
level = "trace", level = "trace",
skip(self) skip(self)
)] )]
pub(super) fn get_type_name_by_request_id(&self, request_id: u32) -> Option<String> { pub(crate) fn get_type_name_by_request_id(&self, request_id: u32) -> Option<String> {
self.registered_objects self.registered_objects
.iter() .iter()
.find(|(_, v)| v.id == request_id) .find(|(_, v)| v.id == request_id)
@@ -455,7 +460,7 @@ impl SimConnect {
/// Get the Type Name of a Request ID. /// Get the Type Name of a Request ID.
#[tracing::instrument(name = "SimConnect::is_transient_request", level = "trace", skip(self))] #[tracing::instrument(name = "SimConnect::is_transient_request", level = "trace", skip(self))]
pub(super) fn is_transient_request(&self, request_id: u32) -> Option<bool> { pub(crate) fn is_transient_request(&self, request_id: u32) -> Option<bool> {
self.registered_objects self.registered_objects
.iter() .iter()
.find(|(_, v)| v.id == request_id) .find(|(_, v)| v.id == request_id)
@@ -472,7 +477,7 @@ impl SimConnect {
fields(type_name, transient), fields(type_name, transient),
skip(self) skip(self)
)] )]
pub(super) fn unregister_potential_transient_request( pub(crate) fn unregister_potential_transient_request(
&mut self, &mut self,
entry_number: u32, entry_number: u32,
out_of: u32, out_of: u32,

View File

@@ -0,0 +1,48 @@
use crate::SimConnectError;
#[derive(Debug)]
pub(crate) struct EventRegister<T>
where
T: std::fmt::Debug + std::cmp::PartialEq,
{
items: Vec<T>,
}
impl<T> EventRegister<T>
where
T: std::fmt::Debug + std::cmp::PartialEq,
{
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn is_registered(&self, item: T) -> bool {
self.items.contains(&item)
}
pub fn register(&mut self, item: T) -> Result<(), SimConnectError> {
if self.items.contains(&item) {
return Err(SimConnectError::EventAlreadySubscribedTo(format!(
"{item:?}"
)));
}
self.items.push(item);
Ok(())
}
pub fn unregister(&mut self, item: T) -> Result<(), SimConnectError> {
if !self.items.contains(&item) {
return Err(SimConnectError::EventNotSubscribedTo(format!("{item:?}")));
}
self.items.retain(|i| *i != item);
Ok(())
}
pub fn clear(&mut self) {
self.items.clear();
}
}

View File

@@ -1,18 +1,68 @@
use crate::{ use crate::{
bindings, success, ClientEvent, NotificationGroup, SimConnect, SimConnectError, bindings, success, ClientEventRequest, SimConnect, SimConnectError, SystemEventRequest,
SystemEventRequest,
}; };
// In order to simplify the usage we're using a single notification group for all client events.
const NOTIFICATION_GROUP_ID: u32 = 0;
impl SimConnect { impl SimConnect {
/// Associates a client defined event with a Microsoft Flight Simulator event name. /// Request that a specific system event is notified.
/// #[tracing::instrument(
/// WIP name = "SimConnect::subscribe_to_system_event",
#[tracing::instrument(name = "SimConnect::register_event", level = "debug", skip(self))] level = "debug",
pub fn register_event( skip(self)
&self, )]
event: ClientEvent, pub fn subscribe_to_system_event(
notification_group: NotificationGroup, &mut self,
event: SystemEventRequest,
) -> Result<(), SimConnectError> { ) -> Result<(), SimConnectError> {
self.system_event_register.register(event)?;
success!(unsafe {
bindings::SimConnect_SubscribeToSystemEvent(
self.handle.as_ptr(),
event as u32,
event.into_c_char(),
)
})?;
Ok(())
}
/// Request that notifications are no longer received for the specified system event.
/// If the system event is not subscribed to, this function does nothing.
#[tracing::instrument(
name = "SimConnect::unsubscribe_from_system_event",
level = "debug",
skip(self)
)]
pub fn unsubscribe_from_system_event(
&mut self,
event: SystemEventRequest,
) -> Result<(), SimConnectError> {
if self.system_event_register.is_registered(event) {
success!(unsafe {
bindings::SimConnect_UnsubscribeFromSystemEvent(self.handle.as_ptr(), event as u32)
})?;
self.system_event_register.clear();
}
Ok(())
}
/// Request that a specific client event is notified.
#[tracing::instrument(
name = "SimConnect::subscribe_to_client_event",
level = "debug",
skip(self)
)]
pub fn subscribe_to_client_event(
&mut self,
event: ClientEventRequest,
) -> Result<(), SimConnectError> {
self.client_event_register.register(event)?;
success!(unsafe { success!(unsafe {
bindings::SimConnect_MapClientEventToSimEvent( bindings::SimConnect_MapClientEventToSimEvent(
self.handle.as_ptr(), self.handle.as_ptr(),
@@ -24,7 +74,7 @@ impl SimConnect {
success!(unsafe { success!(unsafe {
bindings::SimConnect_AddClientEventToNotificationGroup( bindings::SimConnect_AddClientEventToNotificationGroup(
self.handle.as_ptr(), self.handle.as_ptr(),
notification_group as u32, NOTIFICATION_GROUP_ID,
event as u32, event as u32,
0, 0,
) )
@@ -33,43 +83,53 @@ impl SimConnect {
success!(unsafe { success!(unsafe {
bindings::SimConnect_SetNotificationGroupPriority( bindings::SimConnect_SetNotificationGroupPriority(
self.handle.as_ptr(), self.handle.as_ptr(),
notification_group as u32, NOTIFICATION_GROUP_ID,
1, bindings::SIMCONNECT_GROUP_PRIORITY_HIGHEST,
) )
}) })?;
Ok(())
} }
/// Request that a specific system event is notified to the client. /// Request that notifications are no longer received for the specified client event.
/// If the client event is not subscribed to, this function does nothing.
#[tracing::instrument( #[tracing::instrument(
name = "SimConnect::subscribe_to_system_event", name = "SimConnect::unsubscribe_from_client_event",
level = "debug", level = "debug",
skip(self) skip(self)
)] )]
pub fn subscribe_to_system_event( pub fn unsubscribe_from_client_event(
&mut self, &mut self,
event: SystemEventRequest, event: ClientEventRequest,
) -> Result<(), SimConnectError> { ) -> Result<(), SimConnectError> {
if self.client_event_register.is_registered(event) {
success!(unsafe { success!(unsafe {
bindings::SimConnect_SubscribeToSystemEvent( bindings::SimConnect_RemoveClientEvent(
self.handle.as_ptr(), self.handle.as_ptr(),
NOTIFICATION_GROUP_ID,
event as u32, event as u32,
event.into_c_char(),
) )
}) })?;
self.client_event_register.unregister(event)?;
} }
/// Request that notifications are no longer received for the specified system event. Ok(())
}
/// Request that notifications are no longer received for any client event.
#[tracing::instrument( #[tracing::instrument(
name = "SimConnect::unsubscribe_from_system_event", name = "SimConnect::unsubscribe_from_all_client_events",
level = "debug", level = "debug",
skip(self) skip(self)
)] )]
pub fn unsubscribe_from_system_event( pub fn unsubscribe_from_all_client_events(&mut self) -> Result<(), SimConnectError> {
&mut self,
event: SystemEventRequest,
) -> Result<(), SimConnectError> {
success!(unsafe { success!(unsafe {
bindings::SimConnect_UnsubscribeFromSystemEvent(self.handle.as_ptr(), event as u32) bindings::SimConnect_ClearNotificationGroup(self.handle.as_ptr(), NOTIFICATION_GROUP_ID)
}) })?;
self.client_event_register.clear();
Ok(())
} }
} }

View File

@@ -1,8 +1,11 @@
mod base; mod base;
mod event_register;
mod events; mod events;
mod facilities; mod facilities;
mod objects; mod objects;
pub(crate) use event_register::*;
pub use base::*; pub use base::*;
pub use events::*; pub use events::*;
pub use facilities::*; pub use facilities::*;