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}