use std::ffi::c_void;
use std::os::raw::{c_float, c_int};
use xplm_sys::{
xplm_FlightLoop_Phase_AfterFlightModel, xplm_FlightLoop_Phase_BeforeFlightModel,
XPLMCreateFlightLoop, XPLMCreateFlightLoop_t, XPLMDestroyFlightLoop, XPLMFlightLoopID,
XPLMScheduleFlightLoop,
};
type Callback = dyn FnMut(f32, f32, i32) -> f32 + 'static;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FlightLoopPhase {
BeforeFlightModel,
AfterFlightModel,
}
impl From<FlightLoopPhase> for xplm_sys::XPLMFlightLoopPhaseType {
fn from(phase: FlightLoopPhase) -> Self {
match phase {
FlightLoopPhase::BeforeFlightModel => xplm_FlightLoop_Phase_BeforeFlightModel,
FlightLoopPhase::AfterFlightModel => xplm_FlightLoop_Phase_AfterFlightModel,
}
}
}
pub struct FlightLoop {
id: XPLMFlightLoopID,
refcon: *mut Box<Callback>,
}
unsafe impl Send for FlightLoop {}
impl FlightLoop {
pub fn new(
phase: FlightLoopPhase,
callback: impl FnMut(f32, f32, i32) -> f32 + 'static,
) -> Self {
let boxed: Box<Callback> = Box::new(callback);
let refcon = Box::into_raw(Box::new(boxed));
let mut params = XPLMCreateFlightLoop_t {
structSize: std::mem::size_of::<XPLMCreateFlightLoop_t>() as c_int,
phase: phase.into(),
callbackFunc: Some(trampoline),
refcon: refcon as *mut c_void,
};
let id = unsafe { XPLMCreateFlightLoop(&mut params) };
Self { id, refcon }
}
pub fn schedule(&self, interval: f32, relative_to_now: bool) {
unsafe {
XPLMScheduleFlightLoop(self.id, interval, relative_to_now as c_int);
}
}
}
impl Drop for FlightLoop {
fn drop(&mut self) {
unsafe {
XPLMDestroyFlightLoop(self.id);
drop(Box::from_raw(self.refcon));
}
}
}
unsafe extern "C" fn trampoline(
elapsed_since_last_call: c_float,
elapsed_time_since_last_flight_loop: c_float,
counter: c_int,
refcon: *mut c_void,
) -> c_float {
crate::guard(|| {
let callback: &mut Callback = unsafe { &mut *(*(refcon as *mut Box<Callback>)) };
callback(
elapsed_since_last_call,
elapsed_time_since_last_flight_loop,
counter,
)
})
.unwrap_or(0.0)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn trampoline_invokes_boxed_closure() {
let calls: Rc<Cell<i32>> = Rc::new(Cell::new(0));
let calls_clone = calls.clone();
let boxed: Box<Callback> = Box::new(move |elapsed, _since_loop, counter| {
calls_clone.set(calls_clone.get() + 1);
elapsed + counter as f32
});
let refcon = Box::into_raw(Box::new(boxed));
let result = unsafe { trampoline(1.5, 0.0, 3, refcon as *mut c_void) };
assert_eq!(result, 4.5);
assert_eq!(calls.get(), 1);
unsafe { drop(Box::from_raw(refcon)) };
}
#[test]
fn trampoline_catches_panic_and_returns_zero() {
let boxed: Box<Callback> = Box::new(|_, _, _| panic!("boom"));
let refcon = Box::into_raw(Box::new(boxed));
let result = unsafe { trampoline(0.0, 0.0, 0, refcon as *mut c_void) };
assert_eq!(result, 0.0);
unsafe { drop(Box::from_raw(refcon)) };
}
}