Skip to main content

cougr_core/
component.rs

1use alloc::vec::Vec;
2use soroban_sdk::{contracttype, Bytes, BytesN, Env, IntoVal, Symbol, TryFromVal, Val};
3
4/// A unique identifier for a component type
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
6pub struct ComponentId {
7    id: u32,
8}
9
10impl ComponentId {
11    pub fn new(id: u32) -> Self {
12        Self { id }
13    }
14    pub fn id(&self) -> u32 {
15        self.id
16    }
17}
18// Soroban SDK trait implementations for ComponentId
19impl IntoVal<Env, Val> for ComponentId {
20    fn into_val(&self, env: &Env) -> Val {
21        self.id.into_val(env)
22    }
23}
24
25impl TryFromVal<Env, Val> for ComponentId {
26    type Error = soroban_sdk::ConversionError;
27
28    fn try_from_val(env: &Env, val: &Val) -> Result<Self, Self::Error> {
29        let id: u32 = TryFromVal::try_from_val(env, val)?;
30        Ok(ComponentId::new(id))
31    }
32}
33
34#[contracttype]
35#[repr(u32)]
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum ComponentStorage {
38    #[default]
39    Table = 0,
40    Sparse = 1,
41}
42
43#[contracttype]
44#[derive(Debug, Clone)]
45pub struct Component {
46    pub component_type: Symbol,
47    pub data: Bytes,
48    pub storage: ComponentStorage,
49}
50
51impl Component {
52    pub fn new(component_type: Symbol, data: Bytes) -> Self {
53        Self {
54            component_type,
55            data,
56            storage: ComponentStorage::default(),
57        }
58    }
59    pub fn with_storage(component_type: Symbol, data: Bytes, storage: ComponentStorage) -> Self {
60        Self {
61            component_type,
62            data,
63            storage,
64        }
65    }
66    pub fn component_type(&self) -> &Symbol {
67        &self.component_type
68    }
69    pub fn data(&self) -> &Bytes {
70        &self.data
71    }
72    pub fn data_mut(&mut self) -> &mut Bytes {
73        &mut self.data
74    }
75    pub fn storage(&self) -> ComponentStorage {
76        self.storage
77    }
78    pub fn set_storage(&mut self, storage: ComponentStorage) {
79        self.storage = storage;
80    }
81}
82
83/// Registry for managing component types
84#[derive(Debug, Clone)]
85pub struct ComponentRegistry {
86    next_id: u32,
87    components: Vec<(Symbol, ComponentId)>,
88}
89
90impl ComponentRegistry {
91    /// Create a new component registry
92    pub fn new() -> Self {
93        Self {
94            next_id: 1,
95            components: Vec::new(),
96        }
97    }
98
99    /// Register a new component type
100    pub fn register_component(&mut self, component_type: Symbol) -> ComponentId {
101        // Check if component type is already registered
102        for (ctype, id) in &self.components {
103            if ctype == &component_type {
104                return *id;
105            }
106        }
107
108        let id = ComponentId::new(self.next_id);
109        self.next_id += 1;
110        self.components.push((component_type, id));
111        id
112    }
113
114    /// Get the component ID for a component type
115    pub fn get_component_id(&self, component_type: &Symbol) -> Option<ComponentId> {
116        for (ctype, id) in &self.components {
117            if ctype == component_type {
118                return Some(*id);
119            }
120        }
121        None
122    }
123
124    /// Get the component type for a component ID
125    pub fn get_component_type(&self, component_id: ComponentId) -> Option<Symbol> {
126        for (ctype, id) in &self.components {
127            if id == &component_id {
128                return Some(ctype.clone());
129            }
130        }
131        None
132    }
133
134    /// Get the number of registered component types
135    pub fn component_count(&self) -> usize {
136        self.components.len()
137    }
138
139    /// Check if a component type is registered
140    pub fn is_registered(&self, component_type: &Symbol) -> bool {
141        for (ctype, _) in &self.components {
142            if ctype == component_type {
143                return true;
144            }
145        }
146        false
147    }
148}
149
150impl Default for ComponentRegistry {
151    fn default() -> Self {
152        Self::new()
153    }
154}
155
156pub trait ComponentTrait {
157    fn component_type() -> Symbol;
158    fn serialize(&self, env: &Env) -> Bytes;
159    fn deserialize(env: &Env, data: &Bytes) -> Option<Self>
160    where
161        Self: Sized;
162    fn default_storage() -> ComponentStorage {
163        ComponentStorage::Table
164    }
165}
166
167#[contracttype]
168#[derive(Clone)]
169pub struct Position {
170    pub x: i32,
171    pub y: i32,
172}
173impl Position {
174    pub fn new(x: i32, y: i32) -> Self {
175        Self { x, y }
176    }
177}
178impl_component!(Position, "position", Table, { x: i32, y: i32 });
179
180#[contracttype]
181#[derive(Clone)]
182pub struct Velocity {
183    pub x: i32,
184    pub y: i32,
185}
186impl Velocity {
187    pub fn new(x: i32, y: i32) -> Self {
188        Self { x, y }
189    }
190}
191impl_component!(Velocity, "velocity", Table, { x: i32, y: i32 });
192
193// ─── Test types for macro-generated components ────────────────
194
195#[contracttype]
196#[derive(Clone, Debug)]
197pub struct Health {
198    pub current: u128,
199    pub max: u128,
200}
201impl_component!(Health, "health", Table, { current: u128, max: u128 });
202
203#[contracttype]
204#[derive(Clone, Debug)]
205pub struct Token {
206    pub amount: u32,
207    pub hash: BytesN<32>,
208}
209impl_component!(Token, "token", Table, { amount: u32, hash: bytes32 });
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use soroban_sdk::{symbol_short, BytesN, Env};
215
216    #[test]
217    fn test_component_id_creation() {
218        let id = ComponentId::new(1);
219        assert_eq!(id.id(), 1);
220    }
221
222    #[test]
223    fn test_component_creation() {
224        let env = Env::default();
225        let component_type = symbol_short!("test");
226        let mut data = Bytes::new(&env);
227        data.append(&Bytes::from_array(&env, &[1, 2, 3, 4]));
228        let component = Component::new(component_type, data.clone());
229
230        assert_eq!(component.component_type(), &symbol_short!("test"));
231        assert_eq!(component.data(), &data);
232        assert_eq!(component.storage(), ComponentStorage::Table);
233    }
234
235    #[test]
236    fn test_component_registry() {
237        let mut registry = ComponentRegistry::new();
238        assert_eq!(registry.component_count(), 0);
239
240        let component_type = symbol_short!("test");
241        let id = registry.register_component(component_type.clone());
242        assert_eq!(registry.component_count(), 1);
243        assert!(registry.is_registered(&component_type));
244
245        let retrieved_id = registry.get_component_id(&component_type);
246        assert_eq!(retrieved_id, Some(id));
247    }
248
249    #[test]
250    fn test_position_component() {
251        let env = Env::default();
252        let position = Position::new(100, 200);
253        let data = position.serialize(&env);
254        let deserialized = Position::deserialize(&env, &data).unwrap();
255
256        assert_eq!(position.x, deserialized.x);
257        assert_eq!(position.y, deserialized.y);
258    }
259
260    // ─── Extended macro type tests ────────────────────────────────
261
262    #[test]
263    fn test_u128_component() {
264        let env = Env::default();
265        let health = Health {
266            current: 999_999_999_999,
267            max: 1_000_000_000_000,
268        };
269        let data = health.serialize(&env);
270        assert_eq!(data.len(), 32); // 16 + 16 bytes
271        let deserialized = Health::deserialize(&env, &data).unwrap();
272        assert_eq!(deserialized.current, 999_999_999_999);
273        assert_eq!(deserialized.max, 1_000_000_000_000);
274    }
275
276    #[test]
277    fn test_bytes32_component() {
278        let env = Env::default();
279        let hash = BytesN::from_array(&env, &[0xABu8; 32]);
280        let token = Token {
281            amount: 42,
282            hash: hash.clone(),
283        };
284        let data = token.serialize(&env);
285        assert_eq!(data.len(), 36); // 4 + 32 bytes
286        let deserialized = Token::deserialize(&env, &data).unwrap();
287        assert_eq!(deserialized.amount, 42);
288        assert_eq!(deserialized.hash, hash);
289    }
290
291    #[test]
292    fn test_u128_zero() {
293        let env = Env::default();
294        let health = Health { current: 0, max: 0 };
295        let data = health.serialize(&env);
296        let deserialized = Health::deserialize(&env, &data).unwrap();
297        assert_eq!(deserialized.current, 0);
298        assert_eq!(deserialized.max, 0);
299    }
300
301    #[test]
302    fn test_u128_max() {
303        let env = Env::default();
304        let health = Health {
305            current: u128::MAX,
306            max: u128::MAX,
307        };
308        let data = health.serialize(&env);
309        let deserialized = Health::deserialize(&env, &data).unwrap();
310        assert_eq!(deserialized.current, u128::MAX);
311        assert_eq!(deserialized.max, u128::MAX);
312    }
313}