flatland_client_lib/
game.rs1use 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 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 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}