From 08721fbc7914441aba7c98a960d237ae66db5acc Mon Sep 17 00:00:00 2001 From: Pavel Solodovnikov Date: Sat, 16 Nov 2024 17:28:57 +0300 Subject: [PATCH] netplay: move code for async opening of client connections to the base `WzConnectionProvider` Provide the default implementation for `openClientConnectionAsync()` in `WzConnectionProvider`, which just spawns a new thread and piggibacks on the `resolveHost()` + `openClientConnectionAny()` combination. The `TCPConnectionProvider` is now simpler because it can use the default implementation from the base class. Signed-off-by: Pavel Solodovnikov --- lib/netplay/tcp/tcp_connection_provider.cpp | 74 ---------------- lib/netplay/tcp/tcp_connection_provider.h | 1 - lib/netplay/wz_connection_provider.cpp | 93 +++++++++++++++++++++ lib/netplay/wz_connection_provider.h | 7 +- src/screens/joiningscreen.cpp | 21 +++-- 5 files changed, 110 insertions(+), 86 deletions(-) create mode 100644 lib/netplay/wz_connection_provider.cpp diff --git a/lib/netplay/tcp/tcp_connection_provider.cpp b/lib/netplay/tcp/tcp_connection_provider.cpp index d801fc05a14..1bf1edddf4b 100644 --- a/lib/netplay/tcp/tcp_connection_provider.cpp +++ b/lib/netplay/tcp/tcp_connection_provider.cpp @@ -78,80 +78,6 @@ net::result TCPConnectionProvider::openClientConnectionAny(c return new TCPClientConnection(res.value()); } -namespace -{ - -OpenConnectionResult socketOpenTCPConnectionSync(const char* host, uint32_t port) -{ - const auto hostsResult = resolveHost(host, port); - SocketAddress* hosts = hostsResult.value_or(nullptr); - if (hosts == nullptr) - { - const auto hostsErr = hostsResult.error(); - const auto hostsErrMsg = hostsErr.message(); - return OpenConnectionResult(hostsErr, astringf("Cannot resolve host \"%s\": [%d]: %s", host, hostsErr.value(), hostsErrMsg.c_str())); - } - - auto sockResult = socketOpenAny(hosts, 15000); - Socket* client_transient_socket = sockResult.value_or(nullptr); - deleteSocketAddress(hosts); - hosts = nullptr; - - if (client_transient_socket == nullptr) - { - const auto errValue = sockResult.error(); - const auto errMsg = errValue.message(); - return OpenConnectionResult(errValue, astringf("Cannot connect to [%s]:%d, [%d]:%s", host, port, errValue.value(), errMsg.c_str())); - } - - return OpenConnectionResult(new TCPClientConnection(client_transient_socket)); -} - -struct OpenConnectionRequest -{ - std::string host; - uint32_t port = 0; - OpenConnectionToHostResultCallback callback; -}; - -static int openDirectTCPConnectionAsyncImpl(void* data) -{ - OpenConnectionRequest* pRequestInfo = (OpenConnectionRequest*)data; - if (!pRequestInfo) - { - return 1; - } - - pRequestInfo->callback(socketOpenTCPConnectionSync(pRequestInfo->host.c_str(), pRequestInfo->port)); - delete pRequestInfo; - return 0; -} - -} // anonymous namespace - -bool TCPConnectionProvider::openClientConnectionAsync(const std::string& host, uint32_t port, OpenConnectionToHostResultCallback callback) -{ - // spawn background thread to handle this - auto pRequest = new OpenConnectionRequest(); - pRequest->host = host; - pRequest->port = port; - pRequest->callback = callback; - - WZ_THREAD* pOpenConnectionThread = wzThreadCreate(openDirectTCPConnectionAsyncImpl, pRequest); - if (pOpenConnectionThread == nullptr) - { - debug(LOG_ERROR, "Failed to create thread for opening connection"); - delete pRequest; - return false; - } - - wzThreadDetach(pOpenConnectionThread); - // the thread handles deleting pRequest - pOpenConnectionThread = nullptr; - - return true; -} - IConnectionPollGroup* TCPConnectionProvider::newConnectionPollGroup() { auto* sset = allocSocketSet(); diff --git a/lib/netplay/tcp/tcp_connection_provider.h b/lib/netplay/tcp/tcp_connection_provider.h index c34add18b1e..891bf469f9c 100644 --- a/lib/netplay/tcp/tcp_connection_provider.h +++ b/lib/netplay/tcp/tcp_connection_provider.h @@ -40,7 +40,6 @@ class TCPConnectionProvider final : public WzConnectionProvider virtual net::result openListenSocket(uint16_t port) override; virtual net::result openClientConnectionAny(const IConnectionAddress& addr, unsigned timeout) override; - virtual bool openClientConnectionAsync(const std::string& host, uint32_t port, OpenConnectionToHostResultCallback callback) override; virtual IConnectionPollGroup* newConnectionPollGroup() override; }; diff --git a/lib/netplay/wz_connection_provider.cpp b/lib/netplay/wz_connection_provider.cpp new file mode 100644 index 00000000000..89f4b8556b2 --- /dev/null +++ b/lib/netplay/wz_connection_provider.cpp @@ -0,0 +1,93 @@ +/* + This file is part of Warzone 2100. + Copyright (C) 2024 Warzone 2100 Project + + Warzone 2100 is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + Warzone 2100 is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Warzone 2100; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "wz_connection_provider.h" + +#include "lib/framework/wzapp.h" + +namespace +{ + +OpenConnectionResult openClientConnectionSyncImpl(const char* host, uint32_t port, std::chrono::milliseconds timeout, WzConnectionProvider* connProvider) +{ + auto addrResult = connProvider->resolveHost(host, port); + if (!addrResult.has_value()) + { + const auto hostsErr = addrResult.error(); + const auto hostsErrMsg = hostsErr.message(); + return OpenConnectionResult(hostsErr, astringf("Cannot resolve host \"%s\": [%d]: %s", host, hostsErr.value(), hostsErrMsg.c_str())); + } + auto connRes = connProvider->openClientConnectionAny(*addrResult.value(), timeout.count()); + if (!connRes.has_value()) + { + const auto connErr = connRes.error(); + const auto connErrMsg = connErr.message(); + return OpenConnectionResult(connErr, astringf("Cannot resolve host \"%s\": [%d]: %s", host, connErr.value(), connErrMsg.c_str())); + } + return OpenConnectionResult(connRes.value()); +} + +struct OpenConnectionRequest +{ + std::string host; + uint32_t port = 0; + std::chrono::milliseconds timeout{ 15000 }; + OpenConnectionToHostResultCallback callback; + WzConnectionProvider* connProvider; +}; + +int openDirectConnectionAsyncImpl(void* data) +{ + OpenConnectionRequest* pRequestInfo = (OpenConnectionRequest*)data; + if (!pRequestInfo) + { + return 1; + } + pRequestInfo->callback(openClientConnectionSyncImpl( + pRequestInfo->host.c_str(), + pRequestInfo->port, + pRequestInfo->timeout, + pRequestInfo->connProvider)); + delete pRequestInfo; + return 0; +} + +} // anonymous namespace + +bool WzConnectionProvider::openClientConnectionAsync(const std::string& host, uint32_t port, std::chrono::milliseconds timeout, OpenConnectionToHostResultCallback callback) +{ + // spawn background thread to handle this + auto pRequest = new OpenConnectionRequest(); + pRequest->host = host; + pRequest->port = port; + pRequest->timeout = timeout; + pRequest->callback = callback; + pRequest->connProvider = this; + WZ_THREAD* pOpenConnectionThread = wzThreadCreate(openDirectConnectionAsyncImpl, pRequest); + if (pOpenConnectionThread == nullptr) + { + debug(LOG_ERROR, "Failed to create thread for opening connection"); + delete pRequest; + return false; + } + wzThreadDetach(pOpenConnectionThread); + // the thread handles deleting pRequest + pOpenConnectionThread = nullptr; + return true; +} diff --git a/lib/netplay/wz_connection_provider.h b/lib/netplay/wz_connection_provider.h index 9a9d879ff66..91683e3081b 100644 --- a/lib/netplay/wz_connection_provider.h +++ b/lib/netplay/wz_connection_provider.h @@ -20,7 +20,9 @@ #pragma once #include +#include #include +#include #include "lib/netplay/connection_address.h" #include "lib/netplay/net_result.h" @@ -72,9 +74,10 @@ class WzConnectionProvider /// Timeout in milliseconds. virtual net::result openClientConnectionAny(const IConnectionAddress& addr, unsigned timeout) = 0; /// - /// Async variant of `openClientConnectionAny()`. + /// Async variant of `openClientConnectionAny()` with the default implementation, which + /// spawns a new thread and piggybacks on the `resolveHost()` and `openClientConnectionAny()` combination. /// - virtual bool openClientConnectionAsync(const std::string& host, uint32_t port, OpenConnectionToHostResultCallback callback) = 0; + virtual bool openClientConnectionAsync(const std::string& host, uint32_t port, std::chrono::milliseconds timeout, OpenConnectionToHostResultCallback callback); /// /// Create a group for polling client connections. /// diff --git a/src/screens/joiningscreen.cpp b/src/screens/joiningscreen.cpp index 13f5ee5b7ce..bf77aafe2fb 100644 --- a/src/screens/joiningscreen.cpp +++ b/src/screens/joiningscreen.cpp @@ -1239,16 +1239,19 @@ void WzJoiningGameScreen_HandlerRoot::attemptToOpenConnection(size_t connectionI } auto weakSelf = std::weak_ptr(std::dynamic_pointer_cast(shared_from_this())); + constexpr std::chrono::milliseconds CLIENT_OPEN_ASYNC_TIMEOUT{ 15000 }; // Default timeout of 15s + auto& connProvider = ConnectionProviderRegistry::Instance().Get(ConnectionProviderType::TCP_DIRECT); - connProvider.openClientConnectionAsync(description.host, description.port, [weakSelf, connectionIdx](OpenConnectionResult&& result) { - auto strongSelf = weakSelf.lock(); - if (!strongSelf) - { - // background thread ultimately returned after the requester has gone away (join was cancelled?) - just return - return; - } - strongSelf->processOpenConnectionResultOnMainThread(connectionIdx, std::move(result)); - }); + connProvider.openClientConnectionAsync(description.host, description.port, CLIENT_OPEN_ASYNC_TIMEOUT, + [weakSelf, connectionIdx](OpenConnectionResult&& result) { + auto strongSelf = weakSelf.lock(); + if (!strongSelf) + { + // background thread ultimately returned after the requester has gone away (join was cancelled?) - just return + return; + } + strongSelf->processOpenConnectionResultOnMainThread(connectionIdx, std::move(result)); + }); break; } updateJoiningStatus(_("Establishing connection with host"));