diff --git a/CHANGELOG.md b/CHANGELOG.md index 47b8619..002f03c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,23 @@ file. This change log follows the conventions of ## [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 ### Changed diff --git a/FEATURES.md b/FEATURES.md index ea58525..3a840bd 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -2,47 +2,47 @@ ## General -| Feature | Status | Comment | -| --------------------------------------- | ------- | ------- | -| DispatchProc | | | -| SimConnect_Open | ✓ | | -| SimConnect_Close | ✓ | | -| SimConnect_CallDispatch | | | -| SimConnect_GetNextDispatch | ✓ | | -| SimConnect_RequestSystemState | | | -| SimConnect_MapClientEventToSimEvent | - | WIP | -| SimConnect_SubscribeToSystemEvent | ✓ | | -| SimConnect_SetSystemEventState | | | -| SimConnect_UnsubscribeFromSystemEvent | ✓ | | -| SimConnect_SetNotificationGroupPriority | - | WIP | +| Feature | Status | Comment | +| --------------------------------------- | ------- | -------------------------------------------- | +| DispatchProc | | | +| SimConnect_Open | ✓ | | +| SimConnect_Close | ✓ | | +| SimConnect_CallDispatch | | | +| SimConnect_GetNextDispatch | ✓ | | +| SimConnect_RequestSystemState | | | +| SimConnect_MapClientEventToSimEvent | ✓ | Encapsulated by `subscribe_to_client_event`. | +| SimConnect_SubscribeToSystemEvent | ✓ | | +| SimConnect_SetSystemEventState | | | +| SimConnect_UnsubscribeFromSystemEvent | ✓ | | +| SimConnect_SetNotificationGroupPriority | ✓ | Encapsulated by `subscribe_to_client_event`. | ## Events And Data -| Feature | Status | Comment | -| -------------------------------------------- | ------- | ----------------------------------- | -| SimConnect_RequestDataOnSimObject | ✓ | Only for SIMCONNECT_OBJECT_ID_USER | -| SimConnect_RequestDataOnSimObjectType | | | -| SimConnect_AddClientEventToNotificationGroup | - | WIP | -| SimConnect_RemoveClientEvent | | | -| SimConnect_TransmitClientEvent | | | -| SimConnect_TransmitClientEvent_EX1 | | | -| SimConnect_MapClientDataNameToID | | | -| SimConnect_RequestClientData | | | -| SimConnect_CreateClientData | | | -| SimConnect_AddToClientDataDefinition | | | -| SimConnect_AddToDataDefinition | ✓ | Supports `f64`, `bool` and `String` | -| SimConnect_SetClientData | | | -| SimConnect_SetDataOnSimObject | | | -| SimConnect_ClearClientDataDefinition | | | -| SimConnect_ClearDataDefinition | ✓ | | -| SimConnect_MapInputEventToClientEvent | | | -| SimConnect_RequestNotificationGroup | | | -| SimConnect_ClearInputGroup | | | -| SimConnect_ClearNotificationGroup | | | -| SimConnect_RequestReservedKey | | | -| SimConnect_SetInputGroupPriority | | | -| SimConnect_SetInputGroupState | | | -| SimConnect_RemoveInputEvent | | | +| Feature | Status | Comment | +| -------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------- | +| SimConnect_RequestDataOnSimObject | ✓ | Only for `SIMCONNECT_OBJECT_ID_USER`. | +| SimConnect_RequestDataOnSimObjectType | | | +| SimConnect_AddClientEventToNotificationGroup | ✓ | Encapsulated by `subscribe_to_client_event`. | +| SimConnect_RemoveClientEvent | ✓ | | +| SimConnect_TransmitClientEvent | | | +| SimConnect_TransmitClientEvent_EX1 | | | +| SimConnect_MapClientDataNameToID | | | +| SimConnect_RequestClientData | | | +| SimConnect_CreateClientData | | | +| SimConnect_AddToClientDataDefinition | | | +| SimConnect_AddToDataDefinition | ✓ | Encapsulated by `register_object` and the `simconnect` macro. Supports `f64`, `bool` and `String`. | +| SimConnect_SetClientData | | | +| SimConnect_SetDataOnSimObject | | | +| SimConnect_ClearClientDataDefinition | | | +| SimConnect_ClearDataDefinition | ✓ | | +| SimConnect_MapInputEventToClientEvent | | | +| SimConnect_RequestNotificationGroup | | | +| SimConnect_ClearInputGroup | | | +| SimConnect_ClearNotificationGroup | ✓ | Implemented by `unsubscribe_from_all_client_events`. | +| SimConnect_RequestReservedKey | | | +| SimConnect_SetInputGroupPriority | | | +| SimConnect_SetInputGroupState | | | +| SimConnect_RemoveInputEvent | | | ## AI Objects diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 5f308e6..a27b6f8 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -30,8 +30,12 @@ name = "facilities" path = "src/facilities.rs" [[bin]] -name = "system_events" -path = "src/system_events.rs" +name = "subscribe_to_client_events" +path = "src/subscribe_to_client_events.rs" + +[[bin]] +name = "subscribe_to_system_events" +path = "src/subscribe_to_system_events.rs" [dependencies] tracing = "0.1" diff --git a/examples/README.md b/examples/README.md index db4e9c0..6943d33 100644 --- a/examples/README.md +++ b/examples/README.md @@ -45,8 +45,14 @@ cargo run --bin data_multiple_objects cargo run --bin facilities ``` -## Receiving system events +## Subscribe to client events ```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 ``` diff --git a/examples/src/subscribe_to_client_events.rs b/examples/src/subscribe_to_client_events.rs new file mode 100644 index 0000000..6ffd64c --- /dev/null +++ b/examples/src/subscribe_to_client_events.rs @@ -0,0 +1,62 @@ +use simconnect_sdk::{ClientEvent, ClientEventRequest, Notification, SimConnect}; + +fn main() -> Result<(), Box> { + 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(()) +} diff --git a/examples/src/system_events.rs b/examples/src/subscribe_to_system_events.rs similarity index 98% rename from examples/src/system_events.rs rename to examples/src/subscribe_to_system_events.rs index 7c5f1ee..0e0d1ce 100644 --- a/examples/src/system_events.rs +++ b/examples/src/subscribe_to_system_events.rs @@ -1,7 +1,7 @@ use simconnect_sdk::{Notification, SimConnect, SystemEvent, SystemEventRequest}; fn main() -> Result<(), Box> { - let client = SimConnect::new("System events example"); + let client = SimConnect::new("System Events example"); match client { Ok(mut client) => loop { diff --git a/simconnect-sdk/build.rs b/simconnect-sdk/build.rs index 997a080..5aea7d5 100644 --- a/simconnect-sdk/build.rs +++ b/simconnect-sdk/build.rs @@ -25,10 +25,12 @@ fn main() { .allowlist_function("SimConnect_AddToDataDefinition") .allowlist_function("SimConnect_CallDispatch") .allowlist_function("SimConnect_ClearDataDefinition") + .allowlist_function("SimConnect_ClearNotificationGroup") .allowlist_function("SimConnect_Close") .allowlist_function("SimConnect_GetNextDispatch") .allowlist_function("SimConnect_MapClientEventToSimEvent") .allowlist_function("SimConnect_Open") + .allowlist_function("SimConnect_RemoveClientEvent") .allowlist_function("SimConnect_RequestDataOnSimObject") .allowlist_function("SimConnect_RequestFacilitiesList") .allowlist_function("SimConnect_SetNotificationGroupPriority") @@ -52,6 +54,11 @@ fn main() { .allowlist_type("SIMCONNECT_RECV_WAYPOINT_LIST") .allowlist_type("SIMCONNECT_RECV") .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_RECV_ID_VOR_LIST_HAS_DME") .allowlist_var("SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE") diff --git a/simconnect-sdk/src/domain/client_event.rs b/simconnect-sdk/src/domain/client_event.rs new file mode 100644 index 0000000..27b4b4c --- /dev/null +++ b/simconnect-sdk/src/domain/client_event.rs @@ -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 . +/// Extended by . +#[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 . +/// Extended by . +#[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 { + 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 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), + } + } +} diff --git a/simconnect-sdk/src/domain/mod.rs b/simconnect-sdk/src/domain/mod.rs index badc7a6..94a93da 100644 --- a/simconnect-sdk/src/domain/mod.rs +++ b/simconnect-sdk/src/domain/mod.rs @@ -1,15 +1,15 @@ +mod client_event; mod condition; mod data_type; -mod events; mod facilities; mod notification; -mod notification_group; mod period; +mod system_event; +pub use client_event::*; pub use condition::*; pub use data_type::*; -pub use events::*; pub use facilities::*; pub use notification::*; -pub use notification_group::*; pub use period::*; +pub use system_event::*; diff --git a/simconnect-sdk/src/domain/notification_group.rs b/simconnect-sdk/src/domain/notification_group.rs deleted file mode 100644 index 3161495..0000000 --- a/simconnect-sdk/src/domain/notification_group.rs +++ /dev/null @@ -1,6 +0,0 @@ -/// SimConnect event notification group. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[repr(u32)] -pub enum NotificationGroup { - Group0, -} diff --git a/simconnect-sdk/src/domain/events.rs b/simconnect-sdk/src/domain/system_event.rs similarity index 90% rename from simconnect-sdk/src/domain/events.rs rename to simconnect-sdk/src/domain/system_event.rs index eed3427..54ef911 100644 --- a/simconnect-sdk/src/domain/events.rs +++ b/simconnect-sdk/src/domain/system_event.rs @@ -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 . -#[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, - } - } -} diff --git a/simconnect-sdk/src/errors.rs b/simconnect-sdk/src/errors.rs index ed8b4c0..fbd05d5 100644 --- a/simconnect-sdk/src/errors.rs +++ b/simconnect-sdk/src/errors.rs @@ -13,15 +13,19 @@ pub enum SimConnectError { /// An unimplemented event type has been received by the SDK. #[error("Unimplemented event in the SDK: {0}")] 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}")] - UnimplementedMessageType(i32), + UnimplementedNotification(i32), /// Object already registered with the client instance. #[error("Object `{0}` has already been registered")] ObjectAlreadyRegistered(String), /// Object already registered with the client instance. #[error("Object `{0}` has not been registered")] ObjectNotRegistered(String), + #[error("Event `{0}` has already been subscribed to")] + EventAlreadySubscribedTo(String), + #[error("Event `{0}` has not been subscribed to")] + EventNotSubscribedTo(String), /// Object mismatch. #[error("Tried to convert object of type {actual} to {expected}")] ObjectMismatch { actual: String, expected: String }, diff --git a/simconnect-sdk/src/simconnect/base.rs b/simconnect-sdk/src/simconnect/base.rs index 403ae6b..a08e91d 100644 --- a/simconnect-sdk/src/simconnect/base.rs +++ b/simconnect-sdk/src/simconnect/base.rs @@ -2,11 +2,13 @@ use std::{collections::HashMap, ffi::c_void}; use tracing::{error, span, trace, warn, Level}; -use crate::{ - as_c_string, bindings, helpers::fixed_c_str_to_string, ok_if_fail, success, Airport, - ClientEvent, Notification, Object, SimConnectError, SystemEvent, Waypoint, CLIENT_EVENT_START, - NDB, VOR, +use crate::domain::{ + Airport, ClientEvent, ClientEventRequest, Notification, Object, SystemEvent, + SystemEventRequest, Waypoint, CLIENT_EVENT_DISCRIMINANT_START, 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. /// @@ -83,20 +85,22 @@ use crate::{ /// ``` #[derive(Debug)] pub struct SimConnect { - pub(super) handle: std::ptr::NonNull, - pub(super) next_request_id: u32, - pub(super) registered_objects: HashMap, + pub(crate) handle: std::ptr::NonNull, + pub(crate) next_request_id: u32, + pub(crate) registered_objects: HashMap, + pub(crate) system_event_register: EventRegister, + pub(crate) client_event_register: EventRegister, } /// A struct that represents a registered object. #[derive(Debug)] -pub(super) struct RegisteredObject { +pub(crate) struct RegisteredObject { pub id: u32, pub transient: bool, } impl RegisteredObject { - pub(super) fn new(id: u32, transient: bool) -> Self { + pub(crate) fn new(id: u32, transient: bool) -> Self { Self { id, transient } } } @@ -126,6 +130,8 @@ impl SimConnect { })?, next_request_id: 0, 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 = unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) }; - if event.uEventID >= CLIENT_EVENT_START { - let event = ClientEvent::try_from(event.uEventID) - .map_err(|_| SimConnectError::UnimplementedEventType(event.uEventID))?; + if event.uEventID >= CLIENT_EVENT_DISCRIMINANT_START { + let event = ClientEvent::try_from(event)?; Ok(Some(Notification::ClientEvent(event))) } else { @@ -393,7 +398,7 @@ impl SimConnect { bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => Ok(None), 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. #[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, type_name: String, transient: bool, @@ -436,7 +441,7 @@ impl SimConnect { level = "trace", skip(self) )] - pub(super) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option { + pub(crate) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option { self.registered_objects.remove(type_name).map(|obj| obj.id) } @@ -446,7 +451,7 @@ impl SimConnect { level = "trace", skip(self) )] - pub(super) fn get_type_name_by_request_id(&self, request_id: u32) -> Option { + pub(crate) fn get_type_name_by_request_id(&self, request_id: u32) -> Option { self.registered_objects .iter() .find(|(_, v)| v.id == request_id) @@ -455,7 +460,7 @@ impl SimConnect { /// Get the Type Name of a Request ID. #[tracing::instrument(name = "SimConnect::is_transient_request", level = "trace", skip(self))] - pub(super) fn is_transient_request(&self, request_id: u32) -> Option { + pub(crate) fn is_transient_request(&self, request_id: u32) -> Option { self.registered_objects .iter() .find(|(_, v)| v.id == request_id) @@ -472,7 +477,7 @@ impl SimConnect { fields(type_name, transient), skip(self) )] - pub(super) fn unregister_potential_transient_request( + pub(crate) fn unregister_potential_transient_request( &mut self, entry_number: u32, out_of: u32, diff --git a/simconnect-sdk/src/simconnect/event_register.rs b/simconnect-sdk/src/simconnect/event_register.rs new file mode 100644 index 0000000..b1094da --- /dev/null +++ b/simconnect-sdk/src/simconnect/event_register.rs @@ -0,0 +1,48 @@ +use crate::SimConnectError; + +#[derive(Debug)] +pub(crate) struct EventRegister +where + T: std::fmt::Debug + std::cmp::PartialEq, +{ + items: Vec, +} + +impl EventRegister +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(); + } +} diff --git a/simconnect-sdk/src/simconnect/events.rs b/simconnect-sdk/src/simconnect/events.rs index 0ca301a..5c2300a 100644 --- a/simconnect-sdk/src/simconnect/events.rs +++ b/simconnect-sdk/src/simconnect/events.rs @@ -1,18 +1,68 @@ use crate::{ - bindings, success, ClientEvent, NotificationGroup, SimConnect, SimConnectError, - SystemEventRequest, + bindings, success, ClientEventRequest, SimConnect, SimConnectError, 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 { - /// Associates a client defined event with a Microsoft Flight Simulator event name. - /// - /// WIP - #[tracing::instrument(name = "SimConnect::register_event", level = "debug", skip(self))] - pub fn register_event( - &self, - event: ClientEvent, - notification_group: NotificationGroup, + /// Request that a specific system event is notified. + #[tracing::instrument( + name = "SimConnect::subscribe_to_system_event", + level = "debug", + skip(self) + )] + pub fn subscribe_to_system_event( + &mut self, + event: SystemEventRequest, ) -> 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 { bindings::SimConnect_MapClientEventToSimEvent( self.handle.as_ptr(), @@ -24,7 +74,7 @@ impl SimConnect { success!(unsafe { bindings::SimConnect_AddClientEventToNotificationGroup( self.handle.as_ptr(), - notification_group as u32, + NOTIFICATION_GROUP_ID, event as u32, 0, ) @@ -33,43 +83,53 @@ impl SimConnect { success!(unsafe { bindings::SimConnect_SetNotificationGroupPriority( self.handle.as_ptr(), - notification_group as u32, - 1, + NOTIFICATION_GROUP_ID, + 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( - name = "SimConnect::subscribe_to_system_event", + name = "SimConnect::unsubscribe_from_client_event", level = "debug", skip(self) )] - pub fn subscribe_to_system_event( + pub fn unsubscribe_from_client_event( &mut self, - event: SystemEventRequest, + event: ClientEventRequest, ) -> Result<(), SimConnectError> { - success!(unsafe { - bindings::SimConnect_SubscribeToSystemEvent( - self.handle.as_ptr(), - event as u32, - event.into_c_char(), - ) - }) + if self.client_event_register.is_registered(event) { + success!(unsafe { + bindings::SimConnect_RemoveClientEvent( + self.handle.as_ptr(), + NOTIFICATION_GROUP_ID, + event as u32, + ) + })?; + + self.client_event_register.unregister(event)?; + } + + Ok(()) } - /// Request that notifications are no longer received for the specified system event. + /// Request that notifications are no longer received for any client event. #[tracing::instrument( - name = "SimConnect::unsubscribe_from_system_event", + name = "SimConnect::unsubscribe_from_all_client_events", level = "debug", skip(self) )] - pub fn unsubscribe_from_system_event( - &mut self, - event: SystemEventRequest, - ) -> Result<(), SimConnectError> { + pub fn unsubscribe_from_all_client_events(&mut self) -> Result<(), SimConnectError> { 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(()) } } diff --git a/simconnect-sdk/src/simconnect/mod.rs b/simconnect-sdk/src/simconnect/mod.rs index 18de8ff..6289616 100644 --- a/simconnect-sdk/src/simconnect/mod.rs +++ b/simconnect-sdk/src/simconnect/mod.rs @@ -1,8 +1,11 @@ mod base; +mod event_register; mod events; mod facilities; mod objects; +pub(crate) use event_register::*; + pub use base::*; pub use events::*; pub use facilities::*;