simconnect_sdk/simconnect/
base.rs1use std::{collections::HashMap, ffi::c_void};
2
3use tracing::{error, span, trace, warn, Level};
4
5use crate::domain::{
6 Airport, ClientEvent, ClientEventRequest, Notification, Object, SystemEvent,
7 SystemEventRequest, Waypoint, CLIENT_EVENT_DISCRIMINANT_START, NDB, VOR,
8};
9use crate::helpers::fixed_c_str_to_string;
10use crate::simconnect::EventRegister;
11use crate::{as_c_string, bindings, ok_if_fail, success, SimConnectError};
12
13#[derive(Debug)]
87pub struct SimConnect {
88 pub(crate) handle: std::ptr::NonNull<c_void>,
89 pub(crate) next_request_id: u32,
90 pub(crate) registered_objects: HashMap<String, RegisteredObject>,
91 pub(crate) system_event_register: EventRegister<SystemEventRequest>,
92 pub(crate) client_event_register: EventRegister<ClientEventRequest>,
93}
94
95#[derive(Debug)]
97pub(crate) struct RegisteredObject {
98 pub id: u32,
99 pub transient: bool,
100}
101
102impl RegisteredObject {
103 pub(crate) fn new(id: u32, transient: bool) -> Self {
104 Self { id, transient }
105 }
106}
107
108impl SimConnect {
109 #[tracing::instrument(name = "SimConnect::new", level = "debug")]
111 pub fn new(name: &str) -> Result<Self, SimConnectError> {
112 let mut handle = std::ptr::null_mut();
113
114 success!(unsafe {
115 bindings::SimConnect_Open(
116 &mut handle,
117 as_c_string!(name),
118 std::ptr::null_mut(),
119 0,
120 std::ptr::null_mut(),
121 0,
122 )
123 })?;
124
125 Ok(Self {
126 handle: std::ptr::NonNull::new(handle).ok_or_else(|| {
127 SimConnectError::UnexpectedError(
128 "SimConnect_Open returned null pointer on success".to_string(),
129 )
130 })?,
131 next_request_id: 0,
132 registered_objects: HashMap::new(),
133 system_event_register: EventRegister::new(),
134 client_event_register: EventRegister::new(),
135 })
136 }
137
138 pub fn get_next_dispatch(&mut self) -> Result<Option<Notification>, SimConnectError> {
144 let mut data_buf: *mut bindings::SIMCONNECT_RECV = std::ptr::null_mut();
145 let mut size_buf: bindings::DWORD = 32;
146 let size_buf_pointer: *mut bindings::DWORD = &mut size_buf;
147
148 unsafe {
149 ok_if_fail!(
150 bindings::SimConnect_GetNextDispatch(
151 self.handle.as_ptr(),
152 &mut data_buf,
153 size_buf_pointer
154 ),
155 None
156 );
157 };
158
159 let recv_id = unsafe { (*data_buf).dwID as i32 };
160
161 if recv_id == bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL {
162 Ok(None)
163 } else {
164 let span = span!(Level::TRACE, "SimConnect::get_next_dispatch");
165 let _enter = span.enter();
166
167 match recv_id {
168 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_OPEN => {
169 trace!("Received SIMCONNECT_RECV_OPEN");
170 Ok(Some(Notification::Open))
171 }
172 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_QUIT => {
173 trace!("Received SIMCONNECT_RECV_QUIT");
174 Ok(Some(Notification::Quit))
175 }
176 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT => {
177 trace!("Received SIMCONNECT_RECV_EVENT");
178 let event: &bindings::SIMCONNECT_RECV_EVENT =
179 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT) };
180
181 if event.uEventID >= CLIENT_EVENT_DISCRIMINANT_START {
182 let event = ClientEvent::try_from(event)?;
183
184 Ok(Some(Notification::ClientEvent(event)))
185 } else {
186 let event = SystemEvent::try_from(event)?;
187
188 Ok(Some(Notification::SystemEvent(event)))
189 }
190 }
191 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT_FILENAME => {
192 trace!("Received SIMCONNECT_RECV_EVENT_FILENAME");
193 let event: &bindings::SIMCONNECT_RECV_EVENT_FILENAME =
194 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT_FILENAME) };
195
196 let event = SystemEvent::try_from(event)?;
197 Ok(Some(Notification::SystemEvent(event)))
198 }
199 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EVENT_FRAME => {
200 trace!("Received SIMCONNECT_RECV_EVENT_FRAME");
201 let event: &bindings::SIMCONNECT_RECV_EVENT_FRAME =
202 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EVENT_FRAME) };
203
204 let event = SystemEvent::try_from(event)?;
205 Ok(Some(Notification::SystemEvent(event)))
206 }
207 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_SIMOBJECT_DATA => {
208 trace!("Received SIMCONNECT_RECV_SIMOBJECT_DATA");
209
210 let event: &bindings::SIMCONNECT_RECV_SIMOBJECT_DATA =
211 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_SIMOBJECT_DATA) };
212
213 let type_name = self.get_type_name_by_request_id(event.dwDefineID);
214
215 match type_name {
216 Some(type_name) => {
217 let data = Object {
218 type_name,
219 data_addr: std::ptr::addr_of!(event.dwData),
220 };
221
222 Ok(Some(Notification::Object(data)))
223 }
224 _ => Ok(None),
225 }
226 }
227 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_AIRPORT_LIST => {
228 trace!("Received SIMCONNECT_RECV_AIRPORT_LIST");
229
230 let event: &bindings::SIMCONNECT_RECV_AIRPORT_LIST =
231 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_AIRPORT_LIST) };
232
233 self.unregister_potential_transient_request(
234 event._base.dwEntryNumber,
235 event._base.dwOutOf,
236 event._base.dwRequestID,
237 );
238
239 let data = (0..event._base.dwArraySize as usize)
240 .map(|i| {
241 let record = unsafe { event.rgData.get_unchecked(i) };
243
244 Airport {
245 icao: fixed_c_str_to_string(&record.Icao),
246 lat: record.Latitude,
247 lon: record.Longitude,
248 alt: record.Altitude,
249 }
250 })
251 .collect::<Vec<_>>();
252
253 Ok(Some(Notification::AirportList(data)))
254 }
255 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_WAYPOINT_LIST => {
256 trace!("Received SIMCONNECT_RECV_WAYPOINT_LIST");
257
258 let event: &bindings::SIMCONNECT_RECV_WAYPOINT_LIST =
259 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_WAYPOINT_LIST) };
260
261 self.unregister_potential_transient_request(
262 event._base.dwEntryNumber,
263 event._base.dwOutOf,
264 event._base.dwRequestID,
265 );
266
267 let data = (0..event._base.dwArraySize as usize)
268 .map(|i| {
269 let record = unsafe { event.rgData.get_unchecked(i) };
271
272 Waypoint {
273 icao: fixed_c_str_to_string(&record._base.Icao),
274 lat: record._base.Latitude,
275 lon: record._base.Longitude,
276 alt: record._base.Altitude,
277 mag_var: record.fMagVar,
278 }
279 })
280 .collect::<Vec<_>>();
281
282 Ok(Some(Notification::WaypointList(data)))
283 }
284 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NDB_LIST => {
285 trace!("Received SIMCONNECT_RECV_NDB_LIST");
286
287 let event: &bindings::SIMCONNECT_RECV_NDB_LIST =
288 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_NDB_LIST) };
289
290 self.unregister_potential_transient_request(
291 event._base.dwEntryNumber,
292 event._base.dwOutOf,
293 event._base.dwRequestID,
294 );
295
296 let data = (0..event._base.dwArraySize as usize)
297 .map(|i| {
298 let record = unsafe { event.rgData.get_unchecked(i) };
300
301 NDB {
302 icao: fixed_c_str_to_string(&record._base._base.Icao),
303 lat: record._base._base.Latitude,
304 lon: record._base._base.Longitude,
305 alt: record._base._base.Altitude,
306 mag_var: record._base.fMagVar,
307 frequency: record.fFrequency,
308 }
309 })
310 .collect::<Vec<_>>();
311
312 Ok(Some(Notification::NdbList(data)))
313 }
314 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_VOR_LIST => {
315 trace!("Received SIMCONNECT_RECV_VOR_LIST");
316
317 let event: &bindings::SIMCONNECT_RECV_VOR_LIST =
318 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_VOR_LIST) };
319
320 self.unregister_potential_transient_request(
321 event._base.dwEntryNumber,
322 event._base.dwOutOf,
323 event._base.dwRequestID,
324 );
325
326 let data = (0..event._base.dwArraySize as usize)
327 .map(|i| {
328 let record = unsafe { event.rgData.get_unchecked(i) };
330
331 let has_nav_signal = record.Flags
332 & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL
333 == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_NAV_SIGNAL;
334 let has_localizer = record.Flags
335 & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER
336 == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_LOCALIZER;
337 let has_glide_slope = record.Flags
338 & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE
339 == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_GLIDE_SLOPE;
340 let has_dme = record.Flags
341 & bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME
342 == bindings::SIMCONNECT_RECV_ID_VOR_LIST_HAS_DME;
343
344 VOR {
345 icao: fixed_c_str_to_string(&record._base._base._base.Icao),
346 lat: record._base._base._base.Latitude,
347 lon: record._base._base._base.Longitude,
348 alt: record._base._base._base.Altitude,
349 mag_var: record._base._base.fMagVar,
350 has_nav_signal,
351 has_localizer,
352 has_glide_slope,
353 has_dme,
354 localizer: if has_localizer {
355 Some(record.fLocalizer)
356 } else {
357 None
358 },
359 glide_lat: if has_nav_signal {
360 Some(record.GlideLat)
361 } else {
362 None
363 },
364 glide_lon: if has_nav_signal {
365 Some(record.GlideLon)
366 } else {
367 None
368 },
369 glide_alt: if has_nav_signal {
370 Some(record.GlideAlt)
371 } else {
372 None
373 },
374 glide_slope_angle: if has_glide_slope {
375 Some(record.fGlideSlopeAngle)
376 } else {
377 None
378 },
379 frequency: if has_dme {
380 Some(record._base.fFrequency)
381 } else {
382 None
383 },
384 }
385 })
386 .collect::<Vec<_>>();
387
388 Ok(Some(Notification::VorList(data)))
389 }
390 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_EXCEPTION => {
391 let event: &bindings::SIMCONNECT_RECV_EXCEPTION =
392 unsafe { &*(data_buf as *const bindings::SIMCONNECT_RECV_EXCEPTION) };
393
394 warn!("Received {:?}", event);
395
396 Err(SimConnectError::SimConnectException(event.dwException))
397 }
398 bindings::SIMCONNECT_RECV_ID_SIMCONNECT_RECV_ID_NULL => Ok(None),
399 id => {
400 error!("Received unhandled notification ID: {}", id);
401 Err(SimConnectError::UnimplementedNotification(id))
402 }
403 }
404 }
405 }
406
407 #[tracing::instrument(name = "SimConnect::new_request_id", level = "trace", skip(self))]
409 pub(crate) fn new_request_id(
410 &mut self,
411 type_name: String,
412 transient: bool,
413 ) -> Result<u32, SimConnectError> {
414 if self.registered_objects.contains_key(&type_name) {
415 return Err(SimConnectError::ObjectAlreadyRegistered(type_name));
416 }
417
418 let mut request_id = self.next_request_id;
419 self.next_request_id += 1;
420
421 while self
424 .registered_objects
425 .values()
426 .any(|obj| obj.id == request_id)
427 {
428 request_id = self.next_request_id;
429 self.next_request_id += 1;
430 }
431
432 self.registered_objects
433 .insert(type_name, RegisteredObject::new(request_id, transient));
434
435 Ok(request_id)
436 }
437
438 #[tracing::instrument(
440 name = "SimConnect::unregister_request_id_by_type_name",
441 level = "trace",
442 skip(self)
443 )]
444 pub(crate) fn unregister_request_id_by_type_name(&mut self, type_name: &str) -> Option<u32> {
445 self.registered_objects.remove(type_name).map(|obj| obj.id)
446 }
447
448 #[tracing::instrument(
450 name = "SimConnect::get_type_name_by_request_id",
451 level = "trace",
452 skip(self)
453 )]
454 pub(crate) fn get_type_name_by_request_id(&self, request_id: u32) -> Option<String> {
455 self.registered_objects
456 .iter()
457 .find(|(_, v)| v.id == request_id)
458 .map(|(k, _)| k.clone())
459 }
460
461 #[tracing::instrument(name = "SimConnect::is_transient_request", level = "trace", skip(self))]
463 pub(crate) fn is_transient_request(&self, request_id: u32) -> Option<bool> {
464 self.registered_objects
465 .iter()
466 .find(|(_, v)| v.id == request_id)
467 .map(|(_, v)| v.transient)
468 }
469
470 #[tracing::instrument(
475 name = "SimConnect::unregister_potential_transient_request",
476 level = "trace",
477 fields(type_name, transient),
478 skip(self)
479 )]
480 pub(crate) fn unregister_potential_transient_request(
481 &mut self,
482 entry_number: u32,
483 out_of: u32,
484 request_id: u32,
485 ) {
486 if entry_number + 1 >= out_of {
487 let transient = self.is_transient_request(request_id);
489 tracing::Span::current().record("transient", transient);
490 if self.is_transient_request(request_id) == Some(true) {
491 let type_name = self.get_type_name_by_request_id(request_id);
492
493 if let Some(ref type_name) = type_name {
494 tracing::Span::current().record("type_name", type_name);
495
496 trace!("Clearing");
497 self.unregister_request_id_by_type_name(type_name);
498 }
499 }
500 }
501 }
502}
503
504impl Drop for SimConnect {
505 #[tracing::instrument(name = "SimConnect::drop", level = "debug", skip(self))]
506 fn drop(&mut self) {
507 let _ = unsafe { bindings::SimConnect_Close(self.handle.as_ptr()) };
508 }
509}