feat(server): initialize Rust/bevy_ecs simulation server
Server boilerplate epic (276) complete. Establishes the Rust simulation server foundation per D-020 (subprocess/IPC architecture). Structure: - bevy_ecs 0.18 + bevy_app 0.18, MessagePack serialization (rmp-serde) - SimulationPlugin with deterministic resources: SimulationTime (D-031), SimRng (D-030), InputQueue (D-010) - Core IPC types: ObserverSnapshot, PlayerInput, SimBridge trait (D-020) - CauseChain production component for provenance tracking (D-030) - SimulationTier types with LRU eviction support (D-026) - NPC 10-axis model components (D-024) - 15 tests: inline unit tests + integration smoke/serialization tests - make ci-server passes (clippy, fmt, build, test) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Input processing system
|
||||
// Timestamped player input events for deterministic simulation (D-010 principle 4)
|
||||
// PlayerInput: semantic actions (MoveNorth, Interact, UsePerceptionMode)
|
||||
|
||||
use crate::bridge::types::PlayerInput;
|
||||
use bevy_ecs::prelude::*;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Queue of pending player inputs, ordered by tick
|
||||
#[derive(Resource, Debug, Default)]
|
||||
pub struct InputQueue {
|
||||
queue: VecDeque<PlayerInput>,
|
||||
}
|
||||
|
||||
impl InputQueue {
|
||||
/// Add a new input to the queue
|
||||
pub fn push(&mut self, input: PlayerInput) {
|
||||
self.queue.push_back(input);
|
||||
}
|
||||
|
||||
/// Drain all inputs for ticks <= the given tick
|
||||
/// Returns inputs in FIFO order
|
||||
pub fn drain_for_tick(&mut self, tick: u64) -> Vec<PlayerInput> {
|
||||
let mut result = Vec::new();
|
||||
while let Some(front) = self.queue.front() {
|
||||
if front.tick <= tick {
|
||||
result.push(self.queue.pop_front().unwrap());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Get the current queue length
|
||||
pub fn len(&self) -> usize {
|
||||
self.queue.len()
|
||||
}
|
||||
|
||||
/// Check if the queue is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.queue.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bridge::types::PlayerAction;
|
||||
|
||||
#[test]
|
||||
fn drain_returns_inputs_up_to_tick() {
|
||||
let mut queue = InputQueue::default();
|
||||
queue.push(PlayerInput {
|
||||
tick: 1,
|
||||
action: PlayerAction::MoveNorth,
|
||||
});
|
||||
queue.push(PlayerInput {
|
||||
tick: 2,
|
||||
action: PlayerAction::MoveSouth,
|
||||
});
|
||||
queue.push(PlayerInput {
|
||||
tick: 5,
|
||||
action: PlayerAction::Interact,
|
||||
});
|
||||
let inputs = queue.drain_for_tick(3);
|
||||
assert_eq!(inputs.len(), 2);
|
||||
assert_eq!(queue.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drain_empty_queue_returns_empty() {
|
||||
let mut queue = InputQueue::default();
|
||||
let inputs = queue.drain_for_tick(10);
|
||||
assert!(inputs.is_empty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user