use std::{
ffi::{c_void, CString, NulError},
marker::PhantomData,
};
use snafu::prelude::*;
use xplane_sys::{XPLMFindDataRef, XPLMShareData, XPLMUnshareData};
use crate::{
data::{borrowed::DataRef, DataType, ReadWrite},
make_x, XPAPI,
};
#[derive(Debug, Snafu)]
pub enum SharedDataError {
#[snafu(context(false))]
Nul {
source: NulError,
},
#[snafu(display(
"XPLMShareData returned 0. The shared data already exists, but is of the wrong type."
))]
WrongType,
}
pub struct SharedData<T: DataType + ?Sized + 'static> {
ctx: *mut SharedDataContext<T>,
_phantom: PhantomData<(*mut (), T)>,
}
impl<T: DataType + ?Sized + 'static> SharedData<T> {
pub(super) fn new<S: Into<Vec<u8>>>(
name: S,
handler: impl SharedDataHandler<T>,
) -> Result<SharedData<T>, SharedDataError> {
let name = CString::new(name)?;
let handler: *mut dyn SharedDataHandler<T> = Box::into_raw(Box::new(handler));
let ctx = Box::into_raw(Box::new(SharedDataContext {
name,
dref: None,
handler,
_phantom: PhantomData,
}));
let res = unsafe {
XPLMShareData(
(*ctx).name.as_ptr(),
T::sim_type(),
Some(handle_shared_data_change::<T>),
ctx.cast(),
)
};
if res == 1 {
Ok(SharedData {
ctx,
_phantom: PhantomData,
})
} else {
WrongTypeSnafu.fail()
}
}
}
impl<T: DataType + ?Sized + 'static> Drop for SharedData<T> {
fn drop(&mut self) {
unsafe {
let _ = XPLMUnshareData(
(*self.ctx).name.as_ptr(),
T::sim_type(),
Some(handle_shared_data_change::<T>),
self.ctx.cast(),
);
}
let _ = unsafe { Box::from_raw(self.ctx) };
}
}
struct SharedDataContext<T: DataType + ?Sized + 'static> {
name: CString,
dref: Option<DataRef<T, ReadWrite>>,
handler: *mut dyn SharedDataHandler<T>,
_phantom: PhantomData<(*mut (), T)>,
}
impl<T: DataType + ?Sized> Drop for SharedDataContext<T> {
fn drop(&mut self) {
let _ = unsafe { Box::from_raw(self.handler) };
}
}
pub trait SharedDataHandler<T>: 'static
where
T: DataType + ?Sized + 'static,
{
fn data_changed(&mut self, x: &mut XPAPI, dref: &mut DataRef<T, ReadWrite>);
}
unsafe extern "C-unwind" fn handle_shared_data_change<T: DataType + ?Sized + 'static>(
refcon: *mut c_void,
) {
let ctx = unsafe {
refcon.cast::<SharedDataContext<T>>().as_mut().unwrap() };
let dref = if let Some(ref mut dref) = ctx.dref {
dref
} else {
let dref = DataRef {
id: unsafe {
XPLMFindDataRef(ctx.name.as_ptr()) },
_phantom: PhantomData,
};
ctx.dref = Some(dref);
ctx.dref.as_mut().unwrap() };
let cb = unsafe { ctx.handler.as_mut().unwrap() }; let mut x = make_x();
cb.data_changed(&mut x, dref);
}