Compare commits
2 Commits
fee875066f
...
6aba4177b4
Author | SHA1 | Date | |
---|---|---|---|
6aba4177b4 | |||
870c514d52 |
@ -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
|
||||
{
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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 });
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user