1use std::collections::VecDeque;
4
5use crate::barrier::{BarrierState, CommandQueueMode};
6use crate::ids::{ClientId, CommandId, EntityId, Tick};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum CommandPriority {
11 Normal,
13 High,
15 Low,
17}
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21pub struct CommandEnvelope {
22 pub id: CommandId,
24 pub client_id: ClientId,
26 pub entity_id: EntityId,
28 pub sequence: u64,
30 pub received_at: Tick,
32 pub kind: u32,
34 pub priority: CommandPriority,
36 pub payload: Vec<u8>,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub enum CommandDecision {
43 Accept,
45 Reject {
47 reason: CommandRejectReason,
49 },
50 FlagSuspicious {
52 score: u32,
54 reason: CommandRejectReason,
56 },
57}
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum CommandRejectReason {
62 InvalidSchema,
64 RateLimited,
66 ReplayOrStale,
68 NotOwner,
70 GameRule,
72}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
76pub struct CommandQueueLimits {
77 pub high: usize,
79 pub normal: usize,
81 pub low: usize,
83}
84
85impl Default for CommandQueueLimits {
86 fn default() -> Self {
87 Self {
88 high: 1024,
89 normal: 8192,
90 low: 4096,
91 }
92 }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97pub struct CommandIngress {
98 pub barrier_state: BarrierState,
100 pub command_mode: CommandQueueMode,
102}
103
104impl CommandIngress {
105 pub const RUNNING: Self = Self {
107 barrier_state: BarrierState::Running,
108 command_mode: CommandQueueMode::Buffer,
109 };
110}
111
112#[derive(Clone, Copy, Debug, PartialEq, Eq)]
114pub enum CommandPushOutcome {
115 Queued,
117 BufferedByBarrier,
119}
120
121#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123pub enum CommandQueueError {
124 QueueFull(CommandPriority),
126 RejectedByBarrier(CommandQueueMode),
128}
129
130impl core::fmt::Display for CommandQueueError {
131 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132 match self {
133 Self::QueueFull(priority) => write!(f, "{priority:?} command queue is full"),
134 Self::RejectedByBarrier(mode) => {
135 write!(f, "command rejected by active barrier mode {mode:?}")
136 }
137 }
138 }
139}
140
141impl std::error::Error for CommandQueueError {}
142
143#[derive(Clone, Debug)]
145pub struct CommandQueues {
146 limits: CommandQueueLimits,
147 high: VecDeque<CommandEnvelope>,
148 normal: VecDeque<CommandEnvelope>,
149 low: VecDeque<CommandEnvelope>,
150 barrier_buffer: VecDeque<CommandEnvelope>,
151}
152
153impl CommandQueues {
154 pub fn new(limits: CommandQueueLimits) -> Self {
156 Self {
157 limits,
158 high: VecDeque::new(),
159 normal: VecDeque::new(),
160 low: VecDeque::new(),
161 barrier_buffer: VecDeque::new(),
162 }
163 }
164
165 pub fn push(
167 &mut self,
168 command: CommandEnvelope,
169 ingress: CommandIngress,
170 ) -> Result<CommandPushOutcome, CommandQueueError> {
171 match ingress.barrier_state {
172 BarrierState::Running | BarrierState::Resuming => {
173 self.push_ready(command)?;
174 Ok(CommandPushOutcome::Queued)
175 }
176 BarrierState::Requested | BarrierState::WaitingTickBoundary | BarrierState::Frozen => {
177 match ingress.command_mode {
178 CommandQueueMode::Buffer => {
179 if self.barrier_buffer.len() >= self.barrier_buffer_capacity() {
180 Err(CommandQueueError::QueueFull(command.priority))
181 } else {
182 self.barrier_buffer.push_back(command);
183 Ok(CommandPushOutcome::BufferedByBarrier)
184 }
185 }
186 CommandQueueMode::Reject | CommandQueueMode::Drain => {
187 Err(CommandQueueError::RejectedByBarrier(ingress.command_mode))
188 }
189 }
190 }
191 }
192 }
193
194 pub fn release_barrier_buffer(&mut self) -> Result<usize, CommandQueueError> {
199 let mut released = 0;
200 while let Some(priority) = self.barrier_buffer.front().map(|command| command.priority) {
201 if !self.has_ready_capacity(priority) {
202 return Err(CommandQueueError::QueueFull(priority));
203 }
204 let command = self
205 .barrier_buffer
206 .pop_front()
207 .expect("front command was checked above");
208 match priority {
209 CommandPriority::High => self.high.push_back(command),
210 CommandPriority::Normal => self.normal.push_back(command),
211 CommandPriority::Low => self.low.push_back(command),
212 }
213 released += 1;
214 }
215 Ok(released)
216 }
217
218 pub fn clear_barrier_buffer(&mut self) -> usize {
220 let dropped = self.barrier_buffer.len();
221 self.barrier_buffer.clear();
222 dropped
223 }
224
225 pub fn pop_next(&mut self) -> Option<CommandEnvelope> {
227 self.high
228 .pop_front()
229 .or_else(|| self.normal.pop_front())
230 .or_else(|| self.low.pop_front())
231 }
232
233 pub fn ready_len(&self) -> usize {
235 self.high.len() + self.normal.len() + self.low.len()
236 }
237
238 pub fn barrier_buffer_len(&self) -> usize {
240 self.barrier_buffer.len()
241 }
242
243 pub fn ready_retained_capacity(&self, priority: CommandPriority) -> usize {
245 match priority {
246 CommandPriority::High => self.high.capacity(),
247 CommandPriority::Normal => self.normal.capacity(),
248 CommandPriority::Low => self.low.capacity(),
249 }
250 }
251
252 pub fn total_ready_retained_capacity(&self) -> usize {
254 self.high
255 .capacity()
256 .saturating_add(self.normal.capacity())
257 .saturating_add(self.low.capacity())
258 }
259
260 pub fn barrier_buffer_retained_capacity(&self) -> usize {
262 self.barrier_buffer.capacity()
263 }
264
265 pub fn reclaim_retained_capacity(&mut self) {
267 self.high.shrink_to_fit();
268 self.normal.shrink_to_fit();
269 self.low.shrink_to_fit();
270 self.barrier_buffer.shrink_to_fit();
271 }
272
273 pub fn barrier_buffer_capacity(&self) -> usize {
278 self.limits
279 .high
280 .saturating_add(self.limits.normal)
281 .saturating_add(self.limits.low)
282 }
283
284 pub fn total_len(&self) -> usize {
286 self.ready_len() + self.barrier_buffer.len()
287 }
288
289 pub fn is_empty(&self) -> bool {
291 self.total_len() == 0
292 }
293
294 fn push_ready(&mut self, command: CommandEnvelope) -> Result<(), CommandQueueError> {
295 if !self.has_ready_capacity(command.priority) {
296 return Err(CommandQueueError::QueueFull(command.priority));
297 }
298 match command.priority {
299 CommandPriority::High => {
300 self.high.push_back(command);
301 }
302 CommandPriority::Normal => {
303 self.normal.push_back(command);
304 }
305 CommandPriority::Low => {
306 self.low.push_back(command);
307 }
308 }
309 Ok(())
310 }
311
312 fn has_ready_capacity(&self, priority: CommandPriority) -> bool {
313 match priority {
314 CommandPriority::High => self.high.len() < self.limits.high,
315 CommandPriority::Normal => self.normal.len() < self.limits.normal,
316 CommandPriority::Low => self.low.len() < self.limits.low,
317 }
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use super::*;
324
325 fn command(id: u64, priority: CommandPriority) -> CommandEnvelope {
326 CommandEnvelope {
327 id: CommandId::new(id),
328 client_id: ClientId::new(1),
329 entity_id: EntityId::new(10),
330 sequence: id,
331 received_at: Tick::new(0),
332 kind: 1,
333 priority,
334 payload: Vec::new(),
335 }
336 }
337
338 #[test]
339 fn command_queues_pop_by_priority() {
340 let mut queues = CommandQueues::new(CommandQueueLimits {
341 high: 2,
342 normal: 2,
343 low: 2,
344 });
345
346 queues
347 .push(command(1, CommandPriority::Low), CommandIngress::RUNNING)
348 .expect("low should queue");
349 queues
350 .push(command(2, CommandPriority::High), CommandIngress::RUNNING)
351 .expect("high should queue");
352 queues
353 .push(command(3, CommandPriority::Normal), CommandIngress::RUNNING)
354 .expect("normal should queue");
355
356 assert_eq!(queues.pop_next().expect("high").id, CommandId::new(2));
357 assert_eq!(queues.pop_next().expect("normal").id, CommandId::new(3));
358 assert_eq!(queues.pop_next().expect("low").id, CommandId::new(1));
359 }
360
361 #[test]
362 fn command_queues_allocate_lazily_and_retain_peak_capacity() {
363 let mut queues = CommandQueues::new(CommandQueueLimits::default());
364 assert_eq!(queues.total_ready_retained_capacity(), 0);
365 assert_eq!(queues.barrier_buffer_retained_capacity(), 0);
366
367 for (offset, priority) in [
368 CommandPriority::High,
369 CommandPriority::Normal,
370 CommandPriority::Low,
371 ]
372 .into_iter()
373 .enumerate()
374 {
375 for index in 0..8 {
376 queues
377 .push(
378 command(
379 u64::try_from(offset * 8 + index).expect("test id fits u64"),
380 priority,
381 ),
382 CommandIngress::RUNNING,
383 )
384 .expect("ready burst should queue");
385 }
386 assert!(queues.ready_retained_capacity(priority) >= 8);
387 }
388 let ready_peak = queues.total_ready_retained_capacity();
389 while queues.pop_next().is_some() {}
390 assert_eq!(queues.total_ready_retained_capacity(), ready_peak);
391
392 let frozen = CommandIngress {
393 barrier_state: BarrierState::Frozen,
394 command_mode: CommandQueueMode::Buffer,
395 };
396 for index in 0..8 {
397 queues
398 .push(command(100 + index, CommandPriority::Normal), frozen)
399 .expect("barrier burst should queue");
400 }
401 let barrier_peak = queues.barrier_buffer_retained_capacity();
402 assert!(barrier_peak >= 8);
403 assert_eq!(queues.clear_barrier_buffer(), 8);
404 assert_eq!(queues.barrier_buffer_retained_capacity(), barrier_peak);
405 }
406
407 #[test]
408 fn reclaim_retained_capacity_preserves_queued_order() {
409 let mut queues = CommandQueues::new(CommandQueueLimits::default());
410 queues
411 .push(command(1, CommandPriority::Low), CommandIngress::RUNNING)
412 .expect("low");
413 queues
414 .push(command(2, CommandPriority::High), CommandIngress::RUNNING)
415 .expect("high");
416
417 queues.reclaim_retained_capacity();
418
419 assert_eq!(queues.pop_next().expect("high first").id, CommandId::new(2));
420 assert_eq!(queues.pop_next().expect("low second").id, CommandId::new(1));
421 }
422
423 #[test]
424 fn barrier_buffer_mode_holds_and_releases_commands() {
425 let mut queues = CommandQueues::new(CommandQueueLimits::default());
426 let ingress = CommandIngress {
427 barrier_state: BarrierState::Frozen,
428 command_mode: CommandQueueMode::Buffer,
429 };
430
431 let outcome = queues
432 .push(command(1, CommandPriority::Normal), ingress)
433 .expect("buffer should work");
434 assert_eq!(outcome, CommandPushOutcome::BufferedByBarrier);
435 assert_eq!(queues.ready_len(), 0);
436 assert_eq!(queues.barrier_buffer_len(), 1);
437
438 assert_eq!(
439 queues
440 .release_barrier_buffer()
441 .expect("release should work"),
442 1
443 );
444 assert_eq!(queues.ready_len(), 1);
445 }
446
447 #[test]
448 fn barrier_reject_mode_rejects_commands() {
449 let mut queues = CommandQueues::new(CommandQueueLimits::default());
450 let ingress = CommandIngress {
451 barrier_state: BarrierState::Frozen,
452 command_mode: CommandQueueMode::Reject,
453 };
454
455 let error = queues
456 .push(command(1, CommandPriority::Normal), ingress)
457 .expect_err("reject mode should reject");
458 assert_eq!(
459 error,
460 CommandQueueError::RejectedByBarrier(CommandQueueMode::Reject)
461 );
462 }
463
464 #[test]
465 fn barrier_buffer_is_bounded_by_ready_queue_limits() {
466 let mut queues = CommandQueues::new(CommandQueueLimits {
467 high: 1,
468 normal: 0,
469 low: 0,
470 });
471 let ingress = CommandIngress {
472 barrier_state: BarrierState::Frozen,
473 command_mode: CommandQueueMode::Buffer,
474 };
475
476 assert_eq!(queues.barrier_buffer_capacity(), 1);
477 queues
478 .push(command(1, CommandPriority::High), ingress)
479 .expect("first command should buffer");
480 let error = queues
481 .push(command(2, CommandPriority::High), ingress)
482 .expect_err("bounded barrier buffer should reject overflow");
483
484 assert_eq!(error, CommandQueueError::QueueFull(CommandPriority::High));
485 assert_eq!(queues.barrier_buffer_len(), 1);
486 }
487
488 #[test]
489 fn failed_barrier_release_keeps_blocked_command_buffered() {
490 let mut queues = CommandQueues::new(CommandQueueLimits {
491 high: 1,
492 normal: 0,
493 low: 0,
494 });
495 queues
496 .push(command(1, CommandPriority::High), CommandIngress::RUNNING)
497 .expect("ready queue should accept first command");
498 queues
499 .push(
500 command(2, CommandPriority::High),
501 CommandIngress {
502 barrier_state: BarrierState::Frozen,
503 command_mode: CommandQueueMode::Buffer,
504 },
505 )
506 .expect("barrier buffer should accept second command");
507
508 let error = queues
509 .release_barrier_buffer()
510 .expect_err("full ready queue should block release");
511 assert_eq!(error, CommandQueueError::QueueFull(CommandPriority::High));
512 assert_eq!(queues.barrier_buffer_len(), 1);
513 assert_eq!(
514 queues.pop_next().expect("first command should remain").id,
515 CommandId::new(1)
516 );
517 assert_eq!(queues.release_barrier_buffer(), Ok(1));
518 assert_eq!(
519 queues.pop_next().expect("second command should release").id,
520 CommandId::new(2)
521 );
522 }
523}