use paste::paste; use std::sync::OnceLock; use super::DataLoadError; macro_rules! template_id { ($type_name:ident $underlying_type:ident $id_field:ident) => { ::paste::paste! { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, ::serde::Deserialize, ::serde::Serialize)] pub struct [<$type_name ID>]($underlying_type); impl [<$type_name ID>] { pub fn new(id: $underlying_type) -> Option { if crate::tables::[<$type_name:snake _template_tb>]::iter().any(|tmpl| tmpl.$id_field.value() == id) { Some(Self(id)) } else { None } } pub fn new_unchecked(id: $underlying_type) -> Self { Self(id) } pub fn value(&self) -> $underlying_type { self.0 } pub fn template(&self) -> &[<$type_name Template>] { crate::tables::[<$type_name:snake _template_tb>]::iter().find(|tmpl| tmpl.$id_field == *self).unwrap() } } impl ::std::fmt::Display for [<$type_name ID>] { fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { f.write_fmt(format_args!("{}", self.0)) } } } }; } macro_rules! template_tables { ($($template_type:ident;)*) => { $(paste! { mod [<$template_type:snake>]; pub use [<$template_type:snake>]::*; })* $(paste! { static [<$template_type:snake:upper _TB>]: OnceLock> = OnceLock::new(); })* pub(crate) fn load_tables(filecfg_path: &str) -> Result<(), DataLoadError> { $(paste! { let file_name = concat!(stringify!($template_type), "Tb.json"); let path = format!("{filecfg_path}/{file_name}"); let data = std::fs::read_to_string(path)?; [<$template_type:snake:upper _TB>].set(serde_json::from_str(&data).map_err(|err| DataLoadError::FromJsonError(String::from(stringify!($template_type)), err))?).unwrap(); })* Ok(()) } $(paste! { pub mod [<$template_type:snake _tb>] { pub fn iter() -> ::std::slice::Iter<'static, super::$template_type> { super::[<$template_type:snake:upper _TB>].get().unwrap().iter() } } })* }; } template_tables! { AvatarBaseTemplate; UnlockConfigTemplate; SectionConfigTemplate; ProcedureConfigTemplate; PostGirlConfigTemplate; TrainingQuestTemplate; WeaponTemplate; MainCityObjectTemplate; }