aboutsummaryrefslogtreecommitdiff
path: root/src/network
diff options
context:
space:
mode:
authorlizzie <lizzie@eden-emu.dev>2026-02-11 03:00:15 +0100
committercrueter <crueter@eden-emu.dev>2026-02-11 03:00:15 +0100
commit19494bc7ac943478e099f1b28877d33ece685d3f (patch)
tree2cf52c459067d05a7ccb2a11169d46e7a9e7e7b1 /src/network
parent28a78d76fed94255233284abb3df36b3fd41467d (diff)
[network, qt] use jthread and use std::vector for packet list instead of std::list (#254)
Signed-off-by: lizzie <lizzie@eden-emu.dev> Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/254 Reviewed-by: DraVee <dravee@eden-emu.dev> Reviewed-by: Maufeat <sahyno1996@gmail.com> Co-authored-by: lizzie <lizzie@eden-emu.dev> Co-committed-by: lizzie <lizzie@eden-emu.dev>
Diffstat (limited to 'src/network')
-rw-r--r--src/network/announce_multiplayer_session.cpp105
-rw-r--r--src/network/announce_multiplayer_session.h13
-rw-r--r--src/network/room.cpp101
-rw-r--r--src/network/room_member.cpp252
4 files changed, 224 insertions, 247 deletions
diff --git a/src/network/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp
index d2a47de73d..b01c2ba601 100644
--- a/src/network/announce_multiplayer_session.cpp
+++ b/src/network/announce_multiplayer_session.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
@@ -19,9 +19,6 @@
namespace Core {
-// Time between room is announced to web_service
-static constexpr std::chrono::seconds announce_time_interval(15);
-
AnnounceMultiplayerSession::AnnounceMultiplayerSession() {
#ifdef ENABLE_WEB_SERVICE
backend = std::make_unique<WebService::RoomJson>(Settings::values.web_api_url.GetValue(),
@@ -53,18 +50,58 @@ WebService::WebResult AnnounceMultiplayerSession::Register() {
}
void AnnounceMultiplayerSession::Start() {
- if (announce_multiplayer_thread) {
+ if (announce_multiplayer_thread.has_value()) {
Stop();
}
- shutdown_event.Reset();
- announce_multiplayer_thread =
- std::make_unique<std::thread>(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this);
+ announce_multiplayer_thread.emplace([&](std::stop_token stoken) {
+ // Invokes all current bound error callbacks.
+ const auto ErrorCallback = [this](WebService::WebResult result) {
+ std::lock_guard lock(callback_mutex);
+ for (auto callback : error_callbacks)
+ (*callback)(result);
+ };
+
+ if (!registered) {
+ WebService::WebResult result = Register();
+ if (result.result_code != WebService::WebResult::Code::Success) {
+ ErrorCallback(result);
+ return;
+ }
+ }
+
+ // Time between room is announced to web_service
+ std::chrono::seconds const announce_timeslice(15);
+ auto update_time = std::chrono::steady_clock::now();
+ std::future<WebService::WebResult> future;
+ while (!shutdown_event.WaitUntil(update_time)) {
+ update_time = std::chrono::steady_clock::now() + announce_timeslice;
+ auto room = Network::GetRoom().lock();
+ if (!room) {
+ break;
+ }
+ if (room->GetState() != Network::Room::State::Open) {
+ break;
+ }
+ UpdateBackendData(room);
+ WebService::WebResult result = backend->Update();
+ if (result.result_code != WebService::WebResult::Code::Success) {
+ ErrorCallback(result);
+ }
+ if (result.result_string == "404") {
+ registered = false;
+ // Needs to register the room again
+ WebService::WebResult register_result = Register();
+ if (register_result.result_code != WebService::WebResult::Code::Success) {
+ ErrorCallback(register_result);
+ }
+ }
+ }
+ });
}
void AnnounceMultiplayerSession::Stop() {
- if (announce_multiplayer_thread) {
+ if (announce_multiplayer_thread.has_value()) {
shutdown_event.Set();
- announce_multiplayer_thread->join();
announce_multiplayer_thread.reset();
backend->Delete();
registered = false;
@@ -101,58 +138,10 @@ void AnnounceMultiplayerSession::UpdateBackendData(std::shared_ptr<Network::Room
}
}
-void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() {
- // Invokes all current bound error callbacks.
- const auto ErrorCallback = [this](WebService::WebResult result) {
- std::lock_guard lock(callback_mutex);
- for (auto callback : error_callbacks) {
- (*callback)(result);
- }
- };
-
- if (!registered) {
- WebService::WebResult result = Register();
- if (result.result_code != WebService::WebResult::Code::Success) {
- ErrorCallback(result);
- return;
- }
- }
-
- auto update_time = std::chrono::steady_clock::now();
- std::future<WebService::WebResult> future;
- while (!shutdown_event.WaitUntil(update_time)) {
- update_time += announce_time_interval;
- auto room = Network::GetRoom().lock();
- if (!room) {
- break;
- }
- if (room->GetState() != Network::Room::State::Open) {
- break;
- }
- UpdateBackendData(room);
- WebService::WebResult result = backend->Update();
- if (result.result_code != WebService::WebResult::Code::Success) {
- ErrorCallback(result);
- }
- if (result.result_string == "404") {
- registered = false;
- // Needs to register the room again
- WebService::WebResult register_result = Register();
- if (register_result.result_code != WebService::WebResult::Code::Success) {
- ErrorCallback(register_result);
- }
- }
- }
-}
-
AnnounceMultiplayerRoom::RoomList AnnounceMultiplayerSession::GetRoomList() {
return backend->GetRoomList();
}
-bool AnnounceMultiplayerSession::IsRunning() const {
- return announce_multiplayer_thread != nullptr;
-}
-
void AnnounceMultiplayerSession::UpdateCredentials() {
ASSERT_MSG(!IsRunning(), "Credentials can only be updated when session is not running");
#ifdef ENABLE_WEB_SERVICE
diff --git a/src/network/announce_multiplayer_session.h b/src/network/announce_multiplayer_session.h
index 4e5cd33e1f..8fff450367 100644
--- a/src/network/announce_multiplayer_session.h
+++ b/src/network/announce_multiplayer_session.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -72,7 +72,9 @@ public:
/**
* Whether the announce session is still running
*/
- bool IsRunning() const;
+ [[nodiscard]] bool IsRunning() const {
+ return announce_multiplayer_thread.has_value();
+ }
/**
* Recreates the backend, updating the credentials.
@@ -82,16 +84,13 @@ public:
private:
void UpdateBackendData(std::shared_ptr<Network::Room> room);
- void AnnounceMultiplayerLoop();
Common::Event shutdown_event;
- std::mutex callback_mutex;
std::set<CallbackHandle> error_callbacks;
- std::unique_ptr<std::thread> announce_multiplayer_thread;
-
+ std::optional<std::jthread> announce_multiplayer_thread;
/// Backend interface that logs fields
std::unique_ptr<AnnounceMultiplayerRoom::Backend> backend;
-
+ std::mutex callback_mutex;
std::atomic_bool registered = false; ///< Whether the room has been registered
};
diff --git a/src/network/room.cpp b/src/network/room.cpp
index ca3946b521..cf33763da2 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -1,6 +1,5 @@
-// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
-
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -13,6 +12,7 @@
#include <shared_mutex>
#include <sstream>
#include <thread>
+#include "common/polyfill_thread.h"
#include "common/logging/log.h"
#include "enet/enet.h"
#include "network/packet.h"
@@ -54,13 +54,11 @@ public:
RoomImpl() : random_gen(std::random_device()()) {}
/// Thread that receives and dispatches network packets
- std::unique_ptr<std::thread> room_thread;
+ std::optional<std::jthread> room_thread;
/// Verification backend of the room
std::unique_ptr<VerifyUser::Backend> verify_backend;
- /// Thread function that will receive and dispatch messages until the room is destroyed.
- void ServerLoop();
void StartLoop();
/**
@@ -240,59 +238,57 @@ public:
};
// RoomImpl
-void Room::RoomImpl::ServerLoop() {
- while (state != State::Closed) {
- ENetEvent event;
- if (enet_host_service(server, &event, 5) > 0) {
- switch (event.type) {
- case ENET_EVENT_TYPE_RECEIVE:
- switch (event.packet->data[0]) {
- case IdJoinRequest:
- HandleJoinRequest(&event);
- break;
- case IdSetGameInfo:
- HandleGameInfoPacket(&event);
- break;
- case IdProxyPacket:
- HandleProxyPacket(&event);
- break;
- case IdLdnPacket:
- HandleLdnPacket(&event);
- break;
- case IdChatMessage:
- HandleChatPacket(&event);
- break;
- // Moderation
- case IdModKick:
- HandleModKickPacket(&event);
- break;
- case IdModBan:
- HandleModBanPacket(&event);
+void Room::RoomImpl::StartLoop() {
+ room_thread.emplace([&](std::stop_token stoken) {
+ while (state != State::Closed) {
+ ENetEvent event;
+ if (enet_host_service(server, &event, 5) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.packet->data[0]) {
+ case IdJoinRequest:
+ HandleJoinRequest(&event);
+ break;
+ case IdSetGameInfo:
+ HandleGameInfoPacket(&event);
+ break;
+ case IdProxyPacket:
+ HandleProxyPacket(&event);
+ break;
+ case IdLdnPacket:
+ HandleLdnPacket(&event);
+ break;
+ case IdChatMessage:
+ HandleChatPacket(&event);
+ break;
+ // Moderation
+ case IdModKick:
+ HandleModKickPacket(&event);
+ break;
+ case IdModBan:
+ HandleModBanPacket(&event);
+ break;
+ case IdModUnban:
+ HandleModUnbanPacket(&event);
+ break;
+ case IdModGetBanList:
+ HandleModGetBanListPacket(&event);
+ break;
+ }
+ enet_packet_destroy(event.packet);
break;
- case IdModUnban:
- HandleModUnbanPacket(&event);
+ case ENET_EVENT_TYPE_DISCONNECT:
+ HandleClientDisconnection(event.peer);
break;
- case IdModGetBanList:
- HandleModGetBanListPacket(&event);
+ case ENET_EVENT_TYPE_NONE:
+ case ENET_EVENT_TYPE_CONNECT:
break;
}
- enet_packet_destroy(event.packet);
- break;
- case ENET_EVENT_TYPE_DISCONNECT:
- HandleClientDisconnection(event.peer);
- break;
- case ENET_EVENT_TYPE_NONE:
- case ENET_EVENT_TYPE_CONNECT:
- break;
}
}
- }
- // Close the connection to all members:
- SendCloseMessage();
-}
-
-void Room::RoomImpl::StartLoop() {
- room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
+ // Close the connection to all members:
+ SendCloseMessage();
+ });
}
void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
@@ -1133,7 +1129,6 @@ void Room::SetVerifyUID(const std::string& uid) {
void Room::Destroy() {
room_impl->state = State::Closed;
- room_impl->room_thread->join();
room_impl->room_thread.reset();
if (room_impl->server) {
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index a6845273c5..029e103db4 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -1,3 +1,5 @@
+// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -7,6 +9,7 @@
#include <set>
#include <thread>
#include "common/assert.h"
+#include "common/polyfill_thread.h"
#include "common/socket_types.h"
#include "enet/enet.h"
#include "network/packet.h"
@@ -18,6 +21,21 @@ constexpr u32 ConnectionTimeoutMs = 5000;
class RoomMember::RoomMemberImpl {
public:
+ void SetState(const State new_state) noexcept {
+ if (state != new_state) {
+ state = new_state;
+ Invoke<State>(state);
+ }
+ }
+
+ void SetError(const Error new_error) noexcept {
+ Invoke<Error>(new_error);
+ }
+
+ [[nodiscard]] bool IsConnected() const noexcept {
+ return state == State::Joining || state == State::Joined || state == State::Moderator;
+ }
+
ENetHost* client = nullptr; ///< ENet network interface.
ENetPeer* server = nullptr; ///< The server peer the client is connected to
@@ -30,9 +48,6 @@ public:
GameInfo current_game_info;
std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
- void SetState(const State new_state);
- void SetError(const Error new_error);
- bool IsConnected() const;
std::string nickname; ///< The nickname of this member.
@@ -43,9 +58,9 @@ 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;
+ std::optional<std::jthread> loop_thread;
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::vector<Packet> send_list; ///< A list that stores all packets to send the async
template <typename T>
using CallbackSet = std::set<CallbackHandle<T>>;
@@ -68,8 +83,6 @@ public:
};
Callbacks callbacks; ///< All CallbackSets to all events
- void MemberLoop();
-
void StartLoop();
/**
@@ -146,134 +159,117 @@ public:
};
// RoomMemberImpl
-void RoomMember::RoomMemberImpl::SetState(const State new_state) {
- if (state != new_state) {
- state = new_state;
- Invoke<State>(state);
- }
-}
-
-void RoomMember::RoomMemberImpl::SetError(const Error new_error) {
- Invoke<Error>(new_error);
-}
-
-bool RoomMember::RoomMemberImpl::IsConnected() const {
- return state == State::Joining || state == State::Joined || state == State::Moderator;
-}
-
-void RoomMember::RoomMemberImpl::MemberLoop() {
- // Receive packets while the connection is open
- while (IsConnected()) {
- std::lock_guard lock(network_mutex);
- ENetEvent event;
- if (enet_host_service(client, &event, 5) > 0) {
- switch (event.type) {
- case ENET_EVENT_TYPE_RECEIVE:
- switch (event.packet->data[0]) {
- case IdProxyPacket:
- HandleProxyPackets(&event);
- break;
- case IdLdnPacket:
- HandleLdnPackets(&event);
- break;
- case IdChatMessage:
- HandleChatPacket(&event);
- break;
- case IdStatusMessage:
- HandleStatusMessagePacket(&event);
- break;
- case IdRoomInformation:
- HandleRoomInformationPacket(&event);
- break;
- case IdJoinSuccess:
- case IdJoinSuccessAsMod:
- // The join request was successful, we are now in the room.
- // If we joined successfully, there must be at least one client in the room: us.
- ASSERT_MSG(member_information.size() > 0,
- "We have not yet received member information.");
- HandleJoinPacket(&event); // Get the MAC Address for the client
- if (event.packet->data[0] == IdJoinSuccessAsMod) {
- SetState(State::Moderator);
- } else {
- SetState(State::Joined);
+void RoomMember::RoomMemberImpl::StartLoop() {
+ loop_thread.emplace([&](std::stop_token stoken) {
+ // Receive packets while the connection is open
+ while (IsConnected()) {
+ std::lock_guard lock(network_mutex);
+ ENetEvent event;
+ if (enet_host_service(client, &event, 5) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.packet->data[0]) {
+ case IdProxyPacket:
+ HandleProxyPackets(&event);
+ break;
+ case IdLdnPacket:
+ HandleLdnPackets(&event);
+ break;
+ case IdChatMessage:
+ HandleChatPacket(&event);
+ break;
+ case IdStatusMessage:
+ HandleStatusMessagePacket(&event);
+ break;
+ case IdRoomInformation:
+ HandleRoomInformationPacket(&event);
+ break;
+ case IdJoinSuccess:
+ case IdJoinSuccessAsMod:
+ // The join request was successful, we are now in the room.
+ // If we joined successfully, there must be at least one client in the room: us.
+ ASSERT_MSG(member_information.size() > 0,
+ "We have not yet received member information.");
+ HandleJoinPacket(&event); // Get the MAC Address for the client
+ if (event.packet->data[0] == IdJoinSuccessAsMod) {
+ SetState(State::Moderator);
+ } else {
+ SetState(State::Joined);
+ }
+ break;
+ case IdModBanListResponse:
+ HandleModBanListResponsePacket(&event);
+ break;
+ case IdRoomIsFull:
+ SetState(State::Idle);
+ SetError(Error::RoomIsFull);
+ break;
+ case IdNameCollision:
+ SetState(State::Idle);
+ SetError(Error::NameCollision);
+ break;
+ case IdIpCollision:
+ SetState(State::Idle);
+ SetError(Error::IpCollision);
+ break;
+ case IdVersionMismatch:
+ SetState(State::Idle);
+ SetError(Error::WrongVersion);
+ break;
+ case IdWrongPassword:
+ SetState(State::Idle);
+ SetError(Error::WrongPassword);
+ break;
+ case IdCloseRoom:
+ SetState(State::Idle);
+ SetError(Error::LostConnection);
+ break;
+ case IdHostKicked:
+ SetState(State::Idle);
+ SetError(Error::HostKicked);
+ break;
+ case IdHostBanned:
+ SetState(State::Idle);
+ SetError(Error::HostBanned);
+ break;
+ case IdModPermissionDenied:
+ SetError(Error::PermissionDenied);
+ break;
+ case IdModNoSuchUser:
+ SetError(Error::NoSuchUser);
+ break;
}
+ enet_packet_destroy(event.packet);
break;
- case IdModBanListResponse:
- HandleModBanListResponsePacket(&event);
- break;
- case IdRoomIsFull:
- SetState(State::Idle);
- SetError(Error::RoomIsFull);
- break;
- case IdNameCollision:
- SetState(State::Idle);
- SetError(Error::NameCollision);
- break;
- case IdIpCollision:
- SetState(State::Idle);
- SetError(Error::IpCollision);
- break;
- case IdVersionMismatch:
- SetState(State::Idle);
- SetError(Error::WrongVersion);
- break;
- case IdWrongPassword:
- SetState(State::Idle);
- SetError(Error::WrongPassword);
- break;
- case IdCloseRoom:
- SetState(State::Idle);
- SetError(Error::LostConnection);
- break;
- case IdHostKicked:
- SetState(State::Idle);
- SetError(Error::HostKicked);
- break;
- case IdHostBanned:
- SetState(State::Idle);
- SetError(Error::HostBanned);
+ case ENET_EVENT_TYPE_DISCONNECT:
+ if (state == State::Joined || state == State::Moderator) {
+ SetState(State::Idle);
+ SetError(Error::LostConnection);
+ }
break;
- case IdModPermissionDenied:
- SetError(Error::PermissionDenied);
+ case ENET_EVENT_TYPE_NONE:
break;
- case IdModNoSuchUser:
- SetError(Error::NoSuchUser);
+ case ENET_EVENT_TYPE_CONNECT:
+ // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
+ // already connected
+ ASSERT_MSG(false, "Received unexpected connect event while already connected");
break;
}
- enet_packet_destroy(event.packet);
- break;
- case ENET_EVENT_TYPE_DISCONNECT:
- if (state == State::Joined || state == State::Moderator) {
- SetState(State::Idle);
- SetError(Error::LostConnection);
- }
- break;
- case ENET_EVENT_TYPE_NONE:
- break;
- case ENET_EVENT_TYPE_CONNECT:
- // The ENET_EVENT_TYPE_CONNECT event can not possibly happen here because we're
- // already connected
- ASSERT_MSG(false, "Received unexpected connect event while already connected");
- break;
}
+ std::vector<Packet> packets;
+ {
+ std::lock_guard send_lock(send_list_mutex);
+ packets.swap(send_list);
+ }
+ for (auto const& packet : packets) {
+ ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(server, 0, enetPacket);
+ }
+ enet_host_flush(client);
}
- std::list<Packet> 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);
- enet_peer_send(server, 0, enetPacket);
- }
- enet_host_flush(client);
- }
- Disconnect();
-};
-
-void RoomMember::RoomMemberImpl::StartLoop() {
- loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
+ Disconnect();
+ });
}
void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
@@ -747,9 +743,7 @@ void RoomMember::Unbind(CallbackHandle<T> handle) {
void RoomMember::Leave() {
room_member_impl->SetState(State::Idle);
- room_member_impl->loop_thread->join();
room_member_impl->loop_thread.reset();
-
enet_host_destroy(room_member_impl->client);
room_member_impl->client = nullptr;
}