Skip to main content

naia_shared/world/resource/
resource_registry.rs

1use std::{any::TypeId, collections::HashMap};
2
3use crate::GlobalEntity;
4
5/// Per-`World` bidirectional map between Resource `TypeId` and the hidden
6/// `GlobalEntity` carrying that resource as a single component.
7///
8/// Maintained on both sides:
9///
10/// - Sender (`HostWorldManager`): inserted at `insert_resource::<R>(...)`
11///   time, removed at `remove_resource::<R>()` time.
12/// - Receiver (`RemoteWorldManager`): inserted when an incoming
13///   `SpawnWithComponents` carries a component whose kind is registered
14///   in `protocol.resource_kinds`. Removed on entity despawn.
15///
16/// Lookups are O(1) in both directions:
17/// - `entity_for(TypeId)` → "where is the hidden entity for resource R?"
18/// - `type_for(GlobalEntity)` → "is this entity a resource, and which
19///   one?" (used to suppress the entity from user-visible scope/event
20///   streams).
21#[derive(Clone, Debug, Default)]
22pub struct ResourceRegistry {
23    by_type: HashMap<TypeId, GlobalEntity>,
24    by_entity: HashMap<GlobalEntity, TypeId>,
25}
26
27/// Error returned when attempting to insert a resource type that is
28/// already present in the registry.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub struct ResourceAlreadyExists;
31
32impl std::fmt::Display for ResourceAlreadyExists {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(f, "resource of this type is already registered")
35    }
36}
37
38impl std::error::Error for ResourceAlreadyExists {}
39
40impl ResourceRegistry {
41    /// Creates an empty `ResourceRegistry`.
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Insert a (TypeId, GlobalEntity) pair. Fails with
47    /// `ResourceAlreadyExists` if the TypeId is already registered (the
48    /// `commands.replicate_resource` API surface treats this as an error
49    /// per D14/risk-register).
50    pub fn insert<R: 'static>(
51        &mut self,
52        entity: GlobalEntity,
53    ) -> Result<(), ResourceAlreadyExists> {
54        let type_id = TypeId::of::<R>();
55        if self.by_type.contains_key(&type_id) {
56            return Err(ResourceAlreadyExists);
57        }
58        self.by_type.insert(type_id, entity);
59        self.by_entity.insert(entity, type_id);
60        Ok(())
61    }
62
63    /// Receiver-side variant: insert by raw TypeId (the receiver derives
64    /// the TypeId from the incoming `ComponentKind` via the
65    /// `ResourceKinds` registration). Idempotent if the same pair is
66    /// already present (e.g. spawn-after-spawn replay), returns error
67    /// otherwise.
68    pub fn insert_raw(
69        &mut self,
70        type_id: TypeId,
71        entity: GlobalEntity,
72    ) -> Result<(), ResourceAlreadyExists> {
73        if let Some(existing) = self.by_type.get(&type_id) {
74            if *existing == entity {
75                return Ok(());
76            }
77            return Err(ResourceAlreadyExists);
78        }
79        self.by_type.insert(type_id, entity);
80        self.by_entity.insert(entity, type_id);
81        Ok(())
82    }
83
84    /// Remove a resource by type. Returns the removed entity if present.
85    pub fn remove<R: 'static>(&mut self) -> Option<GlobalEntity> {
86        let type_id = TypeId::of::<R>();
87        let entity = self.by_type.remove(&type_id)?;
88        self.by_entity.remove(&entity);
89        Some(entity)
90    }
91
92    /// Receiver-side: remove by entity (used when an incoming Despawn
93    /// for a resource entity arrives).
94    pub fn remove_by_entity(&mut self, entity: &GlobalEntity) -> Option<TypeId> {
95        let type_id = self.by_entity.remove(entity)?;
96        self.by_type.remove(&type_id);
97        Some(type_id)
98    }
99
100    /// O(1): "where is the hidden entity for resource `R`?"
101    pub fn entity_for<R: 'static>(&self) -> Option<GlobalEntity> {
102        self.by_type.get(&TypeId::of::<R>()).copied()
103    }
104
105    /// O(1) raw-TypeId variant.
106    pub fn entity_for_raw(&self, type_id: &TypeId) -> Option<GlobalEntity> {
107        self.by_type.get(type_id).copied()
108    }
109
110    /// O(1): "is this entity a resource entity, and if so which type?"
111    pub fn type_for(&self, entity: &GlobalEntity) -> Option<TypeId> {
112        self.by_entity.get(entity).copied()
113    }
114
115    /// O(1): "is this entity a resource entity?"
116    pub fn is_resource_entity(&self, entity: &GlobalEntity) -> bool {
117        self.by_entity.contains_key(entity)
118    }
119
120    /// Returns the number of registered resources.
121    pub fn len(&self) -> usize {
122        self.by_type.len()
123    }
124
125    /// Returns `true` if no resources have been registered.
126    pub fn is_empty(&self) -> bool {
127        self.by_type.is_empty()
128    }
129
130    /// Iterate over all `(TypeId, GlobalEntity)` pairs. Used by the
131    /// scope resolver to auto-include resource entities in every user's
132    /// scope.
133    pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &GlobalEntity)> {
134        self.by_type.iter()
135    }
136
137    /// Iterate over just the resource entities (for scope auto-inclusion).
138    pub fn entities(&self) -> impl Iterator<Item = &GlobalEntity> {
139        self.by_type.values()
140    }
141}
142
143// Behavioral tests using real `Replicate` types live in the integration
144// suite; this module's tests exercise the raw TypeId/GlobalEntity
145// mechanics directly.
146#[cfg(test)]
147mod tests {
148    use super::*;
149    use crate::BigMapKey;
150
151    fn ge(n: u64) -> GlobalEntity {
152        GlobalEntity::from_u64(n)
153    }
154
155    fn tya() -> TypeId {
156        struct A;
157        TypeId::of::<A>()
158    }
159    fn tyb() -> TypeId {
160        struct B;
161        TypeId::of::<B>()
162    }
163
164    #[test]
165    fn insert_raw_and_lookup_both_directions() {
166        let mut r = ResourceRegistry::new();
167        let e = ge(1);
168        r.insert_raw(tya(), e).unwrap();
169
170        assert_eq!(r.entity_for_raw(&tya()), Some(e));
171        assert_eq!(r.type_for(&e), Some(tya()));
172        assert!(r.is_resource_entity(&e));
173        assert_eq!(r.len(), 1);
174    }
175
176    #[test]
177    fn double_insert_same_type_distinct_entity_errors() {
178        let mut r = ResourceRegistry::new();
179        r.insert_raw(tya(), ge(1)).unwrap();
180        assert_eq!(r.insert_raw(tya(), ge(2)), Err(ResourceAlreadyExists));
181        assert_eq!(r.entity_for_raw(&tya()), Some(ge(1)));
182    }
183
184    #[test]
185    fn insert_raw_idempotent_for_identical_pair() {
186        let mut r = ResourceRegistry::new();
187        let e = ge(7);
188        r.insert_raw(tya(), e).unwrap();
189        r.insert_raw(tya(), e).unwrap(); // same pair: no-op
190        assert_eq!(r.insert_raw(tya(), ge(8)), Err(ResourceAlreadyExists));
191    }
192
193    #[test]
194    fn remove_by_entity_clears_both_indices() {
195        let mut r = ResourceRegistry::new();
196        let e = ge(4);
197        r.insert_raw(tya(), e).unwrap();
198        let ty = r.remove_by_entity(&e);
199        assert_eq!(ty, Some(tya()));
200        assert!(r.is_empty());
201        assert!(!r.is_resource_entity(&e));
202    }
203
204    #[test]
205    fn multi_type_isolation() {
206        let mut r = ResourceRegistry::new();
207        r.insert_raw(tya(), ge(1)).unwrap();
208        r.insert_raw(tyb(), ge(2)).unwrap();
209
210        assert_eq!(r.entity_for_raw(&tya()), Some(ge(1)));
211        assert_eq!(r.entity_for_raw(&tyb()), Some(ge(2)));
212        r.remove_by_entity(&ge(1));
213        assert_eq!(r.entity_for_raw(&tya()), None);
214        assert_eq!(r.entity_for_raw(&tyb()), Some(ge(2)));
215    }
216}