Skip to main content

swarm_engine_core/
extensions.rs

1//! Extensions - 動的 Resource 管理
2//!
3//! Bevy の `Resource` に相当する、型安全な動的リソース管理。
4
5use std::any::{Any, TypeId};
6use std::collections::HashMap;
7
8/// 動的 Resource 管理(Type-Map パターン)
9///
10/// プラグインや拡張機能が独自の状態を登録可能です。
11/// Worker は `SwarmState.shared.extensions` 経由でアクセスできます。
12///
13/// # Example
14///
15/// ```
16/// use swarm_engine_core::extensions::Extensions;
17///
18/// struct DatabaseConnection {
19///     url: String,
20///     pool_size: usize,
21/// }
22///
23/// let mut extensions = Extensions::new();
24/// extensions.insert(DatabaseConnection {
25///     url: "postgres://localhost".to_string(),
26///     pool_size: 10,
27/// });
28///
29/// let db = extensions.get::<DatabaseConnection>().unwrap();
30/// assert_eq!(db.pool_size, 10);
31/// ```
32pub struct Extensions {
33    map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
34}
35
36impl Extensions {
37    /// 新規作成
38    pub fn new() -> Self {
39        Self {
40            map: HashMap::new(),
41        }
42    }
43
44    /// Resource を登録
45    pub fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
46        self.map.insert(TypeId::of::<T>(), Box::new(value));
47    }
48
49    /// Resource を取得
50    pub fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
51        self.map
52            .get(&TypeId::of::<T>())
53            .and_then(|boxed| boxed.downcast_ref())
54    }
55
56    /// Resource を可変で取得
57    pub fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> {
58        self.map
59            .get_mut(&TypeId::of::<T>())
60            .and_then(|boxed| boxed.downcast_mut())
61    }
62
63    /// Resource を削除
64    pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
65        self.map
66            .remove(&TypeId::of::<T>())
67            .and_then(|boxed| boxed.downcast().ok())
68            .map(|boxed| *boxed)
69    }
70
71    /// Resource が存在するか
72    pub fn contains<T: Send + Sync + 'static>(&self) -> bool {
73        self.map.contains_key(&TypeId::of::<T>())
74    }
75
76    /// 登録されている Resource の数
77    pub fn len(&self) -> usize {
78        self.map.len()
79    }
80
81    /// Resource が空かどうか
82    pub fn is_empty(&self) -> bool {
83        self.map.is_empty()
84    }
85}
86
87impl Default for Extensions {
88    fn default() -> Self {
89        Self::new()
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    struct TestResource {
98        value: i32,
99    }
100
101    struct AnotherResource {
102        name: String,
103    }
104
105    #[test]
106    fn test_insert_and_get() {
107        let mut extensions = Extensions::new();
108        extensions.insert(TestResource { value: 42 });
109
110        let resource = extensions.get::<TestResource>().unwrap();
111        assert_eq!(resource.value, 42);
112    }
113
114    #[test]
115    fn test_get_mut() {
116        let mut extensions = Extensions::new();
117        extensions.insert(TestResource { value: 10 });
118
119        {
120            let resource = extensions.get_mut::<TestResource>().unwrap();
121            resource.value = 20;
122        }
123
124        assert_eq!(extensions.get::<TestResource>().unwrap().value, 20);
125    }
126
127    #[test]
128    fn test_remove() {
129        let mut extensions = Extensions::new();
130        extensions.insert(TestResource { value: 100 });
131
132        let removed = extensions.remove::<TestResource>().unwrap();
133        assert_eq!(removed.value, 100);
134        assert!(extensions.get::<TestResource>().is_none());
135    }
136
137    #[test]
138    fn test_contains() {
139        let mut extensions = Extensions::new();
140        assert!(!extensions.contains::<TestResource>());
141
142        extensions.insert(TestResource { value: 0 });
143        assert!(extensions.contains::<TestResource>());
144    }
145
146    #[test]
147    fn test_multiple_types() {
148        let mut extensions = Extensions::new();
149        extensions.insert(TestResource { value: 1 });
150        extensions.insert(AnotherResource {
151            name: "test".to_string(),
152        });
153
154        assert_eq!(extensions.len(), 2);
155        assert_eq!(extensions.get::<TestResource>().unwrap().value, 1);
156        assert_eq!(extensions.get::<AnotherResource>().unwrap().name, "test");
157    }
158
159    #[test]
160    fn test_type_mismatch_returns_none() {
161        let mut extensions = Extensions::new();
162        extensions.insert(TestResource { value: 42 });
163
164        assert!(extensions.get::<AnotherResource>().is_none());
165    }
166}