Skip to main content

flatland_client_lib/
game.rs

1use std::collections::VecDeque;
2
3use flatland_protocol::{EntityId, EntityState, Intent, Seq, SessionId, Tick};
4
5use crate::session::{PlayConnection, SessionEvent};
6
7const MAX_LOG_LINES: usize = 200;
8
9#[derive(Debug, Clone)]
10pub struct GameState {
11    pub session_id: SessionId,
12    pub entity_id: EntityId,
13    pub tick: Tick,
14    pub chunk_rev: u64,
15    pub entities: Vec<EntityState>,
16    pub player: Option<EntityState>,
17    pub logs: VecDeque<String>,
18    pub intents_sent: u64,
19    pub ticks_received: u64,
20    pub connected: bool,
21}
22
23impl GameState {
24    fn push_log(&mut self, line: impl Into<String>) {
25        self.logs.push_back(line.into());
26        while self.logs.len() > MAX_LOG_LINES {
27            self.logs.pop_front();
28        }
29    }
30}
31
32pub struct GameClient<S: PlayConnection> {
33    session: S,
34    seq: Seq,
35    pub state: GameState,
36}
37
38impl<S: PlayConnection> GameClient<S> {
39    pub fn new(session: S) -> Self {
40        let session_id = session.session_id();
41        let entity_id = session.entity_id();
42        Self {
43            session,
44            seq: 0,
45            state: GameState {
46                session_id,
47                entity_id,
48                tick: 0,
49                chunk_rev: 0,
50                entities: Vec::new(),
51                player: None,
52                logs: VecDeque::new(),
53                intents_sent: 0,
54                ticks_received: 0,
55                connected: false,
56            },
57        }
58    }
59
60    pub fn entity_id(&self) -> EntityId {
61        self.state.entity_id
62    }
63
64    pub async fn wait_until_ready(&mut self) -> anyhow::Result<()> {
65        if self.state.connected {
66            return Ok(());
67        }
68
69        loop {
70            match self.session.next_event().await {
71                Some(SessionEvent::Welcome {
72                    session_id,
73                    entity_id,
74                    snapshot,
75                }) => {
76                    self.state.session_id = session_id;
77                    self.state.entity_id = entity_id;
78                    self.state.tick = snapshot.tick;
79                    self.state.chunk_rev = snapshot.chunk_rev;
80                    self.apply_entities(snapshot.entities);
81                    self.state.connected = true;
82                    self.state.push_log(format!(
83                        "Connected — session {session_id}, entity {entity_id}"
84                    ));
85                    return Ok(());
86                }
87                Some(SessionEvent::Disconnected) => {
88                    anyhow::bail!("disconnected before welcome");
89                }
90                Some(_) => continue,
91                None => anyhow::bail!("session closed before welcome"),
92            }
93        }
94    }
95
96    /// Drain all pending server events (non-blocking).
97    pub fn drain_events(&mut self) {
98        while let Some(event) = self.session.try_next_event() {
99            if self.handle_event_sync(event).is_err() {
100                break;
101            }
102        }
103    }
104
105    /// Wait for the next server event.
106    pub async fn next_event(&mut self) -> Option<SessionEvent> {
107        self.session.next_event().await
108    }
109
110    pub async fn handle_event(&mut self, event: SessionEvent) -> anyhow::Result<()> {
111        self.handle_event_sync(event)
112    }
113
114    fn handle_event_sync(&mut self, event: SessionEvent) -> anyhow::Result<()> {
115        match event {
116            SessionEvent::Welcome {
117                session_id,
118                entity_id,
119                snapshot,
120            } => {
121                self.state.session_id = session_id;
122                self.state.entity_id = entity_id;
123                self.state.tick = snapshot.tick;
124                self.state.chunk_rev = snapshot.chunk_rev;
125                self.apply_entities(snapshot.entities);
126                self.state.connected = true;
127            }
128            SessionEvent::Tick(delta) => {
129                self.state.tick = delta.tick;
130                self.apply_entities(delta.entities);
131                self.state.ticks_received += 1;
132            }
133            SessionEvent::IntentAck { .. } => {}
134            SessionEvent::Disconnected => {
135                self.state.connected = false;
136                self.state.push_log("Disconnected from server");
137                anyhow::bail!("disconnected");
138            }
139        }
140        Ok(())
141    }
142
143    pub async fn move_by(&mut self, forward: f32, strafe: f32) -> anyhow::Result<()> {
144        self.seq += 1;
145        self.session
146            .submit_intent(Intent::Move {
147                entity_id: self.state.entity_id,
148                forward,
149                strafe,
150                seq: self.seq,
151            })
152            .await?;
153        self.state.intents_sent += 1;
154        Ok(())
155    }
156
157    pub async fn stop(&mut self) -> anyhow::Result<()> {
158        self.seq += 1;
159        self.session
160            .submit_intent(Intent::Stop {
161                entity_id: self.state.entity_id,
162                seq: self.seq,
163            })
164            .await?;
165        self.state.intents_sent += 1;
166        Ok(())
167    }
168
169    pub fn disconnect(&self) {
170        self.session.disconnect();
171    }
172
173    fn apply_entities(&mut self, entities: Vec<EntityState>) {
174        self.state.player = entities
175            .iter()
176            .find(|e| e.id == self.state.entity_id)
177            .cloned();
178        self.state.entities = entities;
179    }
180}