···2233## Ben
4455-- [ ] Transport : Packet splitting
66-- [ ] Transport : Handle Errors
77-- [ ] Transport : Mark game started on client
55+- [x] Transport : Packet splitting
66+- [x] Transport : Handle Errors
77+- [x] Transport : Mark game started on client
88+- [x] API : Command to check if a game exists and is open for fast error checking
99+- [x] Transport : Switch to burst message processing for less time in the
1010+ critical path
811- [ ] State : Event history tracking
912- [ ] State : Post game sync
1013- [ ] API : Handling Profile Syncing
···1717 /// Contains location history of the given player, used after the game to sync location
1818 /// histories
1919 PostGameSync(Id, Vec<Location>),
2020+ /// A player has been disconnected and removed from the game (because of error or otherwise).
2121+ /// The player should be removed from all state
2222+ DroppedPlayer(Id),
2323+ /// The underlying transport has disconnected
2424+ TransportDisconnect,
2025}
+50-21
backend/src/game/mod.rs
···33use powerups::PowerUpType;
44pub use settings::GameSettings;
55use std::{collections::HashMap, sync::Arc, time::Duration};
66+use tokio_util::sync::CancellationToken;
67use uuid::Uuid;
7889use tokio::{sync::RwLock, time::MissedTickBehavior};
···1314mod settings;
1415mod state;
1516mod transport;
1717+1818+use crate::prelude::*;
16191720pub use location::{Location, LocationService};
1821pub use state::GameState;
···3134 transport: Arc<T>,
3235 location: L,
3336 interval: Duration,
3737+ transport_cancel_token: CancellationToken,
3438}
35393640impl<L: LocationService, T: Transport> Game<L, T> {
···4145 settings: GameSettings,
4246 transport: Arc<T>,
4347 location: L,
4848+ transport_cancel_token: CancellationToken,
4449 ) -> Self {
4550 let state = GameState::new(settings, my_id, initial_caught_state);
46514752 Self {
4853 transport,
5454+ transport_cancel_token,
4955 location,
5056 interval,
5157 state: RwLock::new(state),
···104110 }
105111 }
106112107107- async fn consume_event(&self, event: GameEvent) {
108108- let mut state = self.state.write().await;
109109-113113+ async fn consume_event(&self, state: &mut GameState, event: GameEvent) -> Result {
110114 match event {
111115 GameEvent::Ping(player_ping) => state.add_ping(player_ping),
112116 GameEvent::ForcePing(target, display) => {
113117 if target != state.id {
114114- return;
118118+ return Ok(());
115119 }
116120117121 let ping = if let Some(display) = display {
···129133 GameEvent::PlayerCaught(player) => {
130134 state.mark_caught(player);
131135 state.remove_ping(player);
136136+ }
137137+ GameEvent::DroppedPlayer(id) => {
138138+ state.remove_player(id);
139139+ }
140140+ GameEvent::TransportDisconnect => {
141141+ bail!("Transport disconnected");
132142 }
133143 GameEvent::PostGameSync(_, _locations) => {}
134144 }
145145+146146+ Ok(())
135147 }
136148137149 /// Perform a tick for a specific moment in time
138138- async fn tick(&self, now: UtcDT) {
139139- let mut state = self.state.write().await;
140140-150150+ async fn tick(&self, state: &mut GameState, now: UtcDT) {
141151 // Push to location history
142152 if let Some(location) = self.location.get_loc() {
143153 state.push_loc(location);
···186196187197 #[cfg(test)]
188198 pub async fn force_tick(&self, now: UtcDT) {
189189- self.tick(now).await;
199199+ let mut state = self.state.write().await;
200200+ self.tick(&mut state, now).await;
201201+ }
202202+203203+ pub fn quit_game(&self) {
204204+ self.transport_cancel_token.cancel();
190205 }
191206192207 /// Main loop of the game, handles ticking and receiving messages from [Transport].
193193- pub async fn main_loop(&self) {
208208+ pub async fn main_loop(&self) -> Result {
194209 let mut interval = tokio::time::interval(self.interval);
195210196211 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
197212198198- loop {
213213+ let res = 'game: loop {
199214 tokio::select! {
215215+ biased;
200216201201- biased;
217217+ events = self.transport.receive_messages() => {
218218+ let mut state = self.state.write().await;
219219+ for event in events {
220220+ if let Err(why) = self.consume_event(&mut state, event).await {
221221+ break 'game Err(why);
222222+ }
223223+ }
202224203203- Some(msg) = self.transport.receive_message() => {
204204- self.consume_event(msg).await;
205205- // TODO: Check all caught, end game
225225+ if state.should_end() {
226226+ break Ok(());
227227+ }
206228 }
207229208230 _ = interval.tick() => {
209209- let now = Utc::now();
210210- self.tick(now).await;
231231+ let mut state = self.state.write().await;
232232+ self.tick(&mut state, Utc::now()).await;
211233 }
212212- };
213213- }
234234+ }
235235+ };
236236+237237+ self.transport_cancel_token.cancel();
238238+239239+ res
214240 }
215241}
216242···232258 }
233259234260 impl Transport for MockTransport {
235235- async fn receive_message(&self) -> Option<GameEvent> {
261261+ async fn receive_messages(&self) -> impl Iterator<Item = GameEvent> {
236262 let mut rx = self.rx.lock().await;
237237- rx.recv().await
263263+ let mut buf = Vec::with_capacity(20);
264264+ rx.recv_many(&mut buf, 20).await;
265265+ buf.into_iter()
238266 }
239267240268 async fn send_message(&self, msg: GameEvent) {
···301329 settings.clone(),
302330 Arc::new(transport),
303331 location,
332332+ CancellationToken::new(),
304333 );
305334306335 (id as u32, Arc::new(game))
···319348 for game in self.games.values() {
320349 let game = game.clone();
321350 tokio::spawn(async move {
322322- game.main_loop().await;
351351+ game.main_loop().await.expect("Game Start Fail");
323352 });
324353 yield_now().await;
325354 }
+11
backend/src/game/state.rs
···240240 self.pings.get(&player)
241241 }
242242243243+ /// Check if the game should be ended (due to all players being caught)
244244+ pub fn should_end(&self) -> bool {
245245+ self.caught_state.values().all(|v| *v)
246246+ }
247247+243248 /// Remove a ping from the map
244249 pub fn remove_ping(&mut self, player: Id) -> Option<PlayerPing> {
245250 self.pings.remove(&player)
···281286 /// Create a [PlayerPing] with the latest location as another player
282287 pub fn create_ping(&self, id: Id) -> Option<PlayerPing> {
283288 self.get_loc().map(|loc| PlayerPing::new(*loc, id, self.id))
289289+ }
290290+291291+ /// Remove a player from the game by their ID number
292292+ pub fn remove_player(&mut self, id: Id) {
293293+ self.pings.remove(&id);
294294+ self.caught_state.remove(&id);
284295 }
285296286297 /// Player has gotten a powerup, rolls to see which powerup and stores it