~starkingdoms/starkingdoms

ref: c4082170d81b4295ec4b0973877468af52b5bc93 starkingdoms/server/src/main.rs -rw-r--r-- 7.4 KiB
c4082170 — ghostly_zsh update cargo.toml 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// 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,
}