fmi-export 0.2.0

FMU export support for FMI 3.0
Documentation
use fmi::fmi3::{Fmi3Error, Fmi3Res, GetSet, binding};

use crate::fmi3::{
    Model, UserModel,
    traits::{Context, ModelGetSet},
};

/// Macro to generate getter implementations for ModelInstance
macro_rules! instance_getter {
    ($name:ident, $ty:ty) => {
        paste::paste! {
            fn [<get_ $name>](
                &mut self,
                vrs: &[binding::fmi3ValueReference],
                values: &mut [$ty],
            ) -> Result<Fmi3Res, Fmi3Error> {
                if self.is_dirty_values {
                    self.model.calculate_values(&self.context)?;
                    self.is_dirty_values = false;
                }
                let mut value_index = 0;
                for vr in vrs.iter() {
                    if *vr == 0 {
                        // 'time VR is not valid here
                        return Err(Fmi3Error::Error);
                    }
                    let elements_read = self.model.[<get_ $name>](*vr-1, &mut values[value_index..], &self.context)?;
                    value_index += elements_read;
                }
                Ok(Fmi3Res::OK)
            }
        }
    };
}

/// Macro to generate setter implementations for ModelInstance
macro_rules! instance_setter {
    ($name:ident, $ty:ty) => {
        paste::paste! {
            fn [<set_ $name>](
                &mut self,
                vrs: &[binding::fmi3ValueReference],
                values: &[$ty],
            ) -> Result<Fmi3Res, Fmi3Error> {
                // Validate variable setting restrictions before setting values
                let mut value_index = 0;
                for vr in vrs.iter() {
                    if *vr == 0 {
                        // 'time' VR is not settable
                        return Err(Fmi3Error::Error);
                    }
                    self.validate_variable_setting(*vr - 1)?;
                    let elements_written = self.model.[<set_ $name>](*vr-1, &values[value_index..], &self.context)?;
                    value_index += elements_written;
                }

                self.is_dirty_values = true;
                Ok(Fmi3Res::OK)
            }
        }
    };
}

/// Macro to generate both getter and setter for standard types
macro_rules! instance_getter_setter {
    ($name:ident, $ty:ty) => {
        instance_getter!($name, $ty);
        instance_setter!($name, $ty);
    };
}

/// Blanket implementation of the GetSet trait for ModelInstance.
///
/// These implementations delegate to the underlying user model's get/set methods which are generated by the derive macro).
/// Here we have special handling for the 'time' variable reference (implicitly always VR=0).
///
/// The generated get/set methods *do not* take into account 'time' as VR=0, and expect VRs to be zero-based indices into
/// the model's variables.
///
/// Only the float64 getter has special handling for 'time' VR=0, as it is the only type that can represent time.

impl<M, C> GetSet for super::ModelInstance<M, C>
where
    M: Model + UserModel + ModelGetSet<M>,
    C: Context<M>,
{
    // Standard getter/setter pairs
    instance_getter_setter!(boolean, bool);
    instance_getter_setter!(float32, f32);
    instance_getter_setter!(int8, i8);
    instance_getter_setter!(int16, i16);
    instance_getter_setter!(int32, i32);
    instance_getter_setter!(int64, i64);
    instance_getter_setter!(uint8, u8);
    instance_getter_setter!(uint16, u16);
    instance_getter_setter!(uint32, u32);
    instance_getter_setter!(uint64, u64);

    fn get_float64(
        &mut self,
        vrs: &[binding::fmi3ValueReference],
        values: &mut [f64],
    ) -> Result<Fmi3Res, Fmi3Error> {
        if self.is_dirty_values {
            self.model.calculate_values(&self.context)?;
            self.is_dirty_values = false;
        }
        let mut value_index = 0;
        for vr in vrs.iter() {
            // Special handling for 'time' vr=0
            if *vr == 0 && value_index < values.len() {
                values[value_index] = self.context.time();
                value_index += 1;
            } else {
                let elements_read =
                    self.model
                        .get_float64(*vr - 1, &mut values[value_index..], &self.context)?;
                value_index += elements_read;
            }
        }
        Ok(Fmi3Res::OK)
    }
    instance_setter!(float64, f64);

    fn get_string(
        &mut self,
        vrs: &[binding::fmi3ValueReference],
        values: &mut [std::ffi::CString],
    ) -> Result<(), Fmi3Error> {
        let mut value_index = 0;
        for vr in vrs.iter() {
            if *vr == 0 {
                // 'time' VR is not valid here
                return Err(Fmi3Error::Error);
            }
            let elements_read =
                self.model
                    .get_string(*vr - 1, &mut values[value_index..], &self.context)?;
            value_index += elements_read;
        }
        Ok(())
    }

    fn set_string(
        &mut self,
        vrs: &[binding::fmi3ValueReference],
        values: &[std::ffi::CString],
    ) -> Result<(), Fmi3Error> {
        let mut value_index = 0;
        for vr in vrs.iter() {
            if *vr == 0 {
                // 'time' VR is not settable
                return Err(Fmi3Error::Error);
            }
            self.validate_variable_setting(*vr)?;
            let elements_written =
                self.model
                    .set_string(*vr - 1, &values[value_index..], &self.context)?;
            value_index += elements_written;
        }
        self.is_dirty_values = true;
        Ok(())
    }

    fn get_binary(
        &mut self,
        vrs: &[binding::fmi3ValueReference],
        values: &mut [&mut [u8]],
    ) -> Result<Vec<usize>, Fmi3Error> {
        let mut result_sizes = Vec::new();
        let mut value_index = 0;
        for vr in vrs.iter() {
            if *vr == 0 {
                // 'time' VR is not valid here
                return Err(Fmi3Error::Error);
            }
            let binary_sizes =
                self.model
                    .get_binary(*vr - 1, &mut values[value_index..], &self.context)?;
            result_sizes.extend(binary_sizes.iter());
            value_index += binary_sizes.len();
        }
        Ok(result_sizes)
    }

    fn set_binary(
        &mut self,
        vrs: &[binding::fmi3ValueReference],
        values: &[&[u8]],
    ) -> Result<(), Fmi3Error> {
        let mut value_index = 0;
        for vr in vrs.iter() {
            if *vr == 0 {
                // 'time' VR is not settable
                return Err(Fmi3Error::Error);
            }
            self.validate_variable_setting(*vr - 1)?;
            let elements_written =
                self.model
                    .set_binary(*vr - 1, &values[value_index..], &self.context)?;
            value_index += elements_written;
        }
        self.is_dirty_values = true;
        Ok(())
    }

    fn get_clock(
        &mut self,
        vrs: &[binding::fmi3ValueReference],
        values: &mut [binding::fmi3Clock],
    ) -> Result<Fmi3Res, Fmi3Error> {
        for (vr, value) in vrs.iter().zip(values.iter_mut()) {
            if *vr == 0 {
                // 'time' VR is not valid here
                return Err(Fmi3Error::Error);
            }
            self.model.get_clock(*vr - 1, value, &self.context)?;
        }
        Ok(Fmi3Res::OK)
    }

    fn set_clock(
        &mut self,
        _vrs: &[binding::fmi3ValueReference],
        _values: &[binding::fmi3Clock],
    ) -> Result<Fmi3Res, Fmi3Error> {
        for (vr, value) in _vrs.iter().zip(_values.iter()) {
            if *vr == 0 {
                // 'time' VR is not settable
                return Err(Fmi3Error::Error);
            }
            self.validate_variable_setting(*vr - 1)?;
            self.model.set_clock(*vr - 1, value, &self.context)?;
        }
        Ok(Fmi3Res::OK)
    }
}