#![doc = include_str!("./online.md")]
#![allow(missing_docs)]
use crate::{
networking::{get_network_endpoint, NetworkMatchSocket},
prelude::*,
};
pub use bones_matchmaker_proto::{
GameID, LobbyId, LobbyInfo, LobbyListItem, MatchInfo, PlayerIdxAssignment, MATCH_ALPN,
};
use iroh::{endpoint::Connection, Endpoint, NodeId};
use once_cell::sync::Lazy;
use tracing::{info, warn};
pub const READ_TO_END_BYTE_COUNT: usize = 256;
#[derive(DerefMut, Deref)]
pub struct OnlineMatchmaker(BiChannelClient<OnlineMatchmakerRequest, OnlineMatchmakerResponse>);
#[derive(Debug)]
pub enum OnlineMatchmakerRequest {
SearchForGame {
id: NodeId,
player_count: u32,
game_id: GameID,
match_data: Vec<u8>,
player_idx_assignment: PlayerIdxAssignment,
},
StopSearch {
id: NodeId,
},
ListLobbies {
id: NodeId,
game_id: GameID,
},
CreateLobby {
id: NodeId,
lobby_info: LobbyInfo,
},
JoinLobby {
id: NodeId,
game_id: GameID,
lobby_id: LobbyId,
password: Option<String>,
},
}
impl OnlineMatchmakerRequest {
pub fn node_id(&self) -> NodeId {
match self {
OnlineMatchmakerRequest::SearchForGame { id, .. } => *id,
OnlineMatchmakerRequest::StopSearch { id } => *id,
OnlineMatchmakerRequest::ListLobbies { id, .. } => *id,
OnlineMatchmakerRequest::CreateLobby { id, .. } => *id,
OnlineMatchmakerRequest::JoinLobby { id, .. } => *id,
}
}
}
#[derive(Serialize, Clone)]
pub enum OnlineMatchmakerResponse {
Searching,
MatchmakingUpdate { player_count: u32 },
GameStarting {
#[serde(skip_serializing, skip_deserializing)]
socket: NetworkMatchSocket,
player_idx: usize,
player_count: usize,
random_seed: u64,
},
LobbyUpdate { player_count: u32 },
LobbiesList(Vec<LobbyListItem>),
LobbyCreated(LobbyId),
LobbyJoined {
lobby_id: LobbyId,
player_count: usize,
},
Error(String),
}
impl std::fmt::Debug for OnlineMatchmakerResponse {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let serialized =
serde_yaml::to_string(self).expect("Failed to serialize OnlineMatchmakerResponse");
write!(f, "{:?}", serialized)
}
}
pub static ONLINE_MATCHMAKER: Lazy<OnlineMatchmaker> = Lazy::new(|| {
let (client, server) = bi_channel();
RUNTIME.spawn(async move {
if let Err(err) = process_matchmaker_requests(server).await {
warn!("online matchmaker failed: {err:?}");
}
});
OnlineMatchmaker(client)
});
pub struct MatchmakerConnectionState {
ep: Option<Endpoint>,
conn: Option<Connection>,
node_id: Option<NodeId>,
}
impl Default for MatchmakerConnectionState {
fn default() -> Self {
Self::new()
}
}
impl MatchmakerConnectionState {
pub fn new() -> Self {
Self {
ep: None,
conn: None,
node_id: None,
}
}
pub async fn acquire_connection(&mut self) -> anyhow::Result<&Connection> {
if let Some(id) = self.node_id {
if self.conn.is_none() {
info!("Connecting to online matchmaker");
let ep = get_network_endpoint().await;
let conn = ep.connect(id, MATCH_ALPN).await?;
self.ep = Some(ep.clone());
self.conn = Some(conn);
info!("Connected to online matchmaker");
}
self.conn
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Failed to establish connection"))
} else {
Err(anyhow::anyhow!("NodeId not set"))
}
}
pub fn close_connection(&mut self) {
if let Some(conn) = self.conn.take() {
conn.close(0u32.into(), b"Closing matchmaker connection");
}
self.ep = None;
}
pub fn is_connected(&self) -> bool {
self.conn.is_some()
}
pub fn set_node_id(&mut self, id: NodeId) {
self.node_id = Some(id);
}
}
async fn process_matchmaker_requests(
user_channel: BiChannelServer<OnlineMatchmakerRequest, OnlineMatchmakerResponse>,
) -> anyhow::Result<()> {
let mut matchmaker_connection_state = MatchmakerConnectionState::new();
while let Ok(message) = user_channel.recv().await {
match message {
OnlineMatchmakerRequest::SearchForGame {
id,
player_count,
game_id,
match_data,
player_idx_assignment,
} => {
matchmaker_connection_state.set_node_id(id);
let match_info = MatchInfo {
max_players: player_count,
match_data,
game_id,
player_idx_assignment,
};
if let Err(err) = crate::networking::online_matchmaking::resolve_search_for_match(
&user_channel,
&mut matchmaker_connection_state,
match_info.clone(),
)
.await
{
warn!("Start Matchmaking Search failed: {err:?}");
}
}
OnlineMatchmakerRequest::ListLobbies { id, game_id } => {
matchmaker_connection_state.set_node_id(id);
if let Err(err) = crate::networking::online_lobby::resolve_list_lobbies(
&user_channel,
&mut matchmaker_connection_state,
game_id,
)
.await
{
warn!("Listing lobbies failed: {err:?}");
}
}
OnlineMatchmakerRequest::CreateLobby { id, lobby_info } => {
matchmaker_connection_state.set_node_id(id);
if let Err(err) = crate::networking::online_lobby::resolve_create_lobby(
&user_channel,
&mut matchmaker_connection_state,
lobby_info,
)
.await
{
warn!("Creating lobby failed: {err:?}");
}
}
OnlineMatchmakerRequest::JoinLobby {
id,
game_id,
lobby_id,
password,
} => {
matchmaker_connection_state.set_node_id(id);
if let Err(err) = crate::networking::online_lobby::resolve_join_lobby(
&user_channel,
&mut matchmaker_connection_state,
game_id,
lobby_id,
password,
)
.await
{
warn!("Joining lobby failed: {err:?}");
}
}
_ => {}
}
}
Ok(())
}
impl OnlineMatchmaker {
pub fn read_matchmaker_response() -> Option<OnlineMatchmakerResponse> {
ONLINE_MATCHMAKER.try_recv().ok()
}
pub fn start_search_for_match(
matchmaking_server: NodeId,
game_id: GameID,
player_count: u32,
match_data: Vec<u8>,
player_idx_assignment: PlayerIdxAssignment,
) -> anyhow::Result<()> {
ONLINE_MATCHMAKER
.try_send(OnlineMatchmakerRequest::SearchForGame {
id: matchmaking_server,
player_count,
game_id,
match_data,
player_idx_assignment,
})
.map_err(|e| anyhow::anyhow!("Failed to send matchmaker request: {}", e))?;
Ok(())
}
pub fn stop_search_for_match(matchmaking_server: NodeId) -> anyhow::Result<()> {
ONLINE_MATCHMAKER
.try_send(OnlineMatchmakerRequest::StopSearch {
id: matchmaking_server,
})
.map_err(|e| anyhow::anyhow!("Failed to send matchmaker request: {}", e))?;
Ok(())
}
pub fn list_lobbies(matchmaking_server: NodeId, game_id: GameID) -> anyhow::Result<()> {
ONLINE_MATCHMAKER
.try_send(OnlineMatchmakerRequest::ListLobbies {
id: matchmaking_server,
game_id,
})
.map_err(|e| anyhow::anyhow!("Failed to send list lobbies request: {}", e))?;
Ok(())
}
pub fn create_lobby(matchmaking_server: NodeId, lobby_info: LobbyInfo) -> anyhow::Result<()> {
ONLINE_MATCHMAKER
.try_send(OnlineMatchmakerRequest::CreateLobby {
id: matchmaking_server,
lobby_info,
})
.map_err(|e| anyhow::anyhow!("Failed to send create lobby request: {}", e))?;
Ok(())
}
pub fn join_lobby(
matchmaking_server: NodeId,
game_id: GameID,
lobby_id: LobbyId,
password: Option<String>,
) -> anyhow::Result<()> {
ONLINE_MATCHMAKER
.try_send(OnlineMatchmakerRequest::JoinLobby {
id: matchmaking_server,
game_id,
lobby_id,
password,
})
.map_err(|e| anyhow::anyhow!("Failed to send join lobby request: {}", e))?;
Ok(())
}
}