use std::collections::VecDeque;
use flatland_protocol::{EntityId, EntityState, Intent, Seq, SessionId, Tick};
use crate::session::{PlayConnection, SessionEvent};
const MAX_LOG_LINES: usize = 200;
#[derive(Debug, Clone)]
pub struct GameState {
pub session_id: SessionId,
pub entity_id: EntityId,
pub tick: Tick,
pub chunk_rev: u64,
pub entities: Vec<EntityState>,
pub player: Option<EntityState>,
pub logs: VecDeque<String>,
pub intents_sent: u64,
pub ticks_received: u64,
pub connected: bool,
}
impl GameState {
fn push_log(&mut self, line: impl Into<String>) {
self.logs.push_back(line.into());
while self.logs.len() > MAX_LOG_LINES {
self.logs.pop_front();
}
}
}
pub struct GameClient<S: PlayConnection> {
session: S,
seq: Seq,
pub state: GameState,
}
impl<S: PlayConnection> GameClient<S> {
pub fn new(session: S) -> Self {
let session_id = session.session_id();
let entity_id = session.entity_id();
Self {
session,
seq: 0,
state: GameState {
session_id,
entity_id,
tick: 0,
chunk_rev: 0,
entities: Vec::new(),
player: None,
logs: VecDeque::new(),
intents_sent: 0,
ticks_received: 0,
connected: false,
},
}
}
pub fn entity_id(&self) -> EntityId {
self.state.entity_id
}
pub async fn wait_until_ready(&mut self) -> anyhow::Result<()> {
if self.state.connected {
return Ok(());
}
loop {
match self.session.next_event().await {
Some(SessionEvent::Welcome {
session_id,
entity_id,
snapshot,
}) => {
self.state.session_id = session_id;
self.state.entity_id = entity_id;
self.state.tick = snapshot.tick;
self.state.chunk_rev = snapshot.chunk_rev;
self.apply_entities(snapshot.entities);
self.state.connected = true;
self.state.push_log(format!(
"Connected — session {session_id}, entity {entity_id}"
));
return Ok(());
}
Some(SessionEvent::Disconnected) => {
anyhow::bail!("disconnected before welcome");
}
Some(_) => continue,
None => anyhow::bail!("session closed before welcome"),
}
}
}
pub fn drain_events(&mut self) {
while let Some(event) = self.session.try_next_event() {
if self.handle_event_sync(event).is_err() {
break;
}
}
}
pub async fn next_event(&mut self) -> Option<SessionEvent> {
self.session.next_event().await
}
pub async fn handle_event(&mut self, event: SessionEvent) -> anyhow::Result<()> {
self.handle_event_sync(event)
}
fn handle_event_sync(&mut self, event: SessionEvent) -> anyhow::Result<()> {
match event {
SessionEvent::Welcome {
session_id,
entity_id,
snapshot,
} => {
self.state.session_id = session_id;
self.state.entity_id = entity_id;
self.state.tick = snapshot.tick;
self.state.chunk_rev = snapshot.chunk_rev;
self.apply_entities(snapshot.entities);
self.state.connected = true;
}
SessionEvent::Tick(delta) => {
self.state.tick = delta.tick;
self.apply_entities(delta.entities);
self.state.ticks_received += 1;
}
SessionEvent::IntentAck { .. } => {}
SessionEvent::Disconnected => {
self.state.connected = false;
self.state.push_log("Disconnected from server");
anyhow::bail!("disconnected");
}
}
Ok(())
}
pub async fn move_by(&mut self, forward: f32, strafe: f32) -> anyhow::Result<()> {
self.seq += 1;
self.session
.submit_intent(Intent::Move {
entity_id: self.state.entity_id,
forward,
strafe,
seq: self.seq,
})
.await?;
self.state.intents_sent += 1;
Ok(())
}
pub async fn stop(&mut self) -> anyhow::Result<()> {
self.seq += 1;
self.session
.submit_intent(Intent::Stop {
entity_id: self.state.entity_id,
seq: self.seq,
})
.await?;
self.state.intents_sent += 1;
Ok(())
}
pub fn disconnect(&self) {
self.session.disconnect();
}
fn apply_entities(&mut self, entities: Vec<EntityState>) {
self.state.player = entities
.iter()
.find(|e| e.id == self.state.entity_id)
.cloned();
self.state.entities = entities;
}
}