#pragma once // Ghost multiplayer relay core: a small UDP forwarder shared by the // standalone ghost_relay binary (run on a public VPS) and the in-game host // mode (a player's game relays its LAN room). Clients connect OUTBOUND only // and are matched into rooms by a shareable code. The relay forwards each // client's PosePayload to every OTHER member of the same room. // // Clients are keyed by source address; every packet refreshes last_seen and // silent clients are dropped (their ghosts vanish on the other side). Room // codes are uppercase alphanumeric; a JOIN with an empty room gets one // assigned by the server and returned in the HELLO. #include #include #include #include #include #include #include "skate3_native_ghost_net.h" #include "skate3_native_ghost_proto.h" namespace skate3::native_ghost { class GhostRelay { public: explicit GhostRelay(uint16_t port = kGhostDefaultPort) : port_(port) {} ~GhostRelay(); GhostRelay(const GhostRelay&) = delete; GhostRelay& operator=(const GhostRelay&) = delete; // Bind the socket and start the receive thread. Returns false when the // port cannot be bound (already in use / no permission). bool Start(); void Stop(); bool running() const { return running_.load(std::memory_order_relaxed); } uint16_t port() const { return port_; } size_t ClientCount() const; // When non-empty, every client is forced into this room (the standalone // binary's --room lock); client JOINs and PINGs cannot override it. void SetLockedRoom(const char* room); // Hard cap on simultaneously tracked clients (default 64). New JOIN/PING // sources past the cap are dropped (logged). void SetMaxClients(uint32_t max_clients) { max_clients_ = max_clients; } // Verbose per-event logging (join/leave/refusals); the standalone server's // --verbose flag. Off by default to keep a busy relay quiet. void SetVerbose(bool verbose) { verbose_ = verbose; } private: void Run(); void HandlePacket(const GhostAddr& from, const PacketBuf& p, double now); void Sweep(double now); GhostSock sock_ = GHOST_INVALID_SOCKET; uint16_t port_; std::thread thread_; std::atomic running_{false}; std::atomic stop_{false}; uint32_t max_clients_ = 64; std::atomic verbose_{false}; struct Client { GhostAddr addr; uint32_t session_id = 0; char room[kGhostMaxRoomLen] = {}; uint8_t room_len = 0; char name[kGhostMaxNameLen] = {}; uint8_t name_len = 0; bool refused = false; // version mismatch: poses from this client dropped double last_seen = 0.0; }; mutable std::mutex mu_; std::vector clients_; uint32_t next_session_ = 1; bool locked_ = false; char locked_room_[kGhostMaxRoomLen] = {}; uint8_t locked_room_len_ = 0; }; } // namespace skate3::native_ghost