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 interval_variability: Option<schema::IntervalVariability>,
94
95 dimensions: Vec<schema::Dimension>,
97
98 _phantom: std::marker::PhantomData<T>,
99}
100
101impl<T> VariableBuilder<T>
102where
103 T: FmiVariableBuilder,
104{
105 pub fn new(name: impl Into<String>, value_reference: binding::fmi3ValueReference) -> Self {
107 Self {
108 name: name.into(),
109 value_reference,
110 description: None,
111 causality: None,
112 variability: None,
113 can_handle_multiple_set_per_time_instant: None,
114 intermediate_update: None,
115 previous: None,
116 declared_type: None,
117 initial: None,
118 start: None,
119 derivative: None,
120 reinit: None,
121 min: None,
122 max: None,
123 nominal: None,
124 quantity: None,
125 max_size: None,
126 mime_type: None,
127 clocks: None,
128 interval_variability: None,
129 dimensions: Vec::new(),
130 _phantom: std::marker::PhantomData,
131 }
132 }
133
134 pub fn with_description(mut self, description: impl Into<String>) -> Self {
138 self.description = Some(description.into());
139 self
140 }
141
142 pub fn with_causality(mut self, causality: schema::Causality) -> Self {
144 self.causality = Some(causality);
145 self
146 }
147
148 pub fn with_variability(mut self, variability: schema::Variability) -> Self {
150 self.variability = Some(variability);
151 self
152 }
153
154 pub fn with_can_handle_multiple_set_per_time_instant(mut self, value: bool) -> Self {
156 self.can_handle_multiple_set_per_time_instant = Some(value);
157 self
158 }
159
160 pub fn with_intermediate_update(mut self, value: bool) -> Self {
162 self.intermediate_update = Some(value);
163 self
164 }
165
166 pub fn with_previous(mut self, value_reference: u32) -> Self {
168 self.previous = Some(value_reference);
169 self
170 }
171
172 pub fn with_declared_type(mut self, type_name: impl Into<String>) -> Self {
174 self.declared_type = Some(type_name.into());
175 self
176 }
177
178 pub fn with_initial(mut self, initial: schema::Initial) -> Self {
180 self.initial = Some(initial);
181 self
182 }
183
184 pub fn with_start(mut self, start: impl Into<T::Start>) -> Self {
186 self.start = Some(start.into());
187 self
188 }
189
190 pub fn with_derivative(mut self, derivative_vr: u32) -> Self {
194 self.derivative = Some(derivative_vr);
195 self
196 }
197
198 pub fn with_reinit(mut self, reinit: bool) -> Self {
200 self.reinit = Some(reinit);
201 self
202 }
203
204 pub fn with_min(mut self, min: f64) -> Self {
206 self.min = Some(min);
207 self
208 }
209
210 pub fn with_max(mut self, max: f64) -> Self {
212 self.max = Some(max);
213 self
214 }
215
216 pub fn with_nominal(mut self, nominal: f64) -> Self {
218 self.nominal = Some(nominal);
219 self
220 }
221
222 pub fn with_quantity(mut self, quantity: impl Into<String>) -> Self {
226 self.quantity = Some(quantity.into());
227 self
228 }
229
230 pub fn with_dimensions(mut self, dimensions: Vec<schema::Dimension>) -> Self {
234 self.dimensions = dimensions;
235 self
236 }
237
238 pub fn with_max_size(mut self, max_size: usize) -> Self {
242 self.max_size = Some(max_size);
243 self
244 }
245
246 pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
248 self.mime_type = Some(mime_type.into());
249 self
250 }
251
252 pub fn with_clocks(mut self, clocks: Vec<u32>) -> Self {
256 self.clocks = Some(clocks);
257 self
258 }
259
260 pub fn with_interval_variability(
262 mut self,
263 interval_variability: schema::IntervalVariability,
264 ) -> Self {
265 self.interval_variability = Some(interval_variability);
266 self
267 }
268
269 pub fn finish(self) -> T::Var {
274 T::finish(self)
275 }
276}
277
278pub trait FmiVariableBuilder: Sized {
286 type Var: schema::AbstractVariableTrait;
287 type Start;
288
289 fn variable(name: impl Into<String>, value_reference: u32) -> VariableBuilder<Self> {
291 VariableBuilder::new(name, value_reference)
292 }
293
294 fn finish(builder: VariableBuilder<Self>) -> Self::Var;
299}
300
301macro_rules! impl_fmi_variable_builder_float {
303 ($primitive_type:ty, $fmi_type:ty, $default_variability:expr) => {
304 impl FmiVariableBuilder for $primitive_type {
305 type Var = $fmi_type;
306 type Start = StartValue<Self>;
307
308 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
309 let mut var = <$fmi_type>::new(
310 builder.name,
311 builder.value_reference,
312 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
313 None
314 } else {
315 builder.description
316 },
317 builder.causality.unwrap_or(schema::Causality::Local),
318 builder.variability.unwrap_or($default_variability),
319 builder.start.map(Into::into),
320 builder.initial,
321 );
322
323 if let Some(derivative) = builder.derivative {
325 var.derivative = Some(derivative);
326 }
327 if let Some(reinit) = builder.reinit {
328 var.reinit = Some(reinit);
329 }
330
331 if !builder.dimensions.is_empty() {
333 var.dimensions = builder.dimensions;
334 }
335
336 var
337 }
338 }
339 };
340}
341
342macro_rules! impl_fmi_variable_builder_int {
344 ($primitive_type:ty, $fmi_type:ty) => {
345 impl FmiVariableBuilder for $primitive_type {
346 type Var = $fmi_type;
347 type Start = StartValue<Self>;
348
349 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
350 let mut var = <$fmi_type>::new(
351 builder.name,
352 builder.value_reference,
353 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
354 None
355 } else {
356 builder.description
357 },
358 builder.causality.unwrap_or(schema::Causality::Local),
359 builder.variability.unwrap_or(schema::Variability::Discrete),
360 builder.start.map(Into::into),
361 builder.initial,
362 );
363
364 if !builder.dimensions.is_empty() {
366 var.dimensions = builder.dimensions;
367 }
368
369 var
370 }
371 }
372 };
373}
374
375impl_fmi_variable_builder_float!(f32, schema::FmiFloat32, schema::Variability::Continuous);
376impl_fmi_variable_builder_float!(f64, schema::FmiFloat64, schema::Variability::Continuous);
377
378impl_fmi_variable_builder_int!(i8, schema::FmiInt8);
379impl_fmi_variable_builder_int!(u8, schema::FmiUInt8);
380impl_fmi_variable_builder_int!(i16, schema::FmiInt16);
381impl_fmi_variable_builder_int!(u16, schema::FmiUInt16);
382impl_fmi_variable_builder_int!(i32, schema::FmiInt32);
383impl_fmi_variable_builder_int!(u32, schema::FmiUInt32);
384
385impl FmiVariableBuilder for bool {
387 type Var = schema::FmiBoolean;
388 type Start = StartValue<Self>;
389
390 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
391 let mut var = schema::FmiBoolean::new(
392 builder.name,
393 builder.value_reference,
394 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
395 None
396 } else {
397 builder.description
398 },
399 builder.causality.unwrap_or(schema::Causality::Local),
400 builder.variability.unwrap_or(schema::Variability::Discrete),
401 builder.start.map(Into::into),
402 builder.initial,
403 );
404
405 if !builder.dimensions.is_empty() {
407 var.dimensions = builder.dimensions;
408 }
409
410 var
411 }
412}
413
414impl FmiVariableBuilder for String {
416 type Var = schema::FmiString;
417 type Start = StartValue<Self>;
418
419 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
420 let mut var = schema::FmiString::new(
421 builder.name,
422 builder.value_reference,
423 if builder.description.as_ref().map_or(true, |d| d.is_empty()) {
424 None
425 } else {
426 builder.description
427 },
428 builder.causality.unwrap_or(schema::Causality::Local),
429 builder.variability.unwrap_or(schema::Variability::Discrete),
430 builder.start.map(Into::into),
431 builder.initial,
432 );
433
434 if !builder.dimensions.is_empty() {
436 var.dimensions = builder.dimensions;
437 }
438
439 var
440 }
441}
442
443impl<const N: usize, T> FmiVariableBuilder for [T; N]
445where
446 T: FmiVariableBuilder,
447 T::Var: schema::ArrayableVariableTrait,
448 T::Start: Into<Vec<T>>,
449{
450 type Var = T::Var;
451 type Start = T::Start;
452
453 fn finish(mut builder: VariableBuilder<Self>) -> Self::Var {
454 builder.dimensions.push(schema::Dimension::Fixed(N as _));
456
457 let element_builder = VariableBuilder::<T> {
460 name: builder.name,
461 value_reference: builder.value_reference,
462 description: builder.description,
463 causality: builder.causality,
464 variability: builder.variability,
465 can_handle_multiple_set_per_time_instant: builder
466 .can_handle_multiple_set_per_time_instant,
467 intermediate_update: builder.intermediate_update,
468 previous: builder.previous,
469 declared_type: builder.declared_type,
470 initial: builder.initial,
471 start: builder.start,
472 derivative: builder.derivative,
473 reinit: builder.reinit,
474 min: builder.min,
475 max: builder.max,
476 nominal: builder.nominal,
477 quantity: builder.quantity,
478 max_size: builder.max_size,
479 mime_type: builder.mime_type,
480 clocks: builder.clocks,
481 interval_variability: builder.interval_variability,
482 dimensions: builder.dimensions,
483 _phantom: std::marker::PhantomData,
484 };
485
486 T::finish(element_builder)
487 }
488}
489
490impl<T> FmiVariableBuilder for Vec<T>
492where
493 T: FmiVariableBuilder,
494 T::Var: schema::ArrayableVariableTrait,
495 T::Start: Into<Vec<T>>,
496{
497 type Var = T::Var;
498 type Start = T::Start;
499
500 fn finish(mut builder: VariableBuilder<Self>) -> Self::Var {
501 builder.dimensions.push(schema::Dimension::Variable(0));
503
504 let element_builder = VariableBuilder::<T> {
506 name: builder.name,
507 value_reference: builder.value_reference,
508 description: builder.description,
509 causality: builder.causality,
510 variability: builder.variability,
511 can_handle_multiple_set_per_time_instant: builder
512 .can_handle_multiple_set_per_time_instant,
513 intermediate_update: builder.intermediate_update,
514 previous: builder.previous,
515 declared_type: builder.declared_type,
516 initial: builder.initial,
517 start: builder.start,
518 derivative: builder.derivative,
519 reinit: builder.reinit,
520 min: builder.min,
521 max: builder.max,
522 nominal: builder.nominal,
523 quantity: builder.quantity,
524 max_size: builder.max_size,
525 mime_type: builder.mime_type,
526 clocks: builder.clocks,
527 interval_variability: builder.interval_variability,
528 dimensions: builder.dimensions,
529 _phantom: std::marker::PhantomData,
530 };
531
532 T::finish(element_builder)
533 }
534}
535
536impl FmiVariableBuilder for Clock {
537 type Var = schema::FmiClock;
538
539 type Start = ();
540
541 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
542 let var = schema::FmiClock::new(
543 builder.name,
544 builder.value_reference,
545 builder.description,
546 builder.causality.unwrap_or(schema::Causality::Local),
547 builder.variability.unwrap_or_default(),
548 );
549
550 let mut var = var;
551 var.interval_variability = Some(
552 builder
553 .interval_variability
554 .unwrap_or(schema::IntervalVariability::Triggered),
555 );
556
557 var
558 }
559}
560
561impl FmiVariableBuilder for Binary {
562 type Var = schema::FmiBinary;
563 type Start = StartValue<Vec<u8>>;
564
565 fn finish(builder: VariableBuilder<Self>) -> Self::Var {
566 let start_values = builder.start.map(|start| {
567 let vec_values: Vec<Vec<u8>> = start.into();
568 vec_values
569 .into_iter()
570 .map(|bytes| {
571 bytes
573 .iter()
574 .map(|b| format!("{:02x}", b))
575 .collect::<String>()
576 })
577 .collect()
578 });
579
580 let mut var = schema::FmiBinary::new(
581 builder.name,
582 builder.value_reference,
583 builder.description,
584 builder.causality.unwrap_or(schema::Causality::Local),
585 builder.variability.unwrap_or(schema::Variability::Discrete),
586 start_values,
587 builder.initial,
588 );
589
590 if let Some(max_size) = builder.max_size {
592 var.max_size = Some(max_size as u32);
593 }
594
595 if let Some(mime_type) = builder.mime_type {
597 var.mime_type = Some(mime_type);
598 }
599
600 if let Some(clocks) = builder.clocks {
602 var.clocks = Some(fmi::schema::utils::AttrList(clocks));
603 }
604
605 if !builder.dimensions.is_empty() {
607 var.dimensions = builder.dimensions;
608 }
609
610 var
611 }
612}
613
614#[cfg(test)]
615mod tests {
616 use fmi::schema::fmi3::{AbstractVariableTrait, ArrayableVariableTrait};
617
618 use super::*;
619
620 #[test]
621 fn test_scalar() {
622 let var_f1 = <f64 as FmiVariableBuilder>::variable("f1", 0)
623 .with_description("Description for f1")
624 .with_causality(schema::Causality::Parameter)
625 .with_variability(schema::Variability::Tunable)
626 .with_start(0.0)
627 .finish();
628 assert_eq!(var_f1.dimensions(), &[]);
629 }
630
631 #[test]
632 fn test_array1() {
633 let var_f2 = <[u16; 2] as FmiVariableBuilder>::variable("f2", 0)
634 .with_description("Description for f2")
635 .with_causality(schema::Causality::Parameter)
636 .with_variability(schema::Variability::Tunable)
637 .with_start(vec![0u16, 1])
638 .finish();
639 assert_eq!(var_f2.dimensions(), &[schema::Dimension::Fixed(2)]);
640 }
641
642 #[test]
643 fn test_builder_pattern() {
644 let var = <f64 as FmiVariableBuilder>::variable("test_var", 42)
646 .with_description("A test variable")
647 .with_causality(schema::Causality::Output)
648 .with_variability(schema::Variability::Continuous)
649 .with_start(1.5)
650 .with_initial(schema::Initial::Exact)
651 .finish();
652
653 assert_eq!(var.name(), "test_var");
654 assert_eq!(var.value_reference(), 42);
655 assert_eq!(var.description(), Some("A test variable"));
656 assert_eq!(var.causality(), schema::Causality::Output);
657 assert_eq!(var.variability(), schema::Variability::Continuous);
658 }
659
660 #[test]
661 fn test_builder_with_defaults() {
662 let var = <f64 as FmiVariableBuilder>::variable("minimal", 0)
664 .with_start(0.0)
665 .finish();
666
667 assert_eq!(var.name(), "minimal");
668 assert_eq!(var.value_reference(), 0);
669 assert_eq!(var.causality(), schema::Causality::Local);
670 assert_eq!(var.variability(), schema::Variability::Continuous);
671 }
672
673 #[test]
674 fn test_float_specific_attributes() {
675 let var = <f64 as FmiVariableBuilder>::variable("state_derivative", 10)
677 .with_causality(schema::Causality::Local)
678 .with_variability(schema::Variability::Continuous)
679 .with_derivative(5) .with_reinit(true)
681 .with_min(-100.0)
682 .with_max(100.0)
683 .with_nominal(10.0)
684 .with_start(0.0)
685 .with_initial(schema::Initial::Calculated)
686 .finish();
687
688 assert_eq!(var.name(), "state_derivative");
689 assert_eq!(var.value_reference(), 10);
690 assert_eq!(var.derivative(), Some(5));
691 assert_eq!(var.reinit(), Some(true));
692 }
693
694 #[test]
695 fn test_bool_type_creates_fmi_boolean() {
696 let var = <bool as FmiVariableBuilder>::variable("flag", 20)
698 .with_causality(schema::Causality::Output)
699 .with_variability(schema::Variability::Discrete)
700 .with_start(false)
701 .finish();
702
703 assert_eq!(var.name(), "flag");
704 assert_eq!(var.value_reference(), 20);
705 assert_eq!(var.causality(), schema::Causality::Output);
706 assert_eq!(var.variability(), schema::Variability::Discrete);
707 }
708
709 #[test]
710 fn test_comprehensive_builder() {
711 let var = <f64 as FmiVariableBuilder>::variable("comprehensive", 100)
713 .with_description("A comprehensive test variable")
714 .with_causality(schema::Causality::Parameter)
715 .with_variability(schema::Variability::Tunable)
716 .with_can_handle_multiple_set_per_time_instant(true)
717 .with_intermediate_update(false)
718 .with_declared_type("CustomFloat64Type")
719 .with_start(42.0)
720 .with_initial(schema::Initial::Exact)
721 .with_min(0.0)
722 .with_max(1000.0)
723 .with_nominal(100.0)
724 .finish();
725
726 assert_eq!(var.name(), "comprehensive");
727 assert_eq!(var.value_reference(), 100);
728 assert_eq!(var.description(), Some("A comprehensive test variable"));
729 assert_eq!(var.causality(), schema::Causality::Parameter);
730 assert_eq!(var.variability(), schema::Variability::Tunable);
731 }
732}