···19- [x] Meta : Recipes for type binding generation
20- [x] Signaling: All of it
21- [x] Backend : Better transport error handling
22-- [ ] Backend : Abstract lobby? Separate crate?
23- [x] Transport : Handle transport cancellation better
24- [x] Backend : Add checks for when the `powerup_locations` field is an empty array in settings
25- [ ] Backend : More tests
···19- [x] Meta : Recipes for type binding generation
20- [x] Signaling: All of it
21- [x] Backend : Better transport error handling
22+- [x] Backend : Abstract lobby? Separate crate?
23- [x] Transport : Handle transport cancellation better
24- [x] Backend : Add checks for when the `powerup_locations` field is an empty array in settings
25- [ ] Backend : More tests
+5-11
backend/Cargo.toml
···22tauri-plugin-opener = "2"
23serde = { version = "1", features = ["derive"] }
24serde_json = "1"
25-chrono = { version = "0.4", features = ["serde", "now"] }
26tokio = { version = "1.45", features = ["sync", "macros", "time", "fs"] }
27-rand = { version = "0.9", features = ["thread_rng"] }
28tauri-plugin-geolocation = "2"
29-rand_chacha = "0.9.0"
30-futures = "0.3.31"
31-matchbox_socket = "0.12.0"
32-uuid = { version = "1.17.0", features = ["serde", "v4"] }
33-rmp-serde = "1.3.0"
34tauri-plugin-store = "2.2.0"
35-specta = { version = "=2.0.0-rc.22", features = ["chrono", "uuid"] }
36tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
37specta-typescript = "0.0.9"
38tauri-plugin-log = "2"
39tauri-plugin-notification = "2"
40log = "0.4.27"
41-tokio-util = "0.7.15"
42anyhow = "1.0.98"
43-reqwest = { version = "0.12.20", default-features = false, features = ["charset", "http2", "rustls-tls", "system-proxy"] }
44-const-str = "0.6.2"
45tauri-plugin-dialog = "2"
0000
···22tauri-plugin-opener = "2"
23serde = { version = "1", features = ["derive"] }
24serde_json = "1"
025tokio = { version = "1.45", features = ["sync", "macros", "time", "fs"] }
026tauri-plugin-geolocation = "2"
0000027tauri-plugin-store = "2.2.0"
28+specta = { version = "=2.0.0-rc.22", features = ["chrono", "uuid", "export"] }
29tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
30specta-typescript = "0.0.9"
31tauri-plugin-log = "2"
32tauri-plugin-notification = "2"
33log = "0.4.27"
034anyhow = "1.0.98"
0035tauri-plugin-dialog = "2"
36+manhunt-logic = { version = "0.1.0", path = "../manhunt-logic" }
37+manhunt-transport = { version = "0.1.0", path = "../manhunt-transport" }
38+uuid = { version = "1.17.0", features = ["serde"] }
39+chrono = { version = "0.4.41", features = ["serde"] }
···1use serde::{Deserialize, Serialize};
23-use super::{location::Location, state::PlayerPing, Id, UtcDT};
000045/// An event used between players to update state
6#[derive(Debug, Clone, Serialize, Deserialize, specta::Type)]
···17 /// Contains location history of the given player, used after the game to sync location
18 /// histories
19 PostGameSync(Id, Vec<(UtcDT, Location)>),
20- /// A player has been disconnected and removed from the game (because of error or otherwise).
21- /// The player should be removed from all state
22- DroppedPlayer(Id),
23- /// The underlying transport has disconnected
24- TransportDisconnect,
25- /// The underlying transport encountered an error
26- TransportError(String),
27}
···1use serde::{Deserialize, Serialize};
23+use crate::{
4+ game::{Id, UtcDT},
5+ game_state::PlayerPing,
6+ location::Location,
7+};
89/// An event used between players to update state
10#[derive(Debug, Clone, Serialize, Deserialize, specta::Type)]
···21 /// Contains location history of the given player, used after the game to sync location
22 /// histories
23 PostGameSync(Id, Vec<(UtcDT, Location)>),
000000024}
···1+use std::sync::Arc;
2+3+use super::{game_events::GameEvent, lobby::LobbyMessage};
4+use serde::{Deserialize, Serialize};
5+use uuid::Uuid;
6+7+#[derive(Debug, Serialize, Deserialize, Clone)]
8+pub enum TransportMessage {
9+ /// Message related to the actual game
10+ /// Boxed for space reasons
11+ Game(Box<GameEvent>),
12+ /// Message related to the pre-game lobby
13+ Lobby(Box<LobbyMessage>),
14+ /// Internal message when peer connects
15+ PeerConnect(Uuid),
16+ /// Internal message when peer disconnects
17+ PeerDisconnect(Uuid),
18+ /// Event sent when the transport gets disconnected, used to help consumers know when to stop
19+ /// consuming messages. Note this should represent a success state, the disconnect was
20+ /// triggered by user action.
21+ Disconnected,
22+ /// Event when the transport encounters a critical error and needs to disconnect.
23+ Error(String),
24+}
25+26+impl From<GameEvent> for TransportMessage {
27+ fn from(v: GameEvent) -> Self {
28+ Self::Game(Box::new(v))
29+ }
30+}
31+32+impl From<LobbyMessage> for TransportMessage {
33+ fn from(v: LobbyMessage) -> Self {
34+ Self::Lobby(Box::new(v))
35+ }
36+}
37+38+pub type MsgPair = (Option<Uuid>, TransportMessage);
39+40+pub trait Transport: Send + Sync {
41+ /// Start the transport loop, This is expected to spawn a new job that will loop until
42+ /// cancelled or an error occurs.
43+ fn initialize(
44+ code: &str,
45+ host: bool,
46+ ) -> impl std::future::Future<Output = Result<Arc<Self>, anyhow::Error>> + Send;
47+ /// Get the local user's ID
48+ fn self_id(&self) -> Uuid;
49+ /// Check if a room is open to join, non-host players will call this with a code
50+ fn room_joinable(&self, _code: &str) -> impl std::future::Future<Output = bool> + Send {
51+ async { true }
52+ }
53+ /// Request a room be marked unjoinable (due to a game starting), the host user will call this.
54+ fn mark_room_started(&self, _code: &str) -> impl Future<Output = ()> {
55+ async {}
56+ }
57+ /// Receive an event
58+ fn receive_messages(&self) -> impl Future<Output = impl Iterator<Item = MsgPair>>;
59+ /// Send a message to a specific peer
60+ fn send_message_single(&self, peer: Uuid, msg: TransportMessage) -> impl Future<Output = ()>;
61+ /// Send a message to all other peers
62+ fn send_message(&self, msg: TransportMessage) -> impl Future<Output = ()>;
63+ /// Send a message to the local user
64+ fn send_self(&self, msg: TransportMessage) -> impl Future<Output = ()>;
65+ /// Disconnect from the transport
66+ fn disconnect(&self) -> impl Future<Output = ()> {
67+ async {}
68+ }
69+}
+20
manhunt-transport/Cargo.toml
···00000000000000000000
···1+[package]
2+name = "manhunt-transport"
3+version = "0.1.0"
4+edition = "2024"
5+6+[dependencies]
7+anyhow = "1.0.98"
8+futures = "0.3.31"
9+log = "0.4.27"
10+matchbox_protocol = "0.12.0"
11+matchbox_socket = "0.12.0"
12+rmp-serde = "1.3.0"
13+serde = { version = "1.0.219", features = ["derive"] }
14+tokio = { version = "1.45.1", features = ["macros", "sync", "time", "rt"] }
15+tokio-util = "0.7.15"
16+uuid = { version = "1.17.0", features = ["serde"] }
17+manhunt-logic = { version = "0.1.0", path = "../manhunt-logic" }
18+rand = { version = "0.9.1", features = ["thread_rng"] }
19+reqwest = { version = "0.12.20", default-features = false, features = ["charset", "http2", "rustls-tls", "system-proxy"] }
20+const-str = "0.6.2"
+6
manhunt-transport/src/lib.rs
···000000
···1+mod matchbox;
2+mod packets;
3+mod server;
4+5+pub use matchbox::MatchboxTransport;
6+pub use server::{generate_join_code, room_exists};