use std::any::{Any, TypeId};
use std::collections::HashMap;
pub trait Resource: Send + Sync + 'static {
fn resource_type(&self) -> &'static str {
std::any::type_name::<Self>()
}
}
pub struct Resources {
data: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}
impl Resources {
pub(crate) fn new() -> Self {
Self {
data: HashMap::new(),
}
}
pub fn register<T: Resource>(&mut self, resource: T) {
self.data.insert(TypeId::of::<T>(), Box::new(resource));
}
pub fn get<T: Resource>(&self) -> Option<&T> {
self.data
.get(&TypeId::of::<T>())
.and_then(|boxed| boxed.downcast_ref::<T>())
}
pub fn contains<T: Resource>(&self) -> bool {
self.data.contains_key(&TypeId::of::<T>())
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub(crate) fn into_inner(self) -> HashMap<TypeId, Box<dyn Any + Send + Sync>> {
self.data
}
#[allow(dead_code)]
pub(crate) fn remove<T: Resource>(&mut self) -> Option<T> {
self.data
.remove(&TypeId::of::<T>())
.and_then(|boxed| boxed.downcast::<T>().ok())
.map(|boxed| *boxed)
}
#[allow(dead_code)]
pub(crate) fn clear(&mut self) {
self.data.clear();
}
}
impl Default for Resources {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use issun_macros::Resource;
use super::*;
impl Resource for TestConfig {}
impl Resource for TestDatabase {}
#[derive(Debug, Clone, PartialEq)]
struct TestConfig {
value: i32,
}
#[derive(Debug, Clone, PartialEq)]
struct TestDatabase {
name: String,
}
#[derive(Resource, Debug, Clone, PartialEq)]
#[allow(dead_code)]
struct DerivedConfig {
fps: u32,
difficulty: f32,
}
#[derive(crate::Resource, Debug, Clone)]
#[allow(dead_code)]
struct DerivedDatabase {
items: Vec<String>,
}
#[test]
fn test_register_and_get() {
let mut resources = Resources::new();
resources.register(TestConfig { value: 42 });
let config = resources.get::<TestConfig>();
assert!(config.is_some());
assert_eq!(config.unwrap().value, 42);
}
#[test]
fn test_get_nonexistent() {
let resources = Resources::new();
let config = resources.get::<TestConfig>();
assert!(config.is_none());
}
#[test]
fn test_multiple_types() {
let mut resources = Resources::new();
resources.register(TestConfig { value: 10 });
resources.register(TestDatabase {
name: "Test".to_string(),
});
assert!(resources.get::<TestConfig>().is_some());
assert!(resources.get::<TestDatabase>().is_some());
assert_eq!(resources.get::<TestConfig>().unwrap().value, 10);
assert_eq!(resources.get::<TestDatabase>().unwrap().name, "Test");
}
#[test]
fn test_replace() {
let mut resources = Resources::new();
resources.register(TestConfig { value: 1 });
resources.register(TestConfig { value: 2 });
let config = resources.get::<TestConfig>();
assert_eq!(config.unwrap().value, 2);
}
#[test]
fn test_contains() {
let mut resources = Resources::new();
resources.register(TestConfig { value: 5 });
assert!(resources.contains::<TestConfig>());
assert!(!resources.contains::<TestDatabase>());
}
#[test]
fn test_remove() {
let mut resources = Resources::new();
resources.register(TestConfig { value: 99 });
let removed = resources.remove::<TestConfig>();
assert!(removed.is_some());
assert_eq!(removed.unwrap().value, 99);
assert!(!resources.contains::<TestConfig>());
}
#[test]
fn test_len_and_clear() {
let mut resources = Resources::new();
assert_eq!(resources.len(), 0);
assert!(resources.is_empty());
resources.register(TestConfig { value: 1 });
resources.register(TestDatabase {
name: "DB".to_string(),
});
assert_eq!(resources.len(), 2);
assert!(!resources.is_empty());
resources.clear();
assert_eq!(resources.len(), 0);
assert!(resources.is_empty());
}
#[test]
fn test_derived_resource() {
let mut resources = Resources::new();
resources.register(DerivedConfig {
fps: 60,
difficulty: 1.5,
});
let config = resources.get::<DerivedConfig>();
assert!(config.is_some());
assert_eq!(config.unwrap().fps, 60);
assert_eq!(config.unwrap().difficulty, 1.5);
}
#[test]
fn test_resource_type_name() {
let config = TestConfig { value: 42 };
let type_name = config.resource_type();
assert!(type_name.contains("TestConfig"));
}
#[test]
fn test_derive_macro_single_type() {
let mut resources = Resources::new();
resources.register(DerivedConfig {
fps: 60,
difficulty: 1.5,
});
let config = resources.get::<DerivedConfig>();
assert!(config.is_some());
assert_eq!(config.unwrap().fps, 60);
}
#[test]
fn test_derive_macro_multiple_types() {
let mut resources = Resources::new();
resources.register(DerivedConfig {
fps: 30,
difficulty: 2.0,
});
resources.register(DerivedDatabase {
items: vec!["item1".to_string(), "item2".to_string()],
});
assert!(resources.contains::<DerivedConfig>());
assert!(resources.contains::<DerivedDatabase>());
let db = resources.get::<DerivedDatabase>();
assert_eq!(db.unwrap().items.len(), 2);
}
}