Skip to main content

naia_shared/
protocol.rs

1use std::time::Duration;
2
3use naia_socket_shared::{LinkConditionerConfig, SocketConfig};
4
5use crate::{
6    connection::compression_config::CompressionConfig,
7    messages::{
8        channels::{
9            channel::{Channel, ChannelDirection, ChannelMode, ChannelSettings},
10            channel_kinds::ChannelKinds,
11            default_channels::DefaultChannelsPlugin,
12        },
13        fragment::FragmentedMessage,
14        message::Message,
15        message_kinds::MessageKinds,
16    },
17    protocol_id::ProtocolId,
18    world::{
19        component::{component_kinds::ComponentKinds, replicate::Replicate},
20        resource::ResourceKinds,
21    },
22    Request, RequestOrResponse,
23};
24
25/// Extension point for registering channels, messages, and components into a `Protocol`.
26pub trait ProtocolPlugin {
27    /// Applies this plugin's registrations to `protocol`.
28    fn build(&self, protocol: &mut Protocol);
29}
30
31/// Builder and configuration container for a naia protocol definition.
32///
33/// Collects channels, messages, components, and transport settings before being locked and passed to a server or client.
34#[derive(Clone)]
35pub struct Protocol {
36    /// Registry of all channels registered in this protocol.
37    pub channel_kinds: ChannelKinds,
38    /// Registry of all message types registered in this protocol.
39    pub message_kinds: MessageKinds,
40    /// Registry of all replicated component types registered in this protocol.
41    pub component_kinds: ComponentKinds,
42    /// Marker table — which `ComponentKind`s are Replicated Resources.
43    /// Receiver side checks this on `SpawnWithComponents` to populate
44    /// its `ResourceRegistry`. See `_AGENTS/RESOURCES_PLAN.md`.
45    pub resource_kinds: ResourceKinds,
46    /// Used to configure the underlying socket
47    pub socket: SocketConfig,
48    /// The duration between each tick
49    pub tick_interval: Duration,
50    /// Configuration used to control compression parameters
51    pub compression: Option<CompressionConfig>,
52    /// Whether or not Client Authoritative Entities will be allowed
53    pub client_authoritative_entities: bool,
54    /// Cached protocol ID, computed when lock() is called
55    cached_protocol_id: Option<ProtocolId>,
56    locked: bool,
57}
58
59impl Default for Protocol {
60    fn default() -> Self {
61        let mut message_kinds = MessageKinds::new();
62        message_kinds.add_message::<FragmentedMessage>();
63        message_kinds.add_message::<RequestOrResponse>();
64
65        let channel_kinds = ChannelKinds::new();
66
67        Self {
68            channel_kinds,
69            message_kinds,
70            component_kinds: ComponentKinds::new(),
71            resource_kinds: ResourceKinds::new(),
72            socket: SocketConfig::new(None, None),
73            tick_interval: Duration::from_millis(50),
74            compression: None,
75            client_authoritative_entities: false,
76            cached_protocol_id: None,
77            locked: false,
78        }
79    }
80}
81
82impl Protocol {
83    /// Returns a default `Protocol` ready for builder-style configuration.
84    pub fn builder() -> Self {
85        Self::default()
86    }
87
88    /// Applies `plugin`'s registrations to this protocol. Builder-style.
89    pub fn add_plugin<P: ProtocolPlugin>(&mut self, plugin: P) -> &mut Self {
90        self.check_lock();
91        plugin.build(self);
92        self
93    }
94
95    /// Sets the link conditioning configuration (artificial latency/loss). Builder-style.
96    pub fn link_condition(&mut self, config: LinkConditionerConfig) -> &mut Self {
97        self.check_lock();
98        self.socket.link_condition = Some(config);
99        self
100    }
101
102    /// Sets the WebRTC signalling endpoint path. Builder-style.
103    pub fn rtc_endpoint(&mut self, path: String) -> &mut Self {
104        self.check_lock();
105        self.socket.rtc_endpoint_path = path;
106        self
107    }
108
109    /// Returns the configured WebRTC signalling endpoint path.
110    pub fn get_rtc_endpoint(&self) -> String {
111        self.socket.rtc_endpoint_path.clone()
112    }
113
114    /// Sets the server tick interval. Builder-style.
115    pub fn tick_interval(&mut self, duration: Duration) -> &mut Self {
116        self.check_lock();
117        self.tick_interval = duration;
118        self
119    }
120
121    /// Enables packet compression with the given config. Builder-style.
122    pub fn compression(&mut self, config: CompressionConfig) -> &mut Self {
123        self.check_lock();
124        self.compression = Some(config);
125        self
126    }
127
128    /// Enables client-authoritative entity mode, allowing clients to own and update replicated entities. Builder-style.
129    pub fn enable_client_authoritative_entities(&mut self) -> &mut Self {
130        self.check_lock();
131        self.client_authoritative_entities = true;
132        self
133    }
134
135    /// Registers the six built-in default channels. Builder-style.
136    pub fn add_default_channels(&mut self) -> &mut Self {
137        self.check_lock();
138        let plugin = DefaultChannelsPlugin;
139        plugin.build(self);
140        self
141    }
142
143    /// Registers channel type `C` with the given direction and mode. Builder-style.
144    pub fn add_channel<C: Channel>(
145        &mut self,
146        direction: ChannelDirection,
147        mode: ChannelMode,
148    ) -> &mut Self {
149        self.check_lock();
150        self.channel_kinds
151            .add_channel::<C>(ChannelSettings::new(mode, direction));
152        self
153    }
154
155    /// Register a channel with fully-specified `ChannelSettings` (including
156    /// `criticality`). Use this when you need a non-default priority tier;
157    /// otherwise `add_channel` is sufficient.
158    pub fn add_channel_settings<C: Channel>(&mut self, settings: ChannelSettings) -> &mut Self {
159        self.check_lock();
160        self.channel_kinds.add_channel::<C>(settings);
161        self
162    }
163
164    /// Registers message type `M`. Builder-style.
165    pub fn add_message<M: Message>(&mut self) -> &mut Self {
166        self.check_lock();
167        self.message_kinds.add_message::<M>();
168        self
169    }
170
171    /// Registers request type `Q` and its associated response type. Builder-style.
172    pub fn add_request<Q: Request>(&mut self) -> &mut Self {
173        self.check_lock();
174        // Requests and Responses are handled just like Messages
175        self.message_kinds.add_message::<Q>();
176        self.message_kinds.add_message::<Q::Response>();
177        self
178    }
179
180    /// Registers replicated component type `C`. Builder-style.
181    pub fn add_component<C: Replicate>(&mut self) -> &mut Self {
182        self.check_lock();
183        self.component_kinds.add_component::<C>();
184        self
185    }
186
187    /// Register `R` as a Replicated Resource.
188    ///
189    /// A Resource is internally a hidden 1-component entity carrying `R`
190    /// as its sole replicated component. This call:
191    ///
192    /// 1. Calls `add_component::<R>()` to allocate a normal `ComponentKind`
193    ///    + NetId for `R` (Resources reuse the component wire encoding).
194    /// 2. Records the `ComponentKind` in `resource_kinds` so the receiver
195    ///    side can recognize incoming SpawnWithComponents messages whose
196    ///    components are resources, and populate its `ResourceRegistry`.
197    ///
198    /// Idempotent — registering the same type twice is a no-op (matches
199    /// `add_component` re-registration semantics; the underlying tables
200    /// dedupe on `TypeId`).
201    pub fn add_resource<R: Replicate>(&mut self) -> &mut Self {
202        self.check_lock();
203        // Allocate a ComponentKind for R if not already present.
204        self.component_kinds.add_component::<R>();
205        // Mark the kind as a resource.
206        let kind = crate::ComponentKind::of::<R>();
207        self.resource_kinds.register::<R>(kind);
208        self
209    }
210
211    /// Freezes the protocol, computes and caches the protocol ID. Must be called before use.
212    pub fn lock(&mut self) {
213        self.check_lock();
214        self.cached_protocol_id = Some(self.compute_protocol_id());
215        self.locked = true;
216    }
217
218    /// Panics if the protocol has already been locked.
219    pub fn check_lock(&self) {
220        if self.locked {
221            panic!("Protocol already locked!");
222        }
223    }
224
225    /// Moves out of the builder and returns the owned `Protocol`.
226    pub fn build(&mut self) -> Self {
227        std::mem::take(self)
228    }
229
230    /// Returns the cached protocol ID. Panics if protocol is not locked.
231    pub fn protocol_id(&self) -> ProtocolId {
232        self.cached_protocol_id
233            .expect("Protocol must be locked before calling protocol_id()")
234    }
235
236    /// Compute the protocol ID from current state.
237    fn compute_protocol_id(&self) -> ProtocolId {
238        let mut hasher = blake3::Hasher::new();
239
240        // Channels
241        for name in self.channel_kinds.all_names() {
242            hasher.update(name.as_bytes());
243        }
244        // Messages
245        for name in self.message_kinds.all_names() {
246            hasher.update(name.as_bytes());
247        }
248        // Components
249        for name in self.component_kinds.all_names() {
250            hasher.update(name.as_bytes());
251        }
252        // Resources — fold in a side-channel marker per resource kind so
253        // that two protocols differing only in which kinds are tagged
254        // resource hash differently. Without this, downgrading a resource
255        // to a plain component (or vice-versa) would collide on the wire
256        // mismatch detector.
257        hasher.update(b"naia:resources:");
258        let mut resource_count = 0u32;
259        for _ in self.resource_kinds.iter() {
260            resource_count += 1;
261        }
262        hasher.update(&resource_count.to_le_bytes());
263
264        let hash = hasher.finalize();
265        let mut bytes = [0u8; 8];
266        bytes.copy_from_slice(&hash.as_bytes()[..8]);
267        ProtocolId::new(u64::from_le_bytes(bytes))
268    }
269}