msfs/
commbus.rs

1//! Bindings for the commbus API available in the MSFS SDK.
2use crate::sys;
3use std::{
4    cell::RefCell,
5    ffi::{self, CString},
6    rc::Rc,
7    slice,
8};
9
10// SAFETY: It should be safe to use `FnMut` as callback
11// as the execution is not happen in parallel (hopefully).
12type CommBusCallback<'a> = Box<dyn FnMut(&str) + 'a>;
13
14/// Used to specify the type of module/gauge to broadcast an event to.
15#[derive(Default)]
16pub enum CommBusBroadcastFlags {
17    JS,
18    WASM,
19    WASMSelfCall,
20    #[default]
21    Default,
22    AllWASM,
23    All,
24}
25
26impl From<CommBusBroadcastFlags> for sys::FsCommBusBroadcastFlags {
27    fn from(value: CommBusBroadcastFlags) -> Self {
28        match value {
29            CommBusBroadcastFlags::JS => sys::FsCommBusBroadcastFlags_FsCommBusBroadcast_JS,
30            CommBusBroadcastFlags::WASM => sys::FsCommBusBroadcastFlags_FsCommBusBroadcast_Wasm,
31            CommBusBroadcastFlags::WASMSelfCall => {
32                sys::FsCommBusBroadcastFlags_FsCommBusBroadcast_WasmSelfCall
33            }
34            CommBusBroadcastFlags::Default => {
35                sys::FsCommBusBroadcastFlags_FsCommBusBroadcast_Default
36            }
37            CommBusBroadcastFlags::AllWASM => {
38                sys::FsCommBusBroadcastFlags_FsCommBusBroadcast_AllWasm
39            }
40            CommBusBroadcastFlags::All => sys::FsCommBusBroadcastFlags_FsCommBusBroadcast_All,
41        }
42    }
43}
44
45#[derive(Default)]
46pub struct CommBus<'a> {
47    events: Vec<Rc<RefCell<Option<CommBusEvent<'a>>>>>,
48}
49impl<'a> CommBus<'a> {
50    /// Registers to a communication event.
51    /// Returns the event if the registration was successful.
52    /// By calling `take` on the returned value the event gets unregistered.
53    pub fn register(
54        &mut self,
55        event_name: &str,
56        callback: impl FnMut(&str) + 'a,
57    ) -> Option<Rc<RefCell<Option<CommBusEvent<'a>>>>> {
58        if let Some(event) = CommBusEvent::register(event_name, callback) {
59            let event = Rc::new(RefCell::new(Some(event)));
60            self.events.push(event.clone());
61            Some(event)
62        } else {
63            None
64        }
65    }
66
67    /// Calls a communication event.
68    /// Returns `true` if the call was successful.
69    pub fn call(event_name: &str, args: &str, called: CommBusBroadcastFlags) -> bool {
70        if let (Ok(event_name), Ok(args_cstr)) = (CString::new(event_name), CString::new(args)) {
71            unsafe {
72                sys::fsCommBusCall(
73                    event_name.as_ptr(),
74                    args_cstr.as_ptr(),
75                    (args.len() + 1) as ffi::c_uint,
76                    called.into(),
77                )
78            }
79        } else {
80            false
81        }
82    }
83
84    /// Deregisters all communication events registered with this `CommBus` instance.
85    /// The same functionality can be achieved by dropping the `CommBus` instance and creating a new instance.
86    pub fn unregister_all(&mut self) {
87        for event in &self.events {
88            event.take();
89        }
90        self.events.clear();
91    }
92}
93
94/// CommBus handle. When this handle goes out of scope the callback will be unregistered.
95pub struct CommBusEvent<'a> {
96    event_name: CString,
97    callback: Box<CommBusCallback<'a>>,
98}
99impl<'a> CommBusEvent<'a> {
100    /// Registers to a communication event.
101    pub fn register(event_name: &str, callback: impl FnMut(&str) + 'a) -> Option<Self> {
102        let this = Self {
103            event_name: CString::new(event_name).ok()?,
104            callback: Box::new(Box::new(callback)),
105        };
106        let res = unsafe {
107            sys::fsCommBusRegister(
108                this.event_name.as_ptr(),
109                Some(Self::c_callback),
110                this.callback.as_ref() as *const _ as *mut _,
111            )
112        };
113        if res { Some(this) } else { None }
114    }
115
116    extern "C" fn c_callback(args: *const ffi::c_char, size: ffi::c_uint, ctx: *mut ffi::c_void) {
117        if !ctx.is_null() {
118            let (mut callback, args) = unsafe {
119                (
120                    Box::from_raw(ctx as *mut CommBusCallback<'a>),
121                    // SAFETY: because i8/u8 is 1 byte we can use size directly as length of the slice
122                    slice::from_raw_parts(args as *const u8, size as usize),
123                )
124            };
125            callback(&String::from_utf8_lossy(args));
126            // Don't free callback as it's still registered
127            Box::leak(callback);
128        }
129    }
130}
131impl Drop for CommBusEvent<'_> {
132    fn drop(&mut self) {
133        unsafe {
134            sys::fsCommBusUnregister(self.event_name.as_ptr(), Some(Self::c_callback));
135        }
136    }
137}