use std::any::TypeId;
use std::collections::HashMap;
use crate::Dialect;
pub trait HasDialect: 'static {
const DIALECT: Dialect;
fn dialect_name() -> &'static str {
std::any::type_name::<Self>()
}
}
#[derive(Debug, Clone)]
pub struct RegistryEntry {
pub type_id: TypeId,
pub type_name: &'static str,
pub dialect: Dialect,
}
#[derive(Debug, Default)]
pub struct DialectRegistry {
by_type: HashMap<TypeId, Dialect>,
by_particle: HashMap<Dialect, RegistryEntry>,
}
impl DialectRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register<T: HasDialect>(&mut self) -> Result<(), DialectConflict> {
let type_id = TypeId::of::<T>();
let dialect = T::DIALECT;
match self.by_type.get(&type_id) {
Some(existing) if *existing == dialect => return Ok(()),
Some(existing) => {
return Err(DialectConflict {
type_name: T::dialect_name(),
registered: *existing,
requested: dialect,
});
}
None => {}
}
self.by_type.insert(type_id, dialect);
self.by_particle.insert(
dialect,
RegistryEntry {
type_id,
type_name: T::dialect_name(),
dialect,
},
);
Ok(())
}
pub fn lookup_dialect<T: 'static>(&self) -> Option<Dialect> {
self.by_type.get(&TypeId::of::<T>()).copied()
}
pub fn lookup_type(&self, dialect: Dialect) -> Option<&RegistryEntry> {
self.by_particle.get(&dialect)
}
pub fn agrees_on<T: HasDialect>(&self) -> bool {
self.lookup_dialect::<T>() == Some(T::DIALECT)
}
pub fn len(&self) -> usize {
self.by_type.len()
}
pub fn is_empty(&self) -> bool {
self.by_type.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DialectConflict {
pub type_name: &'static str,
pub registered: Dialect,
pub requested: Dialect,
}
impl core::fmt::Display for DialectConflict {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"dialect conflict for {}: registered {:?} vs requested {:?}",
self.type_name, self.registered, self.requested
)
}
}
impl std::error::Error for DialectConflict {}
#[cfg(test)]
mod tests {
use super::*;
use crate::signature::{dialect_from_struct, FieldSignature};
struct Position {
_x: f32,
_y: f32,
_z: f32,
}
impl HasDialect for Position {
const DIALECT: Dialect = {
ParticleId::from_hash([0xAA; 32])
};
}
struct Velocity(f32, f32, f32);
impl HasDialect for Velocity {
const DIALECT: Dialect = ParticleId::from_hash([0xBB; 32]);
}
use evy_ecs_storage::ParticleId;
#[test]
fn register_and_lookup_by_type() {
let mut reg = DialectRegistry::new();
reg.register::<Position>().unwrap();
assert_eq!(reg.lookup_dialect::<Position>(), Some(Position::DIALECT));
}
#[test]
fn register_and_lookup_by_particle() {
let mut reg = DialectRegistry::new();
reg.register::<Position>().unwrap();
let entry = reg.lookup_type(Position::DIALECT).unwrap();
assert_eq!(entry.dialect, Position::DIALECT);
}
#[test]
fn agrees_on_after_registration() {
let mut reg = DialectRegistry::new();
reg.register::<Position>().unwrap();
assert!(reg.agrees_on::<Position>());
}
#[test]
fn agrees_on_returns_false_for_unregistered_type() {
let reg = DialectRegistry::new();
assert!(!reg.agrees_on::<Position>());
}
#[test]
fn re_register_same_type_is_idempotent() {
let mut reg = DialectRegistry::new();
reg.register::<Position>().unwrap();
reg.register::<Position>().unwrap();
assert_eq!(reg.len(), 1);
}
#[test]
fn distinct_types_register_distinctly() {
let mut reg = DialectRegistry::new();
reg.register::<Position>().unwrap();
reg.register::<Velocity>().unwrap();
assert_eq!(reg.len(), 2);
assert_eq!(reg.lookup_dialect::<Position>(), Some(Position::DIALECT));
assert_eq!(reg.lookup_dialect::<Velocity>(), Some(Velocity::DIALECT));
}
#[test]
fn unregistered_lookup_returns_none() {
struct Unregistered;
let reg = DialectRegistry::new();
assert_eq!(reg.lookup_dialect::<Unregistered>(), None);
}
#[test]
fn derived_dialects_from_signature_round_trip() {
let sc = dialect_from_struct(
"Transform",
&[
FieldSignature::new("x", "f32"),
FieldSignature::new("y", "f32"),
FieldSignature::new("z", "f32"),
],
);
assert_ne!(sc.as_bytes(), &[0u8; 32]);
}
}