/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #![feature(never_type)] pub mod conn_test; pub mod list; pub mod lobby; pub mod register; use hurrycurry_protocol::registry::Entry; use list::{generate_html_list, generate_json_list, r_list}; use lobby::lobby_wrapper; use log::{error, info}; use register::r_register; use rocket::{get, routes, shield::Shield, Config}; use std::{ cmp::Reverse, collections::HashMap, env::var, net::{IpAddr, Ipv4Addr, SocketAddr}, str::FromStr, sync::Arc, time::{Duration, Instant}, }; use tokio::{sync::RwLock, time::interval}; const MAX_ADDR_CACHE: usize = 4096; const MAX_SERVERS: usize = 128; fn main() { env_logger::init_from_env("LOG"); rustls::crypto::ring::default_provider() .install_default() .unwrap(); let address = var("BIND_ADDR") .map(|a| IpAddr::from_str(&a).unwrap()) .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); let http_port = var("PORT").map(|p| p.parse().unwrap()).unwrap_or(27033); let lobby_port = var("LOBBY_PORT") .map(|p| p.parse().unwrap()) .unwrap_or(27034); let lobby_addr = SocketAddr::new(address, lobby_port); let registry = Arc::new(RwLock::new(Registry::default())); tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap() .block_on(async move { tokio::task::spawn(Registry::update_loop(registry.clone())); tokio::task::spawn(lobby_wrapper(registry.clone(), lobby_addr)); rocket::build() .configure(Config { address, port: http_port, ..Default::default() }) .attach(Shield::new()) .manage(registry) .mount("/", routes![r_index, r_list, r_register]) .ignite() .await .unwrap() .launch() .await .unwrap() }); } #[derive(Default)] struct Registry { json_response: Arc, html_response: Arc, entries: Vec, servers: HashMap, } impl Registry { pub async fn update_loop(r: Arc>) { let mut interval = interval(Duration::from_secs( var("UPDATE_INTERVAL") .map(|e| e.parse::().unwrap()) .unwrap_or(60), )); loop { interval.tick().await; if let Err(e) = r.write().await.update() { error!("update failed: {e}") } } } pub fn update(&mut self) -> anyhow::Result<()> { info!("updating list"); self.remove_dead(); let mut list = self .servers .values() .map(|e| Entry { name: e.name.clone(), address: e.address.keys().cloned().collect(), last_game: e.last_game, players_online: e.players_online, version: e.version, }) .collect::>(); list.sort_by_key(|e| Reverse(e.players_online)); self.json_response = generate_json_list(&list)?; self.html_response = generate_html_list(&list)?; self.entries = list; info!("done. {} servers registered", self.servers.len()); Ok(()) } pub fn remove_dead(&mut self) { self.servers.retain(|_, e| { e.address .retain(|_, updated| updated.elapsed() < Duration::from_secs(120)); e.address.len() > 0 }); } } #[derive(Debug)] struct InternalEntry { name: String, address: HashMap, players_online: usize, last_game: i64, version: (u32, u32), } impl Default for InternalEntry { fn default() -> Self { Self { address: HashMap::new(), last_game: 0, name: String::new(), players_online: 0, version: (0, 0), } } } #[get("/")] fn r_index() -> &'static str { "Hurry Curry! Server Registry Service" }