infinity-rs 0.2.4

Safe, idiomatic Rust bindings for the MSFS 2024 WASM SDK.
pub mod a_var;
pub mod l_var;

pub use a_var::AVar;
pub use l_var::LVar;

use crate::sys::*;

use std::{ffi::CString, marker::PhantomData, mem::MaybeUninit, os::raw::c_char};

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum VarError {
    Fs(FsVarError),
    Nul(std::ffi::NulError),
}

impl From<std::ffi::NulError> for VarError {
    fn from(e: std::ffi::NulError) -> Self {
        VarError::Nul(e)
    }
}

pub type VarResult<T> = Result<T, VarError>;

#[repr(transparent)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct UnitId(pub FsUnitId);

impl UnitId {
    pub fn from_str(unit: &str) -> VarResult<Self> {
        let unit_c = CString::new(unit)?;
        let id = unsafe { fsVarsGetUnitId(unit_c.as_ptr() as *const c_char) };
        Ok(UnitId(id))
    }
}

pub trait VarKind {
    type Id: Copy;

    fn register(name: *const c_char) -> Self::Id;

    fn get(
        id: Self::Id,
        unit: FsUnitId,
        param: FsVarParamArray,
        out: *mut f64,
        target: FsObjectId,
    ) -> FsVarError;

    fn set(
        id: Self::Id,
        unit: FsUnitId,
        param: FsVarParamArray,
        value: f64,
        target: FsObjectId,
    ) -> FsVarError;

    fn default_target() -> FsObjectId {
        FS_OBJECT_ID_USER_AIRCRAFT
    }

    fn can_set() -> bool {
        true
    }
}

#[inline]
pub fn empty_param_array() -> FsVarParamArray {
    FsVarParamArray {
        size: 0,
        array: core::ptr::null_mut(),
    }
}

#[derive(Copy, Clone)]
pub struct VarParamArray1 {
    variant: FsVarParamVariant,
}

impl VarParamArray1 {
    #[inline]
    pub fn index(index: u32) -> Self {
        let mut v: FsVarParamVariant = unsafe { core::mem::zeroed() };
        v.type_ = eFsVarParamType_FsVarParamTypeInteger;
        v.__bindgen_anon_1 = FsVarParamVariant__bindgen_ty_1 { intValue: index };
        Self { variant: v }
    }

    #[inline]
    pub fn as_raw_mut(&mut self) -> FsVarParamArray {
        FsVarParamArray {
            size: 1,
            array: &mut self.variant as *mut _,
        }
    }
}

pub struct Var<K: VarKind> {
    id: K::Id,
    unit: UnitId,
    _k: PhantomData<K>,
}

// Hand-rolled Copy/Clone/Debug so they don't demand `K: Copy/Clone/Debug`
// just because `K` appears in `PhantomData`. `K::Id` already implements
// these, which is the only field that actually carries data.
impl<K: VarKind> Copy for Var<K> where K::Id: Copy {}

impl<K: VarKind> Clone for Var<K>
where
    K::Id: Clone,
{
    fn clone(&self) -> Self {
        Self {
            id: self.id.clone(),
            unit: self.unit,
            _k: PhantomData,
        }
    }
}

impl<K: VarKind> std::fmt::Debug for Var<K>
where
    K::Id: std::fmt::Debug,
{
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Var")
            .field("id", &self.id)
            .field("unit", &self.unit)
            .finish()
    }
}

impl<K: VarKind> Var<K> {
    pub fn new(name: &str, unit: &str) -> VarResult<Self> {
        let name_c = CString::new(name)?;
        let unit = UnitId::from_str(unit)?;
        let id = K::register(name_c.as_ptr() as *const c_char);
        Ok(Self {
            id,
            unit,
            _k: PhantomData,
        })
    }

    #[inline]
    pub fn get(&self) -> VarResult<f64> {
        self.get_with(empty_param_array(), K::default_target())
    }

    #[inline]
    pub fn get_target(&self, target: FsObjectId) -> VarResult<f64> {
        self.get_with(empty_param_array(), target)
    }

    #[inline]
    pub fn get_with(&self, param: FsVarParamArray, target: FsObjectId) -> VarResult<f64> {
        let mut out = MaybeUninit::<f64>::uninit();
        let err = K::get(self.id, self.unit.0, param, out.as_mut_ptr(), target);
        if err == FsVarError_FS_VAR_ERROR_NONE {
            Ok(unsafe { out.assume_init() })
        } else {
            Err(VarError::Fs(err))
        }
    }

    #[inline]
    pub fn get_indexed(&self, index: u32) -> VarResult<f64> {
        self.get_indexed_target(index, K::default_target())
    }

    #[inline]
    pub fn get_indexed_target(&self, index: u32, target: FsObjectId) -> VarResult<f64> {
        let mut param = VarParamArray1::index(index);
        self.get_with(param.as_raw_mut(), target)
    }

    #[inline]
    pub fn set(&self, value: f64) -> VarResult<()> {
        self.set_with(empty_param_array(), value, K::default_target())
    }

    #[inline]
    pub fn set_target(&self, target: FsObjectId, value: f64) -> VarResult<()> {
        self.set_with(empty_param_array(), value, target)
    }

    #[inline]
    pub fn set_with(
        &self,
        param: FsVarParamArray,
        value: f64,
        target: FsObjectId,
    ) -> VarResult<()> {
        if !K::can_set() {
            return Err(VarError::Fs(FsVarError_FS_VAR_ERROR_NOT_SUPPORTED));
        }
        let err = K::set(self.id, self.unit.0, param, value, target);
        if err == FsVarError_FS_VAR_ERROR_NONE {
            Ok(())
        } else {
            Err(VarError::Fs(err))
        }
    }

    #[inline]
    pub fn set_indexed(&self, index: u32, value: f64) -> VarResult<()> {
        self.set_indexed_target(index, K::default_target(), value)
    }

    #[inline]
    pub fn set_indexed_target(&self, index: u32, target: FsObjectId, value: f64) -> VarResult<()> {
        let mut param = VarParamArray1::index(index);
        self.set_with(param.as_raw_mut(), value, target)
    }

    #[inline]
    pub fn unit(&self) -> UnitId {
        self.unit
    }

    #[inline]
    pub fn raw_id(&self) -> K::Id {
        self.id
    }
}