From 93ab001d48fe4de130deddd11005e56b37e7d663 Mon Sep 17 00:00:00 2001 From: Mihai Dinculescu Date: Wed, 22 Feb 2023 18:41:01 +0000 Subject: [PATCH] Automatically clean up request_facilities_list requests once completed --- CHANGELOG.md | 4 + examples/src/facilities.rs | 4 +- simconnect-sdk/src/simconnect/base.rs | 102 ++++++++++++++++++-- simconnect-sdk/src/simconnect/facilities.rs | 10 +- simconnect-sdk/src/simconnect/objects.rs | 4 +- 5 files changed, 108 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fcc62b..3749179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ file. This change log follows the conventions of ### Changed - Updated to MSFS SDK v0.20.5.0. +- `SimConnect::get_next_dispatch` now takes a `&mut self` in order to be able to clean up requests that have returned all the results they ever will. + +# Fixed +- `SimConnect::request_facilities_list` calls now automatically clean up the request after all the data is received. ## [v0.2.1] - 2022-10-29 diff --git a/examples/src/facilities.rs b/examples/src/facilities.rs index 48b88ff..618f40f 100644 --- a/examples/src/facilities.rs +++ b/examples/src/facilities.rs @@ -26,8 +26,8 @@ fn main() -> Result<(), Box> { // The returned list is quite large, so we look for a particular record if record.icao == "EGSC" { println!("{record:?}"); - // we've got the entry we're interesting in - we can unsubscribe now - client.unsubscribe_to_facilities(FacilityType::Airport)?; + // there's no need to unsubscribe + // because this is a one-off request, not a subscription } } } diff --git a/simconnect-sdk/src/simconnect/base.rs b/simconnect-sdk/src/simconnect/base.rs index 0dd020d..403ae6b 100644 --- a/simconnect-sdk/src/simconnect/base.rs +++ b/simconnect-sdk/src/simconnect/base.rs @@ -85,7 +85,20 @@ use crate::{ pub struct SimConnect { pub(super) handle: std::ptr::NonNull, pub(super) next_request_id: u32, - pub(super) registered_objects: HashMap, + pub(super) registered_objects: HashMap, +} + +/// A struct that represents a registered object. +#[derive(Debug)] +pub(super) struct RegisteredObject { + pub id: u32, + pub transient: bool, +} + +impl RegisteredObject { + pub(super) fn new(id: u32, transient: bool) -> Self { + Self { id, transient } + } } impl SimConnect { @@ -121,7 +134,7 @@ impl SimConnect { /// # Remarks /// This is a non-blocking function. If there are no messages to receive, it will return None immediately. /// When called in a loop, it is recommended to use a short sleep time. - pub fn get_next_dispatch(&self) -> Result, SimConnectError> { + pub fn get_next_dispatch(&mut self) -> Result, 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; @@ -212,6 +225,12 @@ impl SimConnect { let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST = unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_AIRPORT_LIST) }; + self.unregister_potential_transient_request( + event._base.dwEntryNumber, + event._base.dwOutOf, + event._base.dwRequestID, + ); + let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. @@ -234,6 +253,12 @@ impl SimConnect { let event: &bindings::SIMCONNECT_RECV_WAYPOINT_LIST = unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_WAYPOINT_LIST) }; + self.unregister_potential_transient_request( + event._base.dwEntryNumber, + event._base.dwOutOf, + event._base.dwRequestID, + ); + let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. @@ -257,6 +282,12 @@ impl SimConnect { let event: &bindings::SIMCONNECT_RECV_NDB_LIST = unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_NDB_LIST) }; + self.unregister_potential_transient_request( + event._base.dwEntryNumber, + event._base.dwOutOf, + event._base.dwRequestID, + ); + let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. @@ -281,6 +312,12 @@ impl SimConnect { let event: &bindings::SIMCONNECT_RECV_VOR_LIST = unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_VOR_LIST) }; + self.unregister_potential_transient_request( + event._base.dwEntryNumber, + event._base.dwOutOf, + event._base.dwRequestID, + ); + let data = (0..event._base.dwArraySize as usize) .map(|i| { // `rgData` is defined as a 1-element array, but it is actually a variable-length array. @@ -364,7 +401,11 @@ 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(&mut self, type_name: String) -> Result { + pub(super) fn new_request_id( + &mut self, + type_name: String, + transient: bool, + ) -> Result { if self.registered_objects.contains_key(&type_name) { return Err(SimConnectError::ObjectAlreadyRegistered(type_name)); } @@ -374,12 +415,17 @@ impl SimConnect { // when `next_request_id` overflows some ids might still be in use // so we need to find the next available one - while self.registered_objects.values().any(|id| *id == request_id) { + while self + .registered_objects + .values() + .any(|obj| obj.id == request_id) + { request_id = self.next_request_id; self.next_request_id += 1; } - self.registered_objects.insert(type_name, request_id); + self.registered_objects + .insert(type_name, RegisteredObject::new(request_id, transient)); Ok(request_id) } @@ -391,7 +437,7 @@ impl SimConnect { skip(self) )] pub(super) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option { - self.registered_objects.remove(type_name) + self.registered_objects.remove(type_name).map(|obj| obj.id) } /// Get the Type Name of a Request ID. @@ -403,9 +449,51 @@ impl SimConnect { pub(super) fn get_type_name_by_request_id(&self, request_id: u32) -> Option { self.registered_objects .iter() - .find(|(_, v)| **v == request_id) + .find(|(_, v)| v.id == request_id) .map(|(k, _)| k.clone()) } + + /// 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 { + self.registered_objects + .iter() + .find(|(_, v)| v.id == request_id) + .map(|(_, v)| v.transient) + } + + /// Checks if the request is + /// 1) the last entry in the list and + /// 2) transient + /// and if yes, it gets unregistered. + #[tracing::instrument( + name = "SimConnect::unregister_potential_transient_request", + level = "trace", + fields(type_name, transient), + skip(self) + )] + pub(super) fn unregister_potential_transient_request( + &mut self, + entry_number: u32, + out_of: u32, + request_id: u32, + ) { + if entry_number + 1 >= out_of { + // This is the last entry, so we can clear the request if it's transient. + let transient = self.is_transient_request(request_id); + tracing::Span::current().record("transient", transient); + if self.is_transient_request(request_id) == Some(true) { + let type_name = self.get_type_name_by_request_id(request_id); + + if let Some(ref type_name) = type_name { + tracing::Span::current().record("type_name", type_name); + + trace!("Clearing"); + self.unregister_request_id_by_type_name(type_name); + } + } + } + } } impl Drop for SimConnect { diff --git a/simconnect-sdk/src/simconnect/facilities.rs b/simconnect-sdk/src/simconnect/facilities.rs index 03577c1..5efdbec 100644 --- a/simconnect-sdk/src/simconnect/facilities.rs +++ b/simconnect-sdk/src/simconnect/facilities.rs @@ -4,7 +4,7 @@ impl SimConnect { /// Request a list of all the facilities of a given type currently held in the facilities cache. /// /// # Errors - /// - [`crate::SimConnectError::ObjectAlreadyRegistered`] -- Only one request or subscription per facility type is allowed. If you wish to create a new one, unsubscribe first using [`crate::SimConnect::unsubscribe_to_facilities`]. + /// - [`crate::SimConnectError::ObjectAlreadyRegistered`] -- Only one request per facility type is supported. /// /// # Remarks /// The simulation keeps a facilities cache of all the airports, waypoints, NDB and VOR stations within a certain radius of the user aircraft. @@ -20,7 +20,7 @@ impl SimConnect { facility_type: FacilityType, ) -> Result<(), SimConnectError> { let type_name = facility_type.to_type_name(); - let request_id = self.new_request_id(type_name)?; + let request_id = self.new_request_id(type_name, true)?; success!(unsafe { bindings::SimConnect_RequestFacilitiesList( @@ -38,7 +38,7 @@ impl SimConnect { /// To terminate these notifications use the [`crate::SimConnect::unsubscribe_to_facilities`] function. /// /// # Errors - /// - [`crate::SimConnectError::ObjectAlreadyRegistered`] -- Only one subscription or request per facility type is allowed. If you wish to create a new one, unsubscribe first using [`crate::SimConnect::unsubscribe_to_facilities`]. + /// - [`crate::SimConnectError::ObjectAlreadyRegistered`] -- Only one subscription per facility type is supported. If you wish to create a new one, unsubscribe first using [`crate::SimConnect::unsubscribe_to_facilities`]. /// /// # Remarks /// The simulation keeps a facilities cache of all the airports, waypoints, NDB and VOR stations within a certain radius of the user aircraft. @@ -54,7 +54,7 @@ impl SimConnect { facility_type: FacilityType, ) -> Result<(), SimConnectError> { let type_name = facility_type.to_type_name(); - let request_id = self.new_request_id(type_name)?; + let request_id = self.new_request_id(type_name, false)?; success!(unsafe { bindings::SimConnect_SubscribeToFacilities( @@ -68,7 +68,7 @@ impl SimConnect { /// Request that notifications of additions to the facilities cache are not longer sent. /// /// # Remarks - /// This is used to terminate notifications generated by the [`crate::SimConnect::request_facilities_list`] or [`crate::SimConnect::subscribe_to_facilities`] functions. + /// This is used to terminate notifications generated by the [`crate::SimConnect::subscribe_to_facilities`] function. #[tracing::instrument( name = "SimConnect::unsubscribe_to_facilities", level = "debug", diff --git a/simconnect-sdk/src/simconnect/objects.rs b/simconnect-sdk/src/simconnect/objects.rs index f077034..92b77d6 100644 --- a/simconnect-sdk/src/simconnect/objects.rs +++ b/simconnect-sdk/src/simconnect/objects.rs @@ -9,7 +9,7 @@ impl SimConnect { pub fn register_object(&mut self) -> Result { let type_name: String = std::any::type_name::().into(); - let id = self.new_request_id(type_name)?; + let id = self.new_request_id(type_name, false)?; T::register(self, id)?; @@ -27,7 +27,7 @@ impl SimConnect { .ok_or_else(|| SimConnectError::ObjectNotRegistered(type_name.clone()))?; success!(unsafe { - bindings::SimConnect_ClearDataDefinition(self.handle.as_ptr(), *request_id) + bindings::SimConnect_ClearDataDefinition(self.handle.as_ptr(), request_id.id) })?; self.unregister_request_id_by_type_name(&type_name)