naia_shared/world/delegation/
entity_auth_status.rs1use naia_serde::SerdeInternal;
2
3use crate::HostType;
4
5#[derive(SerdeInternal, Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub enum EntityAuthStatus {
8 Available,
10 Requested,
12 Granted,
14 Releasing,
16 Denied,
18}
19
20impl EntityAuthStatus {
21 pub fn is_available(&self) -> bool {
23 matches!(self, EntityAuthStatus::Available)
24 }
25
26 pub fn is_requested(&self) -> bool {
28 matches!(self, EntityAuthStatus::Requested)
29 }
30
31 pub fn is_granted(&self) -> bool {
33 matches!(self, EntityAuthStatus::Granted)
34 }
35
36 pub fn is_denied(&self) -> bool {
38 matches!(self, EntityAuthStatus::Denied)
39 }
40
41 pub fn is_releasing(&self) -> bool {
43 matches!(self, EntityAuthStatus::Releasing)
44 }
45}
46
47#[derive(Debug)]
49pub struct HostEntityAuthStatus {
50 host_type: HostType,
51 auth_status: EntityAuthStatus,
52}
53
54impl HostEntityAuthStatus {
55 pub fn new(host_type: HostType, auth_status: EntityAuthStatus) -> Self {
57 Self {
58 host_type,
59 auth_status,
60 }
61 }
62
63 pub fn can_request(&self) -> bool {
78 match (self.host_type, self.auth_status) {
79 (HostType::Client, EntityAuthStatus::Available) => true,
80 (HostType::Client, EntityAuthStatus::Requested) => false,
81 (HostType::Client, EntityAuthStatus::Granted) => false,
82 (HostType::Client, EntityAuthStatus::Releasing) => false,
83 (HostType::Client, EntityAuthStatus::Denied) => false,
84 (HostType::Server, status) => unreachable!(
85 "can_request() is a client-side authority-flow predicate; \
86 reached with HostType::Server (status={status:?}). The server \
87 owns entities by default and does not 'request' authority — \
88 only the client request path in \
89 client/src/world/global_world_manager.rs::entity_request_authority \
90 should call this."
91 ),
92 }
93 }
94
95 pub fn can_release(&self) -> bool {
102 match (self.host_type, self.auth_status) {
103 (HostType::Client, EntityAuthStatus::Available) => false,
104 (HostType::Client, EntityAuthStatus::Requested) => true,
105 (HostType::Client, EntityAuthStatus::Granted) => true,
106 (HostType::Client, EntityAuthStatus::Releasing) => false,
107 (HostType::Client, EntityAuthStatus::Denied) => false,
108 (HostType::Server, status) => unreachable!(
109 "can_release() is a client-side authority-flow predicate; \
110 reached with HostType::Server (status={status:?}). Only the \
111 client release path in \
112 client/src/world/global_world_manager.rs::entity_release_authority \
113 should call this."
114 ),
115 }
116 }
117
118 pub fn can_mutate(&self) -> bool {
120 match (self.host_type, self.auth_status) {
121 (HostType::Client, EntityAuthStatus::Available) => false,
122 (HostType::Client, EntityAuthStatus::Requested) => true,
123 (HostType::Client, EntityAuthStatus::Granted) => true,
124 (HostType::Client, EntityAuthStatus::Releasing) => false,
125 (HostType::Client, EntityAuthStatus::Denied) => false,
126 (HostType::Server, EntityAuthStatus::Available) => true,
127 (HostType::Server, EntityAuthStatus::Requested) => true,
128 (HostType::Server, EntityAuthStatus::Granted) => true,
129 (HostType::Server, EntityAuthStatus::Releasing) => true,
130 (HostType::Server, EntityAuthStatus::Denied) => true,
131 }
132 }
133
134 pub fn can_read(&self) -> bool {
136 match (self.host_type, self.auth_status) {
137 (HostType::Client, EntityAuthStatus::Available) => true,
138 (HostType::Client, EntityAuthStatus::Requested) => false,
139 (HostType::Client, EntityAuthStatus::Granted) => false,
140 (HostType::Client, EntityAuthStatus::Releasing) => true,
141 (HostType::Client, EntityAuthStatus::Denied) => true,
142 (HostType::Server, EntityAuthStatus::Available) => true,
143 (HostType::Server, EntityAuthStatus::Requested) => true,
144 (HostType::Server, EntityAuthStatus::Granted) => true,
145 (HostType::Server, EntityAuthStatus::Releasing) => true,
146 (HostType::Server, EntityAuthStatus::Denied) => true,
147 }
148 }
149
150 pub fn can_write(&self) -> bool {
152 match (self.host_type, self.auth_status) {
153 (HostType::Client, EntityAuthStatus::Available) => false,
154 (HostType::Client, EntityAuthStatus::Requested) => false,
155 (HostType::Client, EntityAuthStatus::Granted) => true,
156 (HostType::Client, EntityAuthStatus::Releasing) => true,
157 (HostType::Client, EntityAuthStatus::Denied) => false,
158 (HostType::Server, EntityAuthStatus::Available) => true,
159 (HostType::Server, EntityAuthStatus::Requested) => true,
160 (HostType::Server, EntityAuthStatus::Granted) => true,
161 (HostType::Server, EntityAuthStatus::Releasing) => true,
162 (HostType::Server, EntityAuthStatus::Denied) => true,
163 }
164 }
165
166 pub fn status(&self) -> EntityAuthStatus {
168 self.auth_status
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
179
180 #[test]
181 fn client_can_request_only_when_available() {
182 let s = HostEntityAuthStatus::new(HostType::Client, EntityAuthStatus::Available);
183 assert!(s.can_request());
184 for status in [
185 EntityAuthStatus::Requested,
186 EntityAuthStatus::Granted,
187 EntityAuthStatus::Releasing,
188 EntityAuthStatus::Denied,
189 ] {
190 let s = HostEntityAuthStatus::new(HostType::Client, status);
191 assert!(!s.can_request(), "client must not re-request from {status:?}");
192 }
193 }
194
195 #[test]
196 fn client_can_release_only_when_holding_or_requesting() {
197 for status in [EntityAuthStatus::Requested, EntityAuthStatus::Granted] {
198 let s = HostEntityAuthStatus::new(HostType::Client, status);
199 assert!(s.can_release(), "client must release from {status:?}");
200 }
201 for status in [
202 EntityAuthStatus::Available,
203 EntityAuthStatus::Releasing,
204 EntityAuthStatus::Denied,
205 ] {
206 let s = HostEntityAuthStatus::new(HostType::Client, status);
207 assert!(!s.can_release(), "client must not release from {status:?}");
208 }
209 }
210
211 #[test]
212 #[should_panic(expected = "client-side authority-flow predicate")]
213 fn server_can_request_panics_with_contract_message() {
214 let s = HostEntityAuthStatus::new(HostType::Server, EntityAuthStatus::Available);
215 let _ = s.can_request();
216 }
217
218 #[test]
219 #[should_panic(expected = "client-side authority-flow predicate")]
220 fn server_can_release_panics_with_contract_message() {
221 let s = HostEntityAuthStatus::new(HostType::Server, EntityAuthStatus::Granted);
222 let _ = s.can_release();
223 }
224}