Index: source/network/NetClient.h =================================================================== --- source/network/NetClient.h +++ source/network/NetClient.h @@ -60,8 +60,6 @@ { NONCOPYABLE(CNetClient); - friend class CNetFileReceiveTask_ClientRejoin; - public: /** * Construct a client associated with the given game object. Index: source/network/NetClient.cpp =================================================================== --- source/network/NetClient.cpp +++ source/network/NetClient.cpp @@ -53,37 +53,6 @@ CNetClient *g_NetClient = NULL; -/** - * Async task for receiving the initial game state when rejoining an - * in-progress network game. - */ -class CNetFileReceiveTask_ClientRejoin : public CNetFileReceiveTask -{ - NONCOPYABLE(CNetFileReceiveTask_ClientRejoin); -public: - CNetFileReceiveTask_ClientRejoin(CNetClient& client, const CStr& initAttribs) - : m_Client(client), m_InitAttributes(initAttribs) - { - } - - virtual void OnComplete() - { - // We've received the game state from the server - - // Save it so we can use it after the map has finished loading - m_Client.m_JoinSyncBuffer = m_Buffer; - - // Pretend the server told us to start the game - CGameStartMessage start; - start.m_InitAttributes = m_InitAttributes; - m_Client.HandleMessage(&start); - } - -private: - CNetClient& m_Client; - CStr m_InitAttributes; -}; - CNetClient::CNetClient(CGame* game) : m_Session(NULL), m_UserName(L"anonymous"), @@ -834,8 +803,18 @@ // The server wants us to start downloading the game state from it, so do so client->m_Session->GetFileTransferer().StartTask( - std::shared_ptr(new CNetFileReceiveTask_ClientRejoin(*client, joinSyncStartMessage->m_InitAttributes)) - ); + [client, initAttributes = joinSyncStartMessage->m_InitAttributes](std::string buffer) + { + // We've received the game state from the server + + // Save it so we can use it after the map has finished loading + client->m_JoinSyncBuffer = std::move(buffer); + + // Pretend the server told us to start the game + CGameStartMessage start; + start.m_InitAttributes = initAttributes; + client->HandleMessage(&start); + }); return true; } Index: source/network/NetFileTransfer.h =================================================================== --- source/network/NetFileTransfer.h +++ source/network/NetFileTransfer.h @@ -18,8 +18,10 @@ #ifndef NETFILETRANSFER_H #define NETFILETRANSFER_H +#include #include #include +#include class CNetMessage; class CFileTransferResponseMessage; @@ -40,35 +42,6 @@ // Some arbitrary limit to make it slightly harder to use up all of someone's RAM static const size_t MAX_FILE_TRANSFER_SIZE = 8*MiB; -/** - * Asynchronous file-receiving task. - * Other code should subclass this, implement OnComplete(), - * then pass it to CNetFileTransferer::StartTask. - */ -class CNetFileReceiveTask -{ -public: - CNetFileReceiveTask() : m_RequestID(0), m_Length(0) { } - virtual ~CNetFileReceiveTask() {} - - /** - * Called when m_Buffer contains the full received data. - */ - virtual void OnComplete() = 0; - - // TODO: Ought to have an OnFailure, e.g. when the session drops or there's another error - - /** - * Uniquely identifies the request within the scope of its CNetFileTransferer. - * Set automatically by StartTask. - */ - u32 m_RequestID; - - size_t m_Length; - - std::string m_Buffer; -}; - /** * Handles transferring files between clients and servers. */ @@ -91,7 +64,7 @@ /** * Registers a file-receiving task. */ - void StartTask(const std::shared_ptr& task); + void StartTask(std::function task); /** * Registers data to be sent in response to a request. @@ -127,7 +100,26 @@ u32 m_NextRequestID; - using FileReceiveTasksMap = std::map>; + + /** + * Asynchronous file-receiving task. + */ + struct NetFileReceiveTask + { + public: + /** + * Called when m_Buffer contains the full received data. + */ + std::function m_OnComplete; + + // TODO: Ought to have a failure chanal, e.g. when the session drops or there's another error + + size_t m_Length{0}; + + std::string m_Buffer; + }; + + using FileReceiveTasksMap = std::unordered_map; FileReceiveTasksMap m_FileReceiveTasks; using FileSendTasksMap = std::map; Index: source/network/NetFileTransfer.cpp =================================================================== --- source/network/NetFileTransfer.cpp +++ source/network/NetFileTransfer.cpp @@ -57,7 +57,7 @@ return ERR::FAIL; } - CNetFileReceiveTask& task = *it->second; + NetFileReceiveTask& task = it->second; task.m_Length = message.m_Length; task.m_Buffer.reserve(message.m_Length); @@ -77,7 +77,7 @@ return ERR::FAIL; } - CNetFileReceiveTask& task = *it->second; + NetFileReceiveTask& task = it->second; task.m_Buffer += message.m_Data; @@ -88,7 +88,7 @@ } CFileTransferAckMessage ackMessage; - ackMessage.m_RequestID = task.m_RequestID; + ackMessage.m_RequestID = message.m_RequestID; ackMessage.m_NumPackets = 1; // TODO: would be nice to send a single ack for multiple packets at once m_Session->SendMessage(&ackMessage); @@ -96,8 +96,8 @@ { LOGMESSAGERENDER("Download completed"); - task.OnComplete(); - m_FileReceiveTasks.erase(message.m_RequestID); + task.m_OnComplete(std::move(task.m_Buffer)); + m_FileReceiveTasks.erase(it); return INFO::OK; } @@ -138,12 +138,11 @@ } -void CNetFileTransferer::StartTask(const std::shared_ptr& task) +void CNetFileTransferer::StartTask(std::function task) { u32 requestID = m_NextRequestID++; - task->m_RequestID = requestID; - m_FileReceiveTasks[requestID] = task; + m_FileReceiveTasks.emplace(requestID, NetFileReceiveTask{std::move(task)}); CFileTransferRequestMessage request; request.m_RequestID = requestID; Index: source/network/NetServer.h =================================================================== --- source/network/NetServer.h +++ source/network/NetServer.h @@ -233,7 +233,6 @@ private: friend class CNetServer; - friend class CNetFileReceiveTask_ServerRejoin; CNetServerWorker(bool useLobbyAuth); ~CNetServerWorker(); Index: source/network/NetServer.cpp =================================================================== --- source/network/NetServer.cpp +++ source/network/NetServer.cpp @@ -89,58 +89,6 @@ return "[" + session->GetGUID().substr(0, 8) + "...]"; } -/** - * Async task for receiving the initial game state to be forwarded to another - * client that is rejoining an in-progress network game. - */ -class CNetFileReceiveTask_ServerRejoin : public CNetFileReceiveTask -{ - NONCOPYABLE(CNetFileReceiveTask_ServerRejoin); -public: - CNetFileReceiveTask_ServerRejoin(CNetServerWorker& server, u32 hostID) - : m_Server(server), m_RejoinerHostID(hostID) - { - } - - virtual void OnComplete() - { - // We've received the game state from an existing player - now - // we need to send it onwards to the newly rejoining player - - // Find the session corresponding to the rejoining host (if any) - CNetServerSession* session = NULL; - for (CNetServerSession* serverSession : m_Server.m_Sessions) - { - if (serverSession->GetHostID() == m_RejoinerHostID) - { - session = serverSession; - break; - } - } - - if (!session) - { - LOGMESSAGE("Net server: rejoining client disconnected before we sent to it"); - return; - } - - // Store the received state file, and tell the client to start downloading it from us - // TODO: this will get kind of confused if there's multiple clients downloading in parallel; - // they'll race and get whichever happens to be the latest received by the server, - // which should still work but isn't great - m_Server.m_JoinSyncFile = m_Buffer; - - // Send the init attributes alongside - these should be correct since the game should be started. - CJoinSyncStartMessage message; - message.m_InitAttributes = Script::StringifyJSON(ScriptRequest(m_Server.GetScriptInterface()), &m_Server.m_InitAttributes); - session->SendMessage(&message); - } - -private: - CNetServerWorker& m_Server; - u32 m_RejoinerHostID; -}; - /* * XXX: We use some non-threadsafe functions from the worker thread. * See http://trac.wildfiregames.com/ticket/654 @@ -1150,23 +1098,50 @@ server.OnUserJoin(session); - if (isRejoining) - { - ENSURE(server.m_State != SERVER_STATE_UNCONNECTED && server.m_State != SERVER_STATE_PREGAME); + if (!isRejoining) + return true; - // Request a copy of the current game state from an existing player, - // so we can send it on to the new player + ENSURE(server.m_State != SERVER_STATE_UNCONNECTED && server.m_State != SERVER_STATE_PREGAME); - // Assume session 0 is most likely the local player, so they're - // the most efficient client to request a copy from - CNetServerSession* sourceSession = server.m_Sessions.at(0); + // Request a copy of the current game state from an existing player, + // so we can send it on to the new player - sourceSession->GetFileTransferer().StartTask( - std::shared_ptr(new CNetFileReceiveTask_ServerRejoin(server, newHostID)) - ); + // Assume session 0 is most likely the local player, so they're + // the most efficient client to request a copy from + CNetServerSession* sourceSession = server.m_Sessions.at(0); - session->SetNextState(NSS_JOIN_SYNCING); - } + sourceSession->GetFileTransferer().StartTask([&server, newHostID](std::string buffer) + { + // We've received the game state from an existing player - now + // we need to send it onwards to the newly rejoining player + + const auto session = std::find_if(server.m_Sessions.begin(), server.m_Sessions.end(), + [&](CNetServerSession* serverSession) + { + return serverSession->GetHostID() == newHostID; + }); + + if (session == server.m_Sessions.end()) + { + LOGMESSAGE("Net server: rejoining client disconnected before we sent to it"); + return; + } + + // Store the received state file, and tell the client to start downloading it from us + // TODO: this will get kind of confused if there's multiple clients downloading in + // parallel; they'll race and get whichever happens to be the latest received by the + // server, which should still work but isn't great + server.m_JoinSyncFile = std::move(buffer); + + // Send the init attributes alongside - these should be correct since the game should be + // started. + CJoinSyncStartMessage message; + message.m_InitAttributes = Script::StringifyJSON( + ScriptRequest{server.GetScriptInterface()}, &server.m_InitAttributes); + (*session)->SendMessage(&message); + }); + + session->SetNextState(NSS_JOIN_SYNCING); return true; }