1#![cfg_attr(not(feature = "std"), no_std)]
10
11extern crate alloc;
12
13pub use uom;
14
15macro_rules! define_storage_wrappers {
16 ($storage_mod:ident, $storage_ty:ty, [$(($id:literal, $unit_mod:ident, $quantity:ident, $symbol:literal),)+]) => {
17 pub mod $storage_mod {
18 use core::marker::PhantomData;
19 use serde::{Deserialize, Deserializer, Serialize, Serializer};
20
21 #[cfg(feature = "reflect")]
22 use bevy_reflect::Reflect;
23
24 macro_rules! define_quantity {
25 ($unit_mod_name:ident, $quantity_name:ident, $unit_symbol:literal) => {
26 #[repr(transparent)]
27 #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
28 #[cfg_attr(feature = "reflect", derive(Reflect))]
29 #[cfg_attr(feature = "reflect", reflect(from_reflect = false))]
30 pub struct $quantity_name {
31 pub value: $storage_ty,
32 }
33
34 impl $quantity_name {
35 const STORAGE_UNIT: &'static str = $unit_symbol;
36
37 #[inline]
38 pub fn new<U>(value: $storage_ty) -> Self
39 where
40 U: uom::si::$unit_mod_name::Conversion<$storage_ty>,
41 {
42 Self::from_uom(uom::si::$storage_mod::$quantity_name::new::<U>(value))
43 }
44
45 #[inline]
46 pub fn get<U>(&self) -> $storage_ty
47 where
48 U: uom::si::$unit_mod_name::Conversion<$storage_ty>,
49 {
50 (*self).to_uom().get::<U>()
51 }
52
53 #[inline]
54 pub fn raw(&self) -> $storage_ty {
55 self.value
56 }
57
58 #[inline]
59 pub fn into_uom(self) -> uom::si::$storage_mod::$quantity_name {
60 self.to_uom()
61 }
62
63 #[inline]
64 pub fn as_uom(&self) -> uom::si::$storage_mod::$quantity_name {
65 (*self).to_uom()
66 }
67
68 #[inline]
69 pub fn from_uom(inner: uom::si::$storage_mod::$quantity_name) -> Self {
70 Self::from_base_value(inner.value)
71 }
72
73 #[inline]
74 fn to_uom(self) -> uom::si::$storage_mod::$quantity_name {
75 uom::si::$storage_mod::$quantity_name {
76 dimension: PhantomData,
77 units: PhantomData,
78 value: self.value,
79 }
80 }
81
82 #[inline]
83 fn from_base_value(value: $storage_ty) -> Self {
84 Self { value }
85 }
86 }
87
88 impl Default for $quantity_name {
89 fn default() -> Self {
90 Self::from_base_value(0.0 as $storage_ty)
91 }
92 }
93
94 impl From<uom::si::$storage_mod::$quantity_name> for $quantity_name {
95 fn from(value: uom::si::$storage_mod::$quantity_name) -> Self {
96 Self::from_uom(value)
97 }
98 }
99
100 impl From<$quantity_name> for uom::si::$storage_mod::$quantity_name {
101 fn from(value: $quantity_name) -> Self {
102 value.into_uom()
103 }
104 }
105
106 impl core::ops::Add for $quantity_name {
107 type Output = Self;
108
109 fn add(self, rhs: Self) -> Self::Output {
110 Self::from_base_value(self.raw() + rhs.raw())
111 }
112 }
113
114 impl core::ops::AddAssign for $quantity_name {
115 fn add_assign(&mut self, rhs: Self) {
116 *self = *self + rhs;
117 }
118 }
119
120 impl core::ops::Sub for $quantity_name {
121 type Output = Self;
122
123 fn sub(self, rhs: Self) -> Self::Output {
124 Self::from_base_value(self.raw() - rhs.raw())
125 }
126 }
127
128 impl core::ops::SubAssign for $quantity_name {
129 fn sub_assign(&mut self, rhs: Self) {
130 *self = *self - rhs;
131 }
132 }
133
134 impl core::ops::Mul<$storage_ty> for $quantity_name {
135 type Output = Self;
136
137 fn mul(self, rhs: $storage_ty) -> Self::Output {
138 Self::from_base_value(self.raw() * rhs)
139 }
140 }
141
142 impl core::ops::MulAssign<$storage_ty> for $quantity_name {
143 fn mul_assign(&mut self, rhs: $storage_ty) {
144 *self = *self * rhs;
145 }
146 }
147
148 impl core::ops::Div<$storage_ty> for $quantity_name {
149 type Output = Self;
150
151 fn div(self, rhs: $storage_ty) -> Self::Output {
152 Self::from_base_value(self.raw() / rhs)
153 }
154 }
155
156 impl core::ops::DivAssign<$storage_ty> for $quantity_name {
157 fn div_assign(&mut self, rhs: $storage_ty) {
158 *self = *self / rhs;
159 }
160 }
161
162 impl core::ops::Neg for $quantity_name {
163 type Output = Self;
164
165 fn neg(self) -> Self::Output {
166 Self::from_base_value(-self.raw())
167 }
168 }
169
170 impl Serialize for $quantity_name {
171 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
172 where
173 S: Serializer,
174 {
175 self.raw().serialize(serializer)
176 }
177 }
178
179 impl<'de> Deserialize<'de> for $quantity_name {
180 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
181 where
182 D: Deserializer<'de>,
183 {
184 let value = <$storage_ty>::deserialize(deserializer)?;
185 Ok(Self::from_base_value(value))
186 }
187 }
188
189 impl bincode::ValueDecode for $quantity_name {
190 const DECODE: &'static bincode::ValueDecodeSpec = <$storage_ty as bincode::ValueDecode>::DECODE;
191 const METADATA: &'static [bincode::value_decode::ValueMetadata] = &[
192 bincode::value_decode::ValueMetadata::Quantity(
193 cu29_value_types::QuantityMetadata::coherent(cu29_value_types::Quantity::$quantity_name),
194 ),
195 ];
196 }
197
198 impl cu29_traits::DebugScalarType for $quantity_name {
199 fn debug_scalar_registration() -> cu29_traits::DebugScalarRegistration {
200 cu29_traits::DebugScalarRegistration {
201 type_path: core::any::type_name::<Self>(),
202 scalar_kind: if stringify!($storage_ty) == "f32" {
203 cu29_traits::DebugScalarKind::F32
204 } else {
205 cu29_traits::DebugScalarKind::F64
206 },
207 semantics: cu29_traits::DebugFieldSemantics::Quantity {
208 quantity_name: alloc::string::String::from(stringify!($quantity_name)),
209 unit_symbol: alloc::string::String::from(Self::STORAGE_UNIT),
210 },
211 }
212 }
213 }
214
215 impl bincode::Encode for $quantity_name {
216 fn encode<E: bincode::enc::Encoder>(
217 &self,
218 encoder: &mut E,
219 ) -> Result<(), bincode::error::EncodeError> {
220 bincode::Encode::encode(&self.raw(), encoder)
221 }
222 }
223
224 impl<Context> bincode::Decode<Context> for $quantity_name {
225 fn decode<D: bincode::de::Decoder<Context = Context>>(
226 decoder: &mut D,
227 ) -> Result<Self, bincode::error::DecodeError> {
228 let value: $storage_ty = bincode::Decode::decode(decoder)?;
229 Ok(Self::from_base_value(value))
230 }
231 }
232
233 impl<'de, Context> bincode::BorrowDecode<'de, Context> for $quantity_name {
234 fn borrow_decode<D: bincode::de::BorrowDecoder<'de, Context = Context>>(
235 decoder: &mut D,
236 ) -> Result<Self, bincode::error::DecodeError> {
237 <Self as bincode::Decode<Context>>::decode(decoder)
238 }
239 }
240
241 #[cfg(feature = "reflect")]
242 impl bevy_reflect::FromReflect for $quantity_name {
243 fn from_reflect(
244 reflect: &dyn bevy_reflect::PartialReflect,
245 ) -> Option<Self> {
246 if let Some(existing) = reflect.try_downcast_ref::<Self>() {
247 return Some(*existing);
248 }
249
250 reflect
251 .try_downcast_ref::<$storage_ty>()
252 .map(|value| Self::from_base_value(*value))
253 }
254 }
255 };
256 }
257
258 $(define_quantity!($unit_mod, $quantity, $symbol);)+
259
260 pub(crate) fn debug_scalar_registrations() -> alloc::vec::Vec<cu29_traits::DebugScalarRegistration> {
261 alloc::vec![$(<$quantity as cu29_traits::DebugScalarType>::debug_scalar_registration()),+]
262 }
263
264 #[cfg(all(test, feature = "reflect"))]
265 #[test]
266 fn test_quantity_symbols_and_scalar_encoding() {
267 let registrations = debug_scalar_registrations();
268 assert_eq!(registrations.len(), cu29_value_types::Quantity::ALL.len());
269 $(
270 let quantity = cu29_value_types::QuantityMetadata::coherent(cu29_value_types::Quantity::$quantity);
271 let registration = registrations.iter().find(|entry| entry.type_path == core::any::type_name::<$quantity>()).unwrap();
272 let cu29_traits::DebugFieldSemantics::Quantity { unit_symbol, .. } = ®istration.semantics else {
273 panic!("expected quantity semantics");
274 };
275 assert_eq!(unit_symbol, quantity.storage_unit().symbol());
276 assert_eq!(<$quantity as bincode::ValueDecode>::METADATA, &[bincode::value_decode::ValueMetadata::Quantity(quantity)]);
277 for raw in [0.0, -1.25, 1234.5] {
278 let value = $quantity::from_base_value(raw);
279 let config = bincode::config::standard();
280 let encoded = bincode::encode_to_vec(value, config).unwrap();
281 assert_eq!(encoded, bincode::encode_to_vec(raw, config).unwrap());
282 let (decoded, used): ($quantity, usize) = bincode::decode_from_slice(&encoded, config).unwrap();
283 assert_eq!(decoded.raw(), raw);
284 assert_eq!(used, encoded.len());
285 }
286 )+
287 }
288
289
290 }
291 };
292}
293
294pub mod si {
295 pub use uom::si::{
296 ISQ, SI, absement, acceleration, action, amount_of_substance, angle, angular_absement,
297 angular_acceleration, angular_jerk, angular_momentum, angular_velocity, area,
298 areal_density_of_states, areal_heat_capacity, areal_mass_density, areal_number_density,
299 areal_number_rate, available_energy, capacitance, catalytic_activity,
300 catalytic_activity_concentration, curvature, diffusion_coefficient, dynamic_viscosity,
301 electric_charge, electric_charge_areal_density, electric_charge_linear_density,
302 electric_charge_volumetric_density, electric_current, electric_current_density,
303 electric_dipole_moment, electric_displacement_field, electric_field, electric_flux,
304 electric_permittivity, electric_potential, electric_quadrupole_moment,
305 electrical_conductance, electrical_conductivity, electrical_mobility,
306 electrical_resistance, electrical_resistivity, energy, force, frequency, frequency_drift,
307 heat_capacity, heat_flux_density, heat_transfer, inductance, information, information_rate,
308 inverse_velocity, jerk, kinematic_viscosity, length, linear_density_of_states,
309 linear_mass_density, linear_number_density, linear_number_rate, linear_power_density,
310 luminance, luminous_intensity, magnetic_field_strength, magnetic_flux,
311 magnetic_flux_density, magnetic_moment, magnetic_permeability, mass, mass_concentration,
312 mass_density, mass_flux, mass_per_energy, mass_rate, molality, molar_concentration,
313 molar_energy, molar_flux, molar_heat_capacity, molar_mass, molar_radioactivity,
314 molar_volume, moment_of_inertia, momentum, power, power_rate, pressure, radiant_exposure,
315 radioactivity, ratio, reciprocal_length, solid_angle, specific_area,
316 specific_heat_capacity, specific_power, specific_radioactivity, specific_volume,
317 surface_electric_current_density, surface_tension, temperature_coefficient,
318 temperature_gradient, temperature_interval, thermal_conductance, thermal_conductivity,
319 thermal_resistance, thermodynamic_temperature, time, torque, velocity, volume, volume_rate,
320 volumetric_density_of_states, volumetric_heat_capacity, volumetric_number_density,
321 volumetric_number_rate, volumetric_power_density,
322 };
323
324 cu29_value_types::__quantity_catalogue!(define_storage_wrappers, f32, f32);
325 cu29_value_types::__quantity_catalogue!(define_storage_wrappers, f64, f64);
326}
327
328#[doc(hidden)]
330pub mod constant {
331 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
333 pub struct QuantityDefinition {
334 pub quantity: Quantity,
335 pub quantity_name: &'static str,
336 pub rust_type_f32: &'static str,
337 pub rust_type_f64: &'static str,
338 pub coherent_unit: &'static str,
339 pub preferred_display_unit: &'static str,
340 pub compatible_units: &'static [&'static str],
341 }
342
343 macro_rules! define_constant_catalogue {
344 ($(($unit_mod:ident, $quantity:ident, $coherent_unit:ident, $display_unit:ident,
345 [$($alternative_unit:ident),* $(,)?])),+ $(,)?) => {
346 #[allow(non_camel_case_types)]
348 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
349 pub enum Quantity {
350 $($unit_mod,)+
351 }
352
353 impl Quantity {
354 pub const fn name(self) -> &'static str {
355 match self {
356 $(Self::$unit_mod => stringify!($unit_mod),)+
357 }
358 }
359 }
360
361 #[allow(non_camel_case_types)]
363 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
364 pub enum Unit {
365 $($coherent_unit, $($alternative_unit,)*)+
366 }
367
368 impl Unit {
369 pub const fn name(self) -> &'static str {
370 match self {
371 $(
372 Self::$coherent_unit => stringify!($coherent_unit),
373 $(Self::$alternative_unit => stringify!($alternative_unit),)*
374 )+
375 }
376 }
377
378 pub fn from_name(name: &str) -> Option<Self> {
379 match name {
380 $(
381 stringify!($coherent_unit) => Some(Self::$coherent_unit),
382 $(stringify!($alternative_unit) => Some(Self::$alternative_unit),)*
383 )+
384 _ => None,
385 }
386 }
387 }
388
389 pub const DEFINITIONS: &[QuantityDefinition] = &[
390 $(QuantityDefinition {
391 quantity: Quantity::$unit_mod,
392 quantity_name: stringify!($unit_mod),
393 rust_type_f32: concat!("cu29::units::si::f32::", stringify!($quantity)),
394 rust_type_f64: concat!("cu29::units::si::f64::", stringify!($quantity)),
395 coherent_unit: stringify!($coherent_unit),
396 preferred_display_unit: stringify!($display_unit),
397 compatible_units: &[
398 stringify!($coherent_unit),
399 $(stringify!($alternative_unit),)*
400 ],
401 },)+
402 ];
403
404 pub fn definition(quantity: Quantity) -> Option<&'static QuantityDefinition> {
405 DEFINITIONS
406 .iter()
407 .find(|definition| definition.quantity == quantity)
408 }
409
410 pub fn normalize_f32(quantity: Quantity, unit: Unit, value: f32) -> Option<f32> {
411 match quantity {
412 $(Quantity::$unit_mod => match unit {
413 Unit::$coherent_unit => Some(
414 crate::si::f32::$quantity::new::<
415 crate::uom::si::$unit_mod::$coherent_unit
416 >(value).raw()
417 ),
418 $(Unit::$alternative_unit => Some(
419 crate::si::f32::$quantity::new::<
420 crate::uom::si::$unit_mod::$alternative_unit
421 >(value).raw()
422 ),)*
423 _ => None,
424 },)+
425 }
426 }
427
428 pub fn normalize_f64(quantity: Quantity, unit: Unit, value: f64) -> Option<f64> {
429 match quantity {
430 $(Quantity::$unit_mod => match unit {
431 Unit::$coherent_unit => Some(
432 crate::si::f64::$quantity::new::<
433 crate::uom::si::$unit_mod::$coherent_unit
434 >(value).raw()
435 ),
436 $(Unit::$alternative_unit => Some(
437 crate::si::f64::$quantity::new::<
438 crate::uom::si::$unit_mod::$alternative_unit
439 >(value).raw()
440 ),)*
441 _ => None,
442 },)+
443 }
444 }
445 };
446
447 }
448
449 define_constant_catalogue! {
451 (length, Length, meter, meter,
452 [millimeter, centimeter, kilometer, inch, foot]),
453 (angle, Angle, radian, radian, [degree, revolution]),
454 (mass, Mass, kilogram, gram, [gram, milligram, pound]),
455 (time, Time, second, second, [millisecond, microsecond, minute, hour]),
456 (thermodynamic_temperature, ThermodynamicTemperature, kelvin, kelvin,
457 [degree_celsius, degree_fahrenheit, degree_rankine]),
458 (velocity, Velocity, meter_per_second, meter_per_second,
459 [kilometer_per_hour, foot_per_second, knot]),
460 (acceleration, Acceleration, meter_per_second_squared, meter_per_second_squared,
461 [standard_gravity, foot_per_second_squared]),
462 (angular_velocity, AngularVelocity, radian_per_second, radian_per_second,
463 [degree_per_second, revolution_per_minute]),
464 (angular_acceleration, AngularAcceleration, radian_per_second_squared,
465 radian_per_second_squared, [degree_per_second_squared]),
466 (frequency, Frequency, hertz, hertz, [kilohertz, megahertz]),
467 (force, Force, newton, newton, [kilonewton, pound_force]),
468 (pressure, Pressure, pascal, pascal,
469 [kilopascal, bar, pound_force_per_square_inch]),
470 (energy, Energy, joule, joule, [kilojoule, watt_hour]),
471 (power, Power, watt, watt, [kilowatt, horsepower]),
472 (electric_potential, ElectricPotential, volt, volt, [millivolt]),
473 (electric_current, ElectricCurrent, ampere, ampere, [milliampere]),
474 (ratio, Ratio, ratio, ratio, [percent]),
475 (area, Area, square_meter, square_meter,
476 [square_millimeter, square_centimeter]),
477 (volume, Volume, cubic_meter, cubic_meter, [liter, milliliter]),
478 }
479}
480
481use alloc::vec::Vec;
482
483pub fn debug_scalar_registrations() -> Vec<cu29_traits::DebugScalarRegistration> {
485 let mut registrations = si::f32::debug_scalar_registrations();
486 registrations.extend(si::f64::debug_scalar_registrations());
487 registrations
488}
489
490#[cfg(all(test, feature = "reflect"))]
491mod tests {
492 use super::si::f32::Velocity;
493 use super::si::velocity::{kilometer_per_hour, meter_per_second};
494 use bevy_reflect::{PartialReflect, Reflect, ReflectRef};
495
496 #[derive(Reflect)]
497 #[reflect(from_reflect = false)]
498 struct Msg {
499 speed: Velocity,
500 }
501
502 #[test]
503 fn reflect_velocity_exposes_value_field() {
504 let msg = Msg {
505 speed: Velocity::new::<kilometer_per_hour>(36.0),
506 };
507
508 assert!(matches!(
509 msg.speed.as_partial_reflect().reflect_ref(),
510 ReflectRef::Struct(_)
511 ));
512 assert_eq!(msg.speed.get::<meter_per_second>(), 10.0);
513
514 let speed_reflected = match msg.as_partial_reflect().reflect_ref() {
515 ReflectRef::Struct(s) => s.field("speed").expect("speed field should exist"),
516 _ => panic!("expected struct reflection"),
517 };
518
519 let speed = speed_reflected
520 .try_downcast_ref::<Velocity>()
521 .expect("speed should downcast to cu29_units::si::f32::Velocity");
522 assert_eq!(speed.raw(), 10.0);
523 assert_eq!(speed.value, 10.0);
524 assert_eq!(speed.get::<meter_per_second>(), 10.0);
525 }
526}
527
528#[cfg(test)]
529mod storage_unit_tests {
530 use super::si::f32;
531 use bincode::ValueDecode;
532
533 fn symbol<T: ValueDecode>() -> &'static str {
534 let [bincode::value_decode::ValueMetadata::Quantity(quantity)] = T::METADATA else {
535 panic!("expected typed quantity metadata")
536 };
537 quantity.storage_unit().symbol()
538 }
539
540 #[test]
541 fn test_conventional_si_storage_units() {
542 assert_eq!(symbol::<f32::ElectricPotential>(), "V");
543 assert_eq!(symbol::<f32::MagneticFluxDensity>(), "T");
544 assert_eq!(symbol::<f32::Force>(), "N");
545 assert_eq!(symbol::<f32::Pressure>(), "Pa");
546 assert_eq!(symbol::<f32::Energy>(), "J");
547 assert_eq!(symbol::<f32::Torque>(), "N·m");
548 assert_eq!(symbol::<f32::Power>(), "W");
549 assert_eq!(symbol::<f32::Frequency>(), "Hz");
550 assert_eq!(symbol::<f32::AngularVelocity>(), "rad·s⁻¹");
551 assert_eq!(symbol::<f32::ElectricalResistance>(), "Ω");
552 assert_eq!(symbol::<f32::Absement>(), "m·s");
553 assert_eq!(symbol::<f32::Velocity>(), "m·s⁻¹");
554 assert_eq!(symbol::<f32::Acceleration>(), "m·s⁻²");
555 assert_eq!(symbol::<f32::Area>(), "m²");
556 assert_eq!(symbol::<f32::Volume>(), "m³");
557 assert_eq!(symbol::<f32::Mass>(), "kg");
558 assert_eq!(symbol::<f32::Angle>(), "rad");
559 assert_eq!(symbol::<f32::SolidAngle>(), "sr");
560 assert_eq!(symbol::<f32::Information>(), "bit");
561 assert_eq!(symbol::<f32::InformationRate>(), "bit·s⁻¹");
562 assert_eq!(symbol::<f32::Ratio>(), "1");
563 }
564
565 #[test]
566 fn test_named_units_match_storage_scale() {
567 assert_eq!(
568 f32::ElectricPotential::new::<super::si::electric_potential::millivolt>(1000.0).raw(),
569 1.0
570 );
571 assert_eq!(f32::Mass::new::<super::si::mass::gram>(1000.0).raw(), 1.0);
572 assert_eq!(
573 f32::Pressure::new::<super::si::pressure::kilopascal>(1.0).raw(),
574 1000.0
575 );
576 }
577}