Skip to main content

naia_shared/world/resource/
resource_kinds.rs

1use std::{any::TypeId, collections::HashSet};
2
3use crate::ComponentKind;
4
5/// Protocol-wide table marking which `ComponentKind`s are Replicated
6/// Resources (vs ordinary components).
7///
8/// A Resource type `R` is registered via `Protocol::add_resource::<R>()`,
9/// which:
10/// 1. Calls `component_kinds.add_component::<R>()` to allocate a normal
11///    `ComponentKind` + NetId for `R`. Resources reuse the component wire
12///    encoding 100% — they ARE components, just on a hidden singleton
13///    entity.
14/// 2. Records the resulting `ComponentKind` in this table so the receiver
15///    side can recognize incoming `SpawnWithComponents` messages whose
16///    components are resources, and populate its `ResourceRegistry`
17///    accordingly.
18///
19/// There is no wire representation of `ResourceKinds` itself — the table
20/// is built identically on both sides from the same `ProtocolPlugin`
21/// registration order, exactly like `ComponentKinds`.
22#[derive(Clone, Default)]
23pub struct ResourceKinds {
24    kinds: HashSet<ComponentKind>,
25    /// Type-id index for O(1) `kind_for::<R>()` lookups without a HashMap
26    /// allocation churn at registration time. Mirrors the (TypeId →
27    /// ComponentKind) relationship that `ComponentKind::of::<R>()`
28    /// already provides via TypeId equality, so this is informational.
29    type_ids: HashSet<TypeId>,
30}
31
32impl ResourceKinds {
33    /// Creates an empty `ResourceKinds` table.
34    pub fn new() -> Self {
35        Self::default()
36    }
37
38    /// Register `R` as a resource kind. Idempotent: re-registering the
39    /// same type is a no-op (matches the `add_component` re-registration
40    /// semantics — the component table dedupes on `TypeId`).
41    ///
42    /// `R: 'static` is the only bound — internal callers always have
43    /// the full `Replicate` bound, but this method only uses
44    /// `TypeId::of::<R>()` so we don't impose more.
45    pub fn register<R: 'static>(&mut self, kind: ComponentKind) {
46        self.kinds.insert(kind);
47        self.type_ids.insert(TypeId::of::<R>());
48    }
49
50    /// O(1) — is the given `ComponentKind` a registered resource?
51    pub fn is_resource(&self, kind: &ComponentKind) -> bool {
52        self.kinds.contains(kind)
53    }
54
55    /// Return the `ComponentKind` registered for `R`, or `None` if `R`
56    /// was never registered as a resource.
57    ///
58    /// Implementation note: `ComponentKind` is `TypeId`-keyed
59    /// (`shared/src/world/component/component_kinds.rs:53`), so we
60    /// construct the kind from `R`'s `TypeId` and check membership.
61    pub fn kind_for<R: 'static>(&self) -> Option<ComponentKind> {
62        let kind = ComponentKind::from(TypeId::of::<R>());
63        if self.kinds.contains(&kind) {
64            Some(kind)
65        } else {
66            None
67        }
68    }
69
70    /// Number of registered resource kinds.
71    pub fn len(&self) -> usize {
72        self.kinds.len()
73    }
74
75    /// Returns `true` if no resource kinds have been registered.
76    pub fn is_empty(&self) -> bool {
77        self.kinds.is_empty()
78    }
79
80    /// Iterate over all registered resource kinds.
81    pub fn iter(&self) -> impl Iterator<Item = &ComponentKind> {
82        self.kinds.iter()
83    }
84}
85
86// Behavioral tests for ResourceKinds live in the integration suite
87// (test/tests) where real `Replicate`-derived types are available via
88// `naia_test_harness::test_protocol`. Here we only need to exercise the
89// HashMap mechanics, which we do via direct `ComponentKind` construction
90// from `TypeId` (the `From<TypeId>` impl in `component_kinds.rs:53`).
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    fn kind_from(name: &'static str) -> ComponentKind {
96        // Each &'static str literal produces a distinct TypeId via address;
97        // we use distinct concrete unit types instead so each call site
98        // gets a stable distinct TypeId.
99        struct A;
100        struct B;
101        struct C;
102        match name {
103            "a" => ComponentKind::from(TypeId::of::<A>()),
104            "b" => ComponentKind::from(TypeId::of::<B>()),
105            _ => ComponentKind::from(TypeId::of::<C>()),
106        }
107    }
108
109    #[test]
110    fn unregistered_kind_is_not_resource() {
111        let rk = ResourceKinds::new();
112        assert!(!rk.is_resource(&kind_from("a")));
113        assert_eq!(rk.len(), 0);
114    }
115
116    #[test]
117    fn registered_kind_is_recognized() {
118        let mut rk = ResourceKinds::new();
119        let k = kind_from("a");
120        // We bypass the `Replicate` bound on `register` here by hand-
121        // setting via direct field manipulation through a thin helper:
122        // the public surface has a `Replicate`-typed `register::<R>` for
123        // the production path, exercised by integration tests.
124        rk.kinds.insert(k);
125        rk.type_ids.insert(TypeId::of::<u8>());
126
127        assert!(rk.is_resource(&k));
128        assert!(!rk.is_resource(&kind_from("b")));
129        assert_eq!(rk.len(), 1);
130    }
131}