Skip to main content

naia_shared/world/sync/
auth_channel.rs

1use crate::{
2    world::{
3        host::host_world_manager::SubCommandId,
4        sync::{
5            auth_channel_receiver::AuthChannelReceiver, auth_channel_sender::AuthChannelSender,
6            remote_entity_channel::EntityChannelState,
7        },
8    },
9    EntityAuthStatus, EntityCommand, EntityMessage, EntityMessageType, HostType, MessageIndex,
10};
11
12/// Publication/delegation lifecycle state of an entity's authority channel.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum EntityAuthChannelState {
15    /// Entity has not yet been published (client default).
16    Unpublished,
17    /// Entity is published and visible to other users but not yet delegated.
18    Published,
19    /// Authority delegation is active for this entity.
20    Delegated,
21}
22
23pub(crate) struct AuthChannel {
24    host_type: HostType,
25    state: EntityAuthChannelState,
26    auth_status: Option<EntityAuthStatus>,
27    sender: AuthChannelSender,
28    receiver: AuthChannelReceiver,
29}
30
31impl AuthChannel {
32    pub(crate) fn new(host_type: HostType) -> Self {
33        let state = match host_type {
34            HostType::Client => EntityAuthChannelState::Unpublished,
35            HostType::Server => EntityAuthChannelState::Published,
36        };
37        Self {
38            host_type,
39            state,
40            auth_status: None,
41            sender: AuthChannelSender::new(),
42            receiver: AuthChannelReceiver::new(),
43        }
44    }
45
46    pub(crate) fn validate_command(&mut self, command: &EntityCommand) {
47        let entity = command.entity();
48
49        match command.get_type() {
50            EntityMessageType::Publish => {
51                if self.state != EntityAuthChannelState::Unpublished {
52                    panic!(
53                        "Cannot publish Entity: {:?} that is already published",
54                        entity
55                    );
56                }
57                self.state = EntityAuthChannelState::Published;
58            }
59            EntityMessageType::Unpublish => {
60                if self.state != EntityAuthChannelState::Published {
61                    panic!(
62                        "Cannot unpublish Entity: {:?} that is not published",
63                        entity
64                    );
65                }
66                self.state = EntityAuthChannelState::Unpublished;
67            }
68            EntityMessageType::EnableDelegation => {
69                if self.state != EntityAuthChannelState::Published {
70                    panic!(
71                        "Cannot enable delegation on Entity: {:?} that is not published",
72                        entity
73                    );
74                }
75                self.state = EntityAuthChannelState::Delegated;
76                self.auth_status = Some(EntityAuthStatus::Available);
77            }
78            EntityMessageType::DisableDelegation => {
79                #[cfg(feature = "e2e_debug")]
80                crate::e2e_trace!(
81                    "[CLIENT_RECV] DisableDelegation entity={:?} current_state={:?}",
82                    entity,
83                    self.state
84                );
85                if self.state != EntityAuthChannelState::Delegated {
86                    panic!(
87                        "Cannot disable delegation on Entity: {:?} that is not delegated",
88                        entity
89                    );
90                }
91                self.state = EntityAuthChannelState::Published;
92            }
93            EntityMessageType::ReleaseAuthority => {
94                if self.state != EntityAuthChannelState::Delegated {
95                    panic!(
96                        "Cannot release authority on Entity: {:?} that is not delegated",
97                        entity
98                    );
99                }
100
101                // This is actually valid, because it should be possible for a client to ReleaseAuthority right after EnableDelegation, so that auth isn't automatically set to Granted
102                self.auth_status = Some(EntityAuthStatus::Available);
103            }
104            EntityMessageType::SetAuthority => {
105                if self.state != EntityAuthChannelState::Delegated {
106                    panic!(
107                        "Cannot set authority on Entity: {:?} that is not delegated",
108                        entity
109                    );
110                }
111
112                let EntityCommand::SetAuthority(_, _entity, next_status) = command else {
113                    panic!("Expected SetAuthority command");
114                };
115
116                let from_status = self.auth_status.unwrap();
117                #[cfg(feature = "e2e_debug")]
118                crate::e2e_trace!(
119                    "[CLIENT_RECV] SetAuthority entity={:?} from_status={:?} to_status={:?}",
120                    command.entity(),
121                    from_status,
122                    next_status
123                );
124
125                match (from_status, next_status) {
126                    (EntityAuthStatus::Available, EntityAuthStatus::Requested)
127                    | (EntityAuthStatus::Available, EntityAuthStatus::Granted)
128                    | (EntityAuthStatus::Available, EntityAuthStatus::Denied)
129                    | (EntityAuthStatus::Requested, EntityAuthStatus::Granted)
130                    | (EntityAuthStatus::Requested, EntityAuthStatus::Denied)
131                    | (EntityAuthStatus::Requested, EntityAuthStatus::Available)
132                    | (EntityAuthStatus::Denied, EntityAuthStatus::Granted)
133                    | (EntityAuthStatus::Denied, EntityAuthStatus::Available)
134                    | (EntityAuthStatus::Granted, EntityAuthStatus::Available)
135                    | (EntityAuthStatus::Granted, EntityAuthStatus::Denied)
136                    | (EntityAuthStatus::Granted, EntityAuthStatus::Releasing)
137                    | (EntityAuthStatus::Releasing, EntityAuthStatus::Available)
138                    | (EntityAuthStatus::Releasing, EntityAuthStatus::Denied) => {
139                        // valid transition!
140                    }
141                    (from_status, to_status) => {
142                        panic!(
143                            "Invalid authority transition from {:?} to {:?}",
144                            from_status, to_status
145                        );
146                    }
147                }
148
149                self.auth_status = Some(*next_status);
150            }
151            EntityMessageType::RequestAuthority => {
152                // Client is requesting authority for a delegated entity
153                if self.state != EntityAuthChannelState::Delegated {
154                    panic!(
155                        "Cannot request authority on Entity: {:?} that is not delegated",
156                        entity
157                    );
158                }
159                // Auth status will be updated by server's SetAuthority response
160            }
161            EntityMessageType::EnableDelegationResponse => {
162                // Server is responding to delegation request
163                // This is valid for entities that were just delegated
164                if self.state != EntityAuthChannelState::Delegated {
165                    panic!("Cannot send EnableDelegationResponse for Entity: {:?} that is not delegated", entity);
166                }
167            }
168            EntityMessageType::MigrateResponse => {
169                // Server is responding with entity migration information
170                // This happens during delegation when entity ID changes
171                // Valid for delegated entities
172                if self.state != EntityAuthChannelState::Delegated {
173                    panic!(
174                        "Cannot send MigrateResponse for Entity: {:?} that is not delegated",
175                        entity
176                    );
177                }
178            }
179            EntityMessageType::Noop => {
180                // No-op command, always valid
181            }
182            e => {
183                panic!("Unsupported command type for AuthChannelSender: {:?}", e);
184            }
185        }
186    }
187
188    pub(crate) fn send_command(&mut self, command: EntityCommand) {
189        self.sender.send_command(command);
190    }
191
192    pub(crate) fn sender_drain_messages_into(&mut self, commands: &mut Vec<EntityCommand>) {
193        self.sender.drain_messages_into(commands);
194    }
195
196    /// Get current state of the AuthChannel (for testing)
197    pub fn state(&self) -> EntityAuthChannelState {
198        self.state
199    }
200
201    /// Get current auth status (for testing)
202    pub fn auth_status(&self) -> Option<EntityAuthStatus> {
203        self.auth_status
204    }
205
206    /// Check if in delegated state (for testing)
207    pub fn is_delegated(&self) -> bool {
208        self.state == EntityAuthChannelState::Delegated
209    }
210
211    /// Is invoked by `EntityChannel` when the entity despawns; this wipes all buffered state so a future *re‑spawn* starts clean.
212    pub(crate) fn reset(&mut self) {
213        *self = Self::new(self.host_type);
214    }
215
216    pub(crate) fn receiver_drain_messages_into(
217        &mut self,
218        outgoing_messages: &mut Vec<EntityMessage<()>>,
219    ) {
220        self.receiver.drain_messages_into(outgoing_messages);
221    }
222
223    pub(crate) fn receiver_buffer_pop_front_until_and_including(&mut self, id: MessageIndex) {
224        self.receiver.buffer_pop_front_until_and_including(id);
225    }
226
227    pub(crate) fn receiver_receive_message(
228        &mut self,
229        entity_state_opt: Option<EntityChannelState>,
230        id: MessageIndex,
231        msg: EntityMessage<()>,
232    ) {
233        self.receiver.receive_message(entity_state_opt, id, msg);
234    }
235
236    pub(crate) fn receiver_process_messages(&mut self, entity_state: EntityChannelState) {
237        self.receiver.process_messages(Some(entity_state));
238    }
239
240    /// Set the next expected subcommand_id in the receiver (used after migration to sync with server's sequence)
241    pub(crate) fn receiver_set_next_subcommand_id(&mut self, id: SubCommandId) {
242        self.receiver.set_next_subcommand_id(id);
243    }
244
245    /// Force the AuthChannel into Published state (used during migration setup)
246    pub(crate) fn force_publish(&mut self) {
247        self.state = EntityAuthChannelState::Published;
248    }
249
250    /// Force the AuthChannel into Delegated state with Available authority (used during migration setup)
251    pub(crate) fn force_enable_delegation(&mut self) {
252        self.state = EntityAuthChannelState::Delegated;
253        self.auth_status = Some(EntityAuthStatus::Available);
254    }
255
256    /// Force set the authority status (used to sync with global authority tracker after migration)
257    pub(crate) fn force_set_auth_status(&mut self, auth_status: EntityAuthStatus) {
258        self.auth_status = Some(auth_status);
259    }
260
261    #[cfg(feature = "e2e_debug")]
262    pub(crate) fn receiver_debug_diagnostic(
263        &self,
264    ) -> (SubCommandId, usize, Option<SubCommandId>, usize) {
265        self.receiver.debug_diagnostic()
266    }
267}