1
0
forked from 0ad/0ad

Compare commits

...

2 Commits

Author SHA1 Message Date
6aba4177b4 Use The FileTransferer to transfair the saved state 2024-09-04 08:56:40 +02:00
870c514d52 Enable multiple RequestTypes 2024-09-03 23:38:54 +02:00
9 changed files with 97 additions and 47 deletions

View File

@ -493,10 +493,10 @@ void CNetClient::SendStartGameMessage(const CStr& initAttribs)
void CNetClient::SendStartSavedGameMessage(const CStr& initAttribs, const CStr& savedState)
{
m_SavedState = savedState;
CGameSavedStartMessage gameSavedStart;
gameSavedStart.m_InitAttributes = initAttribs;
CompressZLib(savedState, gameSavedStart.m_SavedState, true);
SendMessage(&gameSavedStart);
}
@ -535,22 +535,29 @@ bool CNetClient::HandleMessage(CNetMessage* message)
{
CFileTransferRequestMessage* reqMessage = static_cast<CFileTransferRequestMessage*>(message);
// TODO: we should support different transfer request types, instead of assuming
// it's always requesting the simulation state
std::string toCompress{[&]
{
if (static_cast<CNetFileTransferer::RequestType>(reqMessage->m_RequestType) ==
CNetFileTransferer::RequestType::LOADGAME)
{
return m_SavedState;
}
std::stringstream stream;
std::stringstream stream;
LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
stream.write((char*)&turn, sizeof(turn));
LOGMESSAGERENDER("Serializing game at turn %u for rejoining player", m_ClientTurnManager->GetCurrentTurn());
u32 turn = to_le32(m_ClientTurnManager->GetCurrentTurn());
stream.write((char*)&turn, sizeof(turn));
bool ok = m_Game->GetSimulation2()->SerializeState(stream);
ENSURE(ok);
bool ok = m_Game->GetSimulation2()->SerializeState(stream);
ENSURE(ok);
return stream.str();
}()};
// Compress the content with zlib to save bandwidth
// (TODO: if this is still too large, compressing with e.g. LZMA works much better)
std::string compressed;
CompressZLib(stream.str(), compressed, true);
CompressZLib(toCompress, compressed, true);
m_Session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, compressed);
@ -613,7 +620,7 @@ void CNetClient::SendAuthenticateMessage()
SendMessage(&authenticate);
}
void CNetClient::StartGame(const std::string& initAttributes, const std::string& savedState)
void CNetClient::StartGame(const JS::MutableHandleValue initAttributes, const std::string& savedState)
{
const auto foundPlayer = m_PlayerAssignments.find(m_GUID);
const i32 player{foundPlayer != m_PlayerAssignments.end() ? foundPlayer->second.m_PlayerID : -1};
@ -621,16 +628,8 @@ void CNetClient::StartGame(const std::string& initAttributes, const std::string&
m_ClientTurnManager = new CNetClientTurnManager{*m_Game->GetSimulation2(), *this,
static_cast<int>(m_HostID), m_Game->GetReplayLogger()};
// Parse init attributes.
const ScriptInterface& scriptInterface{m_Game->GetSimulation2()->GetScriptInterface()};
ScriptRequest rq{scriptInterface};
JS::RootedValue initAttribs{rq.cx};
Script::ParseJSON(rq, initAttributes, &initAttribs);
m_Game->SetPlayerID(player);
m_Game->StartGame(&initAttribs, savedState);
PushGuiMessage("type", "start", "initAttributes", initAttribs);
m_Game->StartGame(initAttributes, savedState);
}
bool CNetClient::OnConnect(CNetClient* client, CFsmEvent* event)
@ -786,18 +785,37 @@ bool CNetClient::OnGameStart(CNetClient* client, CFsmEvent* event)
ENSURE(event->GetType() == (uint)NMT_GAME_START);
CGameStartMessage* message = static_cast<CGameStartMessage*>(event->GetParamRef());
client->StartGame(message->m_InitAttributes, "");
const ScriptInterface& scriptInterface{client->m_Game->GetSimulation2()->GetScriptInterface()};
ScriptRequest rq{scriptInterface};
JS::RootedValue initAttribs{rq.cx};
Script::ParseJSON(rq, message->m_InitAttributes, &initAttribs);
client->PushGuiMessage("type", "start", "initAttributes", initAttribs);
client->StartGame(&initAttribs, "");
return true;
}
bool CNetClient::OnSavedGameStart(CNetClient* context, CFsmEvent* event)
bool CNetClient::OnSavedGameStart(CNetClient* client, CFsmEvent* event)
{
ENSURE(event->GetType() == static_cast<uint>(NMT_SAVED_GAME_START));
std::string state;
CGameSavedStartMessage* message{static_cast<CGameSavedStartMessage*>(event->GetParamRef())};
DecompressZLib(message->m_SavedState, state, true);
context->StartGame(message->m_InitAttributes, state);
const ScriptInterface& scriptInterface{client->m_Game->GetSimulation2()->GetScriptInterface()};
ScriptRequest rq{scriptInterface};
const std::shared_ptr<JS::RootedValue> initAttribs{std::make_shared<JS::RootedValue>(rq.cx)};
Script::ParseJSON(rq, message->m_InitAttributes, &*initAttribs);
client->PushGuiMessage("type", "start", "initAttributes", *initAttribs);
client->m_Session->GetFileTransferer().StartTask(CNetFileTransferer::RequestType::LOADGAME,
[client, initAttribs](std::string buffer)
{
std::string state;
DecompressZLib(buffer, state, true);
client->StartGame(&*initAttribs, state);
});
return true;
}
@ -809,7 +827,7 @@ bool CNetClient::OnJoinSyncStart(CNetClient* client, CFsmEvent* event)
CJoinSyncStartMessage* joinSyncStartMessage = (CJoinSyncStartMessage*)event->GetParamRef();
// The server wants us to start downloading the game state from it, so do so
client->m_Session->GetFileTransferer().StartTask(
client->m_Session->GetFileTransferer().StartTask(CNetFileTransferer::RequestType::SYNCRONIZE,
[client, initAttributes = std::move(joinSyncStartMessage->m_InitAttributes)](std::string buffer)
mutable
{

View File

@ -299,7 +299,7 @@ private:
* Starts a game with the specified init attributes and saved state. Called
* by the start game and start saved game callbacks.
*/
void StartGame(const std::string& initAttributes, const std::string& savedState);
void StartGame(const JS::MutableHandleValue initAttributes, const std::string& savedState);
/**
* Push a message onto the GUI queue listing the current player assignments.
@ -351,6 +351,8 @@ private:
/// Serialized game state received when joining an in-progress game
std::string m_JoinSyncBuffer;
std::string m_SavedState;
/// Time when the server was last checked for timeouts and bad latency
std::time_t m_LastConnectionCheck;
};

View File

@ -141,13 +141,14 @@ Status CNetFileTransferer::OnFileTransferAck(const CFileTransferAckMessage& mess
}
void CNetFileTransferer::StartTask(std::function<void(std::string)> task)
void CNetFileTransferer::StartTask(RequestType requestType, std::function<void(std::string)> task)
{
u32 requestID = m_NextRequestID++;
m_FileReceiveTasks.emplace(requestID, AsyncFileReceiveTask{std::move(task)});
CFileTransferRequestMessage request;
request.m_RequestType = static_cast<i8>(requestType);
request.m_RequestID = requestID;
m_Session->SendMessage(&request);
}

View File

@ -48,6 +48,12 @@ static const size_t MAX_FILE_TRANSFER_SIZE = 8*MiB;
class CNetFileTransferer
{
public:
enum class RequestType : i8
{
LOADGAME,
SYNCRONIZE
};
CNetFileTransferer(INetSession* session)
: m_Session(session), m_NextRequestID(1), m_LastProgressReportTime(0)
{
@ -64,7 +70,7 @@ public:
/**
* Registers a file-receiving task.
*/
void StartTask(std::function<void(std::string)> task);
void StartTask(RequestType requestType, std::function<void(std::string)> task);
/**
* Registers data to be sent in response to a request.

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2024 Wildfire Games.
/* Copyright (C) 2023 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -45,14 +45,14 @@ u8* CNetMessage::Serialize(u8* pBuffer) const
{
size_t size = GetSerializedLength();
Serialize_int_1(pBuffer, m_Type);
Serialize_int_3(pBuffer, size);
Serialize_int_2(pBuffer, size);
return pBuffer;
}
const u8* CNetMessage::Deserialize(const u8* pStart, const u8* pEnd)
{
if (pStart + 4 > pEnd)
if (pStart + 3 > pEnd)
{
LOGERROR("CNetMessage: Corrupt packet (smaller than header)");
return NULL;
@ -63,7 +63,7 @@ const u8* CNetMessage::Deserialize(const u8* pStart, const u8* pEnd)
int type;
size_t size;
Deserialize_int_1(pBuffer, type);
Deserialize_int_3(pBuffer, size);
Deserialize_int_2(pBuffer, size);
m_Type = (NetMessageType)type;
if (pStart + size != pEnd)
@ -78,7 +78,7 @@ const u8* CNetMessage::Deserialize(const u8* pStart, const u8* pEnd)
size_t CNetMessage::GetSerializedLength() const
{
// By default, return header size
return 4;
return 3;
}
CStr CNetMessage::ToString() const

View File

@ -156,6 +156,7 @@ START_NMT_CLASS_(PlayerAssignment, NMT_PLAYER_ASSIGNMENT)
END_NMT_CLASS()
START_NMT_CLASS_(FileTransferRequest, NMT_FILE_TRANSFER_REQUEST)
NMT_FIELD_INT(m_RequestType, i8, 1)
NMT_FIELD_INT(m_RequestID, u32, 4)
END_NMT_CLASS()
@ -220,7 +221,6 @@ END_NMT_CLASS()
START_NMT_CLASS_(GameSavedStart, NMT_SAVED_GAME_START)
NMT_FIELD(CStr, m_InitAttributes)
NMT_FIELD(CStr, m_SavedState)
END_NMT_CLASS()
START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH)

View File

@ -624,11 +624,9 @@ void CNetServerWorker::HandleMessageReceive(const CNetMessage* message, CNetServ
{
CFileTransferRequestMessage* reqMessage = (CFileTransferRequestMessage*)message;
// Rejoining client got our JoinSyncStart after we received the state from
// another client, and has now requested that we forward it to them
ENSURE(!m_JoinSyncFile.empty());
session->GetFileTransferer().StartResponse(reqMessage->m_RequestID, m_JoinSyncFile);
session->GetFileTransferer().StartResponse(reqMessage->m_RequestID,
static_cast<CNetFileTransferer::RequestType>(reqMessage->m_RequestType) ==
CNetFileTransferer::RequestType::LOADGAME ? m_SavedState : m_JoinSyncFile);
return;
}
@ -1123,7 +1121,8 @@ bool CNetServerWorker::OnAuthenticate(CNetServerSession* session, CFsmEvent* eve
// copy from.
CNetServerSession* sourceSession = server.m_Sessions.at(0);
sourceSession->GetFileTransferer().StartTask([&server, newHostID](std::string buffer)
sourceSession->GetFileTransferer().StartTask(CNetFileTransferer::RequestType::SYNCRONIZE,
[&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.
@ -1325,7 +1324,13 @@ bool CNetServerWorker::OnSavedGameStart(CNetServerSession* session, CFsmEvent* e
return true;
CGameSavedStartMessage* message = static_cast<CGameSavedStartMessage*>(event->GetParamRef());
server.StartSavedGame(message->m_InitAttributes, message->m_SavedState);
session->GetFileTransferer().StartTask(CNetFileTransferer::RequestType::LOADGAME,
[&server, initAttributes = std::move(message->m_InitAttributes)](std::string buffer)
{
server.m_SavedState = std::move(buffer);
server.StartSavedGame(initAttributes);
});
return true;
}
@ -1555,13 +1560,12 @@ void CNetServerWorker::StartGame(const CStr& initAttribs)
Broadcast(&gameStart, { NSS_PREGAME });
}
void CNetServerWorker::StartSavedGame(const CStr& initAttribs, const CStr& savedState)
void CNetServerWorker::StartSavedGame(const CStr& initAttribs)
{
PreStartGame(initAttribs);
CGameSavedStartMessage gameSavedStart;
gameSavedStart.m_InitAttributes = initAttribs;
gameSavedStart.m_SavedState = savedState;
Broadcast(&gameSavedStart, { NSS_PREGAME });
}

View File

@ -269,7 +269,7 @@ private:
/**
* Switch in game mode and notify all clients to start the saved game.
*/
void StartSavedGame(const CStr& initAttribs, const CStr& savedData);
void StartSavedGame(const CStr& initAttribs);
/**
* Make a player name 'nicer' by limiting the length and removing forbidden characters etc.
@ -411,6 +411,11 @@ private:
*/
std::string m_JoinSyncFile;
/**
* The loaded game data when a game is loaded.
*/
std::string m_SavedState;
/**
* Time when the clients connections were last checked for timeouts and latency.
*/

View File

@ -89,7 +89,8 @@ public:
bool complete{false};
client.transferer.StartTask([&complete](std::string buffer)
client.transferer.StartTask(CNetFileTransferer::RequestType::LOADGAME,
[&complete](std::string buffer)
{
// This callback is executed exactly once.
const bool previousComplete{std::exchange(complete, true)};
@ -121,4 +122,17 @@ public:
server.transferer.HandleMessageReceive(client.queues.acknowledgements.at(0));
CheckSizes(server.queues, 0, 1, 1, 0);
}
void test_RequestType()
{
for (const auto& requestType : {CNetFileTransferer::RequestType::LOADGAME,
CNetFileTransferer::RequestType::SYNCRONIZE})
{
Participant client;
client.transferer.StartTask(requestType, [](auto&&){});
TS_ASSERT_EQUALS(static_cast<CNetFileTransferer::RequestType>(
client.queues.requests.at(0).m_RequestType), requestType);
}
}
};