bones_framework/networking/
online.rs1#![doc = include_str!("./online.md")]
2#![allow(missing_docs)]
4
5use crate::{
6 networking::{get_network_endpoint, NetworkMatchSocket},
7 prelude::*,
8};
9pub use bones_matchmaker_proto::{
10 GameID, LobbyId, LobbyInfo, LobbyListItem, MatchInfo, PlayerIdxAssignment, MATCH_ALPN,
11};
12use iroh::{endpoint::Connection, Endpoint, NodeId};
13use once_cell::sync::Lazy;
14use tracing::{info, warn};
15
16pub const READ_TO_END_BYTE_COUNT: usize = 256;
18
19#[derive(DerefMut, Deref)]
21pub struct OnlineMatchmaker(BiChannelClient<OnlineMatchmakerRequest, OnlineMatchmakerResponse>);
22
23#[derive(Debug)]
25pub enum OnlineMatchmakerRequest {
26 SearchForGame {
27 id: NodeId,
28 player_count: u32,
29 game_id: GameID,
30 match_data: Vec<u8>,
31 player_idx_assignment: PlayerIdxAssignment,
32 },
33 StopSearch {
34 id: NodeId,
35 },
36 ListLobbies {
37 id: NodeId,
38 game_id: GameID,
39 },
40 CreateLobby {
41 id: NodeId,
42 lobby_info: LobbyInfo,
43 },
44 JoinLobby {
45 id: NodeId,
46 game_id: GameID,
47 lobby_id: LobbyId,
48 password: Option<String>,
49 },
50}
51
52impl OnlineMatchmakerRequest {
53 pub fn node_id(&self) -> NodeId {
55 match self {
56 OnlineMatchmakerRequest::SearchForGame { id, .. } => *id,
57 OnlineMatchmakerRequest::StopSearch { id } => *id,
58 OnlineMatchmakerRequest::ListLobbies { id, .. } => *id,
59 OnlineMatchmakerRequest::CreateLobby { id, .. } => *id,
60 OnlineMatchmakerRequest::JoinLobby { id, .. } => *id,
61 }
62 }
63}
64
65#[derive(Serialize, Clone)]
67pub enum OnlineMatchmakerResponse {
68 Searching,
70 MatchmakingUpdate { player_count: u32 },
72 GameStarting {
74 #[serde(skip_serializing, skip_deserializing)]
75 socket: NetworkMatchSocket,
76 player_idx: usize,
77 player_count: usize,
78 random_seed: u64,
79 },
80 LobbyUpdate { player_count: u32 },
82 LobbiesList(Vec<LobbyListItem>),
84 LobbyCreated(LobbyId),
86 LobbyJoined {
88 lobby_id: LobbyId,
89 player_count: usize,
90 },
91 Error(String),
93}
94
95impl std::fmt::Debug for OnlineMatchmakerResponse {
96 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97 let serialized =
98 serde_yaml::to_string(self).expect("Failed to serialize OnlineMatchmakerResponse");
99 write!(f, "{:?}", serialized)
100 }
101}
102
103pub static ONLINE_MATCHMAKER: Lazy<OnlineMatchmaker> = Lazy::new(|| {
105 let (client, server) = bi_channel();
106
107 RUNTIME.spawn(async move {
108 if let Err(err) = process_matchmaker_requests(server).await {
109 warn!("online matchmaker failed: {err:?}");
110 }
111 });
112
113 OnlineMatchmaker(client)
114});
115
116pub struct MatchmakerConnectionState {
118 ep: Option<Endpoint>,
119 conn: Option<Connection>,
120 node_id: Option<NodeId>,
121}
122
123impl Default for MatchmakerConnectionState {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129impl MatchmakerConnectionState {
130 pub fn new() -> Self {
132 Self {
133 ep: None,
134 conn: None,
135 node_id: None,
136 }
137 }
138
139 pub async fn acquire_connection(&mut self) -> anyhow::Result<&Connection> {
142 if let Some(id) = self.node_id {
143 if self.conn.is_none() {
144 info!("Connecting to online matchmaker");
145 let ep = get_network_endpoint().await;
146 let conn = ep.connect(id, MATCH_ALPN).await?;
147 self.ep = Some(ep.clone());
148 self.conn = Some(conn);
149 info!("Connected to online matchmaker");
150 }
151
152 self.conn
153 .as_ref()
154 .ok_or_else(|| anyhow::anyhow!("Failed to establish connection"))
155 } else {
156 Err(anyhow::anyhow!("NodeId not set"))
157 }
158 }
159
160 pub fn close_connection(&mut self) {
162 if let Some(conn) = self.conn.take() {
163 conn.close(0u32.into(), b"Closing matchmaker connection");
164 }
165 self.ep = None;
166 }
167
168 pub fn is_connected(&self) -> bool {
170 self.conn.is_some()
171 }
172
173 pub fn set_node_id(&mut self, id: NodeId) {
175 self.node_id = Some(id);
176 }
177}
178
179async fn process_matchmaker_requests(
181 user_channel: BiChannelServer<OnlineMatchmakerRequest, OnlineMatchmakerResponse>,
182) -> anyhow::Result<()> {
183 let mut matchmaker_connection_state = MatchmakerConnectionState::new();
184
185 while let Ok(message) = user_channel.recv().await {
186 match message {
187 OnlineMatchmakerRequest::SearchForGame {
188 id,
189 player_count,
190 game_id,
191 match_data,
192 player_idx_assignment,
193 } => {
194 matchmaker_connection_state.set_node_id(id);
195 let match_info = MatchInfo {
196 max_players: player_count,
197 match_data,
198 game_id,
199 player_idx_assignment,
200 };
201
202 if let Err(err) = crate::networking::online_matchmaking::resolve_search_for_match(
203 &user_channel,
204 &mut matchmaker_connection_state,
205 match_info.clone(),
206 )
207 .await
208 {
209 warn!("Start Matchmaking Search failed: {err:?}");
210 }
211 }
212 OnlineMatchmakerRequest::ListLobbies { id, game_id } => {
213 matchmaker_connection_state.set_node_id(id);
214 if let Err(err) = crate::networking::online_lobby::resolve_list_lobbies(
215 &user_channel,
216 &mut matchmaker_connection_state,
217 game_id,
218 )
219 .await
220 {
221 warn!("Listing lobbies failed: {err:?}");
222 }
223 }
224 OnlineMatchmakerRequest::CreateLobby { id, lobby_info } => {
225 matchmaker_connection_state.set_node_id(id);
226 if let Err(err) = crate::networking::online_lobby::resolve_create_lobby(
227 &user_channel,
228 &mut matchmaker_connection_state,
229 lobby_info,
230 )
231 .await
232 {
233 warn!("Creating lobby failed: {err:?}");
234 }
235 }
236 OnlineMatchmakerRequest::JoinLobby {
237 id,
238 game_id,
239 lobby_id,
240 password,
241 } => {
242 matchmaker_connection_state.set_node_id(id);
243 if let Err(err) = crate::networking::online_lobby::resolve_join_lobby(
244 &user_channel,
245 &mut matchmaker_connection_state,
246 game_id,
247 lobby_id,
248 password,
249 )
250 .await
251 {
252 warn!("Joining lobby failed: {err:?}");
253 }
254 }
255 _ => {}
257 }
258 }
259
260 Ok(())
261}
262
263impl OnlineMatchmaker {
265 pub fn read_matchmaker_response() -> Option<OnlineMatchmakerResponse> {
267 ONLINE_MATCHMAKER.try_recv().ok()
268 }
269
270 pub fn start_search_for_match(
272 matchmaking_server: NodeId,
273 game_id: GameID,
274 player_count: u32,
275 match_data: Vec<u8>,
276 player_idx_assignment: PlayerIdxAssignment,
277 ) -> anyhow::Result<()> {
278 ONLINE_MATCHMAKER
279 .try_send(OnlineMatchmakerRequest::SearchForGame {
280 id: matchmaking_server,
281 player_count,
282 game_id,
283 match_data,
284 player_idx_assignment,
285 })
286 .map_err(|e| anyhow::anyhow!("Failed to send matchmaker request: {}", e))?;
287 Ok(())
288 }
289
290 pub fn stop_search_for_match(matchmaking_server: NodeId) -> anyhow::Result<()> {
292 ONLINE_MATCHMAKER
293 .try_send(OnlineMatchmakerRequest::StopSearch {
294 id: matchmaking_server,
295 })
296 .map_err(|e| anyhow::anyhow!("Failed to send matchmaker request: {}", e))?;
297 Ok(())
298 }
299
300 pub fn list_lobbies(matchmaking_server: NodeId, game_id: GameID) -> anyhow::Result<()> {
302 ONLINE_MATCHMAKER
303 .try_send(OnlineMatchmakerRequest::ListLobbies {
304 id: matchmaking_server,
305 game_id,
306 })
307 .map_err(|e| anyhow::anyhow!("Failed to send list lobbies request: {}", e))?;
308 Ok(())
309 }
310
311 pub fn create_lobby(matchmaking_server: NodeId, lobby_info: LobbyInfo) -> anyhow::Result<()> {
313 ONLINE_MATCHMAKER
314 .try_send(OnlineMatchmakerRequest::CreateLobby {
315 id: matchmaking_server,
316 lobby_info,
317 })
318 .map_err(|e| anyhow::anyhow!("Failed to send create lobby request: {}", e))?;
319 Ok(())
320 }
321
322 pub fn join_lobby(
324 matchmaking_server: NodeId,
325 game_id: GameID,
326 lobby_id: LobbyId,
327 password: Option<String>,
328 ) -> anyhow::Result<()> {
329 ONLINE_MATCHMAKER
330 .try_send(OnlineMatchmakerRequest::JoinLobby {
331 id: matchmaking_server,
332 game_id,
333 lobby_id,
334 password,
335 })
336 .map_err(|e| anyhow::anyhow!("Failed to send join lobby request: {}", e))?;
337 Ok(())
338 }
339}