1use fmi::fmi3::{binding, schema};
2
3use crate::fmi3::types::{Binary, Clock};
4
5pub enum StartValue<T> {
7 Scalar(T),
8 Vector(Vec<T>),
9}
10
11impl<T> From<T> for StartValue<T> {
12 fn from(value: T) -> Self {
13 StartValue::Scalar(value)
14 }
15}
16
17impl<T> From<Vec<T>> for StartValue<T> {
18 fn from(value: Vec<T>) -> Self {
19 StartValue::Vector(value)
20 }
21}
22
23impl<T, const N: usize> From<[T; N]> for StartValue<T> {
24 fn from(value: [T; N]) -> Self {
25 StartValue::Vector(value.into())
26 }
27}
28
29impl<T: Clone> From<StartValue<T>> for Vec<T> {
30 fn from(value: StartValue<T>) -> Self {
31 match value {
32 StartValue::Scalar(v) => vec![v],
33 StartValue::Vector(v) => v,
34 }
35 }
36}
37
38impl<const N: usize> From<&[u8; N]> for StartValue<Vec<u8>> {
40 fn from(value: &[u8; N]) -> Self {
41 StartValue::Scalar(value.to_vec())
42 }
43}
44
45impl From<&[u8]> for StartValue<Vec<u8>> {
46 fn from(value: &[u8]) -> Self {
47 StartValue::Scalar(value.to_vec())
48 }
49}
50
51pub struct VariableBuilder<T>
57where
58 T: FmiVariableBuilder,
59{
60 name: String,
62 value_reference: binding::fmi3ValueReference,
63 description: Option<String>,
64 causality: Option<schema::Causality>,
65 variability: Option<schema::Variability>,
66 can_handle_multiple_set_per_time_instant: Option<bool>,
67 intermediate_update: Option<bool>,
68 previous: Option<u32>,
69 declared_type: Option<String>,
70 initial: Option<schema::Initial>,
71
72 start: Option<T::Start>,
74
75 derivative: Option<u32>, reinit: Option<bool>,
78
79 min: Option<f64>,
81 max: Option<f64>,
82 nominal: Option<f64>,
83
84 quantity: Option<String>,
86
87 max_size: Option<usize>,
89 mime_type: Option<String>,
90
91 clocks: Option<Vec<u32>>,
93
94 dimensions: Vec<schema::Dimension>,
96
97 _phantom: std::marker::PhantomData<T>,
98}
99
100impl<T> VariableBuilder<T>
101where
102 T: FmiVariableBuilder,
103{
104 pub fn new(name: impl Into<String>, value_reference: binding::fmi3ValueReference) -> Self {
106 Self {
107 name: name.into(),
108 value_reference,
109 description: None,
110 causality: None,
111 variability: None,
112 can_handle_multiple_set_per_time_instant: None,
113 intermediate_update: None,
114 previous: None,
115 declared_type: None,
116 initial: None,
117 start: None,
118 derivative: None,
119 reinit: None,
120 min: None,
121 max: None,
122 nominal: None,
123 quantity: None,
124 max_size: None,
125 mime_type: None,
126 clocks: None,
127 dimensions: Vec::new(),
128 _phantom: std::marker::PhantomData,
129 }
130 }
131
132 pub fn with_description(mut self, description: impl Into<String>) -> Self {
136 self.description = Some(description.into());
137 self
138 }
139
140 pub fn with_causality(mut self, causality: schema::Causality) -> Self {
142 self.causality = Some(causality);
143 self
144 }
145
146 pub fn with_variability(mut self, variability: schema::Variability) -> Self {
148 self.variability = Some(variability);
149 self
150 }
151
152 pub fn with_can_handle_multiple_set_per_time_instant(mut self, value: bool) -> Self {
154 self.can_handle_multiple_set_per_time_instant = Some(value);
155 self
156 }
157
158 pub fn with_intermediate_update(mut self, value: bool) -> Self {
160 self.intermediate_update = Some(value);
161 self
162 }
163
164 pub fn with_previous(mut self, value_reference: u32) -> Self {
166 self.previous = Some(value_reference);
167 self
168 }
169
170 pub fn with_declared_type(mut self, type_name: impl Into<String>) -> Self {
172 self.declared_type = Some(type_name.into());
173 self
174 }
175
176 pub fn with_initial(mut self, initial: schema::Initial) -> Self {
178 self.initial = Some(initial);
179 self
180 }
181
182 pub fn with_start(mut self, start: impl Into<T::Start>) -> Self {
184 self.start = Some(start.into());
185 self
186 }
187
188 pub fn with_derivative(mut self, derivative_vr: u32) -> Self {
192 self.derivative = Some(derivative_vr);
193 self
194 }
195
196 pub fn with_reinit(mut self, reinit: bool) -> Self {
198 self.reinit = Some(reinit);
199 self
200 }
201
202 pub fn with_min(mut self, min: f64) -> Self {
204 self.min = Some(min);
205 self
206 }
207
208 pub fn with_max(mut self, max: f64) -> Self {
210 self.max = Some(max);
211 self
212 }
213
214 pub fn with_nominal(mut self, nominal: f64) -> Self {
216 self.nominal = Some(nominal);
217 self
218 }
219
220 pub fn with_quantity(mut self, quantity: impl Into<String>) -> Self {
224 self.quantity = Some(quantity.into());
225 self
226 }
227
228 pub fn with_dimensions(mut self, dimensions: Vec<schema::Dimension>) -> Self {
232 self.dimensions = dimensions;
233 self
234 }
235
236 pub fn with_max_size(mut self, max_size: usize) -> Self {
240 self.max_size = Some(max_size);
241 self
242 }
243
244 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
246 self.mime_type = Some(mime_type.into());
247 self
248 }
249
250 pub fn with_clocks(mut self, clocks: Vec<u32>) -> Self {
254 self.clocks = Some(clocks);
255 self
256 }
257
258 pub fn finish(self) -> T::Var {
263 T::finish(self)
264 }
265}
266
267pub trait FmiVariableBuilder: Sized {
275 type Var: schema::AbstractVariableTrait;
276 type Start;
277
278 fn variable(name: impl Into<String>, value_reference: u32) -> VariableBuilder<Self> {
280 VariableBuilder::new(name, value_reference)
281 }
282
283 fn finish(builder: VariableBuilder<Self>) -> Self::Var;
288}
289
290macro_rules! impl_fmi_variable_builder_float {
292 ($primitive_type:ty, $fmi_type:ty, $default_variability:expr) => {
293 impl FmiVariableBuilder for $primitive_type {
294 type Var = $fmi_type;
295 type Start = StartValue<Self>;
296
297 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
298 let mut var = <$fmi_type>::new(
299 builder.name,
300 builder.value_reference,
301 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
302 None
303 } else {
304 builder.description
305 },
306 builder.causality.unwrap_or(schema::Causality::Local),
307 builder.variability.unwrap_or($default_variability),
308 builder.start.map(Into::into),
309 builder.initial,
310 );
311
312 if let Some(derivative) = builder.derivative {
314 var.derivative = Some(derivative);
315 }
316 if let Some(reinit) = builder.reinit {
317 var.reinit = Some(reinit);
318 }
319
320 if !builder.dimensions.is_empty() {
322 var.dimensions = builder.dimensions;
323 }
324
325 var
326 }
327 }
328 };
329}
330
331macro_rules! impl_fmi_variable_builder_int {
333 ($primitive_type:ty, $fmi_type:ty) => {
334 impl FmiVariableBuilder for $primitive_type {
335 type Var = $fmi_type;
336 type Start = StartValue<Self>;
337
338 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
339 let mut var = <$fmi_type>::new(
340 builder.name,
341 builder.value_reference,
342 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
343 None
344 } else {
345 builder.description
346 },
347 builder.causality.unwrap_or(schema::Causality::Local),
348 builder.variability.unwrap_or(schema::Variability::Discrete),
349 builder.start.map(Into::into),
350 builder.initial,
351 );
352
353 if !builder.dimensions.is_empty() {
355 var.dimensions = builder.dimensions;
356 }
357
358 var
359 }
360 }
361 };
362}
363
364impl_fmi_variable_builder_float!(f32, schema::FmiFloat32, schema::Variability::Continuous);
365impl_fmi_variable_builder_float!(f64, schema::FmiFloat64, schema::Variability::Continuous);
366
367impl_fmi_variable_builder_int!(i8, schema::FmiInt8);
368impl_fmi_variable_builder_int!(u8, schema::FmiUInt8);
369impl_fmi_variable_builder_int!(i16, schema::FmiInt16);
370impl_fmi_variable_builder_int!(u16, schema::FmiUInt16);
371impl_fmi_variable_builder_int!(i32, schema::FmiInt32);
372impl_fmi_variable_builder_int!(u32, schema::FmiUInt32);
373
374impl FmiVariableBuilder for bool {
376 type Var = schema::FmiBoolean;
377 type Start = StartValue<Self>;
378
379 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
380 let mut var = schema::FmiBoolean::new(
381 builder.name,
382 builder.value_reference,
383 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
384 None
385 } else {
386 builder.description
387 },
388 builder.causality.unwrap_or(schema::Causality::Local),
389 builder.variability.unwrap_or(schema::Variability::Discrete),
390 builder.start.map(Into::into),
391 builder.initial,
392 );
393
394 if !builder.dimensions.is_empty() {
396 var.dimensions = builder.dimensions;
397 }
398
399 var
400 }
401}
402
403impl FmiVariableBuilder for String {
405 type Var = schema::FmiString;
406 type Start = StartValue<Self>;
407
408 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
409 let mut var = schema::FmiString::new(
410 builder.name,
411 builder.value_reference,
412 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
413 None
414 } else {
415 builder.description
416 },
417 builder.causality.unwrap_or(schema::Causality::Local),
418 builder.variability.unwrap_or(schema::Variability::Discrete),
419 builder.start.map(Into::into),
420 builder.initial,
421 );
422
423 if !builder.dimensions.is_empty() {
425 var.dimensions = builder.dimensions;
426 }
427
428 var
429 }
430}
431
432impl<const N: usize, T> FmiVariableBuilder for [T; N]
434where
435 T: FmiVariableBuilder,
436 T::Var: schema::ArrayableVariableTrait,
437 T::Start: Into<Vec<T>>,
438{
439 type Var = T::Var;
440 type Start = T::Start;
441
442 fn finish(mut builder: VariableBuilder<Self>) -> Self::Var {
443 builder.dimensions.push(schema::Dimension::Fixed(N as _));
445
446 let element_builder = VariableBuilder::<T> {
449 name: builder.name,
450 value_reference: builder.value_reference,
451 description: builder.description,
452 causality: builder.causality,
453 variability: builder.variability,
454 can_handle_multiple_set_per_time_instant: builder
455 .can_handle_multiple_set_per_time_instant,
456 intermediate_update: builder.intermediate_update,
457 previous: builder.previous,
458 declared_type: builder.declared_type,
459 initial: builder.initial,
460 start: builder.start,
461 derivative: builder.derivative,
462 reinit: builder.reinit,
463 min: builder.min,
464 max: builder.max,
465 nominal: builder.nominal,
466 quantity: builder.quantity,
467 max_size: builder.max_size,
468 mime_type: builder.mime_type,
469 clocks: builder.clocks,
470 dimensions: builder.dimensions,
471 _phantom: std::marker::PhantomData,
472 };
473
474 T::finish(element_builder)
475 }
476}
477
478impl<T> FmiVariableBuilder for Vec<T>
480where
481 T: FmiVariableBuilder,
482 T::Var: schema::ArrayableVariableTrait,
483 T::Start: Into<Vec<T>>,
484{
485 type Var = T::Var;
486 type Start = T::Start;
487
488 fn finish(mut builder: VariableBuilder<Self>) -> Self::Var {
489 builder.dimensions.push(schema::Dimension::Variable(0));
491
492 let element_builder = VariableBuilder::<T> {
494 name: builder.name,
495 value_reference: builder.value_reference,
496 description: builder.description,
497 causality: builder.causality,
498 variability: builder.variability,
499 can_handle_multiple_set_per_time_instant: builder
500 .can_handle_multiple_set_per_time_instant,
501 intermediate_update: builder.intermediate_update,
502 previous: builder.previous,
503 declared_type: builder.declared_type,
504 initial: builder.initial,
505 start: builder.start,
506 derivative: builder.derivative,
507 reinit: builder.reinit,
508 min: builder.min,
509 max: builder.max,
510 nominal: builder.nominal,
511 quantity: builder.quantity,
512 max_size: builder.max_size,
513 mime_type: builder.mime_type,
514 clocks: builder.clocks,
515 dimensions: builder.dimensions,
516 _phantom: std::marker::PhantomData,
517 };
518
519 T::finish(element_builder)
520 }
521}
522
523impl FmiVariableBuilder for Clock {
524 type Var = schema::FmiClock;
525
526 type Start = ();
527
528 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
529 let var = schema::FmiClock::new(
530 builder.name,
531 builder.value_reference,
532 builder.description,
533 builder.causality.unwrap_or(schema::Causality::Local),
534 builder.variability.unwrap_or_default(),
535 );
536
537 var
538 }
539}
540
541impl FmiVariableBuilder for Binary {
542 type Var = schema::FmiBinary;
543 type Start = StartValue<Vec<u8>>;
544
545 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
546 let start_values = builder.start.map(|start| {
547 let vec_values: Vec<Vec<u8>> = start.into();
548 vec_values
549 .into_iter()
550 .map(|bytes| {
551 bytes
553 .iter()
554 .map(|b| format!("{:02x}", b))
555 .collect::<String>()
556 })
557 .collect()
558 });
559
560 let mut var = schema::FmiBinary::new(
561 builder.name,
562 builder.value_reference,
563 builder.description,
564 builder.causality.unwrap_or(schema::Causality::Local),
565 builder.variability.unwrap_or(schema::Variability::Discrete),
566 start_values,
567 builder.initial,
568 );
569
570 if let Some(max_size) = builder.max_size {
572 var.max_size = Some(max_size as u32);
573 }
574
575 if let Some(mime_type) = builder.mime_type {
577 var.mime_type = Some(mime_type);
578 }
579
580 if let Some(clocks) = builder.clocks {
582 var.clocks = Some(fmi::schema::utils::AttrList(clocks));
583 }
584
585 if !builder.dimensions.is_empty() {
587 var.dimensions = builder.dimensions;
588 }
589
590 var
591 }
592}
593
594#[cfg(test)]
595mod tests {
596 use fmi::schema::fmi3::{AbstractVariableTrait, ArrayableVariableTrait};
597
598 use super::*;
599
600 #[test]
601 fn test_scalar() {
602 let var_f1 = <f64 as FmiVariableBuilder>::variable("f1", 0)
603 .with_description("Description for f1")
604 .with_causality(schema::Causality::Parameter)
605 .with_variability(schema::Variability::Tunable)
606 .with_start(0.0)
607 .finish();
608 assert_eq!(var_f1.dimensions(), &[]);
609 }
610
611 #[test]
612 fn test_array1() {
613 let var_f2 = <[u16; 2] as FmiVariableBuilder>::variable("f2", 0)
614 .with_description("Description for f2")
615 .with_causality(schema::Causality::Parameter)
616 .with_variability(schema::Variability::Tunable)
617 .with_start(vec![0u16, 1])
618 .finish();
619 assert_eq!(var_f2.dimensions(), &[schema::Dimension::Fixed(2)]);
620 }
621
622 #[test]
623 fn test_builder_pattern() {
624 let var = <f64 as FmiVariableBuilder>::variable("test_var", 42)
626 .with_description("A test variable")
627 .with_causality(schema::Causality::Output)
628 .with_variability(schema::Variability::Continuous)
629 .with_start(1.5)
630 .with_initial(schema::Initial::Exact)
631 .finish();
632
633 assert_eq!(var.name(), "test_var");
634 assert_eq!(var.value_reference(), 42);
635 assert_eq!(var.description(), Some("A test variable"));
636 assert_eq!(var.causality(), schema::Causality::Output);
637 assert_eq!(var.variability(), schema::Variability::Continuous);
638 }
639
640 #[test]
641 fn test_builder_with_defaults() {
642 let var = <f64 as FmiVariableBuilder>::variable("minimal", 0)
644 .with_start(0.0)
645 .finish();
646
647 assert_eq!(var.name(), "minimal");
648 assert_eq!(var.value_reference(), 0);
649 assert_eq!(var.causality(), schema::Causality::Local);
650 assert_eq!(var.variability(), schema::Variability::Continuous);
651 }
652
653 #[test]
654 fn test_float_specific_attributes() {
655 let var = <f64 as FmiVariableBuilder>::variable("state_derivative", 10)
657 .with_causality(schema::Causality::Local)
658 .with_variability(schema::Variability::Continuous)
659 .with_derivative(5) .with_reinit(true)
661 .with_min(-100.0)
662 .with_max(100.0)
663 .with_nominal(10.0)
664 .with_start(0.0)
665 .with_initial(schema::Initial::Calculated)
666 .finish();
667
668 assert_eq!(var.name(), "state_derivative");
669 assert_eq!(var.value_reference(), 10);
670 assert_eq!(var.derivative(), Some(5));
671 assert_eq!(var.reinit(), Some(true));
672 }
673
674 #[test]
675 fn test_bool_type_creates_fmi_boolean() {
676 let var = <bool as FmiVariableBuilder>::variable("flag", 20)
678 .with_causality(schema::Causality::Output)
679 .with_variability(schema::Variability::Discrete)
680 .with_start(false)
681 .finish();
682
683 assert_eq!(var.name(), "flag");
684 assert_eq!(var.value_reference(), 20);
685 assert_eq!(var.causality(), schema::Causality::Output);
686 assert_eq!(var.variability(), schema::Variability::Discrete);
687 }
688
689 #[test]
690 fn test_comprehensive_builder() {
691 let var = <f64 as FmiVariableBuilder>::variable("comprehensive", 100)
693 .with_description("A comprehensive test variable")
694 .with_causality(schema::Causality::Parameter)
695 .with_variability(schema::Variability::Tunable)
696 .with_can_handle_multiple_set_per_time_instant(true)
697 .with_intermediate_update(false)
698 .with_declared_type("CustomFloat64Type")
699 .with_start(42.0)
700 .with_initial(schema::Initial::Exact)
701 .with_min(0.0)
702 .with_max(1000.0)
703 .with_nominal(100.0)
704 .finish();
705
706 assert_eq!(var.name(), "comprehensive");
707 assert_eq!(var.value_reference(), 100);
708 assert_eq!(var.description(), Some("A comprehensive test variable"));
709 assert_eq!(var.causality(), schema::Causality::Parameter);
710 assert_eq!(var.variability(), schema::Variability::Tunable);
711 }
712}