Files
netcode-demo/server/sv_main.cpp
2026-01-11 01:37:39 +04:00

480 lines
16 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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;
}