// 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 #include #include #include #include #include #include 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(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(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(duration(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(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(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(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(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 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(MessageType::ServerReject)); buf.write_u8(static_cast(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(MessageType::ServerAccept)); buf.write_u16(client_id); buf.write_u32(entity_id); buf.write_u8(static_cast(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(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 sessions_; std::unordered_map 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(std::stoi(argv[++i])); } else if (arg == "--tick-rate") { if (i + 1 < argc) { params.tick_rate = static_cast(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(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; }