Files
netcode-demo/server/sv_main.cpp

480 lines
16 KiB
C++
Raw Permalink Normal View History

2026-01-11 01:37:39 +04:00
// server/sv_main.cpp
#include "common/types.hpp"
#include "common/enet_wrapper.hpp"
#include "common/serialization.hpp"
#include "common/network_messages.hpp"
#include "common/timestamp.hpp"
#include "common/simulation_params.hpp"
#include "sapp/server_world.hpp"
#include <iostream>
#include <unordered_map>
#include <string>
#include <format>
#include <thread>
#include <chrono>
#include <vector>
using namespace netcode;
struct ClientSession {
ENetPeer* peer = nullptr;
ClientId client_id = INVALID_CLIENT_ID;
EntityId entity_id = INVALID_ENTITY_ID;
std::string name;
bool authenticated = false;
uint64_t last_ping_time = 0;
uint64_t estimated_rtt_us = 0;
uint64_t last_activity_time = 0;
CompensationAlgorithm client_algorithm = CompensationAlgorithm::Hybrid;
};
class GameServer {
public:
explicit GameServer(uint16_t port = 7777, const SimulationParams& params = DEFAULT_SIMULATION_PARAMS)
: port_(port)
, params_(params)
, world_(params)
{}
bool start() {
if (!network_.create_server(port_, 16)) {
std::cerr << "Failed to create server on port " << port_ << "\n";
return false;
}
std::cout << "Server started on port " << port_ << "\n";
std::cout << "Tick rate: " << params_.tick_rate << " Hz\n";
std::cout << "Snapshot rate: " << params_.snapshot_rate << " Hz\n";
running_ = true;
return true;
}
void run() {
using namespace std::chrono;
auto last_tick_time = steady_clock::now();
auto last_timeout_check = steady_clock::now();
double tick_accumulator = 0.0;
double snapshot_accumulator = 0.0;
double log_timer = 0.0;
while (running_) {
auto now = steady_clock::now();
// Прошедшее время с последнего тика
double dt = duration<double>(now - last_tick_time).count();
// Обработка сети
process_network();
// тики
uint32_t ticks_performed = 0;
tick_accumulator += dt;
while (tick_accumulator >= params_.tick_interval() && ticks_performed < 8) {
world_.tick();
tick_accumulator -= params_.tick_interval();
tick_count_++;
ticks_performed++;
}
// снапшоты
snapshot_accumulator += dt;
if (snapshot_accumulator >= params_.snapshot_interval()) {
send_snapshots();
snapshot_count_++;
snapshot_accumulator -= params_.snapshot_interval();
}
// проверка таймаутов
if (duration<double>(now - last_timeout_check).count() >= 5.0) {
check_timeouts();
last_timeout_check = now;
}
// логирование
log_timer += dt;
if (log_timer >= 1.0) {
print_stats();
log_timer -= 1.0;
}
// вычисление следующего тика
double time_to_next_tick = params_.tick_interval() - tick_accumulator;
double time_to_next_snapshot = params_.snapshot_interval() - snapshot_accumulator;
double time_to_next_event = (std::min)(time_to_next_tick, time_to_next_snapshot);
time_to_next_event = (std::max)(time_to_next_event, 0.0);
// Обновляем время последнего тика
last_tick_time = now;
// Спим до следующего события
if (time_to_next_event > 0.0005) { // > 500 мкс
auto sleep_duration = duration_cast<microseconds>(duration<double>(time_to_next_event));
std::this_thread::sleep_for(sleep_duration);
} else {
std::this_thread::yield(); // если система решит забрать это время то ждем, если нет -- сразу начинаем выполнение
}
}
}
void stop() {
running_ = false;
}
private:
void process_network() {
ENetEvent event;
while (network_.service(event, 0) > 0) {
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT:
handle_connect(event.peer);
break;
case ENET_EVENT_TYPE_RECEIVE:
handle_packet(event.peer, event.packet);
enet_packet_destroy(event.packet);
break;
case ENET_EVENT_TYPE_DISCONNECT:
handle_disconnect(event.peer);
break;
default:
break;
}
}
}
void handle_connect(ENetPeer* peer) {
char hostStr[46];
if (enet_address_get_host_ip(&peer->address, hostStr, sizeof(hostStr)) == 0) {
std::cout << "New connection from "
<< hostStr << ":"
<< peer->address.port << "\n";
} else {
std::cout << "New connection from [unknown address]:"
<< peer->address.port << "\n";
}
}
void handle_disconnect(ENetPeer* peer) {
auto it = sessions_by_peer_.find(peer);
if (it != sessions_by_peer_.end()) {
ClientId client_id = it->second;
auto session_it = sessions_.find(client_id);
if (session_it != sessions_.end()) {
std::cout << "Player disconnected: " << session_it->second.name
<< " (ID: " << client_id << ")\n";
world_.remove_player(client_id);
sessions_.erase(session_it);
}
sessions_by_peer_.erase(it);
}
}
void handle_packet(ENetPeer* peer, ENetPacket* packet) {
if (packet->dataLength < 1) return;
ReadBuffer buf(packet->data, packet->dataLength);
uint8_t type_byte;
if (!buf.read_u8(type_byte)) return;
MessageType type = static_cast<MessageType>(type_byte);
switch (type) {
case MessageType::ClientConnect:
handle_client_connect(peer, buf);
break;
case MessageType::ClientInput:
handle_client_input(peer, buf);
break;
case MessageType::ClientSetAlgorithm:
handle_client_set_algorithm(peer, buf);
break;
case MessageType::PingRequest:
handle_ping_request(peer, buf);
break;
default:
break;
}
}
void handle_client_connect(ENetPeer* peer, ReadBuffer& buf) {
uint32_t protocol_version;
if (!buf.read_u32(protocol_version)) return;
uint8_t name_len;
if (!buf.read_u8(name_len)) return;
char name[33] = {};
if (name_len > 0 && name_len <= 32) {
buf.read_bytes(name, name_len);
}
uint8_t team_pref_byte;
TeamId team_preference = TeamId::None;
if (buf.read_u8(team_pref_byte)) {
team_preference = static_cast<TeamId>(team_pref_byte);
}
if (protocol_version != 1) {
send_reject(peer, ServerRejectMessage::Reason::VersionMismatch);
return;
}
if (sessions_.size() >= 16) {
send_reject(peer, ServerRejectMessage::Reason::ServerFull);
return;
}
ClientId client_id = next_client_id_++;
EntityId entity_id = world_.create_player(client_id, name, team_preference);
TeamId team = world_.get_player_team(client_id);
ClientSession session;
session.peer = peer;
session.client_id = client_id;
session.entity_id = entity_id;
session.name = name;
session.authenticated = true;
session.last_activity_time = Timestamp::now_us(); // НОВОЕ
sessions_[client_id] = session;
sessions_by_peer_[peer] = client_id;
send_accept(peer, client_id, entity_id, team);
send_config(peer);
std::cout << "Player connected: " << name
<< " (ID: " << client_id
<< ", Entity: " << entity_id
<< ", Team: " << (team == TeamId::Red ? "Red" : "Blue")
<< ", Preferred: " << (team_preference == TeamId::Red ? "Red" :
team_preference == TeamId::Blue ? "Blue" : "Auto") << ")\n";
}
void handle_client_input(ENetPeer* peer, ReadBuffer& buf) {
auto it = sessions_by_peer_.find(peer);
if (it == sessions_by_peer_.end()) return;
ClientId client_id = it->second;
auto session_it = sessions_.find(client_id);
if (session_it != sessions_.end()) {
session_it->second.last_activity_time = Timestamp::now_us();
}
uint8_t input_count;
if (!buf.read_u8(input_count)) return;
for (uint8_t i = 0; i < input_count && i < 16; ++i) {
InputCommand cmd;
if (!buf.read_u32(cmd.sequence)) break;
if (!buf.read_u32(cmd.client_tick)) break;
if (!buf.read_u64(cmd.timestamp_us)) break;
uint8_t input_byte;
if (!buf.read_u8(input_byte)) break;
cmd.input = InputState::from_byte(input_byte);
world_.add_player_input(client_id, cmd);
inputs_received_++;
}
}
void handle_client_set_algorithm(ENetPeer* peer, ReadBuffer& buf) {
auto it = sessions_by_peer_.find(peer);
if (it == sessions_by_peer_.end()) return;
uint8_t algo_byte;
if (!buf.read_u8(algo_byte)) return;
ClientId client_id = it->second;
auto session_it = sessions_.find(client_id);
if (session_it != sessions_.end()) {
session_it->second.client_algorithm = static_cast<CompensationAlgorithm>(algo_byte);
std::cout << "Client " << client_id << " switched to algorithm: "
<< algorithm_name(session_it->second.client_algorithm) << "\n";
}
}
void handle_ping_request(ENetPeer* peer, ReadBuffer& buf) {
uint32_t ping_id;
uint64_t client_time;
if (!buf.read_u32(ping_id)) return;
if (!buf.read_u64(client_time)) return;
WriteBuffer response;
response.write_u8(static_cast<uint8_t>(MessageType::PingResponse));
response.write_u32(ping_id);
response.write_u64(client_time);
response.write_u64(Timestamp::now_us());
response.write_u32(world_.current_tick());
network_.send(peer, NetworkChannel::Unreliable, response.data(), response.size(), false);
auto it = sessions_by_peer_.find(peer);
if (it != sessions_by_peer_.end()) {
auto session_it = sessions_.find(it->second);
if (session_it != sessions_.end()) {
uint64_t now = Timestamp::now_us();
if (session_it->second.last_ping_time > 0) {
session_it->second.estimated_rtt_us = now - session_it->second.last_ping_time;
world_.update_player_rtt(it->second, session_it->second.estimated_rtt_us);
}
session_it->second.last_ping_time = now;
}
}
}
void check_timeouts() {
uint64_t now = Timestamp::now_us();
const uint64_t timeout_us = 30'000'000; // 30 секунд
std::vector<ClientId> to_disconnect;
for (const auto& [client_id, session] : sessions_) {
if (session.authenticated &&
session.last_activity_time > 0 &&
now - session.last_activity_time > timeout_us) {
to_disconnect.push_back(client_id);
}
}
for (ClientId client_id : to_disconnect) {
auto it = sessions_.find(client_id);
if (it != sessions_.end()) {
std::cout << "Client timeout: " << it->second.name << "\n";
if (it->second.peer) {
enet_peer_disconnect(it->second.peer, 0);
}
world_.remove_player(client_id);
sessions_by_peer_.erase(it->second.peer);
sessions_.erase(it);
}
}
}
void send_reject(ENetPeer* peer, ServerRejectMessage::Reason reason) {
WriteBuffer buf;
buf.write_u8(static_cast<uint8_t>(MessageType::ServerReject));
buf.write_u8(static_cast<uint8_t>(reason));
network_.send(peer, NetworkChannel::Reliable, buf.data(), buf.size(), true);
}
void send_accept(ENetPeer* peer, ClientId client_id, EntityId entity_id, TeamId team) {
WriteBuffer buf;
buf.write_u8(static_cast<uint8_t>(MessageType::ServerAccept));
buf.write_u16(client_id);
buf.write_u32(entity_id);
buf.write_u8(static_cast<uint8_t>(team));
buf.write_u32(world_.current_tick());
buf.write_u64(Timestamp::now_us());
network_.send(peer, NetworkChannel::Reliable, buf.data(), buf.size(), true);
}
void send_config(ENetPeer* peer) {
WriteBuffer buf;
buf.write_u8(static_cast<uint8_t>(MessageType::ServerConfig));
buf.write_u32(params_.tick_rate);
buf.write_u32(params_.snapshot_rate);
buf.write_float(params_.arena_width);
buf.write_float(params_.arena_height);
buf.write_float(params_.player_radius);
buf.write_float(params_.player_max_speed);
buf.write_float(params_.ball_radius);
network_.send(peer, NetworkChannel::Reliable, buf.data(), buf.size(), true);
}
void send_snapshots() {
if (sessions_.empty()) return;
WorldSnapshot snapshot = world_.create_snapshot();
WriteBuffer buf;
serialize_snapshot(buf, snapshot);
network_.broadcast(NetworkChannel::Unreliable, buf.data(), buf.size(), false);
snapshots_sent_++;
}
void print_stats() {
std::cout << std::format("[Tick {}] Players: {} | Inputs/s: {} | Snapshots/s: {} | Red: {:.1f}s | Blue: {:.1f}s\n",
world_.current_tick(),
sessions_.size(),
inputs_received_,
snapshots_sent_,
world_.red_team_time(),
world_.blue_team_time());
inputs_received_ = 0;
snapshots_sent_ = 0;
}
private:
uint16_t port_;
SimulationParams params_;
ENetInitializer enet_init_;
NetworkHost network_;
ServerWorld world_;
std::unordered_map<ClientId, ClientSession> sessions_;
std::unordered_map<ENetPeer*, ClientId> sessions_by_peer_;
ClientId next_client_id_ = 1;
bool running_ = false;
uint32_t tick_count_ = 0;
uint32_t snapshot_count_ = 0;
uint32_t inputs_received_ = 0;
uint32_t snapshots_sent_ = 0;
double log_timer_ = 0.0;
};
int main(int argc, char* argv[]) {
uint16_t port = 7777;
SimulationParams params = DEFAULT_SIMULATION_PARAMS;
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-p" || arg == "--port") {
if (i + 1 < argc) port = static_cast<uint16_t>(std::stoi(argv[++i]));
} else if (arg == "--tick-rate") {
if (i + 1 < argc) {
params.tick_rate = static_cast<uint32_t>(std::stoi(argv[++i]));
params.fixed_delta_time = 1.0 / params.tick_rate;
}
} else if (arg == "--snapshot-rate") {
if (i + 1 < argc) {
params.snapshot_rate = static_cast<uint32_t>(std::stoi(argv[++i]));
}
} else if (arg == "--help") {
std::cout << "Usage: server [-p port] [--tick-rate N] [--snapshot-rate N]\n";
return 0;
}
}
try {
GameServer server(port, params);
if (server.start()) {
server.run();
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
return 1;
}
return 0;
}