From b7e97d742c71539e8994266b83e0fcb1b849fe98 Mon Sep 17 00:00:00 2001 From: tintinhamans <5984296+tintinhamans@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:10:57 +0200 Subject: [PATCH] feat(matchmaking): support mesh retries and requeue --- .../GeneralsOnline/OnlineServices_Init.h | 10 ++-- .../OnlineServices_LobbyInterface.h | 42 ++++++++++++++ .../GUICallbacks/Menus/WOLQuickMatchMenu.cpp | 47 ++++++++++----- .../OnlineServices_LobbyInterface.cpp | 57 +++++++++++++++++-- .../OnlineServices_RoomsInterface.cpp | 40 +++++++++++++ 5 files changed, 173 insertions(+), 23 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h index 7b2e31001f8..18cb27fb57c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_Init.h @@ -91,10 +91,12 @@ enum EWebSocketMessageID SOCIAL_FRIENDS_LIST_DIRTY = 37, SOCIAL_CANT_ADD_FRIEND_LIST_FULL = 38, PROBE_RESP = 39, - AC_REGISTER_PLAYER = 40, - AC_DEREGISTER_PLAYER = 41, - WS_KEEPALIVE = 42, - WS_KEEPALIVE_CLIENT = 43 + AC_REGISTER_PLAYER = 40, + AC_DEREGISTER_PLAYER = 41, + WS_KEEPALIVE = 42, + WS_KEEPALIVE_CLIENT = 43, + MATCHMAKING_ACTION_REQUEUE = 44, + MATCHMAKING_ACTION_SETUP_PROGRESS = 45 }; enum class EQoSRegions diff --git a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h index 91304e02be5..7071aa8814e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h +++ b/GeneralsMD/Code/GameEngine/Include/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.h @@ -8,6 +8,7 @@ #include "Common/Player.h" #include "GameClient/InGameUI.h" #include "GameLogic/VictoryConditions.h" +#include extern NGMPGame* TheNGMPGame; @@ -165,6 +166,44 @@ class NGMP_OnlineServices_LobbyInterface m_fnCallbackMatchmakingMatchFound(); } } + + std::function m_fnCallbackMatchmakingRequeue = nullptr; + void RegisterForMatchmakingRequeueCallback(std::function cb) + { + m_fnCallbackMatchmakingRequeue = cb; + } + + void DeregisterForMatchmakingRequeueCallback() + { + m_fnCallbackMatchmakingRequeue = nullptr; + } + + void InvokeMatchmakingRequeueCallback() + { + if (m_fnCallbackMatchmakingRequeue != nullptr) + { + m_fnCallbackMatchmakingRequeue(); + } + } + + std::function m_fnCallbackMatchmakingSetupProgress = nullptr; + void RegisterForMatchmakingSetupProgressCallback(std::function cb) + { + m_fnCallbackMatchmakingSetupProgress = cb; + } + + void DeregisterForMatchmakingSetupProgressCallback() + { + m_fnCallbackMatchmakingSetupProgress = nullptr; + } + + void InvokeMatchmakingSetupProgressCallback(int timeoutMs) + { + if (m_fnCallbackMatchmakingSetupProgress != nullptr) + { + m_fnCallbackMatchmakingSetupProgress(timeoutMs); + } + } // updates @@ -396,6 +435,7 @@ class NGMP_OnlineServices_LobbyInterface void JoinLobby(LobbyEntry lobby, std::string strPassword); void LeaveCurrentLobby(); + void ResetForMatchmakingRequeue(); void ResetHostMigrationFlags() { @@ -471,6 +511,8 @@ class NGMP_OnlineServices_LobbyInterface #endif bool m_bAttemptingToJoinLobby = false; + // Invalidates asynchronous callbacks left behind by an abandoned lobby join. + std::atomic m_LobbyJoinGeneration = 0; LobbyEntry m_LobbyTryingToJoin; bool m_bSearchInProgress = false; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp index bea1ddea001..a6b7061bc73 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp @@ -151,8 +151,9 @@ static Int maxPingEntries = 0; static Int maxPoints= 100; static Int minPoints = 0; -static Int matchFoundTimeoutStart = 0; -static const Int lobbyTimeoutMs = 10000; +static Int matchFoundTimeoutStart = 0; +static const Int lobbyTimeoutMs = 10000; +static Int matchFoundTimeoutDurationMs = lobbyTimeoutMs; static const LadderInfo * getLadderInfo(); @@ -1215,19 +1216,35 @@ void WOLQuickMatchMenuInit( WindowLayout *layout, void *userData ) GadgetListBoxSetItemData(quickmatchTextWindow, (void*)-1, index); }); - pLobbyInterface->RegisterForMatchmakingMatchFoundCallback([]() - { - buttonBack->winEnable(FALSE); - buttonStop->winEnable(FALSE); - matchFoundTimeoutStart = timeGetTime(); + pLobbyInterface->RegisterForMatchmakingMatchFoundCallback([]() + { + buttonBack->winEnable(FALSE); + buttonStop->winEnable(FALSE); + matchFoundTimeoutDurationMs = lobbyTimeoutMs; + matchFoundTimeoutStart = timeGetTime(); if (TheAudio) { AudioEventRTS evt("GUICommunicatorOpen"); TheAudio->addAudioEvent(&evt); } - }); - - pLobbyInterface->RegisterForMatchmakingStartGameCallback([]() + }); + + pLobbyInterface->RegisterForMatchmakingRequeueCallback([]() + { + matchFoundTimeoutStart = 0; + matchFoundTimeoutDurationMs = lobbyTimeoutMs; + buttonBack->winEnable(TRUE); + buttonStop->winEnable(TRUE); + buttonWiden->winEnable(TRUE); + }); + + pLobbyInterface->RegisterForMatchmakingSetupProgressCallback([](int timeoutMs) + { + matchFoundTimeoutDurationMs = timeoutMs; + matchFoundTimeoutStart = timeGetTime(); + }); + + pLobbyInterface->RegisterForMatchmakingStartGameCallback([]() { matchFoundTimeoutStart = 0; NetworkLog(ELogVerbosity::LOG_DEBUG, "[QUICKMATCH] GOT START GAME EVENT"); @@ -1448,9 +1465,11 @@ void WOLQuickMatchMenuShutdown( WindowLayout *layout, void *userData ) NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); if (pLobbyInterface != nullptr) { - pLobbyInterface->DeregisterForMatchmakingMessageCallback(); - pLobbyInterface->DeRegisterForMatchmakingMatchFoundCallback(); - pLobbyInterface->DeregisterForMatchmakingStartGameCallback(); + pLobbyInterface->DeregisterForMatchmakingMessageCallback(); + pLobbyInterface->DeRegisterForMatchmakingMatchFoundCallback(); + pLobbyInterface->DeregisterForMatchmakingRequeueCallback(); + pLobbyInterface->DeregisterForMatchmakingSetupProgressCallback(); + pLobbyInterface->DeregisterForMatchmakingStartGameCallback(); pLobbyInterface->DeregisterForJoinLobbyCallback(); pLobbyInterface->DeregisterForCannotConnectToLobbyCallback(); @@ -1578,7 +1597,7 @@ void WOLQuickMatchMenuUpdate( WindowLayout * layout, void *userData) HandleBuddyResponses(); #endif - if (matchFoundTimeoutStart != 0 && timeGetTime() - matchFoundTimeoutStart >= lobbyTimeoutMs) + if (matchFoundTimeoutStart != 0 && timeGetTime() - matchFoundTimeoutStart >= matchFoundTimeoutDurationMs) { matchFoundTimeoutStart = 0; buttonBack->winEnable(TRUE); diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp index fcc2e1d2d16..ba4430b925d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_LobbyInterface.cpp @@ -794,13 +794,14 @@ void NGMP_OnlineServices_LobbyInterface::UpdateRoomDataCache(std::function mapHeaders; NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendGETRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) { // safety, lobby could've been torn down by the time we get our response - if (m_CurrentLobby.lobbyID != -1 && TheNGMPGame != nullptr) + if (m_CurrentLobby.lobbyID == requestedLobbyID && TheNGMPGame != nullptr) { // TODO_NGMP: Error handling try @@ -1057,6 +1058,8 @@ void NGMP_OnlineServices_LobbyInterface::JoinLobby(LobbyEntry lobbyInfo, std::st return; } + const uint64_t lobbyJoinGeneration = ++m_LobbyJoinGeneration; + AnticheatPlugInterface::EndSession(); m_bAttemptingToJoinLobby = true; @@ -1064,6 +1067,12 @@ void NGMP_OnlineServices_LobbyInterface::JoinLobby(LobbyEntry lobbyInfo, std::st NGMP_OnlineServicesManager::GetInstance()->GetAndParseServiceConfig([=]() { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface == nullptr || pLobbyInterface != this || pLobbyInterface->m_LobbyJoinGeneration.load() != lobbyJoinGeneration) + { + return; + } + std::string strURI = std::format("{}/{}", NGMP_OnlineServicesManager::GetAPIEndpoint("Lobby"), lobbyInfo.lobbyID); std::map mapHeaders; @@ -1095,8 +1104,11 @@ void NGMP_OnlineServices_LobbyInterface::JoinLobby(LobbyEntry lobbyInfo, std::st // convert NGMP_OnlineServicesManager::GetInstance()->GetHTTPManager()->SendPUTRequest(strURI.c_str(), EIPProtocolVersion::DONT_CARE, mapHeaders, strPostData.c_str(), [=](bool bSuccess, int statusCode, std::string strBody, HTTPRequest* pReq) { - if (NGMP_OnlineServicesManager::GetInterface() == nullptr) + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface == nullptr || pLobbyInterface != this || pLobbyInterface->m_LobbyJoinGeneration.load() != lobbyJoinGeneration) + { return; + } // reset trying to join ResetLobbyTryingToJoin(); @@ -1184,9 +1196,14 @@ void NGMP_OnlineServices_LobbyInterface::JoinLobby(LobbyEntry lobbyInfo, std::st OnJoinedOrCreatedLobby(false, [=](bool bSuccess) { - m_bAttemptingToJoinLobby = false; NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); - if (pLobbyInterface != nullptr && pLobbyInterface->m_callbackJoinedLobby != nullptr) + if (pLobbyInterface == nullptr || pLobbyInterface != this || pLobbyInterface->m_LobbyJoinGeneration.load() != lobbyJoinGeneration) + { + return; + } + + pLobbyInterface->m_bAttemptingToJoinLobby = false; + if (pLobbyInterface->m_callbackJoinedLobby != nullptr) { pLobbyInterface->m_callbackJoinedLobby(bSuccess ? EJoinLobbyResult::JoinLobbyResult_Success : EJoinLobbyResult::JoinLobbyResult_JoinFailed); } @@ -1270,6 +1287,36 @@ void NGMP_OnlineServices_LobbyInterface::LeaveCurrentLobby() ResetCachedRoomData(); } +void NGMP_OnlineServices_LobbyInterface::ResetForMatchmakingRequeue() +{ + // The service has already removed us from the failed temporary lobby. Tear down only + // local state here; sending the normal DELETE would cancel the server-side requeue. + ++m_LobbyJoinGeneration; + ResetHostMigrationFlags(); + AnticheatPlugInterface::EndSession(); + + if (m_pLobbyMesh != nullptr) + { + m_pLobbyMesh->Disconnect(); + delete m_pLobbyMesh; + m_pLobbyMesh = nullptr; + } + + if (TheNGMPGame != nullptr) + { + delete TheNGMPGame; + TheNGMPGame = nullptr; + } + +#if !defined(GENERALS_ONLINE_DISABLE_AUTO_ACCEPT) + m_timeStartAutoReadyCountdown = -1; +#endif + + m_bAttemptingToJoinLobby = false; + ResetLobbyTryingToJoin(); + ResetCachedRoomData(); +} + LobbyEntry NGMP_OnlineServices_LobbyInterface::GetLobbyFromID(int64_t lobbyID) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp index 2c2e1d394e5..edd77420958 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameNetwork/GeneralsOnline/OnlineServices_RoomsInterface.cpp @@ -995,6 +995,17 @@ void WebSocket::Tick() case EWebSocketMessageID::FULL_MESH_CONNECTIVITY_CHECK_RESPONSE: { + int64_t meshCheckID = 0; + int meshCheckAttempt = 0; + if (jsonObject.contains("mesh_check_id") && jsonObject["mesh_check_id"].is_number_integer()) + { + meshCheckID = jsonObject["mesh_check_id"].get(); + } + if (jsonObject.contains("attempt") && jsonObject["attempt"].is_number_integer()) + { + meshCheckAttempt = jsonObject["attempt"].get(); + } + // respond with our state std::vector connectivityMap; NetworkMesh* pMesh = nullptr; @@ -1025,6 +1036,8 @@ void WebSocket::Tick() // send response nlohmann::json j; j["msg_id"] = EWebSocketMessageID::FULL_MESH_CONNECTIVITY_CHECK_RESPONSE; + j["mesh_check_id"] = meshCheckID; + j["attempt"] = meshCheckAttempt; j["connectivity_map"] = connectivityMap; std::string strBody = j.dump(); @@ -1367,6 +1380,33 @@ void WebSocket::Tick() } break; + case EWebSocketMessageID::MATCHMAKING_ACTION_REQUEUE: + { + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr) + { + pLobbyInterface->ResetForMatchmakingRequeue(); + pLobbyInterface->InvokeMatchmakingRequeueCallback(); + } + } + break; + + case EWebSocketMessageID::MATCHMAKING_ACTION_SETUP_PROGRESS: + { + int timeoutMs = 0; + if (jsonObject.contains("timeout_ms") && jsonObject["timeout_ms"].is_number_integer()) + { + timeoutMs = jsonObject["timeout_ms"].get(); + } + + NGMP_OnlineServices_LobbyInterface* pLobbyInterface = NGMP_OnlineServicesManager::GetInterface(); + if (pLobbyInterface != nullptr && timeoutMs > 0) + { + pLobbyInterface->InvokeMatchmakingSetupProgressCallback(timeoutMs); + } + } + break; + case EWebSocketMessageID::MATCHMAKING_MESSAGE: { WebSocketMessage_MatchmakingMessage matchmakingMsg;