Refactor async server joining with eJoinState enum and dedicated progress UI

Replace the boolean-flag-based async join system with a clean state machine
(eJoinState enum) and move connection progress handling from UIScene_JoinMenu
into UIScene_ConnectingProgress as a dedicated UI class.

Combines the best of two approaches: non-blocking sockets with select()
timeout and SO_RCVTIMEO clearing (prevents random disconnects) with the
upstream's state enum, FinalizeJoin separation, and ConnectingProgress UI.

JoinGame() now returns JOINGAME_PENDING on Win64, and
PlatformNetworkManagerStub::DoWork() polls the join state to finalize
the connection when the background thread succeeds.
This commit is contained in:
itsRevela
2026-03-26 11:51:17 -05:00
parent 39b0ad1cb4
commit f1310abe08
10 changed files with 414 additions and 204 deletions

View File

@@ -173,6 +173,9 @@ bool CPlatformNetworkManagerStub::Initialise(CGameNetworkManager *pGameNetworkMa
m_bSearchPending = false;
m_bIsOfflineGame = false;
#ifdef _WINDOWS64
m_bJoinPending = false;
#endif
m_pSearchParam = nullptr;
m_SessionsUpdatedCallback = nullptr;
@@ -282,6 +285,39 @@ void CPlatformNetworkManagerStub::DoWork()
m_bLeaveGameOnTick = false;
}
}
// Async join finalization: when the background thread reports success,
// register players and transition the session to starting state.
if (m_bJoinPending)
{
WinsockNetLayer::eJoinState state = WinsockNetLayer::GetJoinState();
if (state == WinsockNetLayer::eJoinState_Success)
{
WinsockNetLayer::FinalizeJoin();
BYTE localSmallId = WinsockNetLayer::GetLocalSmallId();
IQNet::m_player[localSmallId].m_smallId = localSmallId;
IQNet::m_player[localSmallId].m_isRemote = false;
IQNet::m_player[localSmallId].m_isHostPlayer = false;
IQNet::m_player[localSmallId].m_resolvedXuid = Win64Xuid::ResolvePersistentXuid();
Minecraft* pMinecraft = Minecraft::GetInstance();
wcscpy_s(IQNet::m_player[localSmallId].m_gamertag, 32, pMinecraft->user->name.c_str());
IQNet::s_playerCount = localSmallId + 1;
NotifyPlayerJoined(&IQNet::m_player[0]);
NotifyPlayerJoined(&IQNet::m_player[localSmallId]);
m_pGameNetworkManager->StateChange_AnyToStarting();
m_bJoinPending = false;
}
else if (state == WinsockNetLayer::eJoinState_Failed ||
state == WinsockNetLayer::eJoinState_Rejected ||
state == WinsockNetLayer::eJoinState_Cancelled)
{
m_bJoinPending = false;
}
}
#endif
}
@@ -517,90 +553,19 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l
WinsockNetLayer::StopDiscovery();
if (!WinsockNetLayer::JoinGame(hostIP, hostPort))
if (!WinsockNetLayer::BeginJoinGame(hostIP, hostPort))
{
app.DebugPrintf("Win64 LAN: Failed to connect to %s:%d\n", hostIP, hostPort);
app.DebugPrintf("Win64 LAN: Failed to start async join to %s:%d\n", hostIP, hostPort);
return CGameNetworkManager::JOINGAME_FAIL_GENERAL;
}
BYTE localSmallId = WinsockNetLayer::GetLocalSmallId();
IQNet::m_player[localSmallId].m_smallId = localSmallId;
IQNet::m_player[localSmallId].m_isRemote = false;
IQNet::m_player[localSmallId].m_isHostPlayer = false;
// Local non-host identity is the persistent uid.dat XUID.
IQNet::m_player[localSmallId].m_resolvedXuid = Win64Xuid::ResolvePersistentXuid();
Minecraft* pMinecraft = Minecraft::GetInstance();
wcscpy_s(IQNet::m_player[localSmallId].m_gamertag, 32, pMinecraft->user->name.c_str());
IQNet::s_playerCount = localSmallId + 1;
NotifyPlayerJoined(&IQNet::m_player[0]);
NotifyPlayerJoined(&IQNet::m_player[localSmallId]);
m_pGameNetworkManager->StateChange_AnyToStarting();
return CGameNetworkManager::JOINGAME_SUCCESS;
m_bJoinPending = true;
return CGameNetworkManager::JOINGAME_PENDING;
#else
return CGameNetworkManager::JOINGAME_SUCCESS;
#endif
}
#ifdef _WINDOWS64
bool CPlatformNetworkManagerStub::BeginJoinGameAsync(FriendSessionInfo* searchResult, int localUsersMask, int primaryUserIndex)
{
if (searchResult == nullptr)
return false;
const char* hostIP = searchResult->data.hostIP;
int hostPort = searchResult->data.hostPort;
if (hostPort <= 0 || hostIP[0] == 0)
return false;
m_bLeavingGame = false;
m_bLeaveGameOnTick = false;
IQNet::s_isHosting = false;
m_pIQNet->ClientJoinGame();
IQNet::m_player[0].m_smallId = 0;
IQNet::m_player[0].m_isRemote = true;
IQNet::m_player[0].m_isHostPlayer = true;
IQNet::m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, searchResult->data.hostName, _TRUNCATE);
WinsockNetLayer::StopDiscovery();
return WinsockNetLayer::StartJoinGameAsync(hostIP, hostPort);
}
int CPlatformNetworkManagerStub::FinishJoinGame(FriendSessionInfo* searchResult)
{
BYTE localSmallId = WinsockNetLayer::GetLocalSmallId();
IQNet::m_player[localSmallId].m_smallId = localSmallId;
IQNet::m_player[localSmallId].m_isRemote = false;
IQNet::m_player[localSmallId].m_isHostPlayer = false;
IQNet::m_player[localSmallId].m_resolvedXuid = Win64Xuid::ResolvePersistentXuid();
Minecraft* pMinecraft = Minecraft::GetInstance();
wcscpy_s(IQNet::m_player[localSmallId].m_gamertag, 32, pMinecraft->user->name.c_str());
IQNet::s_playerCount = localSmallId + 1;
NotifyPlayerJoined(&IQNet::m_player[0]);
NotifyPlayerJoined(&IQNet::m_player[localSmallId]);
m_pGameNetworkManager->StateChange_AnyToStarting();
return CGameNetworkManager::JOINGAME_SUCCESS;
}
void CPlatformNetworkManagerStub::CancelJoinGame()
{
WinsockNetLayer::CancelJoinGame();
}
#endif
bool CPlatformNetworkManagerStub::SetLocalGame(bool isLocal)
{
m_bIsOfflineGame = isLocal;