Skip to main content

naia_shared/messages/
message_container.rs

1use std::{any::Any, collections::HashSet, sync::Arc};
2
3use naia_serde::BitWrite;
4
5use crate::world::local::local_entity::RemoteEntity;
6use crate::{
7    world::entity::entity_converters::LocalEntityAndGlobalEntityConverterMut,
8    LocalEntityAndGlobalEntityConverter, Message, MessageKind, MessageKinds,
9};
10
11/// A reference-counted wrapper around a heap-allocated [`Message`] trait object.
12///
13/// ## Why `Arc<Box<dyn Message>>`?
14///
15/// `broadcast_message` and `room_broadcast_message` send the same logical
16/// message to every connected user. With a plain `Box<dyn Message>` this
17/// required one `clone_box()` call (heap allocation + copy) per user. At
18/// 1,262 CCU that is 1,262 allocations per broadcast tick.
19///
20/// Wrapping in `Arc` makes `clone()` a single atomic refcount increment
21/// regardless of how many users share the message. Each connection still
22/// serialises the message independently through its own entity converter —
23/// the shared data is immutable (only `&self` methods called on the send path).
24///
25/// `to_boxed_any` (receive path only) extracts the inner `Box<dyn Message>`
26/// via `Arc::try_unwrap`; in the rare case the Arc is still shared it falls
27/// back to `clone_box()`, preserving correctness without unsafe code.
28#[derive(Clone)]
29pub struct MessageContainer {
30    inner: Arc<Box<dyn Message>>,
31}
32
33impl MessageContainer {
34    /// Wraps `message` in an `Arc` so it can be cheaply shared across broadcast targets.
35    pub fn new(message: Box<dyn Message>) -> Self {
36        Self {
37            inner: Arc::new(message),
38        }
39    }
40
41    /// Returns the protocol name of the contained message type.
42    pub fn name(&self) -> String {
43        self.inner.name()
44    }
45
46    /// Returns the serialized bit length of this message given `converter` for entity references.
47    pub fn bit_length(
48        &self,
49        message_kinds: &MessageKinds,
50        converter: &mut dyn LocalEntityAndGlobalEntityConverterMut,
51    ) -> u32 {
52        self.inner.bit_length(message_kinds, converter)
53    }
54
55    /// Writes the message's kind tag and payload bits into `writer`.
56    pub fn write(
57        &self,
58        message_kinds: &MessageKinds,
59        writer: &mut dyn BitWrite,
60        converter: &mut dyn LocalEntityAndGlobalEntityConverterMut,
61    ) {
62        // Counter mode and real-write mode share the same path: every
63        // `write_bit` against a `BitCounter` is a no-op-write-increment, so
64        // the inner traversal counts bits correctly without a separate
65        // bit_length() round-trip.
66        self.inner.write(message_kinds, writer, converter);
67    }
68
69    /// Returns `true` if this message is a fragment of a larger logical message.
70    pub fn is_fragment(&self) -> bool {
71        self.inner.is_fragment()
72    }
73
74    /// Returns `true` if this message envelope carries a request or response (not a plain message).
75    pub fn is_request_or_response(&self) -> bool {
76        self.inner.is_request()
77    }
78
79    /// Converts this container into a `Box<dyn Any>` for downcasting to the concrete message type.
80    pub fn to_boxed_any(self) -> Box<dyn Any> {
81        // Fast path: if this is the only Arc reference (always true after the
82        // message is dequeued from a connection's send buffer), extract without
83        // allocating. Fallback clones only in the pathological case where a
84        // broadcast Arc is still live when to_boxed_any is called — not expected
85        // in practice but required for correctness.
86        match Arc::try_unwrap(self.inner) {
87            Ok(boxed) => boxed.to_boxed_any(),
88            Err(arc) => (*arc).clone_box().to_boxed_any(),
89        }
90    }
91
92    /// Returns the `MessageKind` identifying the concrete message type inside this container.
93    pub fn kind(&self) -> MessageKind {
94        self.inner.kind()
95    }
96
97    /// Returns the set of remote entities this message is still waiting on, or `None` if ready.
98    pub fn relations_waiting(&self) -> Option<HashSet<RemoteEntity>> {
99        self.inner.relations_waiting()
100    }
101
102    /// Notifies the inner message that all awaited entity relations have been resolved.
103    pub fn relations_complete(&mut self, converter: &dyn LocalEntityAndGlobalEntityConverter) {
104        // relations_complete requires &mut self on the inner message.
105        // Since we hold an Arc, we must have exclusive ownership to mutate.
106        // This is only called on the receive path where no other Arc clones
107        // are live, so make_mut gives us a unique clone if needed (which is
108        // already a Box<dyn Message> clone — same cost as before this change).
109        Arc::make_mut(&mut self.inner).relations_complete(converter);
110    }
111}