#include "ZoneServer.hpp" #include "Cluster.pb.h" #include "monitoring/NullMetricsReporter.hpp" #include "monitoring/TimescaleDbMetricsReporter.hpp" #include "runtime/LockStep.hpp" #include #include #include #include namespace tw::net { static std::unique_ptr make_reporter(const ZoneServerConfiguration& config) { if (config.timescaledb) return std::make_unique(*config.timescaledb); return std::make_unique(); } ZoneServer::ZoneServer(ZoneServerConfiguration config) : m_metrics_reporter(make_reporter(config)), m_player_session_registry(std::make_unique()), m_network_receiver(std::make_unique( m_player_session_registry.get(), config.quicr_port, m_metrics_reporter.get() )), m_replicator(std::make_unique>( m_player_session_registry.get(), m_network_receiver.get() )), m_cluster_link(config.cluster_port), m_coordinator("127.0.0.1", config.cluster_port) { auto [assigned_id, area_bounds, peers] = m_coordinator.register_zone(); m_own_zone_id = assigned_id; m_zones.push_back(std::make_unique(area_bounds)); spdlog::info("Registered as zone {} ({},{}) ({},{})", assigned_id, area_bounds.min.x, area_bounds.min.y, area_bounds.max.x, area_bounds.max.y); mmo::cluster::ZoneHello hello; hello.mutable_zone()->set_id(assigned_id); hello.mutable_zone()->mutable_bounds()->set_min_x(area_bounds.min.x); hello.mutable_zone()->mutable_bounds()->set_min_z(area_bounds.min.y); hello.mutable_zone()->mutable_bounds()->set_max_x(area_bounds.max.x); hello.mutable_zone()->mutable_bounds()->set_max_z(area_bounds.max.y); for (const auto& peer : peers) { spdlog::info("Connecting to peer zone {} at {}:{}", peer.id, peer.host, peer.port); auto* conn = m_cluster_link.connect_to_peer(peer.host, peer.port); if (conn) m_cluster_link.send_mesg(conn, hello); } } void ZoneServer::player_update_handler(SessionId session_id, mmo::PlayerMoveMessage message) { auto it = m_session_zone.find(session_id); if (it == m_session_zone.end()) return; // Echoed back in the next snapshot, so the client can tell how long its // input took to come back. if (auto* session = m_player_session_registry->session(session_id)) { session->last_frame = message.frame_idx(); } it->second->on_player_move(session_id, std::move(message)); } static std::atomic quit(false); static void got_signal(int) { quit.store(true); } static void register_signal_handler() { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = got_signal; sigfillset(&sa.sa_mask); sigaction(SIGINT, &sa, NULL); } void ZoneServer::update_clients(uint32_t frame_idx) { m_network_receiver->update(); while (m_network_receiver->peek_new_session()) { auto session_id = m_network_receiver->pop_new_session(); glm::vec3 position = glm::vec3( (float)std::rand() / RAND_MAX * 20.0f, 10.0f, (float)std::rand() / RAND_MAX * 20.0f ); ZoneManager* zone = m_zones.front().get(); entt::entity entity = zone->spawn_entity({ .name = "Pepik", .position = position }); zone->add_client(session_id, entity); m_session_zone[session_id] = zone; mmo::SetControlledEntity mesg = {}; mesg.set_entity_id((uint32_t)entity); m_network_receiver->send_mesg(session_id, mesg); spdlog::info("Client {} connected", session_id); } } void ZoneServer::run() { LockStep lock_step(20); register_signal_handler(); uint32_t frame_idx = 1; m_network_receiver->set_handler( [this](SessionId session_id, const mmo::PlayerMoveMessage& mesg) { player_update_handler(session_id, mesg); }); m_network_receiver->set_handler( [this](SessionId session_id, const mmo::chat::SendChatMessageRequest& mesg) { }); while (!quit.load()) { if (lock_step.wait_for_next_step()) { continue; } m_cluster_link.update(); FrameMarkStart("Update clients"); update_clients(frame_idx); FrameMarkEnd("Update clients"); for (auto& zone : m_zones) { zone->tick(frame_idx, lock_step.delta_time()); // Copy acked frames from zone to sessions before replication. // This ensures snapshots carry the input frame actually consumed by this tick, // not frames that arrive in the trailing network update. for (const auto& [session_id, session_zone] : m_session_zone) { if (session_zone != zone.get()) continue; if (auto* session = m_player_session_registry->session(session_id)) { session->acked_frame = zone->acked_input_frame(session_id); } } m_replicator->replicate(zone->registry(), zone->interest()); } m_network_receiver->update(); m_metrics_reporter->set_player_count(m_session_zone.size()); m_metrics_reporter->tick(); frame_idx++; FrameMark; } // Notify all peers that this zone server is going away. for (auto& zone : m_zones) { mmo::cluster::ZoneBye bye; bye.mutable_zone()->set_id(m_own_zone_id); bye.mutable_zone()->mutable_bounds()->set_min_x(zone->area().min.x); bye.mutable_zone()->mutable_bounds()->set_min_z(zone->area().min.y); bye.mutable_zone()->mutable_bounds()->set_max_x(zone->area().max.x); bye.mutable_zone()->mutable_bounds()->set_max_z(zone->area().max.y); m_cluster_link.broadcast(bye); } } } // namespace tw::net