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}