temper 0.2.0

A simulation-first Rust game engine
Documentation
use std::{
    any::{Any, TypeId},
    collections::HashMap,
};

pub trait Resource: 'static {}

impl<T: 'static> Resource for T {}

#[derive(Default)]
pub struct Resources {
    values: HashMap<TypeId, Box<dyn Any>>,
}

impl Resources {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert<T: Resource>(&mut self, resource: T) -> Option<T> {
        self.values
            .insert(TypeId::of::<T>(), Box::new(resource))
            .and_then(|previous| previous.downcast::<T>().ok())
            .map(|previous| *previous)
    }

    #[must_use]
    pub fn get<T: Resource>(&self) -> Option<&T> {
        self.values.get(&TypeId::of::<T>())?.downcast_ref()
    }

    #[must_use]
    pub fn get_mut<T: Resource>(&mut self) -> Option<&mut T> {
        self.values.get_mut(&TypeId::of::<T>())?.downcast_mut()
    }

    pub fn remove<T: Resource>(&mut self) -> Option<T> {
        self.values
            .remove(&TypeId::of::<T>())?
            .downcast::<T>()
            .ok()
            .map(|resource| *resource)
    }

    #[must_use]
    pub fn contains<T: Resource>(&self) -> bool {
        self.values.contains_key(&TypeId::of::<T>())
    }

    #[must_use]
    pub fn len(&self) -> usize {
        self.values.len()
    }

    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.values.is_empty()
    }
}

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

    #[derive(Debug, PartialEq)]
    struct Time {
        tick: u64,
    }

    #[derive(Debug, PartialEq)]
    struct Settings {
        speed: u32,
    }

    #[test]
    fn insert_and_get_resource() {
        let mut resources = Resources::new();

        assert_eq!(resources.insert(Time { tick: 10 }), None);
        assert_eq!(resources.get::<Time>(), Some(&Time { tick: 10 }));
    }

    #[test]
    fn insert_replaces_existing_resource() {
        let mut resources = Resources::new();

        resources.insert(Time { tick: 10 });

        assert_eq!(resources.insert(Time { tick: 20 }), Some(Time { tick: 10 }));

        assert_eq!(resources.get::<Time>(), Some(&Time { tick: 20 }));
    }

    #[test]
    fn get_mut_updates_resource() {
        let mut resources = Resources::new();

        resources.insert(Time { tick: 10 });

        resources.get_mut::<Time>().expect("Time should exist").tick += 1;

        assert_eq!(resources.get::<Time>(), Some(&Time { tick: 11 }));
    }

    #[test]
    fn remove_returns_resource() {
        let mut resources = Resources::new();

        resources.insert(Settings { speed: 2 });

        assert_eq!(resources.remove::<Settings>(), Some(Settings { speed: 2 }));

        assert!(!resources.contains::<Settings>());
        assert!(resources.is_empty());
    }

    #[test]
    fn resources_are_stored_by_type() {
        let mut resources = Resources::new();

        resources.insert(Time { tick: 10 });
        resources.insert(Settings { speed: 2 });

        assert_eq!(resources.len(), 2);
        assert_eq!(resources.get::<Time>(), Some(&Time { tick: 10 }));
        assert_eq!(resources.get::<Settings>(), Some(&Settings { speed: 2 }));
    }
}