mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/LCE-Revelations.git
synced 2026-07-09 10:38:13 +00:00
Fix server list refresh and add cancellable non-blocking connection
Server list: edits and deletions now update the UI immediately by calling SearchForGames() in ForceFriendsSessionRefresh() and UpdateGamesList() on nav-back to LoadOrJoinMenu. Connection: moved WinsockNetLayer::JoinGame() to a background thread with non-blocking sockets (5s timeout, 3 retries). Users can cancel with B/Escape during the attempt. Failed connections always show an error dialog.
This commit is contained in:
@@ -67,6 +67,13 @@ SOCKET WinsockNetLayer::s_splitScreenSocket[XUSER_MAX_COUNT] = { INVALID_SOCKET,
|
||||
BYTE WinsockNetLayer::s_splitScreenSmallId[XUSER_MAX_COUNT] = { 0xFF, 0xFF, 0xFF, 0xFF };
|
||||
HANDLE WinsockNetLayer::s_splitScreenRecvThread[XUSER_MAX_COUNT] = {nullptr, nullptr, nullptr, nullptr};
|
||||
|
||||
volatile bool WinsockNetLayer::s_joinCancelled = false;
|
||||
volatile bool WinsockNetLayer::s_joinComplete = false;
|
||||
bool WinsockNetLayer::s_joinResult = false;
|
||||
HANDLE WinsockNetLayer::s_joinGameThread = nullptr;
|
||||
char WinsockNetLayer::s_joinIP[256] = {};
|
||||
int WinsockNetLayer::s_joinPort = 0;
|
||||
|
||||
bool g_Win64MultiplayerHost = false;
|
||||
bool g_Win64MultiplayerJoin = false;
|
||||
int g_Win64MultiplayerPort = WIN64_NET_DEFAULT_PORT;
|
||||
@@ -337,10 +344,17 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
||||
|
||||
bool connected = false;
|
||||
BYTE assignedSmallId = 0;
|
||||
const int maxAttempts = 12;
|
||||
const int maxAttempts = 3;
|
||||
const int connectTimeoutSec = 5;
|
||||
|
||||
for (int attempt = 0; attempt < maxAttempts; ++attempt)
|
||||
{
|
||||
if (s_joinCancelled)
|
||||
{
|
||||
app.DebugPrintf("JoinGame cancelled by user\n");
|
||||
break;
|
||||
}
|
||||
|
||||
s_hostConnectionSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
|
||||
if (s_hostConnectionSocket == INVALID_SOCKET)
|
||||
{
|
||||
@@ -351,17 +365,55 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
||||
int noDelay = 1;
|
||||
setsockopt(s_hostConnectionSocket, IPPROTO_TCP, TCP_NODELAY, (const char*)&noDelay, sizeof(noDelay));
|
||||
|
||||
// Use non-blocking connect with select() timeout so we don't freeze
|
||||
// the game for the full OS TCP timeout when the server is unreachable.
|
||||
u_long nonBlocking = 1;
|
||||
ioctlsocket(s_hostConnectionSocket, FIONBIO, &nonBlocking);
|
||||
|
||||
iResult = connect(s_hostConnectionSocket, result->ai_addr, static_cast<int>(result->ai_addrlen));
|
||||
if (iResult == SOCKET_ERROR)
|
||||
{
|
||||
int err = WSAGetLastError();
|
||||
app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err);
|
||||
closesocket(s_hostConnectionSocket);
|
||||
s_hostConnectionSocket = INVALID_SOCKET;
|
||||
Sleep(200);
|
||||
continue;
|
||||
if (err == WSAEWOULDBLOCK)
|
||||
{
|
||||
fd_set writeSet, errorSet;
|
||||
FD_ZERO(&writeSet);
|
||||
FD_SET(s_hostConnectionSocket, &writeSet);
|
||||
FD_ZERO(&errorSet);
|
||||
FD_SET(s_hostConnectionSocket, &errorSet);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = connectTimeoutSec;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
int selectResult = select(0, nullptr, &writeSet, &errorSet, &tv);
|
||||
if (selectResult <= 0 || FD_ISSET(s_hostConnectionSocket, &errorSet))
|
||||
{
|
||||
app.DebugPrintf("connect() to %s:%d timed out or failed (attempt %d/%d)\n", ip, port, attempt + 1, maxAttempts);
|
||||
closesocket(s_hostConnectionSocket);
|
||||
s_hostConnectionSocket = INVALID_SOCKET;
|
||||
continue;
|
||||
}
|
||||
// Connection succeeded via non-blocking path
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("connect() to %s:%d failed (attempt %d/%d): %d\n", ip, port, attempt + 1, maxAttempts, err);
|
||||
closesocket(s_hostConnectionSocket);
|
||||
s_hostConnectionSocket = INVALID_SOCKET;
|
||||
Sleep(200);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore blocking mode for normal socket I/O
|
||||
u_long blocking = 0;
|
||||
ioctlsocket(s_hostConnectionSocket, FIONBIO, &blocking);
|
||||
|
||||
// Set a recv timeout so we don't block forever waiting for the small ID
|
||||
DWORD recvTimeout = connectTimeoutSec * 1000;
|
||||
setsockopt(s_hostConnectionSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&recvTimeout, sizeof(recvTimeout));
|
||||
|
||||
BYTE assignBuf[1];
|
||||
int bytesRecv = recv(s_hostConnectionSocket, (char*)assignBuf, 1, 0);
|
||||
if (bytesRecv != 1)
|
||||
@@ -369,7 +421,6 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
||||
app.DebugPrintf("Failed to receive small ID assignment from host (attempt %d/%d)\n", attempt + 1, maxAttempts);
|
||||
closesocket(s_hostConnectionSocket);
|
||||
s_hostConnectionSocket = INVALID_SOCKET;
|
||||
Sleep(200);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -421,6 +472,56 @@ bool WinsockNetLayer::JoinGame(const char* ip, int port)
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD WINAPI WinsockNetLayer::JoinGameThreadProc(LPVOID param)
|
||||
{
|
||||
s_joinResult = JoinGame(s_joinIP, s_joinPort);
|
||||
s_joinComplete = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool WinsockNetLayer::StartJoinGameAsync(const char* ip, int port)
|
||||
{
|
||||
// Wait for any previous join thread to finish
|
||||
if (s_joinGameThread != nullptr)
|
||||
{
|
||||
WaitForSingleObject(s_joinGameThread, 5000);
|
||||
CloseHandle(s_joinGameThread);
|
||||
s_joinGameThread = nullptr;
|
||||
}
|
||||
|
||||
strncpy_s(s_joinIP, sizeof(s_joinIP), ip, _TRUNCATE);
|
||||
s_joinPort = port;
|
||||
s_joinCancelled = false;
|
||||
s_joinComplete = false;
|
||||
s_joinResult = false;
|
||||
|
||||
s_joinGameThread = CreateThread(nullptr, 0, JoinGameThreadProc, nullptr, 0, nullptr);
|
||||
return s_joinGameThread != nullptr;
|
||||
}
|
||||
|
||||
bool WinsockNetLayer::IsJoinComplete()
|
||||
{
|
||||
return s_joinComplete;
|
||||
}
|
||||
|
||||
bool WinsockNetLayer::GetJoinResult()
|
||||
{
|
||||
return s_joinResult;
|
||||
}
|
||||
|
||||
void WinsockNetLayer::CancelJoinGame()
|
||||
{
|
||||
s_joinCancelled = true;
|
||||
|
||||
// Close the socket to immediately unblock any in-progress connect/select/recv
|
||||
SOCKET sock = s_hostConnectionSocket;
|
||||
if (sock != INVALID_SOCKET)
|
||||
{
|
||||
s_hostConnectionSocket = INVALID_SOCKET;
|
||||
closesocket(sock);
|
||||
}
|
||||
}
|
||||
|
||||
bool WinsockNetLayer::SendOnSocket(SOCKET sock, const void* data, int dataSize)
|
||||
{
|
||||
if (sock == INVALID_SOCKET || dataSize <= 0 || dataSize > WIN64_NET_MAX_PACKET_SIZE) return false;
|
||||
|
||||
Reference in New Issue
Block a user