···2020- [x] Signaling: All of it
2121- [x] Backend : Better transport error handling
2222- [ ] Backend : Abstract lobby? Separate crate?
2323+- [x] Transport : Handle transport cancellation better
2324- [x] Backend : Add checks for when the `powerup_locations` field is an empty array in settings
2425- [ ] Backend : More tests
+13-8
backend/src/game/mod.rs
···117117 }
118118 }
119119120120- async fn consume_event(&self, state: &mut GameState, event: GameEvent) -> Result {
120120+ async fn consume_event(&self, state: &mut GameState, event: GameEvent) -> Result<bool> {
121121 if !state.game_ended() {
122122 state.event_history.push((Utc::now(), event.clone()));
123123 }
···126126 GameEvent::Ping(player_ping) => state.add_ping(player_ping),
127127 GameEvent::ForcePing(target, display) => {
128128 if target != state.id {
129129- return Ok(());
129129+ return Ok(false);
130130 }
131131132132 let ping = if let Some(display) = display {
···149149 state.remove_player(id);
150150 }
151151 GameEvent::TransportDisconnect => {
152152- bail!("Transport disconnected");
152152+ return Ok(true);
153153 }
154154 GameEvent::TransportError(err) => {
155155 bail!("Transport error: {err}");
···161161162162 self.state_update_sender.send_update();
163163164164- Ok(())
164164+ Ok(false)
165165 }
166166167167 /// Perform a tick for a specific moment in time
···253253 }
254254255255 /// Main loop of the game, handles ticking and receiving messages from [Transport].
256256- pub async fn main_loop(&self) -> Result<GameHistory> {
256256+ pub async fn main_loop(&self) -> Result<Option<GameHistory>> {
257257 let mut interval = tokio::time::interval(self.interval);
258258259259 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
···265265 events = self.transport.receive_messages() => {
266266 let mut state = self.state.write().await;
267267 for event in events {
268268- if let Err(why) = self.consume_event(&mut state, event).await {
269269- break 'game Err(why);
268268+ match self.consume_event(&mut state, event).await {
269269+ Ok(should_break) => {
270270+ if should_break {
271271+ break 'game Ok(None);
272272+ }
273273+ }
274274+ Err(why) => { break 'game Err(why); }
270275 }
271276 }
272277 }
···277282278283 if should_break {
279284 let history = state.as_game_history();
280280- break Ok(history);
285285+ break Ok(Some(history));
281286 }
282287 }
283288 }
+18-8
backend/src/lib.rs
···1717use serde::{Deserialize, Serialize};
1818use tauri::{AppHandle, Manager, State};
1919use tauri_plugin_dialog::{DialogExt, MessageDialogKind};
2020-use tauri_specta::{collect_commands, collect_events, Event};
2020+use tauri_specta::{collect_commands, collect_events, ErrorHandlingMode, Event};
2121use tokio::sync::RwLock;
2222use transport::MatchboxTransport;
2323use uuid::Uuid;
···104104 let state_handle = app.state::<AppStateHandle>();
105105 let mut state = state_handle.write().await;
106106 match res {
107107- Ok(history) => {
107107+ Ok(Some(history)) => {
108108 let history = AppGameHistory::new(history, profiles);
109109 if let Err(why) = history.save_history(&app2) {
110110 error!("Failed to save game history: {why:?}");
···115115 }
116116 state.quit_to_menu(app2);
117117 }
118118+ Ok(None) => {
119119+ info!("User quit game");
120120+ }
118121 Err(why) => {
119122 error!("Game Error: {why:?}");
120120- app2.dialog().message("There was a connection error in the game, you have been disconnected").kind(MessageDialogKind::Error).show(|_| {});
123123+ app2.dialog()
124124+ .message(format!("Connection Error: {why}"))
125125+ .kind(MessageDialogKind::Error)
126126+ .show(|_| {});
121127 state.quit_to_menu(app2);
122128 }
123129 }
···225231 let state_handle = app2.state::<AppStateHandle>();
226232 let mut state = state_handle.write().await;
227233 match res {
228228- Ok((my_id, start)) => {
234234+ Ok(Some((my_id, start))) => {
229235 info!("Starting game as {my_id}");
230236 state.start_game(app_game, my_id, start).await;
237237+ }
238238+ Ok(None) => {
239239+ info!("User quit lobby");
231240 }
232241 Err(why) => {
233233- error!("Lobby Error: {why:?}");
242242+ error!("Lobby Error: {why}");
234243 app_game
235244 .dialog()
236236- .message("Error joining the lobby")
245245+ .message(format!("Error joining the lobby: {why}"))
237246 .kind(MessageDialogKind::Error)
238247 .show(|_| {});
239248 state.quit_to_menu(app_game);
···470479#[specta::specta]
471480/// (Screen: Game) Use the currently held powerup in the player's held_powerup. Does nothing if the
472481/// player has none. Returns the updated game state
473473-async fn use_powerup(state: State<'_, AppStateHandle>) -> Result {
482482+async fn activate_powerup(state: State<'_, AppStateHandle>) -> Result {
474483 let game = state.read().await.get_game()?;
475484 game.use_powerup().await;
476485 Ok(())
···488497489498pub fn mk_specta() -> tauri_specta::Builder {
490499 tauri_specta::Builder::<tauri::Wry>::new()
500500+ .error_handling(ErrorHandlingMode::Throw)
491501 .commands(collect_commands![
492502 start_lobby,
493503 get_profile,
···500510 host_start_game,
501511 mark_caught,
502512 grab_powerup,
503503- use_powerup,
513513+ activate_powerup,
504514 check_room_code,
505515 get_profiles,
506516 replay_game,