Skip to main content

banished_net/
lib.rs

1//! Un-Banished multiplayer networking: the protocol between a game and the relay, and a WebSocket
2//! connection to the relay that works natively and in the browser.
3//!
4//! The relay (`apps/relay`) keeps rooms. A host opens one and gets its code; players join with
5//! the code. The relay numbers the players in the order they come (the host is 0) and passes
6//! their data on, to one player or to all the others. It never reads the data: the game's own
7//! messages (its lockstep turns, digests and saves) travel inside [`ToRelay::Send`] and
8//! [`FromRelay::Data`]. Every WebSocket message is one value, encoded with postcard.
9//!
10//! Not in the original game, which has no multiplayer; see docs/MULTIPLAYER-PLAN.md, step 5.
11#![doc(
12    html_logo_url = "https://tanepiper.github.io/unbanished/assets/un-banished-icon.svg",
13    html_favicon_url = "https://tanepiper.github.io/unbanished/favicon.svg"
14)]
15
16use serde::{Deserialize, Serialize};
17
18/// A game to the relay.
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub enum ToRelay {
21    /// Open a room (the code it asks for, or a new one) and be its host, player 0.
22    Host {
23        /// The code to use, or none for a new one.
24        room: Option<String>,
25    },
26    /// Join a room.
27    Join {
28        /// The room's code.
29        room: String,
30    },
31    /// Data for one player, or for every other player in the room.
32    Send {
33        /// The player it is for, or none for every other player.
34        to: Option<u8>,
35        /// The data; the relay does not read it.
36        data: Vec<u8>,
37    },
38}
39
40/// The relay to a game.
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub enum FromRelay {
43    /// The room is open: its code (this game is player 0).
44    Hosted {
45        /// The room's code.
46        room: String,
47    },
48    /// This game joined the room as `player`.
49    Joined {
50        /// The room's code.
51        room: String,
52        /// This game's player number.
53        player: u8,
54    },
55    /// Another player came into the room.
56    PeerJoined {
57        /// Its player number.
58        player: u8,
59    },
60    /// Another player left the room.
61    PeerLeft {
62        /// Its player number.
63        player: u8,
64    },
65    /// Data from a player.
66    Data {
67        /// The sender's player number.
68        from: u8,
69        /// The data.
70        data: Vec<u8>,
71    },
72    /// A request failed (no such room, ...).
73    Error {
74        /// What went wrong.
75        message: String,
76    },
77}
78
79/// Encodes a protocol value (or a game message) for a WebSocket binary message.
80pub fn encode<T: Serialize>(value: &T) -> Vec<u8> {
81    postcard::to_stdvec(value).expect("a protocol value encodes")
82}
83
84/// Decodes a WebSocket binary message.
85pub fn decode<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, String> {
86    postcard::from_bytes(bytes).map_err(|e| e.to_string())
87}
88
89/// The state of a connection.
90#[derive(Debug, Clone, PartialEq)]
91pub enum State {
92    /// The connection is being made.
93    Connecting,
94    /// The relay has answered.
95    Open,
96    /// The connection is closed, for this reason.
97    Closed(String),
98}
99
100#[cfg(feature = "native")]
101mod native;
102#[cfg(feature = "native")]
103pub use native::Connection;
104
105#[cfg(target_arch = "wasm32")]
106mod web;
107#[cfg(target_arch = "wasm32")]
108pub use web::Connection;
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn values_round_trip() {
116        let values = [
117            ToRelay::Host { room: None },
118            ToRelay::Join {
119                room: "ABCDEF".into(),
120            },
121            ToRelay::Send {
122                to: Some(2),
123                data: vec![1, 2, 3],
124            },
125        ];
126        for v in values {
127            assert_eq!(decode::<ToRelay>(&encode(&v)).unwrap(), v);
128        }
129        let big = FromRelay::Data {
130            from: 1,
131            data: vec![7; 1 << 20],
132        };
133        let bytes = encode(&big);
134        assert!(bytes.len() < (1 << 20) + 16, "{}", bytes.len());
135        assert_eq!(decode::<FromRelay>(&bytes).unwrap(), big);
136    }
137}