spru-util 0.1.0

An experimental framework for building multiplayer strategy and digital board games.
Documentation
use std::{
    any,
    collections::{HashMap, hash_map},
    marker::PhantomData,
};

use derive_where::derive_where;
use spru::{
    Item,
    item::{self, IdT},
};

/// A standalone item storage.
#[derive_where(Debug, Default; )]
pub struct Standalone<State> {
    map: HashMap<any::TypeId, Box<dyn any::Any>>,
    _state: PhantomData<fn() -> State>,
}

impl<State> Standalone<State> {
    pub fn new() -> Self {
        Self::default()
    }
}

impl<State: spru::State> spru::item::Storage for Standalone<State> {
    type State = State;

    fn get<T>(&self, id: IdT<T>) -> spru::item::storage::Result<&Item<T>>
    where
        T: item::storage::Storable<Self::State>,
    {
        let type_id = any::TypeId::of::<T>();
        let item = if let Some(inner_map) = self.map.get(&type_id) {
            let inner_map = inner_map
                .downcast_ref::<HashMap<item::IdT<T>, Item<T>>>()
                .expect("Internal type mismatch");

            inner_map.get(&id)
        } else {
            None
        };
        item.ok_or(Error::IdNotFound(id.untyped()))
            .map_err(Into::into)
    }

    #[allow(refining_impl_trait)]
    fn get_mut<T>(&mut self, id: IdT<T>) -> spru::item::storage::Result<&mut spru::Item<T>>
    where
        T: item::storage::Storable<Self::State>,
    {
        let type_id = any::TypeId::of::<T>();
        let item = if let Some(inner_map) = self.map.get_mut(&type_id) {
            let inner_map = inner_map
                .downcast_mut::<InnerMap<T>>()
                .expect("Interal type mismatch");

            inner_map.get_mut(&id)
        } else {
            None
        };
        item.ok_or(Error::IdNotFound(id.untyped()))
            .map_err(Into::into)
    }

    fn create<T>(&mut self, value: spru::Item<T>) -> spru::item::storage::Result<()>
    where
        T: item::storage::Storable<Self::State>,
    {
        let id = value.id();
        let type_id = any::TypeId::of::<T>();
        let inner_map = self
            .map
            .entry(type_id)
            .or_insert(Box::new(InnerMap::<T>::new()));

        let inner_map = inner_map
            .downcast_mut::<InnerMap<T>>()
            .expect("Interal type mismatch");
        if let hash_map::Entry::Vacant(v) = inner_map.entry(id) {
            v.insert(value);
            Ok(())
        } else {
            Err(Error::IdAlreadyExists(id.untyped()).into())
        }
    }

    fn destroy<T>(&mut self, id: IdT<T>) -> spru::item::storage::Result<spru::Item<T>>
    where
        T: item::storage::Storable<Self::State>,
    {
        let type_id = any::TypeId::of::<T>();
        let item = if let Some(inner_map) = self.map.get_mut(&type_id) {
            let inner_map = inner_map
                .downcast_mut::<InnerMap<T>>()
                .expect("Interal type mismatch");

            inner_map.remove(&id)
        } else {
            None
        };

        item.ok_or(Error::IdNotFound(id.untyped()).into())
    }
}

type InnerMap<T> = HashMap<item::IdT<T>, Item<T>>;

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Id '{0}' was not found")]
    IdNotFound(item::Id),
    #[error("Id '{0}' already exists")]
    IdAlreadyExists(item::Id),
}

#[cfg(test)]
mod test {
    use super::*;

    #[tagset::tagset(impl spru::State)]
    #[tagset(String)]
    struct State;

    #[test]
    fn standalone() {
        use spru::item::storage::Storage as _;
        let mut storage = Standalone::<State>::new();

        let id0 = spru::item::IdT::test_new(0);
        let id1 = spru::item::IdT::test_new(1);
        let id2 = spru::item::IdT::test_new(2);

        storage
            .create(spru::Item::test_new(id0.clone(), "zero".to_string()))
            .unwrap();

        storage
            .create(spru::Item::test_new(id1.clone(), "one".to_string()))
            .unwrap();

        storage
            .create(spru::Item::test_new(id2.clone(), "two".to_string()))
            .unwrap();

        let s0 = storage.get_mut(id0).unwrap().test_get_mut();

        *s0 = "ZERO".to_string();

        let s0 = storage.get(id0).unwrap().get();
        assert_eq!(s0, &"ZERO");

        let s1 = storage.get(id1).unwrap().get();
        assert_eq!(s1, &"one");

        storage.destroy(id2).unwrap();
    }
}