use crate::units::{Unit, UnitType}; use fastnum::D128; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::RwLock; #[derive(Clone, Copy)] pub(crate) struct ExtraUnitDef { pub(crate) category: UnitType, pub(crate) weight: D128, pub(crate) singular: &'static str, pub(crate) plural: &'static str, } #[derive(Default)] struct Registry { by_name: HashMap<&'static str, u16>, units: Vec, } static REGISTRY: Lazy> = Lazy::new(|| RwLock::new(Registry::default())); fn leak_str(s: String) -> &'static str { Box::leak(s.into_boxed_str()) } pub(crate) fn lookup_unit(word: &str) -> Option { let reg = REGISTRY.read().ok()?; let id = *reg.by_name.get(word)?; Some(Unit::Extra(id)) } pub(crate) fn upsert_unit( name_lowercase: &str, singular: &str, plural: &str, category: UnitType, weight: D128, ) -> Unit { let mut reg = REGISTRY .write() .expect("extra unit registry lock poisoned"); if let Some(existing_id) = reg.by_name.get(name_lowercase).copied() { let idx = existing_id as usize; reg.units[idx] = ExtraUnitDef { category, weight, singular: reg.units[idx].singular, plural: reg.units[idx].plural, }; return Unit::Extra(existing_id); } let id: u16 = reg .units .len() .try_into() .expect("too many extra units registered"); let key = leak_str(name_lowercase.to_string()); let singular = leak_str(singular.to_string()); let plural = leak_str(plural.to_string()); reg.by_name.insert(key, id); reg.units.push(ExtraUnitDef { category, weight, singular, plural, }); Unit::Extra(id) } pub(crate) fn get_category(id: u16) -> UnitType { let reg = REGISTRY .read() .expect("extra unit registry lock poisoned"); reg.units .get(id as usize) .map(|u| u.category) .unwrap_or(UnitType::NoType) } pub(crate) fn get_weight(id: u16) -> D128 { let reg = REGISTRY .read() .expect("extra unit registry lock poisoned"); reg.units .get(id as usize) .map(|u| u.weight) .unwrap_or(D128::NAN) } pub(crate) fn get_singular(id: u16) -> &'static str { let reg = REGISTRY .read() .expect("extra unit registry lock poisoned"); reg.units .get(id as usize) .map(|u| u.singular) .unwrap_or("") } pub(crate) fn get_plural(id: u16) -> &'static str { let reg = REGISTRY .read() .expect("extra unit registry lock poisoned"); reg.units .get(id as usize) .map(|u| u.plural) .unwrap_or("") } #[cfg(test)] pub(crate) fn clear_for_tests() { let mut reg = REGISTRY .write() .expect("extra unit registry lock poisoned"); reg.by_name.clear(); reg.units.clear(); }