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}