bones_matchmaker/
matchmaker.rs1use super::lobbies::{handle_create_lobby, handle_join_lobby, handle_list_lobbies};
2use super::matchmaking::{handle_request_matchaking, handle_stop_matchmaking};
3use crate::helpers::generate_random_seed;
4use anyhow::Result;
5use bones_matchmaker_proto::{
6 GameID, LobbyId, LobbyInfo, MatchInfo, MatchmakerRequest, MatchmakerResponse,
7 PlayerIdxAssignment,
8};
9use futures::future::BoxFuture;
10use iroh::{endpoint::Connection, Endpoint, NodeAddr};
11use once_cell::sync::Lazy;
12use rand::{prelude::SliceRandom, SeedableRng};
13use scc::HashMap as SccHashMap;
14use std::cmp::Ordering;
15use std::collections::HashMap;
16use std::sync::Arc;
17use tokio::sync::Mutex;
18
19pub struct GameLobbies {
21 #[allow(dead_code)]
22 pub game_id: GameID,
23 pub lobbies: HashMap<LobbyId, LobbyInfo>,
24}
25
26#[derive(Default)]
28pub struct State {
29 pub game_lobbies: HashMap<GameID, GameLobbies>,
30 pub lobby_connections: SccHashMap<(GameID, LobbyId), Vec<Connection>>,
31 pub matchmaking_rooms: SccHashMap<MatchInfo, Vec<Connection>>,
32}
33
34pub static MATCHMAKER_STATE: Lazy<Arc<Mutex<State>>> =
36 Lazy::new(|| Arc::new(Mutex::new(State::default())));
37
38#[derive(Debug)]
39pub struct Matchmaker {
40 endpoint: Endpoint,
41}
42
43impl iroh::protocol::ProtocolHandler for Matchmaker {
44 fn accept(self: Arc<Self>, conn: iroh::endpoint::Connecting) -> BoxFuture<'static, Result<()>> {
45 Box::pin(async move {
46 let connection = conn.await;
47
48 match connection {
49 Ok(conn) => {
50 info!(
51 connection_id = conn.stable_id(),
52 addr = ?conn.remote_address(),
53 "Accepted connection from client"
54 );
55
56 self.handle_connection(conn).await?;
58 }
59 Err(e) => error!("Error opening client connection: {e:?}"),
60 }
61
62 Ok(())
63 })
64 }
65}
66
67impl Matchmaker {
68 pub fn new(endpoint: Endpoint) -> Self {
69 Matchmaker { endpoint }
70 }
71
72 async fn handle_connection(&self, conn: Connection) -> Result<()> {
74 let connection_id = conn.stable_id();
75 loop {
76 tokio::select! {
77 _ = conn.closed() => {
78 info!("[{}] Client closed connection.", connection_id);
79 return Ok(());
80 }
81 bi = conn.accept_bi() => {
82 let (mut send, mut recv) = bi?;
83 let request: MatchmakerRequest = postcard::from_bytes(&recv.read_to_end(256).await?)?;
85
86 match request {
88 MatchmakerRequest::RequestMatchmaking(match_info) => {
89 handle_request_matchaking(&self.endpoint, conn.clone(), match_info, &mut send).await?;
90 send.finish()?;
91 send.stopped().await?;
92 }
93 MatchmakerRequest::StopMatchmaking(match_info) => {
94 handle_stop_matchmaking(conn.clone(), match_info, &mut send).await?;
95 }
96 MatchmakerRequest::ListLobbies(game_id) => {
97 handle_list_lobbies(game_id, &mut send).await?;
98 }
99 MatchmakerRequest::CreateLobby(lobby_info) => {
100 handle_create_lobby(conn.clone(), lobby_info, &mut send).await?;
101 }
102 MatchmakerRequest::JoinLobby(game_id, lobby_id, password) => {
103 handle_join_lobby(&self.endpoint, conn.clone(), game_id, lobby_id, password, &mut send).await?;
104 }
105 }
106 }
107 }
108 }
109 }
110}
111
112pub async fn start_game(
114 ep: Endpoint,
115 members: Vec<Connection>,
116 match_info: &MatchInfo,
117) -> Result<()> {
118 let random_seed = generate_random_seed();
119 let mut player_ids = Vec::new();
120 let player_count = members.len();
121
122 let player_indices = match &match_info.player_idx_assignment {
124 PlayerIdxAssignment::Ordered => (0..player_count).collect::<Vec<_>>(),
125 PlayerIdxAssignment::Random => {
126 let mut indices: Vec<_> = (0..player_count).collect();
127 let mut rng = rand::rngs::StdRng::seed_from_u64(random_seed);
128 indices.shuffle(&mut rng);
129 indices
130 }
131 PlayerIdxAssignment::SpecifiedOrder(order) => {
132 let mut indices = order.clone();
133 match indices.len().cmp(&player_count) {
134 Ordering::Less => {
135 indices.extend(indices.len()..player_count);
136 }
137 Ordering::Greater => {
138 indices.truncate(player_count);
139 }
140 _ => (),
141 }
142 indices
143 }
144 };
145
146 for (conn_idx, conn) in members.iter().enumerate() {
148 let id = iroh::endpoint::get_remote_node_id(conn)?;
149 let mut addr = NodeAddr::new(id);
150 if let Some(info) = ep.remote_info(id) {
151 if let Some(relay_url) = info.relay_url {
152 addr = addr.with_relay_url(relay_url.relay_url);
153 }
154 addr = addr.with_direct_addresses(info.addrs.into_iter().map(|addr| addr.addr));
155 }
156 let player_idx = player_indices[conn_idx];
157 player_ids.push((player_idx as u32, addr));
158 }
159
160 player_ids.sort_by_key(|&(idx, _)| idx);
162
163 for (conn_idx, conn) in members.into_iter().enumerate() {
165 let player_idx = player_indices[conn_idx];
166 let message = postcard::to_allocvec(&MatchmakerResponse::Success {
167 random_seed,
168 player_count: player_ids.len() as u32,
169 player_idx: player_idx as u32,
170 player_ids: player_ids.clone(),
171 })?;
172 let mut send = conn.open_uni().await?;
173 send.write_all(&message).await?;
174 send.finish()?;
175 send.stopped().await?;
176 conn.close(0u32.into(), b"done");
177 }
178
179 Ok(())
180}