// StarKingdoms.IO, an open source browser game
// Copyright (C) 2023 ghostly_zsh (and contributors, depending on the license you choose)
//
// <license disclaimer here>
#![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<RwLock<PhysicsData>> = 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<RwLock<EntityHandler>> = 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,
}