swarm-engine-core 0.1.6

Core types and orchestration for SwarmEngine
Documentation
//! Extensions - 動的 Resource 管理
//!
//! Bevy の `Resource` に相当する、型安全な動的リソース管理。

use std::any::{Any, TypeId};
use std::collections::HashMap;

/// 動的 Resource 管理(Type-Map パターン)
///
/// プラグインや拡張機能が独自の状態を登録可能です。
/// Worker は `SwarmState.shared.extensions` 経由でアクセスできます。
///
/// # Example
///
/// ```
/// use swarm_engine_core::extensions::Extensions;
///
/// struct DatabaseConnection {
///     url: String,
///     pool_size: usize,
/// }
///
/// let mut extensions = Extensions::new();
/// extensions.insert(DatabaseConnection {
///     url: "postgres://localhost".to_string(),
///     pool_size: 10,
/// });
///
/// let db = extensions.get::<DatabaseConnection>().unwrap();
/// assert_eq!(db.pool_size, 10);
/// ```
pub struct Extensions {
    map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}

impl Extensions {
    /// 新規作成
    pub fn new() -> Self {
        Self {
            map: HashMap::new(),
        }
    }

    /// Resource を登録
    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
        self.map.insert(TypeId::of::<T>(), Box::new(value));
    }

    /// Resource を取得
    pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
        self.map
            .get(&TypeId::of::<T>())
            .and_then(|boxed| boxed.downcast_ref())
    }

    /// Resource を可変で取得
    pub fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
        self.map
            .get_mut(&TypeId::of::<T>())
            .and_then(|boxed| boxed.downcast_mut())
    }

    /// Resource を削除
    pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
        self.map
            .remove(&TypeId::of::<T>())
            .and_then(|boxed| boxed.downcast().ok())
            .map(|boxed| *boxed)
    }

    /// Resource が存在するか
    pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
        self.map.contains_key(&TypeId::of::<T>())
    }

    /// 登録されている Resource の数
    pub fn len(&self) -> usize {
        self.map.len()
    }

    /// Resource が空かどうか
    pub fn is_empty(&self) -> bool {
        self.map.is_empty()
    }
}

impl Default for Extensions {
    fn default() -> Self {
        Self::new()
    }
}

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

    struct TestResource {
        value: i32,
    }

    struct AnotherResource {
        name: String,
    }

    #[test]
    fn test_insert_and_get() {
        let mut extensions = Extensions::new();
        extensions.insert(TestResource { value: 42 });

        let resource = extensions.get::<TestResource>().unwrap();
        assert_eq!(resource.value, 42);
    }

    #[test]
    fn test_get_mut() {
        let mut extensions = Extensions::new();
        extensions.insert(TestResource { value: 10 });

        {
            let resource = extensions.get_mut::<TestResource>().unwrap();
            resource.value = 20;
        }

        assert_eq!(extensions.get::<TestResource>().unwrap().value, 20);
    }

    #[test]
    fn test_remove() {
        let mut extensions = Extensions::new();
        extensions.insert(TestResource { value: 100 });

        let removed = extensions.remove::<TestResource>().unwrap();
        assert_eq!(removed.value, 100);
        assert!(extensions.get::<TestResource>().is_none());
    }

    #[test]
    fn test_contains() {
        let mut extensions = Extensions::new();
        assert!(!extensions.contains::<TestResource>());

        extensions.insert(TestResource { value: 0 });
        assert!(extensions.contains::<TestResource>());
    }

    #[test]
    fn test_multiple_types() {
        let mut extensions = Extensions::new();
        extensions.insert(TestResource { value: 1 });
        extensions.insert(AnotherResource {
            name: "test".to_string(),
        });

        assert_eq!(extensions.len(), 2);
        assert_eq!(extensions.get::<TestResource>().unwrap().value, 1);
        assert_eq!(extensions.get::<AnotherResource>().unwrap().name, "test");
    }

    #[test]
    fn test_type_mismatch_returns_none() {
        let mut extensions = Extensions::new();
        extensions.insert(TestResource { value: 42 });

        assert!(extensions.get::<AnotherResource>().is_none());
    }
}