Automatically clean up request_facilities_list requests once completed
This commit is contained in:
@@ -8,6 +8,10 @@ file. This change log follows the conventions of
|
|||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Updated to MSFS SDK v0.20.5.0.
|
- 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
|
## [v0.2.1] - 2022-10-29
|
||||||
|
|
||||||
|
@@ -26,8 +26,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
// The returned list is quite large, so we look for a particular record
|
// The returned list is quite large, so we look for a particular record
|
||||||
if record.icao == "EGSC" {
|
if record.icao == "EGSC" {
|
||||||
println!("{record:?}");
|
println!("{record:?}");
|
||||||
// we've got the entry we're interesting in - we can unsubscribe now
|
// there's no need to unsubscribe
|
||||||
client.unsubscribe_to_facilities(FacilityType::Airport)?;
|
// because this is a one-off request, not a subscription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -85,7 +85,20 @@ use crate::{
|
|||||||
pub struct SimConnect {
|
pub struct SimConnect {
|
||||||
pub(super) handle: std::ptr::NonNull<c_void>,
|
pub(super) handle: std::ptr::NonNull<c_void>,
|
||||||
pub(super) next_request_id: u32,
|
pub(super) next_request_id: u32,
|
||||||
pub(super) registered_objects: HashMap<String, u32>,
|
pub(super) registered_objects: HashMap<String, RegisteredObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 {
|
impl SimConnect {
|
||||||
@@ -121,7 +134,7 @@ impl SimConnect {
|
|||||||
/// # Remarks
|
/// # Remarks
|
||||||
/// This is a non-blocking function. If there are no messages to receive, it will return None immediately.
|
/// 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.
|
/// When called in a loop, it is recommended to use a short sleep time.
|
||||||
pub fn get_next_dispatch(&self) -> Result<Option<Notification>, SimConnectError> {
|
pub fn get_next_dispatch(&mut self) -> Result<Option<Notification>, SimConnectError> {
|
||||||
let mut data_buf: *mut bindings::SIMCONNECT_RECV = std::ptr::null_mut();
|
let mut data_buf: *mut bindings::SIMCONNECT_RECV = std::ptr::null_mut();
|
||||||
let mut size_buf: bindings::DWORD = 32;
|
let mut size_buf: bindings::DWORD = 32;
|
||||||
let size_buf_pointer: *mut bindings::DWORD = &mut size_buf;
|
let size_buf_pointer: *mut bindings::DWORD = &mut size_buf;
|
||||||
@@ -212,6 +225,12 @@ impl SimConnect {
|
|||||||
let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST =
|
let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST =
|
||||||
unsafe { &*(data_buf as *const 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)
|
let data = (0..event._base.dwArraySize as usize)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
// `rgData` is defined as a 1-element array, but it is actually a variable-length array.
|
// `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 =
|
let event: &bindings::SIMCONNECT_RECV_WAYPOINT_LIST =
|
||||||
unsafe { &*(data_buf as *const 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)
|
let data = (0..event._base.dwArraySize as usize)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
// `rgData` is defined as a 1-element array, but it is actually a variable-length array.
|
// `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 =
|
let event: &bindings::SIMCONNECT_RECV_NDB_LIST =
|
||||||
unsafe { &*(data_buf as *const 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)
|
let data = (0..event._base.dwArraySize as usize)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
// `rgData` is defined as a 1-element array, but it is actually a variable-length array.
|
// `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 =
|
let event: &bindings::SIMCONNECT_RECV_VOR_LIST =
|
||||||
unsafe { &*(data_buf as *const 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)
|
let data = (0..event._base.dwArraySize as usize)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
// `rgData` is defined as a 1-element array, but it is actually a variable-length array.
|
// `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.
|
/// 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(&mut self, type_name: String) -> Result<u32, SimConnectError> {
|
pub(super) fn new_request_id(
|
||||||
|
&mut self,
|
||||||
|
type_name: String,
|
||||||
|
transient: bool,
|
||||||
|
) -> Result<u32, SimConnectError> {
|
||||||
if self.registered_objects.contains_key(&type_name) {
|
if self.registered_objects.contains_key(&type_name) {
|
||||||
return Err(SimConnectError::ObjectAlreadyRegistered(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
|
// when `next_request_id` overflows some ids might still be in use
|
||||||
// so we need to find the next available one
|
// 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;
|
request_id = self.next_request_id;
|
||||||
self.next_request_id += 1;
|
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)
|
Ok(request_id)
|
||||||
}
|
}
|
||||||
@@ -391,7 +437,7 @@ impl SimConnect {
|
|||||||
skip(self)
|
skip(self)
|
||||||
)]
|
)]
|
||||||
pub(super) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option<u32> {
|
pub(super) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option<u32> {
|
||||||
self.registered_objects.remove(type_name)
|
self.registered_objects.remove(type_name).map(|obj| obj.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the Type Name of a Request 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<String> {
|
pub(super) fn get_type_name_by_request_id(&self, request_id: u32) -> Option<String> {
|
||||||
self.registered_objects
|
self.registered_objects
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, v)| **v == request_id)
|
.find(|(_, v)| v.id == request_id)
|
||||||
.map(|(k, _)| k.clone())
|
.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<bool> {
|
||||||
|
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 {
|
impl Drop for SimConnect {
|
||||||
|
@@ -4,7 +4,7 @@ impl SimConnect {
|
|||||||
/// Request a list of all the facilities of a given type currently held in the facilities cache.
|
/// Request a list of all the facilities of a given type currently held in the facilities cache.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # 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
|
/// # Remarks
|
||||||
/// The simulation keeps a facilities cache of all the airports, waypoints, NDB and VOR stations within a certain radius of the user aircraft.
|
/// 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,
|
facility_type: FacilityType,
|
||||||
) -> Result<(), SimConnectError> {
|
) -> Result<(), SimConnectError> {
|
||||||
let type_name = facility_type.to_type_name();
|
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 {
|
success!(unsafe {
|
||||||
bindings::SimConnect_RequestFacilitiesList(
|
bindings::SimConnect_RequestFacilitiesList(
|
||||||
@@ -38,7 +38,7 @@ impl SimConnect {
|
|||||||
/// To terminate these notifications use the [`crate::SimConnect::unsubscribe_to_facilities`] function.
|
/// To terminate these notifications use the [`crate::SimConnect::unsubscribe_to_facilities`] function.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # 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
|
/// # Remarks
|
||||||
/// The simulation keeps a facilities cache of all the airports, waypoints, NDB and VOR stations within a certain radius of the user aircraft.
|
/// 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,
|
facility_type: FacilityType,
|
||||||
) -> Result<(), SimConnectError> {
|
) -> Result<(), SimConnectError> {
|
||||||
let type_name = facility_type.to_type_name();
|
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 {
|
success!(unsafe {
|
||||||
bindings::SimConnect_SubscribeToFacilities(
|
bindings::SimConnect_SubscribeToFacilities(
|
||||||
@@ -68,7 +68,7 @@ impl SimConnect {
|
|||||||
/// Request that notifications of additions to the facilities cache are not longer sent.
|
/// Request that notifications of additions to the facilities cache are not longer sent.
|
||||||
///
|
///
|
||||||
/// # Remarks
|
/// # 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(
|
#[tracing::instrument(
|
||||||
name = "SimConnect::unsubscribe_to_facilities",
|
name = "SimConnect::unsubscribe_to_facilities",
|
||||||
level = "debug",
|
level = "debug",
|
||||||
|
@@ -9,7 +9,7 @@ impl SimConnect {
|
|||||||
pub fn register_object<T: SimConnectObjectExt>(&mut self) -> Result<u32, SimConnectError> {
|
pub fn register_object<T: SimConnectObjectExt>(&mut self) -> Result<u32, SimConnectError> {
|
||||||
let type_name: String = std::any::type_name::<T>().into();
|
let type_name: String = std::any::type_name::<T>().into();
|
||||||
|
|
||||||
let id = self.new_request_id(type_name)?;
|
let id = self.new_request_id(type_name, false)?;
|
||||||
|
|
||||||
T::register(self, id)?;
|
T::register(self, id)?;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ impl SimConnect {
|
|||||||
.ok_or_else(|| SimConnectError::ObjectNotRegistered(type_name.clone()))?;
|
.ok_or_else(|| SimConnectError::ObjectNotRegistered(type_name.clone()))?;
|
||||||
|
|
||||||
success!(unsafe {
|
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)
|
self.unregister_request_id_by_type_name(&type_name)
|
||||||
|
Reference in New Issue
Block a user