From de30196320d7dfca4ec2e56d6082009b5497b23b Mon Sep 17 00:00:00 2001 From: lizzie Date: Wed, 13 Aug 2025 10:16:49 +0100 Subject: [PATCH] [network] use jthread and use std::vector for packet list instead of std::list Signed-off-by: lizzie --- src/network/announce_multiplayer_session.cpp | 103 ++++---- src/network/announce_multiplayer_session.h | 16 +- src/network/room.cpp | 98 ++++---- src/network/room_member.cpp | 251 +++++++++---------- 4 files changed, 221 insertions(+), 247 deletions(-) diff --git a/src/network/announce_multiplayer_session.cpp b/src/network/announce_multiplayer_session.cpp index d2a47de73d..5e4d59755f 100644 --- a/src/network/announce_multiplayer_session.cpp +++ b/src/network/announce_multiplayer_session.cpp @@ -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(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(&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 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 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 9d9673d97a..835423bfc1 100644 --- a/src/network/announce_multiplayer_session.h +++ b/src/network/announce_multiplayer_session.h @@ -1,8 +1,7 @@ -// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - // SPDX-FileCopyrightText: Copyright 2025 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 #pragma once @@ -73,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. @@ -83,16 +84,13 @@ public: private: void UpdateBackendData(std::shared_ptr room); - void AnnounceMultiplayerLoop(); Common::Event shutdown_event; - std::mutex callback_mutex; std::set error_callbacks; - std::unique_ptr announce_multiplayer_thread; - + std::optional announce_multiplayer_thread; /// Backend interface that logs fields std::unique_ptr 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 99dcf0c3b4..d160652cab 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -1,6 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2025 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 @@ -54,13 +53,11 @@ public: RoomImpl() : random_gen(std::random_device()()) {} /// Thread that receives and dispatches network packets - std::unique_ptr room_thread; + std::optional room_thread; /// Verification backend of the room std::unique_ptr verify_backend; - /// Thread function that will receive and dispatch messages until the room is destroyed. - void ServerLoop(); void StartLoop(); /** @@ -240,59 +237,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); +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 IdSetGameInfo: - HandleGameInfoPacket(&event); + case ENET_EVENT_TYPE_DISCONNECT: + HandleClientDisconnection(event.peer); 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); + 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(&Room::RoomImpl::ServerLoop, this); + // Close the connection to all members: + SendCloseMessage(); + }); } void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { @@ -1132,7 +1127,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..886850b5c4 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -1,3 +1,5 @@ +// SPDX-FileCopyrightText: Copyright 2025 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 @@ -18,6 +20,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); + } + } + + void SetError(const Error new_error) noexcept { + Invoke(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 +47,6 @@ public: GameInfo current_game_info; std::atomic 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 +57,9 @@ public: std::mutex network_mutex; ///< Mutex that controls access to the `client` variable. /// Thread that receives and dispatches network packets - std::unique_ptr loop_thread; + std::optional loop_thread; std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. - std::list send_list; ///< A list that stores all packets to send the async + std::vector send_list; ///< A list that stores all packets to send the async template using CallbackSet = std::set>; @@ -68,8 +82,6 @@ public: }; Callbacks callbacks; ///< All CallbackSets to all events - void MemberLoop(); - void StartLoop(); /** @@ -146,134 +158,117 @@ public: }; // RoomMemberImpl -void RoomMember::RoomMemberImpl::SetState(const State new_state) { - if (state != new_state) { - state = new_state; - Invoke(state); - } -} - -void RoomMember::RoomMemberImpl::SetError(const Error new_error) { - Invoke(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); +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 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); + case ENET_EVENT_TYPE_DISCONNECT: + if (state == State::Joined || state == State::Moderator) { + SetState(State::Idle); + SetError(Error::LostConnection); } break; - case IdModBanListResponse: - HandleModBanListResponsePacket(&event); + case ENET_EVENT_TYPE_NONE: 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); + 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 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 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(&RoomMember::RoomMemberImpl::MemberLoop, this); + Disconnect(); + }); } void RoomMember::RoomMemberImpl::Send(Packet&& packet) { @@ -747,9 +742,7 @@ void RoomMember::Unbind(CallbackHandle 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; }