feat: implement multiplayer networking improvements for reduced latency

Major networking enhancements to improve multiplayer performance and
reduce desync issues in games like Mario Kart 8 Deluxe:

Network Performance:
- Add socket connection pooling in BSD service to reduce overhead
- Implement unreliable packet delivery for latency-sensitive game data
- Add packet reliability control for both ProxyPacket and LDNPacket
- Use ENET_PACKET_FLAG_UNSEQUENCED for small UDP packets (<1200 bytes)

Monitoring & Debugging:
- Add PacketStatistics struct to track sent/received/dropped packets
- Enhanced logging for proxy packet handling and socket lifecycle
- Periodic stats logging every 100 packets for diagnostics

Configuration:
- Update lobby_api_url and web_api_url to https://api.ynet-fun.xyz
- Add lobby API URL configuration support

Socket Management:
- Implement SocketPoolKey for efficient socket reuse
- Store domain/type/protocol info in FileDescriptor
- Max pool size limit (8 sockets per type) to prevent memory bloat
- Return closed sockets to pool when room is still connected

Protocol Updates:
- Add 'reliable' field to ProxyPacket and LDNPacket structures
- Update room.cpp packet handlers to respect reliability flags
- Maintain backward compatibility with default reliable=true

These changes significantly reduce packet latency for real-time game
traffic while maintaining reliability for control packets.

Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
Zephyron
2025-11-05 18:52:06 +10:00
parent ee124b44e3
commit 5117ff3702
9 changed files with 154 additions and 21 deletions

View File

@@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <atomic>
@@ -44,8 +45,15 @@ public:
std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
/// Thread that receives and dispatches network packets
std::unique_ptr<std::thread> loop_thread;
/// Structure to hold a packet and its reliability flag
struct PacketWithReliability {
Packet packet;
bool reliable;
};
std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
std::list<Packet> send_list; ///< A list that stores all packets to send the async
std::list<PacketWithReliability> send_list; ///< A list that stores all packets to send the async
template <typename T>
using CallbackSet = std::set<CallbackHandle<T>>;
@@ -73,10 +81,11 @@ public:
void StartLoop();
/**
* Sends data to the room. It will be send on channel 0 with flag RELIABLE
* Sends data to the room. It will be send on channel 0 with specified reliability
* @param packet The data to send
* @param reliable Whether to use reliable delivery (true) or unreliable/unsequenced (false)
*/
void Send(Packet&& packet);
void Send(Packet&& packet, bool reliable = true);
/**
* Sends a request to the server, asking for permission to join a room with the specified
@@ -257,14 +266,17 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
break;
}
}
std::list<Packet> packets;
std::list<PacketWithReliability> packets;
{
std::lock_guard send_lock(send_list_mutex);
packets.swap(send_list);
}
for (const auto& packet : packets) {
ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
ENET_PACKET_FLAG_RELIABLE);
for (const auto& packet_data : packets) {
const u32 enet_flags = packet_data.reliable ? ENET_PACKET_FLAG_RELIABLE
: ENET_PACKET_FLAG_UNSEQUENCED;
ENetPacket* enetPacket = enet_packet_create(packet_data.packet.GetData(),
packet_data.packet.GetDataSize(),
enet_flags);
enet_peer_send(server, 0, enetPacket);
}
enet_host_flush(client);
@@ -276,9 +288,9 @@ void RoomMember::RoomMemberImpl::StartLoop() {
loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
}
void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
void RoomMember::RoomMemberImpl::Send(Packet&& packet, bool reliable) {
std::lock_guard lock(send_list_mutex);
send_list.push_back(std::move(packet));
send_list.push_back({std::move(packet), reliable});
}
void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname_,
@@ -377,6 +389,7 @@ void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
proxy_packet.protocol = static_cast<Protocol>(protocol_type);
packet.Read(proxy_packet.broadcast);
packet.Read(proxy_packet.reliable);
packet.Read(proxy_packet.data);
Invoke<ProxyPacket>(proxy_packet);
@@ -397,6 +410,7 @@ void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) {
packet.Read(ldn_packet.local_ip);
packet.Read(ldn_packet.remote_ip);
packet.Read(ldn_packet.broadcast);
packet.Read(ldn_packet.reliable);
packet.Read(ldn_packet.data);
@@ -638,9 +652,10 @@ void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
packet.Write(static_cast<u8>(proxy_packet.protocol));
packet.Write(proxy_packet.broadcast);
packet.Write(proxy_packet.reliable);
packet.Write(proxy_packet.data);
room_member_impl->Send(std::move(packet));
room_member_impl->Send(std::move(packet), proxy_packet.reliable);
}
void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) {
@@ -652,10 +667,11 @@ void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) {
packet.Write(ldn_packet.local_ip);
packet.Write(ldn_packet.remote_ip);
packet.Write(ldn_packet.broadcast);
packet.Write(ldn_packet.reliable);
packet.Write(ldn_packet.data);
room_member_impl->Send(std::move(packet));
room_member_impl->Send(std::move(packet), ldn_packet.reliable);
}
void RoomMember::SendChatMessage(const std::string& message) {