td

registry.rs at trunk
Login

registry.rs at trunk

File src/registry.rs artifact 54c3a1a987 on branch trunk


//! The generic content registry: one `Registry<K>` implementation shared by every "id ->
//! factory closure" content table (weapons, enemies, damage types, and future kinds like
//! status/animation/tower/drone). Each concrete content kind is a zero-sized marker
//! implementing `ContentKind`, which just declares the factory's argument and output types —
//! the interning, storage, and lookup logic is written exactly once here.

use std::{collections::HashMap, hash::Hash, marker::PhantomData};

use crate::{
    behavior::{
        damage::DamageBehavior,
        projectile::{BehaviorPriority, ProjectileBehavior},
        status::{PeriodDamage, StatusBehavior},
    },
    enemies::{EnemyKind as EnemyKindLifecycle, blob::Blob},
    enemy::Enemy,
    weapons::impact::TowerBaseWeapon,
};

/// Declares what a content kind's factory closures look like: the arguments they take
/// (`u32` tier for weapons, `()` for enemies, ...) and what they produce
/// (`Box<dyn ProjectileBehavior>`, `Enemy`, ...). One zero-sized marker per content kind.
pub trait ContentKind {
    type Args;
    type Output;
}

/// A dense, type-distinct id into a `Registry<K>`. Generic over the content kind so
/// `Id<WeaponContent>` and `Id<EnemyContent>` can never be mixed up, without hand-writing a
/// newtype per registry.
pub struct Id<K> {
    idx: u32,
    _marker: PhantomData<fn() -> K>,
}

impl<K> Id<K> {
    const fn new(idx: u32) -> Self {
        Self {
            idx,
            _marker: PhantomData,
        }
    }
}

// Manual impls instead of `#[derive]`: derive would add a `K: Trait` bound from the
// `PhantomData<K>` field, which is wrong here — `K` is a marker type, never actually stored.
impl<K> Copy for Id<K> {}
impl<K> Clone for Id<K> {
    fn clone(&self) -> Self {
        *self
    }
}
impl<K> PartialEq for Id<K> {
    fn eq(&self, other: &Self) -> bool {
        self.idx == other.idx
    }
}
impl<K> Eq for Id<K> {}
impl<K> Hash for Id<K> {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.idx.hash(state);
    }
}
impl<K> std::fmt::Debug for Id<K> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Id({})", self.idx)
    }
}

struct Def<K: ContentKind> {
    display_name: String,
    priority: i32,
    make: Box<dyn Fn(K::Args) -> K::Output + Send + Sync>,
}

/// String <-> dense u32 id table, shared by every content registry.
#[derive(Default)]
struct Interner {
    to_id: HashMap<String, u32>,
    names: Vec<String>,
}
impl Interner {
    fn intern(&mut self, s: &str) -> u32 {
        if let Some(&id) = self.to_id.get(s) {
            return id;
        }
        let id = self.names.len() as u32;
        self.names.push(s.to_owned());
        self.to_id.insert(s.to_owned(), id);
        id
    }
    fn get(&self, s: &str) -> Option<u32> {
        self.to_id.get(s).copied()
    }
    #[allow(dead_code)]
    fn name(&self, id: u32) -> &str {
        &self.names[id as usize]
    }
}

/// One generic content table: intern a name, store its factory closure, hand back a
/// type-distinct `Id<K>`. `WeaponRegistry`, `EnemyRegistry`, `DamageRegistry`, and any future
/// registry are all just `Registry<SomeMarker>` — see the aliases below.
pub struct Registry<K: ContentKind> {
    interner: Interner,
    defs: HashMap<Id<K>, Def<K>>,
    all: Vec<Id<K>>,
}

impl<K: ContentKind> Default for Registry<K> {
    fn default() -> Self {
        Self {
            interner: Interner::default(),
            defs: HashMap::new(),
            all: Vec::new(),
        }
    }
}

impl<K: ContentKind> Registry<K> {
    pub fn register(
        &mut self,
        name: &str,
        display: &str,
        priority: i32,
        make: impl Fn(K::Args) -> K::Output + Send + Sync + 'static,
    ) -> Id<K> {
        let id = Id::new(self.interner.intern(name));
        self.defs.insert(
            id,
            Def {
                display_name: display.into(),
                priority,
                make: Box::new(make),
            },
        );
        self.all.push(id);
        id
    }

    pub fn make(&self, id: Id<K>, args: K::Args) -> K::Output {
        (self.defs[&id].make)(args)
    }
    pub fn display_name(&self, id: Id<K>) -> &str {
        &self.defs[&id].display_name
    }
    pub fn priority(&self, id: Id<K>) -> i32 {
        self.defs[&id].priority
    }
    pub fn id_of(&self, name: &str) -> Option<Id<K>> {
        self.interner.get(name).map(Id::new)
    }
    pub fn all(&self) -> &[Id<K>] {
        &self.all
    }
}

// ---------------------------------------------------------------------------------------
// Content kinds. Adding a new registry (status, animation, tower loadout, drone, ...) is
// just one marker + two aliases below, plus a `new()` that seeds its own content — no new
// storage/lookup code required.
// ---------------------------------------------------------------------------------------

pub struct WeaponContent;
impl ContentKind for WeaponContent {
    type Args = u32; // tier
    type Output = Box<dyn ProjectileBehavior>;
}
pub type WeaponID = Id<WeaponContent>;
pub type WeaponRegistry = Registry<WeaponContent>;

impl WeaponRegistry {
    pub fn new(damage: &DamageRegistry) -> Self {
        let physical = damage
            .id_of("physical")
            .expect("DamageRegistry must seed \"physical\" before WeaponRegistry is built");
        let poison = damage
            .id_of("poison")
            .expect("DamageRegistry must seed \"poison\" before WeaponRegistry is built");
        let mut reg = Self::default();
        reg.register(
            "impact",
            "Impact",
            crate::behavior::projectile::BehaviorPriority::DEFAULT,
            move |tier| TowerBaseWeapon::create(tier, physical),
        );
        reg.register(
            "poison_coating",
            "Poison Coating",
            crate::behavior::projectile::BehaviorPriority::DEFAULT,
            move |tier| crate::weapons::poison::PoisonCoating::create(tier, poison),
        );
        reg
    }
}

pub struct EnemyContent;
impl ContentKind for EnemyContent {
    type Args = u32;
    type Output = Enemy;
}
pub type EnemyID = Id<EnemyContent>;
pub type EnemyRegistry = Registry<EnemyContent>;

impl EnemyID {
    pub const PLACEHOLDER: EnemyID = Id::new(0);
}

impl EnemyRegistry {
    pub fn new() -> Self {
        let mut reg = Self::default();
        reg.register("blob", "Blob", 0, |tier| Blob::create(tier));
        reg
    }

    pub fn make_kind(&self, id: EnemyID, tier: u32) -> Enemy {
        let mut e = self.make(id, tier);
        e.kind = id;
        e
    }
}

pub struct DamageContent;
impl ContentKind for DamageContent {
    type Args = u32; // potency / scaling input
    type Output = Box<dyn DamageBehavior>;
}
pub type DamageID = Id<DamageContent>;
pub type DamageRegistry = Registry<DamageContent>;

impl DamageRegistry {
    pub fn new() -> Self {
        let mut reg = Self::default();
        reg.register(
            "physical",
            "Physical",
            crate::behavior::projectile::BehaviorPriority::DEFAULT,
            |_| Box::new(crate::behavior::damage::Physical) as Box<dyn DamageBehavior>,
        );
        reg.register(
            "poison",
            "Poison",
            crate::behavior::projectile::BehaviorPriority::DEFAULT,
            |_| Box::new(crate::behavior::damage::Poison) as Box<dyn DamageBehavior>,
        );
        reg
    }
}

pub struct StatusContent;
impl ContentKind for StatusContent {
    type Args = u32;
    type Output = Box<dyn StatusBehavior>;
}

pub type StatusID = Id<StatusContent>;
pub type StatusRegistry = Registry<StatusContent>;

impl StatusRegistry {
    pub fn new(damage: &DamageRegistry) -> Self {
        let poison = damage.id_of("poison").unwrap();
        let mut reg = Self::default();
        reg.register("poison", "Poison", BehaviorPriority::MODIFY, move |_| {
            PeriodDamage::make(poison, 3.0, 1.0, 5.0, crate::types::Source::None)
        });
        reg
    }
}