Skip to content

Commit a79dd3a

Browse files
committed
msfs 2024 gauge support
1 parent 4dba29f commit a79dd3a

File tree

2 files changed

+290
-1
lines changed

2 files changed

+290
-1
lines changed

msfs/src/msfs.rs

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
use crate::{executor, sys};
22

3+
impl sys::sGaugeInstallData {
4+
/// Get the width of the target gauge texture.
5+
pub fn width(&self) -> usize {
6+
self.iSizeX as usize
7+
}
8+
9+
/// Get the height of the target gauge texture.
10+
pub fn height(&self) -> usize {
11+
self.iSizeY as usize
12+
}
13+
14+
/// Get the optional parameter string passed to the gauge.
15+
pub fn parameters(&self) -> Option<&std::ffi::CStr> {
16+
if self.strParameters.is_null() {
17+
None
18+
} else {
19+
Some(unsafe { std::ffi::CStr::from_ptr(self.strParameters) })
20+
}
21+
}
22+
}
23+
324
impl sys::sGaugeDrawData {
425
/// Get the width of the target instrument texture.
526
pub fn width(&self) -> usize {
@@ -18,7 +39,7 @@ impl sys::sGaugeDrawData {
1839
}
1940

2041
use crate::sim_connect::{SimConnect, SimConnectRecv};
21-
pub use msfs_derive::{gauge, standalone_module, system};
42+
pub use msfs_derive::{gauge, gauge2024, standalone_module, system};
2243

2344
/// Used in Gauges to dispatch lifetime events, mouse events, and SimConnect events.
2445
#[derive(Debug)]
@@ -243,6 +264,152 @@ impl GaugeExecutor {
243264
}
244265
}
245266

267+
/// Event data for MSFS 2024 gauges, containing delta time and the event type.
268+
pub struct Gauge2024Data<'a> {
269+
pub delta_time: f32,
270+
pub event: Gauge2024Event<'a>,
271+
}
272+
273+
/// Events dispatched to MSFS 2024 gauges.
274+
#[derive(Debug)]
275+
pub enum Gauge2024Event<'a> {
276+
/// Gauge initialization event with install data.
277+
Init(&'a sys::sGaugeInstallData),
278+
/// Gauge update event (called each frame, no drawing allowed).
279+
Update,
280+
/// Gauge draw event (called each frame, drawing is allowed).
281+
Draw(&'a sys::sGaugeDrawData),
282+
/// Gauge kill/cleanup event.
283+
Kill,
284+
/// Mouse input event.
285+
Mouse { x: f32, y: f32, flags: i32 },
286+
/// SimConnect event.
287+
SimConnect(SimConnectRecv<'a>),
288+
}
289+
290+
/// Handle to an MSFS 2024 gauge used in async gauge callbacks.
291+
pub struct Gauge2024 {
292+
executor: *mut Gauge2024Executor,
293+
rx: futures::channel::mpsc::Receiver<Gauge2024Data<'static>>,
294+
}
295+
296+
impl Gauge2024 {
297+
/// Send a request to the Microsoft Flight Simulator server to open up communications with a new client.
298+
pub fn open_simconnect<'a>(
299+
&self,
300+
name: &str,
301+
) -> Result<std::pin::Pin<Box<crate::sim_connect::SimConnect<'a>>>, Box<dyn std::error::Error>>
302+
{
303+
let executor = self.executor;
304+
let sim = crate::sim_connect::SimConnect::open(name, move |_sim, recv| {
305+
let executor = unsafe { &mut *executor };
306+
let recv =
307+
unsafe { std::mem::transmute::<SimConnectRecv<'_>, SimConnectRecv<'static>>(recv) };
308+
let data = Gauge2024Data {
309+
delta_time: 0.,
310+
event: Gauge2024Event::SimConnect(recv),
311+
};
312+
executor
313+
.executor
314+
.send(Some(data))
315+
.unwrap();
316+
})?;
317+
Ok(sim)
318+
}
319+
320+
/// Create a NanoVG rendering context. See `Context` for more details.
321+
#[cfg(any(target_arch = "wasm32", doc))]
322+
pub fn create_nanovg(&self) -> Option<crate::nvg::Context> {
323+
crate::nvg::Context::create(unsafe { (*self.executor).fs_ctx.unwrap() })
324+
}
325+
326+
/// Consume the next event from MSFS.
327+
pub fn next_event(&mut self) -> impl futures::Future<Output = Option<Gauge2024Data<'_>>> + '_ {
328+
use futures::stream::StreamExt;
329+
async move { self.rx.next().await }
330+
}
331+
}
332+
333+
#[doc(hidden)]
334+
pub struct Gauge2024Executor {
335+
pub fs_ctx: Option<sys::FsContext>,
336+
pub executor: executor::Executor<Gauge2024, Gauge2024Data<'static>>,
337+
}
338+
339+
#[doc(hidden)]
340+
impl Gauge2024Executor {
341+
pub fn handle_gauge_init(
342+
&mut self,
343+
ctx: sys::FsContext,
344+
p_install_data: *const sys::sGaugeInstallData,
345+
) -> bool {
346+
let executor = self as *mut Gauge2024Executor;
347+
self.fs_ctx = Some(ctx);
348+
if self.executor
349+
.start(Box::new(move |rx| Gauge2024 { executor, rx }))
350+
.is_err()
351+
{
352+
return false;
353+
}
354+
let data = Gauge2024Data {
355+
delta_time: 0.,
356+
event: Gauge2024Event::Init(unsafe { &*p_install_data }),
357+
};
358+
// Transmute to static lifetime - safe because we process synchronously
359+
let data = unsafe { std::mem::transmute::<Gauge2024Data<'_>, Gauge2024Data<'static>>(data) };
360+
self.executor.send(Some(data)).is_ok()
361+
}
362+
363+
pub fn handle_gauge_update(
364+
&mut self,
365+
ctx: sys::FsContext,
366+
delta_time: f32,
367+
) -> bool {
368+
self.fs_ctx = Some(ctx);
369+
let data = Gauge2024Data {
370+
delta_time,
371+
event: Gauge2024Event::Update,
372+
};
373+
self.executor.send(Some(data)).is_ok()
374+
}
375+
376+
pub fn handle_gauge_draw(
377+
&mut self,
378+
ctx: sys::FsContext,
379+
p_draw_data: *const sys::sGaugeDrawData,
380+
) -> bool {
381+
self.fs_ctx = Some(ctx);
382+
let data = Gauge2024Data {
383+
delta_time: unsafe { (*p_draw_data).dt as f32 },
384+
event: Gauge2024Event::Draw(unsafe { &*p_draw_data }),
385+
};
386+
// Transmute to static lifetime - safe because we process synchronously
387+
let data = unsafe { std::mem::transmute::<Gauge2024Data<'_>, Gauge2024Data<'static>>(data) };
388+
self.executor.send(Some(data)).is_ok()
389+
}
390+
391+
pub fn handle_gauge_kill(&mut self, ctx: sys::FsContext) -> bool {
392+
self.fs_ctx = Some(ctx);
393+
let data = Gauge2024Data {
394+
delta_time: 0.,
395+
event: Gauge2024Event::Kill,
396+
};
397+
if !self.executor.send(Some(data)).is_ok() {
398+
return false;
399+
}
400+
self.executor.send(None).is_ok()
401+
}
402+
403+
pub fn handle_mouse(&mut self, ctx: sys::FsContext, x: f32, y: f32, flags: i32) {
404+
self.fs_ctx = Some(ctx);
405+
let data = Gauge2024Data {
406+
delta_time: 0.,
407+
event: Gauge2024Event::Mouse { x, y, flags },
408+
};
409+
let _ = self.executor.send(Some(data));
410+
}
411+
}
412+
246413
pub struct StandaloneModule {
247414
executor: *mut StandaloneModuleExecutor,
248415
rx: futures::channel::mpsc::Receiver<SimConnectRecv<'static>>,

msfs_derive/src/lib.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,128 @@ pub fn gauge(args: TokenStream, item: TokenStream) -> TokenStream {
221221
TokenStream::from(output)
222222
}
223223

224+
/// Declare a gauge callback using the new MSFS 2024 gauge API.
225+
/// This generates the new-style callbacks: `NAME_gauge_init`, `NAME_gauge_update`,
226+
/// `NAME_gauge_draw`, `NAME_gauge_kill`, and `NAME_gauge_mouse_handler`.
227+
///
228+
/// ```rs
229+
/// use futures::stream::{Stream, StreamExt};
230+
/// // Declare and export MSFS 2024 gauge callbacks
231+
/// #[msfs::gauge2024]
232+
/// async fn FOO(mut gauge: msfs::Gauge2024) -> Result<(), Box<dyn std::error::Error>> {
233+
/// while let Some(event) = gauge.next_event().await {
234+
/// match event.event {
235+
/// msfs::Gauge2024Event::Init(install_data) => { /* initialization */ }
236+
/// msfs::Gauge2024Event::Update => { /* update logic, no drawing */ }
237+
/// msfs::Gauge2024Event::Draw(draw_data) => { /* drawing */ }
238+
/// msfs::Gauge2024Event::Kill => { /* cleanup */ }
239+
/// msfs::Gauge2024Event::Mouse { x, y, flags } => { /* mouse input */ }
240+
/// _ => {}
241+
/// }
242+
/// }
243+
/// Ok(())
244+
/// }
245+
/// ```
246+
///
247+
/// The macro can also be given a parameter, `name`, to rename the exported functions.
248+
/// ```rs
249+
/// // Declare and export FOO_gauge_init, FOO_gauge_update, etc.
250+
/// #[msfs::gauge2024(name=FOO)]
251+
/// async fn xyz(...) {}
252+
/// ```
253+
#[proc_macro_attribute]
254+
pub fn gauge2024(args: TokenStream, item: TokenStream) -> TokenStream {
255+
let args = parse_macro_input!(args as GaugeArgs);
256+
let input = parse_macro_input!(item as ItemFn);
257+
258+
let rusty_name = input.sig.ident.clone();
259+
let executor_name = format_ident!(
260+
"{}_executor_do_not_use_or_you_will_be_fired",
261+
input.sig.ident
262+
);
263+
264+
let extern_name = args.name.unwrap_or_else(|| input.sig.ident.to_string());
265+
let extern_gauge_init = format_ident!("{}_gauge_init", extern_name);
266+
let extern_gauge_update = format_ident!("{}_gauge_update", extern_name);
267+
let extern_gauge_draw = format_ident!("{}_gauge_draw", extern_name);
268+
let extern_gauge_kill = format_ident!("{}_gauge_kill", extern_name);
269+
let extern_mouse_handler = format_ident!("{}_gauge_mouse_handler", extern_name);
270+
271+
let output = quote! {
272+
#input
273+
274+
// SAFETY: it is safe to create references of this static since all WASM modules are single threaded
275+
// and there is only 1 reference in use at all times
276+
#[allow(non_upper_case_globals)]
277+
static mut #executor_name: ::msfs::Gauge2024Executor = ::msfs::Gauge2024Executor {
278+
fs_ctx: None,
279+
executor: ::msfs::executor::Executor {
280+
handle: |gauge| std::boxed::Box::pin(#rusty_name(gauge)),
281+
tx: None,
282+
future: None,
283+
},
284+
};
285+
286+
#[doc(hidden)]
287+
#[no_mangle]
288+
pub extern "C" fn #extern_gauge_init(
289+
ctx: ::msfs::sys::FsContext,
290+
p_install_data: *const ::msfs::sys::sGaugeInstallData,
291+
) -> bool {
292+
unsafe {
293+
::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_gauge_init(ctx, p_install_data))
294+
}
295+
}
296+
297+
#[doc(hidden)]
298+
#[no_mangle]
299+
pub extern "C" fn #extern_gauge_update(
300+
ctx: ::msfs::sys::FsContext,
301+
d_time: std::os::raw::c_float,
302+
) -> bool {
303+
unsafe {
304+
::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_gauge_update(ctx, d_time))
305+
}
306+
}
307+
308+
#[doc(hidden)]
309+
#[no_mangle]
310+
pub extern "C" fn #extern_gauge_draw(
311+
ctx: ::msfs::sys::FsContext,
312+
p_draw_data: *const ::msfs::sys::sGaugeDrawData,
313+
) -> bool {
314+
unsafe {
315+
::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_gauge_draw(ctx, p_draw_data))
316+
}
317+
}
318+
319+
#[doc(hidden)]
320+
#[no_mangle]
321+
pub extern "C" fn #extern_gauge_kill(
322+
ctx: ::msfs::sys::FsContext,
323+
) -> bool {
324+
unsafe {
325+
::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_gauge_kill(ctx))
326+
}
327+
}
328+
329+
#[doc(hidden)]
330+
#[no_mangle]
331+
pub extern "C" fn #extern_mouse_handler(
332+
ctx: ::msfs::sys::FsContext,
333+
fx: std::os::raw::c_float,
334+
fy: std::os::raw::c_float,
335+
i_flags: std::os::raw::c_int,
336+
) {
337+
unsafe {
338+
::msfs::wrap_executor(&raw mut #executor_name, |e| e.handle_mouse(ctx, fx, fy, i_flags));
339+
}
340+
}
341+
};
342+
343+
TokenStream::from(output)
344+
}
345+
224346
fn parse_struct_fields(
225347
input: &mut ItemStruct,
226348
attributes: &[&str],

0 commit comments

Comments
 (0)