// StarKingdoms.IO, an open source browser game // Copyright (C) 2023 ghostly_zsh (and contributors, depending on the license you choose) // // #![warn(clippy::pedantic)] #![warn(clippy::nursery)] #![deny(clippy::unwrap_used)] #![deny(clippy::expect_used)] #![allow(clippy::must_use_candidate)] #![allow(clippy::too_many_lines)] #![allow(clippy::module_name_repetitions)] #![allow(clippy::missing_errors_doc)] #![allow(clippy::missing_safety_doc)] #![allow(clippy::missing_panics_doc)] use crate::manager::{ClientManager}; use crate::timer::timer_main; use crate::entity::Entity; use async_std::net::{TcpListener}; use async_std::sync::Arc; use async_std::sync::RwLock; use entity::EntityHandler; use lazy_static::lazy_static; use log::{error, info, warn, Level}; use manager::PhysicsData; use nalgebra::vector; use parking_lot::deadlock::check_deadlock; use rapier2d_f64::prelude::{ BroadPhase, CCDSolver, ColliderSet, ImpulseJointSet, IntegrationParameters, IslandManager, MultibodyJointSet, NarrowPhase, RigidBodySet, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::SocketAddr; use std::thread; use std::time::Duration; use prometheus::{Gauge, register_gauge, opts, labels, TextEncoder, Counter, register_counter}; use crate::tcp_handler::handle_request; pub mod handler; pub mod manager; pub mod timer; #[macro_use] pub mod macros; pub mod api; pub mod entity; pub mod module; pub mod orbit; pub mod planet; pub mod tcp_handler; const SCALE: f64 = 10.0; lazy_static! { static ref CMGR: ClientManager = ClientManager { handlers: Arc::new(RwLock::new(HashMap::default())), usernames: Arc::new(RwLock::new(HashMap::default())), }; static ref DATA: Arc> = Arc::new(RwLock::new(PhysicsData { gravity: vector![0.0, 0.0], integration_parameters: IntegrationParameters { dt: 1.0 / 20.0, joint_erp: 0.2, erp: 0.5, max_stabilization_iterations: 16, ..Default::default() }, island_manager: IslandManager::new(), broad_phase: BroadPhase::new(), narrow_phase: NarrowPhase::new(), rigid_body_set: RigidBodySet::new(), collider_set: ColliderSet::new(), impulse_joint_set: ImpulseJointSet::new(), multibody_joint_set: MultibodyJointSet::new(), ccd_solver: CCDSolver::new(), })); static ref ENTITIES: Arc> = Arc::new(RwLock::new(EntityHandler::new())); // Prometheus stuff // static ref ENCODER: TextEncoder = TextEncoder::new(); static ref BUILD_INFO_GAGUE: Gauge = register_gauge!( opts!("starkingdoms_build_info", "A constant gague with value 1 containing build information in the labels.", labels! { "version_name" => env!("STK_VERSION_NAME"), "version" => env!("STK_VERSION"), "protocol" => stringify!(PROTOCOL_VERSION), "channel" => env!("STK_CHANNEL"), "build" => env!("STK_BUILD"), "server_description" => env!("STK_SLP_DESCRIPTION") } ) ).unwrap(); static ref ONLINE_PLAYERS_GAGUE: Gauge = register_gauge!( opts!( "starkingdoms_online_players", "Number of players currently online" ) ).unwrap(); static ref CMGR_HANDLERS_GAUGE: Gauge = register_gauge!( opts!( "starkingdoms_cmgr_handlers", "Number of active handlers in the client manager" ) ).unwrap(); static ref CMGR_USERNAMES_GAUGE: Gauge = register_gauge!( opts!( "starkingdoms_cmgr_usernames", "Number of active usernames in the client manager" ) ).unwrap(); static ref ENTITIES_GAUGE: Gauge = register_gauge!( opts!( "starkingdoms_entities", "Number of entities in the entity manager" ) ).unwrap(); static ref ENTITY_ID_COUNTER: Counter = register_counter!( opts!( "starkingdoms_entity_id", "Number of entities ever created in the entity manager" ) ).unwrap(); } pub const PANIC_ON_DEADLOCK: bool = true; //noinspection ALL #[async_std::main] async fn main() { #[allow(clippy::expect_used)] { simple_logger::init_with_level(Level::Debug).expect("Unable to start logging service"); } info!( "StarKingdoms server (v: {}, build {}) - initializing", env!("STK_VERSION"), env!("STK_BUILD") ); // Initialize metrics // BUILD_INFO_GAGUE.inc(); // Create the build info metric // All done with metrics // if std::env::var("STK_API_KEY").is_err() { error!( "Unable to read the API key from STK_API_KEY. Ensure it is set, and has a valid value." ); std::process::exit(1); } if std::env::var("STK_API_URL").is_err() { error!("Unable to read the API server URL from STK_API_URL. Ensure it is set, and has a valid value."); std::process::exit(1); } info!("Starting deadlock detector..."); thread::spawn(move || loop { thread::sleep(Duration::from_secs(10)); let deadlocks = check_deadlock(); if deadlocks.is_empty() { continue; } error!("---- DEADLOCK DETECTED ----"); error!("{} deadlocks were detected.", deadlocks.len()); for (i, threads) in deadlocks.iter().enumerate() { error!("-= Deadlock #{}", i); for t in threads { error!("-= Thread ID = {:#?}", t.thread_id()); error!("-= Backtrace:\n{:#?}", t.backtrace()); } } if PANIC_ON_DEADLOCK { error!("StarKingdoms is configured to panic when deadlocks are detected."); error!("Bye!"); panic!("Deadlock detected on one or more threads"); } else { error!("StarKingdoms is not configured to panic when deadlocks are detected."); } }); let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); info!("Listening on {} for HTTP/WebSocket connections", addr); let mgr_timer = CMGR.clone(); let physics_data = DATA.clone(); let entities_timer = ENTITIES.clone(); let _timer_thread = async_std::task::spawn(async move { match timer_main(mgr_timer, physics_data, entities_timer).await { Ok(_) => (), Err(e) => { error!("timer thread exited with error: {}", e); std::process::exit(1); } } }); let try_socket = TcpListener::bind(&addr).await; let listener = match try_socket { Ok(l) => l, Err(e) => { error!("error binding to socket: {}", e); std::process::exit(1); } }; while let Ok((stream, peer_addr)) = listener.accept().await { async_std::task::spawn(handle_request( stream, peer_addr, CMGR.clone(), ENTITIES.clone(), DATA.clone(), )); } } #[derive(Serialize, Deserialize)] pub struct ServerPingResponse { pub version: ServerPingResponseVersion, pub players: u32, pub description: String, } #[derive(Serialize, Deserialize)] pub struct ServerPingResponseVersion { pub name: String, pub number: String, pub protocol: u32, pub channel: String, pub build: String, }