1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00

Switch client-server communication to new API

This commit is contained in:
Ivan Savenko 2023-11-18 16:34:18 +02:00
parent de5227142b
commit 0a1153e1c6
34 changed files with 623 additions and 694 deletions

View File

@ -45,17 +45,17 @@
#include "../lib/mapping/CMapInfo.h" #include "../lib/mapping/CMapInfo.h"
#include "../lib/mapObjects/MiscObjects.h" #include "../lib/mapObjects/MiscObjects.h"
#include "../lib/modding/ModIncompatibility.h" #include "../lib/modding/ModIncompatibility.h"
#include "../lib/network/NetworkClient.h"
#include "../lib/rmg/CMapGenOptions.h" #include "../lib/rmg/CMapGenOptions.h"
#include "../lib/serializer/Connection.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h" #include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/CMemorySerializer.h" #include "../lib/serializer/CMemorySerializer.h"
#include "../lib/UnlockGuard.h" #include "../lib/UnlockGuard.h"
#include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp> #include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp> #include <boost/uuid/uuid_generators.hpp>
#include <boost/asio.hpp>
#include "../lib/serializer/Cast.h" #include "../lib/serializer/Cast.h"
#include "LobbyClientNetPackVisitors.h" #include "LobbyClientNetPackVisitors.h"
@ -131,8 +131,16 @@ public:
static const std::string NAME_AFFIX = "client"; static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CServerHandler::~CServerHandler() = default;
CServerHandler::CServerHandler() CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false) : state(EClientState::NONE)
, mx(std::make_shared<boost::recursive_mutex>())
, networkClient(std::make_unique<NetworkClient>(*this))
, client(nullptr)
, loadMode(0)
, campaignStateToSend(nullptr)
, campaignServerRestartLock(false)
{ {
uuid = boost::uuids::to_string(boost::uuids::random_generator()()); uuid = boost::uuids::to_string(boost::uuids::random_generator()());
//read from file to restore last session //read from file to restore last session
@ -161,7 +169,7 @@ void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::
myNames.push_back(settings["general"]["playerName"].String()); myNames.push_back(settings["general"]["playerName"].String());
} }
void CServerHandler::startLocalServerAndConnect() void CServerHandler::startLocalServerAndConnect(const std::function<void()> & onConnected)
{ {
if(threadRunLocalServer) if(threadRunLocalServer)
threadRunLocalServer->join(); threadRunLocalServer->join();
@ -169,17 +177,13 @@ void CServerHandler::startLocalServerAndConnect()
th->update(); th->update();
auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess"); auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess");
try
if (!checkNetworkPortIsFree(localhostAddress, getDefaultPort()))
{ {
CConnection testConnection(localhostAddress, getDefaultPort(), NAME, uuid);
logNetwork->error("Port is busy, check if another instance of vcmiserver is working"); logNetwork->error("Port is busy, check if another instance of vcmiserver is working");
CInfoWindow::showInfoDialog(errorMsg, {}); CInfoWindow::showInfoDialog(errorMsg, {});
return; return;
} }
catch(std::runtime_error & error)
{
//no connection means that port is not busy and we can start local server
}
#if defined(SINGLE_PROCESS_APP) #if defined(SINGLE_PROCESS_APP)
boost::condition_variable cond; boost::condition_variable cond;
@ -243,38 +247,15 @@ void CServerHandler::startLocalServerAndConnect()
th->update(); //put breakpoint here to attach to server before it does something stupid th->update(); //put breakpoint here to attach to server before it does something stupid
justConnectToServer(localhostAddress, 0); justConnectToServer(localhostAddress, 0, onConnected);
logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff()); logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff());
} }
void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port) void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port, const std::function<void()> & onConnected)
{ {
logNetwork->info("Establishing connection...");
state = EClientState::CONNECTING; state = EClientState::CONNECTING;
while(!c && state != EClientState::CONNECTION_CANCELLED)
{
try
{
logNetwork->info("Establishing connection...");
c = std::make_shared<CConnection>(
addr.size() ? addr : getHostAddress(),
port ? port : getHostPort(),
NAME, uuid);
}
catch(std::runtime_error & error)
{
logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", error.what());
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
}
}
if(state == EClientState::CONNECTION_CANCELLED)
{
logNetwork->info("Connection aborted by player!");
return;
}
c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this);
if(!addr.empty() && addr != getHostAddress()) if(!addr.empty() && addr != getHostAddress())
{ {
@ -286,13 +267,39 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po
Settings serverPort = settings.write["server"]["port"]; Settings serverPort = settings.write["server"]["port"];
serverPort->Integer() = port; serverPort->Integer() = port;
} }
networkClient->start(addr.size() ? addr : getHostAddress(), port ? port : getHostPort());
}
void CServerHandler::onConnectionFailed(const std::string & errorMessage)
{
if(state == EClientState::CONNECTION_CANCELLED)
{
logNetwork->info("Connection aborted by player!");
return;
}
logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", errorMessage);
//FIXME: replace with asio timer
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
//FIXME: pass parameters from initial attempt
networkClient->start(getHostAddress(), getHostPort());
}
void CServerHandler::onConnectionEstablished()
{
c->enterLobbyConnectionMode();
sendClientConnecting();
//FIXME: call functor provided in CServerHandler::justConnectToServer
assert(0);
//onConnected();
} }
void CServerHandler::applyPacksOnLobbyScreen() void CServerHandler::applyPacksOnLobbyScreen()
{ {
if(!c || !c->handler)
return;
boost::unique_lock<boost::recursive_mutex> lock(*mx); boost::unique_lock<boost::recursive_mutex> lock(*mx);
while(!packsForLobbyScreen.empty()) while(!packsForLobbyScreen.empty())
{ {
@ -306,16 +313,6 @@ void CServerHandler::applyPacksOnLobbyScreen()
} }
} }
void CServerHandler::stopServerConnection()
{
if(c->handler)
{
while(!c->handler->timed_join(boost::chrono::milliseconds(50)))
applyPacksOnLobbyScreen();
c->handler->join();
}
}
std::set<PlayerColor> CServerHandler::getHumanColors() std::set<PlayerColor> CServerHandler::getHumanColors()
{ {
return clientHumanColors(c->connectionID); return clientHumanColors(c->connectionID);
@ -421,7 +418,6 @@ void CServerHandler::sendClientDisconnecting()
{ {
// Network thread might be applying network pack at this moment // Network thread might be applying network pack at this moment
auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex); auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
c->close();
c.reset(); c.reset();
} }
} }
@ -814,7 +810,7 @@ void CServerHandler::restoreLastSession()
myNames.push_back(name.String()); myNames.push_back(name.String());
resetStateForLobby(StartInfo::LOAD_GAME, &myNames); resetStateForLobby(StartInfo::LOAD_GAME, &myNames);
screenType = ESelectionScreen::loadGame; screenType = ESelectionScreen::loadGame;
justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer()); justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer(), {});
}; };
auto cleanUpSession = []() auto cleanUpSession = []()
@ -844,9 +840,9 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
screenType = ESelectionScreen::newGame; screenType = ESelectionScreen::newGame;
} }
if(settings["session"]["donotstartserver"].Bool()) if(settings["session"]["donotstartserver"].Bool())
justConnectToServer(localhostAddress, 3030); justConnectToServer(localhostAddress, 3030, {});
else else
startLocalServerAndConnect(); startLocalServerAndConnect({});
boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
@ -902,65 +898,49 @@ public:
} }
}; };
void CServerHandler::threadHandleConnection() void CServerHandler::onPacketReceived(const std::vector<uint8_t> & message)
{ {
setThreadName("handleConnection"); CPack * pack = c->retrievePack(message);
c->enterLobbyConnectionMode(); if(state == EClientState::DISCONNECTING)
try
{ {
sendClientConnecting(); // FIXME: server shouldn't really send netpacks after it's tells client to disconnect
while(c && c->connected) // Though currently they'll be delivered and might cause crash.
{ vstd::clear_pointer(pack);
while(state == EClientState::STARTING)
boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
CPack * pack = c->retrievePack();
if(state == EClientState::DISCONNECTING)
{
// FIXME: server shouldn't really send netpacks after it's tells client to disconnect
// Though currently they'll be delivered and might cause crash.
vstd::clear_pointer(pack);
}
else
{
ServerHandlerCPackVisitor visitor(*this);
pack->visit(visitor);
}
}
} }
//catch only asio exceptions else
catch(const boost::system::system_error & e)
{ {
if(state == EClientState::DISCONNECTING) ServerHandlerCPackVisitor visitor(*this);
pack->visit(visitor);
}
}
void CServerHandler::onDisconnected()
{
if(state == EClientState::DISCONNECTING)
{
logNetwork->info("Successfully closed connection to server, ending listening thread!");
}
else
{
logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed");
if(client)
{ {
logNetwork->info("Successfully closed connection to server, ending listening thread!"); state = EClientState::DISCONNECTING;
GH.dispatchMainThread([]()
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
});
} }
else else
{ {
if (e.code() == boost::asio::error::eof) auto lcd = new LobbyClientDisconnected();
logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed"); lcd->clientId = c->connectionID;
else boost::unique_lock<boost::recursive_mutex> lock(*mx);
logNetwork->error("Lost connection to server, ending listening thread! Reason: %s", e.what()); packsForLobbyScreen.push_back(lcd);
if(client)
{
state = EClientState::DISCONNECTING;
GH.dispatchMainThread([]()
{
CSH->endGameplay();
GH.defActionsDef = 63;
CMM->menu->switchToTab("main");
});
}
else
{
auto lcd = new LobbyClientDisconnected();
lcd->clientId = c->connectionID;
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(lcd);
}
} }
} }
} }

View File

@ -11,6 +11,7 @@
#include "../lib/CStopWatch.h" #include "../lib/CStopWatch.h"
#include "../lib/network/NetworkListener.h"
#include "../lib/StartInfo.h" #include "../lib/StartInfo.h"
#include "../lib/CondSh.h" #include "../lib/CondSh.h"
@ -80,8 +81,10 @@ public:
}; };
/// structure to handle running server and connecting to it /// structure to handle running server and connecting to it
class CServerHandler : public IServerAPI, public LobbyInfo class CServerHandler : public IServerAPI, public LobbyInfo, public INetworkClientListener, boost::noncopyable
{ {
std::unique_ptr<NetworkClient> networkClient;
friend class ApplyOnLobbyHandlerNetPackVisitor; friend class ApplyOnLobbyHandlerNetPackVisitor;
std::shared_ptr<CApplier<CBaseForLobbyApply>> applier; std::shared_ptr<CApplier<CBaseForLobbyApply>> applier;
@ -95,12 +98,18 @@ class CServerHandler : public IServerAPI, public LobbyInfo
std::shared_ptr<HighScoreCalculation> highScoreCalc; std::shared_ptr<HighScoreCalculation> highScoreCalc;
void threadHandleConnection();
void threadRunServer(); void threadRunServer();
void onServerFinished(); void onServerFinished();
void sendLobbyPack(const CPackForLobby & pack) const override; void sendLobbyPack(const CPackForLobby & pack) const override;
void onPacketReceived(const std::vector<uint8_t> & message) override;
void onConnectionFailed(const std::string & errorMessage) override;
void onConnectionEstablished() override;
void onDisconnected() override;
public: public:
std::shared_ptr<CConnection> c;
std::atomic<EClientState> state; std::atomic<EClientState> state;
//////////////////// ////////////////////
// FIXME: Bunch of crutches to glue it all together // FIXME: Bunch of crutches to glue it all together
@ -115,7 +124,6 @@ public:
std::unique_ptr<CStopWatch> th; std::unique_ptr<CStopWatch> th;
std::shared_ptr<boost::thread> threadRunLocalServer; std::shared_ptr<boost::thread> threadRunLocalServer;
std::shared_ptr<CConnection> c;
CClient * client; CClient * client;
CondSh<bool> campaignServerRestartLock; CondSh<bool> campaignServerRestartLock;
@ -123,15 +131,15 @@ public:
static const std::string localhostAddress; static const std::string localhostAddress;
CServerHandler(); CServerHandler();
~CServerHandler();
std::string getHostAddress() const; std::string getHostAddress() const;
ui16 getHostPort() const; ui16 getHostPort() const;
void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr); void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr);
void startLocalServerAndConnect(); void startLocalServerAndConnect(const std::function<void()> & onConnected);
void justConnectToServer(const std::string & addr, const ui16 port); void justConnectToServer(const std::string & addr, const ui16 port, const std::function<void()> & onConnected);
void applyPacksOnLobbyScreen(); void applyPacksOnLobbyScreen();
void stopServerConnection();
// Helpers for lobby state access // Helpers for lobby state access
std::set<PlayerColor> getHumanColors(); std::set<PlayerColor> getHumanColors();

View File

@ -30,11 +30,12 @@
#include "../lib/UnlockGuard.h" #include "../lib/UnlockGuard.h"
#include "../lib/battle/BattleInfo.h" #include "../lib/battle/BattleInfo.h"
#include "../lib/serializer/BinaryDeserializer.h" #include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMapService.h"
#include "../lib/pathfinder/CGPathNode.h" #include "../lib/pathfinder/CGPathNode.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/registerTypes/RegisterTypesClientPacks.h" #include "../lib/registerTypes/RegisterTypesClientPacks.h"
#include "../lib/serializer/Connection.h"
#include <memory> #include <memory>
#include <vcmi/events/EventBus.h> #include <vcmi/events/EventBus.h>

View File

@ -27,8 +27,8 @@
#include "../CCallback.h" #include "../CCallback.h"
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileInfo.h" #include "../lib/filesystem/FileInfo.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/BinarySerializer.h" #include "../lib/serializer/BinarySerializer.h"
#include "../lib/serializer/Connection.h"
#include "../lib/CGeneralTextHandler.h" #include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h" #include "../lib/CHeroHandler.h"
#include "../lib/VCMI_Lib.h" #include "../lib/VCMI_Lib.h"

View File

@ -54,8 +54,6 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClient
result = false; result = false;
return; return;
} }
handler.stopServerConnection();
} }
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)

View File

@ -47,7 +47,7 @@
#include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/Filesystem.h"
#include "../../lib/mapping/CMapInfo.h" #include "../../lib/mapping/CMapInfo.h"
#include "../../lib/mapping/CMapHeader.h" #include "../../lib/mapping/CMapHeader.h"
#include "../../lib/serializer/Connection.h" #include "../../lib/CRandomGenerator.h"
ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType) ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
: screenType(ScreenType) : screenType(ScreenType)

View File

@ -43,7 +43,6 @@
#include "../../lib/mapping/CMapHeader.h" #include "../../lib/mapping/CMapHeader.h"
#include "../../lib/mapping/MapFormat.h" #include "../../lib/mapping/MapFormat.h"
#include "../../lib/TerrainHandler.h" #include "../../lib/TerrainHandler.h"
#include "../../lib/serializer/Connection.h"
bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb) bool mapSorter::operator()(const std::shared_ptr<ElementInfo> aaa, const std::shared_ptr<ElementInfo> bbb)
{ {

View File

@ -45,7 +45,6 @@
#include "../../lib/CGeneralTextHandler.h" #include "../../lib/CGeneralTextHandler.h"
#include "../../lib/JsonNode.h" #include "../../lib/JsonNode.h"
#include "../../lib/campaign/CampaignHandler.h" #include "../../lib/campaign/CampaignHandler.h"
#include "../../lib/serializer/Connection.h"
#include "../../lib/serializer/CTypeList.h" #include "../../lib/serializer/CTypeList.h"
#include "../../lib/filesystem/Filesystem.h" #include "../../lib/filesystem/Filesystem.h"
#include "../../lib/filesystem/CCompressedStream.h" #include "../../lib/filesystem/CCompressedStream.h"
@ -559,7 +558,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
{ {
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverConnecting")); textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverConnecting"));
buttonOk->block(true); buttonOk->block(true);
startConnectThread(); startConnection();
} }
else else
{ {
@ -582,7 +581,7 @@ void CSimpleJoinScreen::connectToServer()
buttonOk->block(true); buttonOk->block(true);
GH.stopTextInput(); GH.stopTextInput();
startConnectThread(inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText())); startConnection(inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
} }
void CSimpleJoinScreen::leaveScreen() void CSimpleJoinScreen::leaveScreen()
@ -603,7 +602,7 @@ void CSimpleJoinScreen::onChange(const std::string & newText)
buttonOk->block(inputAddress->getText().empty() || inputPort->getText().empty()); buttonOk->block(inputAddress->getText().empty() || inputPort->getText().empty());
} }
void CSimpleJoinScreen::startConnectThread(const std::string & addr, ui16 port) void CSimpleJoinScreen::startConnection(const std::string & addr, ui16 port)
{ {
#if defined(SINGLE_PROCESS_APP) && defined(VCMI_ANDROID) #if defined(SINGLE_PROCESS_APP) && defined(VCMI_ANDROID)
// in single process build server must use same JNIEnv as client // in single process build server must use same JNIEnv as client
@ -611,35 +610,31 @@ void CSimpleJoinScreen::startConnectThread(const std::string & addr, ui16 port)
// https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md#threads-and-the-java-vm // https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md#threads-and-the-java-vm
CVCMIServer::reuseClientJNIEnv(SDL_AndroidGetJNIEnv()); CVCMIServer::reuseClientJNIEnv(SDL_AndroidGetJNIEnv());
#endif #endif
boost::thread connector(&CSimpleJoinScreen::connectThread, this, addr, port);
connector.detach(); auto const & onConnected = [this]()
} {
// async call to prevent thread race
GH.dispatchMainThread([this](){
if(CSH->state == EClientState::CONNECTION_FAILED)
{
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {});
void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port) textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter"));
{ GH.startTextInput(inputAddress->pos);
setThreadName("connectThread"); buttonOk->block(false);
if(!addr.length()) }
CSH->startLocalServerAndConnect();
if(GH.windows().isTopWindow(this))
{
close();
}
});
};
if(addr.empty())
CSH->startLocalServerAndConnect(onConnected);
else else
CSH->justConnectToServer(addr, port); CSH->justConnectToServer(addr, port, onConnected);
// async call to prevent thread race
GH.dispatchMainThread([this](){
if(CSH->state == EClientState::CONNECTION_FAILED)
{
CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {});
textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter"));
GH.startTextInput(inputAddress->pos);
buttonOk->block(false);
}
if(GH.windows().isTopWindow(this))
{
close();
}
});
} }
CLoadingScreen::CLoadingScreen() CLoadingScreen::CLoadingScreen()

View File

@ -177,8 +177,7 @@ class CSimpleJoinScreen : public WindowBase
void connectToServer(); void connectToServer();
void leaveScreen(); void leaveScreen();
void onChange(const std::string & newText); void onChange(const std::string & newText);
void startConnectThread(const std::string & addr = {}, ui16 port = 0); void startConnection(const std::string & addr = {}, ui16 port = 0);
void connectThread(const std::string & addr, ui16 port);
public: public:
CSimpleJoinScreen(bool host = true); CSimpleJoinScreen(bool host = true);

View File

@ -18,12 +18,12 @@
#include "../../lib/MetaString.h" #include "../../lib/MetaString.h"
#include "../../lib/CConfigHandler.h" #include "../../lib/CConfigHandler.h"
#include "../../lib/network/NetworkClient.h"
LobbyClient::LobbyClient(LobbyWindow * window) LobbyClient::LobbyClient(LobbyWindow * window)
: window(window) : networkClient(std::make_unique<NetworkClient>(*this))
{ , window(window)
{}
}
static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0) static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0)
{ {
@ -100,7 +100,22 @@ void LobbyClient::sendMessage(const JsonNode & data)
std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd); std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd);
sendPacket(payloadBuffer); networkClient->sendPacket(payloadBuffer);
}
void LobbyClient::start(const std::string & host, uint16_t port)
{
networkClient->start(host, port);
}
void LobbyClient::run()
{
networkClient->run();
}
void LobbyClient::poll()
{
networkClient->poll();
} }
LobbyWidget::LobbyWidget(LobbyWindow * window) LobbyWidget::LobbyWidget(LobbyWindow * window)

View File

@ -12,7 +12,7 @@
#include "../gui/InterfaceObjectConfigurable.h" #include "../gui/InterfaceObjectConfigurable.h"
#include "../windows/CWindowObject.h" #include "../windows/CWindowObject.h"
#include "../../lib/network/NetworkClient.h" #include "../../lib/network/NetworkListener.h"
class LobbyWindow; class LobbyWindow;
@ -27,8 +27,9 @@ public:
std::shared_ptr<CTextBox> getGameChat(); std::shared_ptr<CTextBox> getGameChat();
}; };
class LobbyClient : public NetworkClient class LobbyClient : public INetworkClientListener
{ {
std::unique_ptr<NetworkClient> networkClient;
LobbyWindow * window; LobbyWindow * window;
void onPacketReceived(const std::vector<uint8_t> & message) override; void onPacketReceived(const std::vector<uint8_t> & message) override;
@ -40,6 +41,10 @@ public:
explicit LobbyClient(LobbyWindow * window); explicit LobbyClient(LobbyWindow * window);
void sendMessage(const JsonNode & data); void sendMessage(const JsonNode & data);
void start(const std::string & host, uint16_t port);
void run();
void poll();
}; };
class LobbyWindow : public CWindowObject class LobbyWindow : public CWindowObject

View File

@ -478,6 +478,7 @@ macro(add_main_lib TARGET_NAME LIBRARY_TYPE)
${MAIN_LIB_DIR}/network/NetworkClient.h ${MAIN_LIB_DIR}/network/NetworkClient.h
${MAIN_LIB_DIR}/network/NetworkConnection.h ${MAIN_LIB_DIR}/network/NetworkConnection.h
${MAIN_LIB_DIR}/network/NetworkDefines.h ${MAIN_LIB_DIR}/network/NetworkDefines.h
${MAIN_LIB_DIR}/network/NetworkListener.h
${MAIN_LIB_DIR}/network/NetworkServer.h ${MAIN_LIB_DIR}/network/NetworkServer.h
${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h ${MAIN_LIB_DIR}/networkPacks/ArtifactLocation.h

View File

@ -47,8 +47,6 @@
#include "RiverHandler.h" #include "RiverHandler.h"
#include "TerrainHandler.h" #include "TerrainHandler.h"
#include "serializer/Connection.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const

View File

@ -123,7 +123,12 @@ bool LobbyInfo::isClientHost(int clientId) const
return clientId == hostClientId; return clientId == hostClientId;
} }
std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId) bool LobbyInfo::isPlayerHost(const PlayerColor & color) const
{
return vstd::contains(getAllClientPlayers(hostClientId), color);
}
std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId) const
{ {
std::set<PlayerColor> players; std::set<PlayerColor> players;
for(auto & elem : si->playerInfos) for(auto & elem : si->playerInfos)

View File

@ -197,7 +197,6 @@ struct DLL_LINKAGE LobbyState
struct DLL_LINKAGE LobbyInfo : public LobbyState struct DLL_LINKAGE LobbyInfo : public LobbyState
{ {
boost::mutex stateMutex;
std::string uuid; std::string uuid;
LobbyInfo() {} LobbyInfo() {}
@ -205,7 +204,8 @@ struct DLL_LINKAGE LobbyInfo : public LobbyState
void verifyStateBeforeStart(bool ignoreNoHuman = false) const; void verifyStateBeforeStart(bool ignoreNoHuman = false) const;
bool isClientHost(int clientId) const; bool isClientHost(int clientId) const;
std::set<PlayerColor> getAllClientPlayers(int clientId); bool isPlayerHost(const PlayerColor & color) const;
std::set<PlayerColor> getAllClientPlayers(int clientId) const;
std::vector<ui8> getConnectedPlayerIdsForClient(int clientId) const; std::vector<ui8> getConnectedPlayerIdsForClient(int clientId) const;
// Helpers for lobby state access // Helpers for lobby state access

View File

@ -13,10 +13,27 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
NetworkClient::NetworkClient() DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port)
{
boost::asio::io_service io;
NetworkAcceptor acceptor(io);
boost::system::error_code ec;
acceptor.open(boost::asio::ip::tcp::v4(), ec);
if (ec)
return false;
acceptor.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), ec);
if (ec)
return false;
return true;
}
NetworkClient::NetworkClient(INetworkClientListener & listener)
: io(new NetworkService) : io(new NetworkService)
, socket(new NetworkSocket(*io)) , socket(new NetworkSocket(*io))
// , timer(new NetworkTimer(*io)) , listener(listener)
{ {
} }
@ -32,14 +49,14 @@ void NetworkClient::onConnected(const boost::system::error_code & ec)
{ {
if (ec) if (ec)
{ {
onConnectionFailed(ec.message()); listener.onConnectionFailed(ec.message());
return; return;
} }
connection = std::make_shared<NetworkConnection>(socket, *this); connection = std::make_shared<NetworkConnection>(socket, *this);
connection->start(); connection->start();
onConnectionEstablished(); listener.onConnectionEstablished();
} }
void NetworkClient::run() void NetworkClient::run()
@ -59,12 +76,12 @@ void NetworkClient::sendPacket(const std::vector<uint8_t> & message)
void NetworkClient::onDisconnected(const std::shared_ptr<NetworkConnection> & connection) void NetworkClient::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
{ {
onDisconnected(); listener.onDisconnected();
} }
void NetworkClient::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) void NetworkClient::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
{ {
onPacketReceived(message); listener.onPacketReceived(message);
} }

View File

@ -10,9 +10,14 @@
#pragma once #pragma once
#include "NetworkDefines.h" #include "NetworkDefines.h"
#include "NetworkListener.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
/// Function that attempts to open specified port on local system to determine whether port is in use
/// Returns: true if port is free and can be used to receive connections
DLL_LINKAGE bool checkNetworkPortIsFree(const std::string & host, uint16_t port);
class NetworkConnection; class NetworkConnection;
class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener
@ -20,23 +25,19 @@ class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionL
std::shared_ptr<NetworkService> io; std::shared_ptr<NetworkService> io;
std::shared_ptr<NetworkSocket> socket; std::shared_ptr<NetworkSocket> socket;
std::shared_ptr<NetworkConnection> connection; std::shared_ptr<NetworkConnection> connection;
std::shared_ptr<NetworkTimer> timer;
INetworkClientListener & listener;
void onConnected(const boost::system::error_code & ec); void onConnected(const boost::system::error_code & ec);
void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override; void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override;
void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override; void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override;
protected: public:
virtual void onPacketReceived(const std::vector<uint8_t> & message) = 0; NetworkClient(INetworkClientListener & listener);
virtual void onConnectionFailed(const std::string & errorMessage) = 0; virtual ~NetworkClient() = default;
virtual void onConnectionEstablished() = 0;
virtual void onDisconnected() = 0;
void sendPacket(const std::vector<uint8_t> & message); void sendPacket(const std::vector<uint8_t> & message);
public:
NetworkClient();
virtual ~NetworkClient() = default;
void start(const std::string & host, uint16_t port); void start(const std::string & host, uint16_t port);
void run(); void run();

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "NetworkDefines.h" #include "NetworkDefines.h"
#include "NetworkListener.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN

View File

@ -17,17 +17,5 @@ using NetworkService = boost::asio::io_service;
using NetworkSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp>; using NetworkSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp>;
using NetworkAcceptor = boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>; using NetworkAcceptor = boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>;
using NetworkBuffer = boost::asio::streambuf; using NetworkBuffer = boost::asio::streambuf;
using NetworkTimer = boost::asio::steady_timer;
class NetworkConnection;
class DLL_LINKAGE INetworkConnectionListener
{
friend class NetworkConnection;
protected:
virtual void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) = 0;
virtual void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) = 0;
};
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -0,0 +1,50 @@
/*
* NetworkListener.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class NetworkConnection;
class NetworkServer;
class NetworkClient;
class DLL_LINKAGE INetworkConnectionListener
{
friend class NetworkConnection;
protected:
virtual void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) = 0;
virtual void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) = 0;
~INetworkConnectionListener() = default;
};
class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener
{
friend class NetworkServer;
protected:
virtual void onNewConnection(const std::shared_ptr<NetworkConnection> &) = 0;
~INetworkServerListener() = default;
};
class DLL_LINKAGE INetworkClientListener
{
friend class NetworkClient;
protected:
virtual void onPacketReceived(const std::vector<uint8_t> & message) = 0;
virtual void onConnectionFailed(const std::string & errorMessage) = 0;
virtual void onConnectionEstablished() = 0;
virtual void onDisconnected() = 0;
~INetworkClientListener() = default;
};
VCMI_LIB_NAMESPACE_END

View File

@ -13,6 +13,12 @@
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
NetworkServer::NetworkServer(INetworkServerListener & listener)
:listener(listener)
{
}
void NetworkServer::start(uint16_t port) void NetworkServer::start(uint16_t port)
{ {
io = std::make_shared<boost::asio::io_service>(); io = std::make_shared<boost::asio::io_service>();
@ -32,6 +38,11 @@ void NetworkServer::run()
io->run(); io->run();
} }
void NetworkServer::run(std::chrono::milliseconds duration)
{
io->run_for(duration);
}
void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingConnection, const boost::system::error_code & ec) void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingConnection, const boost::system::error_code & ec)
{ {
if(ec) if(ec)
@ -43,7 +54,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
auto connection = std::make_shared<NetworkConnection>(upcomingConnection, *this); auto connection = std::make_shared<NetworkConnection>(upcomingConnection, *this);
connections.insert(connection); connections.insert(connection);
connection->start(); connection->start();
onNewConnection(connection); listener.onNewConnection(connection);
startAsyncAccept(); startAsyncAccept();
} }
@ -56,7 +67,12 @@ void NetworkServer::onDisconnected(const std::shared_ptr<NetworkConnection> & co
{ {
assert(connections.count(connection)); assert(connections.count(connection));
connections.erase(connection); connections.erase(connection);
onConnectionLost(connection); listener.onDisconnected(connection);
}
void NetworkServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
{
listener.onPacketReceived(connection, message);
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -10,6 +10,7 @@
#pragma once #pragma once
#include "NetworkDefines.h" #include "NetworkDefines.h"
#include "NetworkListener.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -21,20 +22,20 @@ class DLL_LINKAGE NetworkServer : boost::noncopyable, public INetworkConnectionL
std::shared_ptr<NetworkAcceptor> acceptor; std::shared_ptr<NetworkAcceptor> acceptor;
std::set<std::shared_ptr<NetworkConnection>> connections; std::set<std::shared_ptr<NetworkConnection>> connections;
INetworkServerListener & listener;
void connectionAccepted(std::shared_ptr<NetworkSocket>, const boost::system::error_code & ec); void connectionAccepted(std::shared_ptr<NetworkSocket>, const boost::system::error_code & ec);
void startAsyncAccept(); void startAsyncAccept();
void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override; void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override;
protected: void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override;
virtual void onNewConnection(const std::shared_ptr<NetworkConnection> &) = 0; public:
virtual void onConnectionLost(const std::shared_ptr<NetworkConnection> &) = 0; explicit NetworkServer(INetworkServerListener & listener);
void sendPacket(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message); void sendPacket(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message);
public:
virtual ~NetworkServer() = default;
void start(uint16_t port); void start(uint16_t port);
void run(std::chrono::milliseconds duration);
void run(); void run();
}; };

View File

@ -51,7 +51,7 @@ struct VectorizedObjectInfo
}; };
/// Base class for serializers capable of reading or writing data /// Base class for serializers capable of reading or writing data
class DLL_LINKAGE CSerializer class DLL_LINKAGE CSerializer : boost::noncopyable
{ {
template<typename Numeric, std::enable_if_t<std::is_arithmetic_v<Numeric>, bool> = true> template<typename Numeric, std::enable_if_t<std::is_arithmetic_v<Numeric>, bool> = true>
static int32_t idToNumber(const Numeric &t) static int32_t idToNumber(const Numeric &t)

View File

@ -10,9 +10,52 @@
#include "StdInc.h" #include "StdInc.h"
#include "Connection.h" #include "Connection.h"
#include "../networkPacks/NetPacksBase.h" #include "BinaryDeserializer.h"
#include "BinarySerializer.h"
#include <boost/asio.hpp> //#include "../networkPacks/NetPacksBase.h"
CConnection::CConnection(std::weak_ptr<NetworkConnection> networkConnection)
{
}
void CConnection::sendPack(const CPack * pack)
{
}
CPack * CConnection::retrievePack(const std::vector<uint8_t> & data)
{
return nullptr;
}
void CConnection::disableStackSendingByID()
{
}
void CConnection::enterLobbyConnectionMode()
{
}
void CConnection::enterGameplayConnectionMode(CGameState * gs)
{
}
int CConnection::write(const void * data, unsigned size)
{
return 0;
}
int CConnection::read(void * data, unsigned size)
{
return 0;
}
#if 0
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@ -362,3 +405,5 @@ std::string CConnection::toString() const
} }
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END
#endif

View File

@ -9,123 +9,84 @@
*/ */
#pragma once #pragma once
#include "BinaryDeserializer.h" #include "CSerializer.h"
#include "BinarySerializer.h"
#if BOOST_VERSION >= 107000 // Boost version >= 1.70
#include <boost/asio.hpp>
using TSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp>;
using TAcceptor = boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>;
#else
namespace boost
{
namespace asio
{
namespace ip
{
class tcp;
}
#if BOOST_VERSION >= 106600 // Boost version >= 1.66
class io_context;
typedef io_context io_service;
#else
class io_service;
#endif
template <typename Protocol> class stream_socket_service;
template <typename Protocol,typename StreamSocketService>
class basic_stream_socket;
template <typename Protocol> class socket_acceptor_service;
template <typename Protocol,typename SocketAcceptorService>
class basic_socket_acceptor;
}
class mutex;
}
typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::stream_socket_service<boost::asio::ip::tcp> > TSocket;
typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::socket_acceptor_service<boost::asio::ip::tcp> > TAcceptor;
#endif
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
class BinaryDeserializer;
class BinarySerializer;
struct CPack; struct CPack;
struct ConnectionBuffers; struct ConnectionBuffers;
class NetworkConnection;
/// Main class for network communication /// Main class for network communication
/// Allows establishing connection and bidirectional read-write /// Allows establishing connection and bidirectional read-write
class DLL_LINKAGE CConnection class DLL_LINKAGE CConnection : public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
: public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
{ {
void init(); /// Non-owning pointer to underlying connection
void reportState(vstd::CLoggerBase * out) override; std::weak_ptr<NetworkConnection> networkConnection;
// void init();
// void reportState(vstd::CLoggerBase * out) override;
//
int write(const void * data, unsigned size) override; int write(const void * data, unsigned size) override;
int read(void * data, unsigned size) override; int read(void * data, unsigned size) override;
void flushBuffers(); // void flushBuffers();
//
std::shared_ptr<boost::asio::io_service> io_service; //can be empty if connection made from socket // bool enableBufferedWrite;
// bool enableBufferedRead;
bool enableBufferedWrite; // std::unique_ptr<ConnectionBuffers> connectionBuffers;
bool enableBufferedRead; //
std::unique_ptr<ConnectionBuffers> connectionBuffers; std::unique_ptr<BinaryDeserializer> iser;
std::unique_ptr<BinarySerializer> oser;
//
// std::string contactUuid;
// std::string name; //who uses this connection
public: public:
BinaryDeserializer iser;
BinarySerializer oser;
std::shared_ptr<boost::mutex> mutexRead;
std::shared_ptr<boost::mutex> mutexWrite;
std::shared_ptr<TSocket> socket;
bool connected;
bool myEndianess, contactEndianess; //true if little endian, if endianness is different we'll have to revert received multi-byte vars
std::string contactUuid;
std::string name; //who uses this connection
std::string uuid; std::string uuid;
int connectionID; int connectionID;
std::shared_ptr<boost::thread> handler;
CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID); CConnection(std::weak_ptr<NetworkConnection> networkConnection);
CConnection(const std::shared_ptr<TAcceptor> & acceptor, const std::shared_ptr<boost::asio::io_service> & Io_service, std::string Name, std::string UUID); // CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID);
CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket // CConnection(const std::shared_ptr<TAcceptor> & acceptor, const std::shared_ptr<boost::asio::io_service> & Io_service, std::string Name, std::string UUID);
// CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket
// virtual ~CConnection();
void close(); // void close();
bool isOpen() const; // bool isOpen() const;
template<class T> //
CConnection &operator&(const T&); // CPack * retrievePack();
virtual ~CConnection();
CPack * retrievePack();
void sendPack(const CPack * pack); void sendPack(const CPack * pack);
CPack * retrievePack(const std::vector<uint8_t> & data);
// std::vector<uint8_t> serializePack(const CPack * pack);
//
void disableStackSendingByID(); void disableStackSendingByID();
void enableStackSendingByID(); // void enableStackSendingByID();
void disableSmartPointerSerialization(); // void disableSmartPointerSerialization();
void enableSmartPointerSerialization(); // void enableSmartPointerSerialization();
void disableSmartVectorMemberSerialization(); // void disableSmartVectorMemberSerialization();
void enableSmartVectorMemberSerializatoin(); // void enableSmartVectorMemberSerializatoin();
//
void enterLobbyConnectionMode(); void enterLobbyConnectionMode();
void enterGameplayConnectionMode(CGameState * gs); void enterGameplayConnectionMode(CGameState * gs);
//
std::string toString() const; // std::string toString() const;
//
template<class T> // template<class T>
CConnection & operator>>(T &t) // CConnection & operator>>(T &t)
{ // {
iser & t; // iser & t;
return * this; // return * this;
} // }
//
template<class T> // template<class T>
CConnection & operator<<(const T &t) // CConnection & operator<<(const T &t)
{ // {
oser & t; // oser & t;
return * this; // return * this;
} // }
}; };
VCMI_LIB_NAMESPACE_END VCMI_LIB_NAMESPACE_END

View File

@ -104,14 +104,14 @@ void LobbyServer::sendMessage(const std::shared_ptr<NetworkConnection> & target,
std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd); std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd);
sendPacket(target, payloadBuffer); networkServer->sendPacket(target, payloadBuffer);
} }
void LobbyServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection) void LobbyServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
{ {
} }
void LobbyServer::onConnectionLost(const std::shared_ptr<NetworkConnection> & connection) void LobbyServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
{ {
activeAccounts.erase(connection); activeAccounts.erase(connection);
} }
@ -169,9 +169,20 @@ void LobbyServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & co
LobbyServer::LobbyServer() LobbyServer::LobbyServer()
: database(new LobbyDatabase()) : database(new LobbyDatabase())
, networkServer(new NetworkServer(*this))
{ {
} }
void LobbyServer::start(uint16_t port)
{
networkServer->start(port);
}
void LobbyServer::run()
{
networkServer->run();
}
int main(int argc, const char * argv[]) int main(int argc, const char * argv[])
{ {
LobbyServer server; LobbyServer server;

View File

@ -41,7 +41,7 @@ public:
std::vector<ChatMessage> getRecentMessageHistory(); std::vector<ChatMessage> getRecentMessageHistory();
}; };
class LobbyServer : public NetworkServer class LobbyServer : public INetworkServerListener
{ {
struct AccountState struct AccountState
{ {
@ -51,12 +51,16 @@ class LobbyServer : public NetworkServer
std::map<std::shared_ptr<NetworkConnection>, AccountState> activeAccounts; std::map<std::shared_ptr<NetworkConnection>, AccountState> activeAccounts;
std::unique_ptr<LobbyDatabase> database; std::unique_ptr<LobbyDatabase> database;
std::unique_ptr<NetworkServer> networkServer;
void onNewConnection(const std::shared_ptr<NetworkConnection> &) override; void onNewConnection(const std::shared_ptr<NetworkConnection> &) override;
void onConnectionLost(const std::shared_ptr<NetworkConnection> &) override; void onDisconnected(const std::shared_ptr<NetworkConnection> &) override;
void onPacketReceived(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message) override; void onPacketReceived(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message) override;
void sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json); void sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json);
public: public:
LobbyServer(); LobbyServer();
void start(uint16_t port);
void run();
}; };

View File

@ -61,6 +61,7 @@
#include "../lib/serializer/CSaveFile.h" #include "../lib/serializer/CSaveFile.h"
#include "../lib/serializer/CLoadFile.h" #include "../lib/serializer/CLoadFile.h"
#include "../lib/serializer/Connection.h"
#include "../lib/spells/CSpellHandler.h" #include "../lib/spells/CSpellHandler.h"
@ -969,11 +970,11 @@ void CGameHandler::onNewTurn()
synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that synchronizeArtifactHandlerLists(); //new day events may have changed them. TODO better of managing that
} }
void CGameHandler::run(bool resume) void CGameHandler::start(bool resume)
{ {
LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume); LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
for (auto cc : lobby->connections) for (auto cc : lobby->activeConnections)
{ {
auto players = lobby->getAllClientPlayers(cc->connectionID); auto players = lobby->getAllClientPlayers(cc->connectionID);
std::stringstream sbuffer; std::stringstream sbuffer;
@ -1004,18 +1005,11 @@ void CGameHandler::run(bool resume)
events::GameResumed::defaultExecute(serverEventBus.get()); events::GameResumed::defaultExecute(serverEventBus.get());
turnOrder->onGameStarted(); turnOrder->onGameStarted();
}
//wait till game is done void CGameHandler::tick(int millisecondsPassed)
auto clockLast = std::chrono::steady_clock::now(); {
while(lobby->getState() == EServerState::GAMEPLAY) turnTimerHandler.update(millisecondsPassed);
{
const auto clockDuration = std::chrono::steady_clock::now() - clockLast;
const int timePassed = std::chrono::duration_cast<std::chrono::milliseconds>(clockDuration).count();
clockLast += clockDuration;
turnTimerHandler.update(timePassed);
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
}
} }
void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h) void CGameHandler::giveSpells(const CGTownInstance *t, const CGHeroInstance *h)
@ -1677,13 +1671,8 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
void CGameHandler::sendToAllClients(CPackForClient * pack) void CGameHandler::sendToAllClients(CPackForClient * pack)
{ {
logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name()); logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name());
for (auto c : lobby->connections) for (auto c : lobby->activeConnections)
{
if(!c->isOpen())
continue;
c->sendPack(pack); c->sendPack(pack);
}
} }
void CGameHandler::sendAndApply(CPackForClient * pack) void CGameHandler::sendAndApply(CPackForClient * pack)

View File

@ -261,7 +261,8 @@ public:
bool isPlayerOwns(CPackForServer * pack, ObjectInstanceID id); bool isPlayerOwns(CPackForServer * pack, ObjectInstanceID id);
void run(bool resume); void start(bool resume);
void tick(int millisecondsPassed);
bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot); bool sacrificeArtifact(const IMarket * m, const CGHeroInstance * hero, const std::vector<ArtifactPosition> & slot);
void spawnWanderingMonsters(CreatureID creatureID); void spawnWanderingMonsters(CreatureID creatureID);

View File

@ -8,12 +8,11 @@
* *
*/ */
#include "StdInc.h" #include "StdInc.h"
#include <boost/asio.hpp> #include <boost/program_options.hpp>
#include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/Filesystem.h"
#include "../lib/campaign/CampaignState.h" #include "../lib/campaign/CampaignState.h"
#include "../lib/CThreadHelper.h" #include "../lib/CThreadHelper.h"
#include "../lib/serializer/Connection.h"
#include "../lib/CArtHandler.h" #include "../lib/CArtHandler.h"
#include "../lib/CGeneralTextHandler.h" #include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h" #include "../lib/CHeroHandler.h"
@ -37,12 +36,15 @@
#include "CGameHandler.h" #include "CGameHandler.h"
#include "processors/PlayerMessageProcessor.h" #include "processors/PlayerMessageProcessor.h"
#include "../lib/mapping/CMapInfo.h" #include "../lib/mapping/CMapInfo.h"
#include "../lib/network/NetworkServer.h"
#include "../lib/network/NetworkClient.h"
#include "../lib/GameConstants.h" #include "../lib/GameConstants.h"
#include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/CConfigHandler.h" #include "../lib/CConfigHandler.h"
#include "../lib/ScopeGuard.h" #include "../lib/ScopeGuard.h"
#include "../lib/serializer/CMemorySerializer.h" #include "../lib/serializer/CMemorySerializer.h"
#include "../lib/serializer/Cast.h" #include "../lib/serializer/Cast.h"
#include "../lib/serializer/Connection.h"
#include "../lib/UnlockGuard.h" #include "../lib/UnlockGuard.h"
@ -81,11 +83,8 @@ public:
if(checker.getResult()) if(checker.getResult())
{ {
boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);
ApplyOnServerNetPackVisitor applier(*srv); ApplyOnServerNetPackVisitor applier(*srv);
ptr->visit(applier); ptr->visit(applier);
return applier.getResult(); return applier.getResult();
} }
else else
@ -117,48 +116,101 @@ public:
} }
}; };
class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
{
private:
CVCMIServer & handler;
std::shared_ptr<CGameHandler> gh;
public:
CVCMIServerPackVisitor(CVCMIServer & handler, std::shared_ptr<CGameHandler> gh)
:handler(handler), gh(gh)
{
}
virtual bool callTyped() override { return false; }
virtual void visitForLobby(CPackForLobby & packForLobby) override
{
handler.handleReceivedPack(std::unique_ptr<CPackForLobby>(&packForLobby));
}
virtual void visitForServer(CPackForServer & serverPack) override
{
if (gh)
gh->handleReceivedPack(&serverPack);
else
logNetwork->error("Received pack for game server while in lobby!");
}
virtual void visitForClient(CPackForClient & clientPack) override
{
}
};
std::string SERVER_NAME_AFFIX = "server"; std::string SERVER_NAME_AFFIX = "server";
std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')'; std::string SERVER_NAME = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')';
CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts) CVCMIServer::CVCMIServer(boost::program_options::variables_map & opts)
: port(3030), io(std::make_shared<boost::asio::io_service>()), state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false) : state(EServerState::LOBBY), cmdLineOptions(opts), currentClientId(1), currentPlayerId(1), restartGameplay(false)
{ {
uuid = boost::uuids::to_string(boost::uuids::random_generator()()); uuid = boost::uuids::to_string(boost::uuids::random_generator()());
logNetwork->trace("CVCMIServer created! UUID: %s", uuid); logNetwork->trace("CVCMIServer created! UUID: %s", uuid);
applier = std::make_shared<CApplier<CBaseForServerApply>>(); applier = std::make_shared<CApplier<CBaseForServerApply>>();
registerTypesLobbyPacks(*applier); registerTypesLobbyPacks(*applier);
uint16_t port = 3030;
if(cmdLineOptions.count("port")) if(cmdLineOptions.count("port"))
port = cmdLineOptions["port"].as<ui16>(); port = cmdLineOptions["port"].as<uint16_t>();
logNetwork->info("Port %d will be used", port); logNetwork->info("Port %d will be used", port);
try
{ networkServer = std::make_unique<NetworkServer>(*this);
acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)); networkServer->start(port);
}
catch(...)
{
logNetwork->info("Port %d is busy, trying to use random port instead", port);
if(cmdLineOptions.count("run-by-client"))
{
logNetwork->error("Port must be specified when run-by-client is used!!");
#if (defined(__ANDROID_API__) && __ANDROID_API__ < 21) || (defined(__MINGW32__)) || defined(VCMI_APPLE)
::exit(0);
#else
std::quick_exit(0);
#endif
}
acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
port = acceptor->local_endpoint().port();
}
logNetwork->info("Listening for connections at port %d", port); logNetwork->info("Listening for connections at port %d", port);
} }
CVCMIServer::~CVCMIServer() CVCMIServer::~CVCMIServer() = default;
{
announceQueue.clear();
if(announceLobbyThread) void CVCMIServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
announceLobbyThread->join(); {
if (activeConnections.empty())
establishOutgoingConnection();
if(state == EServerState::LOBBY)
activeConnections.push_back(std::make_shared<CConnection>(connection));//, SERVER_NAME, uuid);)
// TODO: else: deny connection
// TODO: else: try to reconnect / send state to reconnected client
}
void CVCMIServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
{
std::shared_ptr<CConnection> c = findConnection(connection);
CPack * pack = c->retrievePack(message);
pack->c = c;
CVCMIServerPackVisitor visitor(*this, this->gh);
pack->visit(visitor);
//FIXME: delete pack?
}
void CVCMIServer::onPacketReceived(const std::vector<uint8_t> & message)
{
//TODO: handle pack received from lobby
}
void CVCMIServer::onConnectionFailed(const std::string & errorMessage)
{
//TODO: handle failure to connect to lobby
}
void CVCMIServer::onConnectionEstablished()
{
//TODO: handle connection to lobby - login?
}
void CVCMIServer::onDisconnected()
{
//TODO: handle disconnection from lobby
} }
void CVCMIServer::setState(EServerState value) void CVCMIServer::setState(EServerState value)
@ -171,101 +223,60 @@ EServerState CVCMIServer::getState() const
return state.load(); return state.load();
} }
std::shared_ptr<CConnection> CVCMIServer::findConnection(const std::shared_ptr<NetworkConnection> & netConnection)
{
//TODO
assert(0);
return nullptr;
}
void CVCMIServer::run() void CVCMIServer::run()
{ {
#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
if(!restartGameplay) if(!restartGameplay)
{ {
this->announceLobbyThread = std::make_unique<boost::thread>(&CVCMIServer::threadAnnounceLobby, this);
startAsyncAccept();
if(!remoteConnectionsThread && cmdLineOptions.count("lobby"))
{
remoteConnectionsThread = std::make_unique<boost::thread>(&CVCMIServer::establishRemoteConnections, this);
}
#if defined(VCMI_ANDROID)
#ifndef SINGLE_PROCESS_APP
CAndroidVMHelper vmHelper; CAndroidVMHelper vmHelper;
vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady"); vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
}
#endif #endif
#endif
}
while(state == EServerState::LOBBY || state == EServerState::GAMEPLAY_STARTING) static const int serverUpdateIntervalMilliseconds = 50;
boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); auto clockInitial = std::chrono::steady_clock::now();
int64_t msPassedLast = 0;
logNetwork->info("Thread handling connections ended");
if(state == EServerState::GAMEPLAY)
{
gh->run(si->mode == StartInfo::LOAD_GAME);
}
while(state == EServerState::GAMEPLAY_ENDED)
boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
}
void CVCMIServer::establishRemoteConnections()
{
setThreadName("establishConnection");
//wait for host connection
while(connections.empty())
boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
uuid = cmdLineOptions["lobby-uuid"].as<std::string>();
int numOfConnections = cmdLineOptions["connections"].as<ui16>();
for(int i = 0; i < numOfConnections; ++i)
connectToRemote();
}
void CVCMIServer::connectToRemote()
{
std::shared_ptr<CConnection> c;
try
{
auto address = cmdLineOptions["lobby"].as<std::string>();
int port = cmdLineOptions["lobby-port"].as<ui16>();
logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid);
c = std::make_shared<CConnection>(address, port, SERVER_NAME, uuid);
}
catch(...)
{
logNetwork->error("\nCannot establish remote connection!");
}
if(c)
{
connections.insert(c);
remoteConnections.insert(c);
c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
}
}
void CVCMIServer::threadAnnounceLobby()
{
setThreadName("announceLobby");
while(state != EServerState::SHUTDOWN) while(state != EServerState::SHUTDOWN)
{ {
{ networkServer->run(std::chrono::milliseconds(serverUpdateIntervalMilliseconds));
boost::unique_lock<boost::recursive_mutex> myLock(mx);
while(!announceQueue.empty())
{
announcePack(std::move(announceQueue.front()));
announceQueue.pop_front();
}
if(acceptor) const auto clockNow = std::chrono::steady_clock::now();
{ const auto clockPassed = clockNow - clockInitial;
io->reset(); const int64_t msPassedNow = std::chrono::duration_cast<std::chrono::milliseconds>(clockPassed).count();
io->poll(); const int64_t msDelta = msPassedNow - msPassedLast;
} msPassedLast = msPassedNow;
}
boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); if (state == EServerState::GAMEPLAY)
gh->tick(msDelta);
} }
} }
void CVCMIServer::establishOutgoingConnection()
{
if(!cmdLineOptions.count("lobby"))
return;
uuid = cmdLineOptions["lobby-uuid"].as<std::string>();
auto address = cmdLineOptions["lobby"].as<std::string>();
int port = cmdLineOptions["lobby-port"].as<ui16>();
logNetwork->info("Establishing connection to remote at %s:%d with uuid %s", address, port, uuid);
outgoingConnection = std::make_unique<NetworkClient>(*this);
outgoingConnection->start(address, port);//, SERVER_NAME, uuid);
// connections.insert(c);
// remoteConnections.insert(c);
}
void CVCMIServer::prepareToRestart() void CVCMIServer::prepareToRestart()
{ {
if(state == EServerState::GAMEPLAY) if(state == EServerState::GAMEPLAY)
@ -280,11 +291,9 @@ void CVCMIServer::prepareToRestart()
campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0)); campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0));
campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1); campaignBonus = si->campState->getBonusID(campaignMap).value_or(-1);
} }
// FIXME: dirry hack to make sure old CGameHandler::run is finished
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
} }
for(auto c : connections) for(auto c : activeConnections)
{ {
c->enterLobbyConnectionMode(); c->enterLobbyConnectionMode();
c->disableStackSendingByID(); c->disableStackSendingByID();
@ -306,10 +315,11 @@ bool CVCMIServer::prepareToStartGame()
{ {
if(progressTracking.get() != currentProgress) if(progressTracking.get() != currentProgress)
{ {
//FIXME: UNGUARDED MULTITHREADED ACCESS!!!
currentProgress = progressTracking.get(); currentProgress = progressTracking.get();
std::unique_ptr<LobbyLoadProgress> loadProgress(new LobbyLoadProgress); std::unique_ptr<LobbyLoadProgress> loadProgress(new LobbyLoadProgress);
loadProgress->progress = currentProgress; loadProgress->progress = currentProgress;
addToAnnounceQueue(std::move(loadProgress)); announcePack(std::move(loadProgress));
} }
boost::this_thread::sleep(boost::posix_time::milliseconds(50)); boost::this_thread::sleep(boost::posix_time::milliseconds(50));
} }
@ -355,149 +365,55 @@ bool CVCMIServer::prepareToStartGame()
return true; return true;
} }
void CVCMIServer::startGameImmidiately() void CVCMIServer::startGameImmediately()
{ {
for(auto c : connections) for(auto c : activeConnections)
c->enterGameplayConnectionMode(gh->gs); c->enterGameplayConnectionMode(gh->gs);
gh->start(si->mode == StartInfo::LOAD_GAME);
state = EServerState::GAMEPLAY; state = EServerState::GAMEPLAY;
} }
void CVCMIServer::startAsyncAccept() void CVCMIServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
{ {
assert(!upcomingConnection); logNetwork->error("Network error receiving a pack. Connection has been closed");
assert(acceptor);
#if BOOST_VERSION >= 107000 // Boost version >= 1.70 std::shared_ptr<CConnection> c = findConnection(connection);
upcomingConnection = std::make_shared<TSocket>(acceptor->get_executor());
#else
upcomingConnection = std::make_shared<TSocket>(acceptor->get_io_service());
#endif
acceptor->async_accept(*upcomingConnection, std::bind(&CVCMIServer::connectionAccepted, this, _1));
}
void CVCMIServer::connectionAccepted(const boost::system::error_code & ec) inactiveConnections.push_back(c);
{ vstd::erase(activeConnections, c);
if(ec)
if(activeConnections.empty() || hostClientId == c->connectionID)
state = EServerState::SHUTDOWN;
if(gh && state == EServerState::GAMEPLAY)
{ {
if(state != EServerState::SHUTDOWN) gh->handleClientDisconnection(c);
logNetwork->info("Something wrong during accepting: %s", ec.message());
return;
}
try
{
if(state == EServerState::LOBBY || !hangingConnections.empty())
{
logNetwork->info("We got a new connection! :)");
auto c = std::make_shared<CConnection>(upcomingConnection, SERVER_NAME, uuid);
upcomingConnection.reset();
connections.insert(c);
c->handler = std::make_shared<boost::thread>(&CVCMIServer::threadHandleClient, this, c);
}
}
catch(std::exception & e)
{
logNetwork->error("Failure processing new connection! %s", e.what());
upcomingConnection.reset();
}
startAsyncAccept();
}
class CVCMIServerPackVisitor : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
{
private:
CVCMIServer & handler;
std::shared_ptr<CGameHandler> gh;
public:
CVCMIServerPackVisitor(CVCMIServer & handler, std::shared_ptr<CGameHandler> gh)
:handler(handler), gh(gh)
{
}
virtual bool callTyped() override { return false; }
virtual void visitForLobby(CPackForLobby & packForLobby) override
{
handler.handleReceivedPack(std::unique_ptr<CPackForLobby>(&packForLobby));
}
virtual void visitForServer(CPackForServer & serverPack) override
{
if (gh)
gh->handleReceivedPack(&serverPack);
else
logNetwork->error("Received pack for game server while in lobby!");
}
virtual void visitForClient(CPackForClient & clientPack) override
{
}
};
void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
{
setThreadName("handleClient");
c->enterLobbyConnectionMode();
while(c->connected)
{
CPack * pack;
try
{
pack = c->retrievePack();
pack->c = c;
}
catch(boost::system::system_error & e)
{
if (e.code() == boost::asio::error::eof)
logNetwork->error("Network error receiving a pack. Connection has been closed");
else
logNetwork->error("Network error receiving a pack. Connection %s dies. What happened: %s", c->toString(), e.what());
hangingConnections.insert(c);
connections.erase(c);
if(connections.empty() || hostClient == c)
state = EServerState::SHUTDOWN;
if(gh && state == EServerState::GAMEPLAY)
{
gh->handleClientDisconnection(c);
}
break;
}
CVCMIServerPackVisitor visitor(*this, this->gh);
pack->visit(visitor);
} }
boost::unique_lock<boost::recursive_mutex> queueLock(mx); boost::unique_lock<boost::recursive_mutex> queueLock(mx);
if(c->connected) // if(c->connected)
{ // {
auto lcd = std::make_unique<LobbyClientDisconnected>(); // auto lcd = std::make_unique<LobbyClientDisconnected>();
lcd->c = c; // lcd->c = c;
lcd->clientId = c->connectionID; // lcd->clientId = c->connectionID;
handleReceivedPack(std::move(lcd)); // handleReceivedPack(std::move(lcd));
} // }
//
logNetwork->info("Thread listening for %s ended", c->toString()); // logNetwork->info("Thread listening for %s ended", c->toString());
c->handler.reset();
} }
void CVCMIServer::handleReceivedPack(std::unique_ptr<CPackForLobby> pack) void CVCMIServer::handleReceivedPack(std::unique_ptr<CPackForLobby> pack)
{ {
CBaseForServerApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack.get())); CBaseForServerApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack.get()));
if(apply->applyOnServerBefore(this, pack.get())) if(apply->applyOnServerBefore(this, pack.get()))
addToAnnounceQueue(std::move(pack)); announcePack(std::move(pack));
} }
void CVCMIServer::announcePack(std::unique_ptr<CPackForLobby> pack) void CVCMIServer::announcePack(std::unique_ptr<CPackForLobby> pack)
{ {
for(auto c : connections) for(auto c : activeConnections)
{ {
// FIXME: we need to avoid sending something to client that not yet get answer for LobbyClientConnected // FIXME: we need to avoid sending something to client that not yet get answer for LobbyClientConnected
// Until UUID set we only pass LobbyClientConnected to this client // Until UUID set we only pass LobbyClientConnected to this client
@ -515,7 +431,7 @@ void CVCMIServer::announceMessage(const std::string & txt)
logNetwork->info("Show message: %s", txt); logNetwork->info("Show message: %s", txt);
auto cm = std::make_unique<LobbyShowMessage>(); auto cm = std::make_unique<LobbyShowMessage>();
cm->message = txt; cm->message = txt;
addToAnnounceQueue(std::move(cm)); announcePack(std::move(cm));
} }
void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName) void CVCMIServer::announceTxt(const std::string & txt, const std::string & playerName)
@ -524,25 +440,18 @@ void CVCMIServer::announceTxt(const std::string & txt, const std::string & playe
auto cm = std::make_unique<LobbyChatMessage>(); auto cm = std::make_unique<LobbyChatMessage>();
cm->playerName = playerName; cm->playerName = playerName;
cm->message = txt; cm->message = txt;
addToAnnounceQueue(std::move(cm)); announcePack(std::move(cm));
}
void CVCMIServer::addToAnnounceQueue(std::unique_ptr<CPackForLobby> pack)
{
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
announceQueue.push_back(std::move(pack));
} }
bool CVCMIServer::passHost(int toConnectionId) bool CVCMIServer::passHost(int toConnectionId)
{ {
for(auto c : connections) for(auto c : activeConnections)
{ {
if(isClientHost(c->connectionID)) if(isClientHost(c->connectionID))
continue; continue;
if(c->connectionID != toConnectionId) if(c->connectionID != toConnectionId)
continue; continue;
hostClient = c;
hostClientId = c->connectionID; hostClientId = c->connectionID;
announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId)); announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId));
return true; return true;
@ -555,9 +464,8 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
if(state == EServerState::LOBBY) if(state == EServerState::LOBBY)
c->connectionID = currentClientId++; c->connectionID = currentClientId++;
if(!hostClient) if(hostClientId == -1)
{ {
hostClient = c;
hostClientId = c->connectionID; hostClientId = c->connectionID;
si->mode = mode; si->mode = mode;
} }
@ -592,8 +500,9 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c) void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
{ {
connections -= c; vstd::erase(activeConnections, c);
if(connections.empty() || hostClient == c)
if(activeConnections.empty() || hostClientId == c->connectionID)
{ {
state = EServerState::SHUTDOWN; state = EServerState::SHUTDOWN;
return; return;
@ -626,7 +535,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
if(gh && si && state == EServerState::GAMEPLAY) if(gh && si && state == EServerState::GAMEPLAY)
{ {
gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText); gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText);
gh->connections[playerSettings->color].insert(hostClient); // gh->connections[playerSettings->color].insert(hostClient);
startAiPack.players.push_back(playerSettings->color); startAiPack.players.push_back(playerSettings->color);
} }
} }
@ -753,7 +662,6 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
void CVCMIServer::updateAndPropagateLobbyState() void CVCMIServer::updateAndPropagateLobbyState()
{ {
boost::unique_lock<boost::mutex> stateLock(stateMutex);
// Update player settings for RMG // Update player settings for RMG
// TODO: find appropriate location for this code // TODO: find appropriate location for this code
if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME) if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
@ -772,7 +680,7 @@ void CVCMIServer::updateAndPropagateLobbyState()
auto lus = std::make_unique<LobbyUpdateState>(); auto lus = std::make_unique<LobbyUpdateState>();
lus->state = *this; lus->state = *this;
addToAnnounceQueue(std::move(lus)); announcePack(std::move(lus));
} }
void CVCMIServer::setPlayer(PlayerColor clickedColor) void CVCMIServer::setPlayer(PlayerColor clickedColor)
@ -1188,32 +1096,9 @@ int main(int argc, const char * argv[])
cond->notify_one(); cond->notify_one();
#endif #endif
try CVCMIServer server(opts);
{ server.run();
boost::asio::io_service io_service;
CVCMIServer server(opts);
try
{
while(server.getState() != EServerState::SHUTDOWN)
{
server.run();
}
io_service.run();
}
catch(boost::system::system_error & e) //for boost errors just log, not crash - probably client shut down connection
{
logNetwork->error(e.what());
server.setState(EServerState::SHUTDOWN);
}
}
catch(boost::system::system_error & e)
{
logNetwork->error(e.what());
//catch any startup errors (e.g. can't access port) errors
//and return non-zero status so client can detect error
throw;
}
#if VCMI_ANDROID_DUAL_PROCESS #if VCMI_ANDROID_DUAL_PROCESS
CAndroidVMHelper envHelper; CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer"); envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");

View File

@ -9,10 +9,10 @@
*/ */
#pragma once #pragma once
#include "../lib/serializer/Connection.h" #include "../lib/network/NetworkListener.h"
#include "../lib/StartInfo.h" #include "../lib/StartInfo.h"
#include <boost/program_options.hpp> #include <boost/program_options/variables_map.hpp>
#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP) #if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
#define VCMI_ANDROID_DUAL_PROCESS 1 #define VCMI_ANDROID_DUAL_PROCESS 1
@ -24,6 +24,7 @@ class CMapInfo;
struct CPackForLobby; struct CPackForLobby;
class CConnection;
struct StartInfo; struct StartInfo;
struct LobbyInfo; struct LobbyInfo;
struct PlayerSettings; struct PlayerSettings;
@ -46,53 +47,66 @@ enum class EServerState : ui8
SHUTDOWN SHUTDOWN
}; };
class CVCMIServer : public LobbyInfo class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkClientListener
{ {
/// Network server instance that receives and processes incoming connections on active socket
std::unique_ptr<NetworkServer> networkServer;
/// Outgoing connection established by this server to game lobby for proxy mode (only in lobby game)
std::unique_ptr<NetworkClient> outgoingConnection;
public:
/// List of all active connections
std::vector<std::shared_ptr<CConnection>> activeConnections;
private:
/// List of all connections that were closed (but can still reconnect later)
std::vector<std::shared_ptr<CConnection>> inactiveConnections;
std::atomic<bool> restartGameplay; // FIXME: this is just a hack std::atomic<bool> restartGameplay; // FIXME: this is just a hack
std::shared_ptr<boost::asio::io_service> io;
std::shared_ptr<TAcceptor> acceptor;
std::shared_ptr<TSocket> upcomingConnection;
std::list<std::unique_ptr<CPackForLobby>> announceQueue;
boost::recursive_mutex mx; boost::recursive_mutex mx;
std::shared_ptr<CApplier<CBaseForServerApply>> applier; std::shared_ptr<CApplier<CBaseForServerApply>> applier;
std::unique_ptr<boost::thread> announceLobbyThread;
std::unique_ptr<boost::thread> remoteConnectionsThread; std::unique_ptr<boost::thread> remoteConnectionsThread;
std::atomic<EServerState> state; std::atomic<EServerState> state;
public: // INetworkServerListener impl
std::shared_ptr<CGameHandler> gh; void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override;
ui16 port; void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override;
void onNewConnection(const std::shared_ptr<NetworkConnection> &) override;
boost::program_options::variables_map cmdLineOptions; // INetworkClientListener impl
std::set<std::shared_ptr<CConnection>> connections; void onPacketReceived(const std::vector<uint8_t> & message) override;
std::set<std::shared_ptr<CConnection>> remoteConnections; void onConnectionFailed(const std::string & errorMessage) override;
std::set<std::shared_ptr<CConnection>> hangingConnections; //keep connections of players disconnected during the game void onConnectionEstablished() override;
void onDisconnected() override;
void establishOutgoingConnection();
std::shared_ptr<CConnection> findConnection(const std::shared_ptr<NetworkConnection> &);
std::atomic<int> currentClientId; std::atomic<int> currentClientId;
std::atomic<ui8> currentPlayerId; std::atomic<ui8> currentPlayerId;
std::shared_ptr<CConnection> hostClient;
public:
std::shared_ptr<CGameHandler> gh;
boost::program_options::variables_map cmdLineOptions;
CVCMIServer(boost::program_options::variables_map & opts); CVCMIServer(boost::program_options::variables_map & opts);
~CVCMIServer(); ~CVCMIServer();
void run(); void run();
bool prepareToStartGame(); bool prepareToStartGame();
void prepareToRestart(); void prepareToRestart();
void startGameImmidiately(); void startGameImmediately();
void establishRemoteConnections();
void connectToRemote();
void startAsyncAccept();
void connectionAccepted(const boost::system::error_code & ec);
void threadHandleClient(std::shared_ptr<CConnection> c); void threadHandleClient(std::shared_ptr<CConnection> c);
void threadAnnounceLobby();
void handleReceivedPack(std::unique_ptr<CPackForLobby> pack);
void announcePack(std::unique_ptr<CPackForLobby> pack); void announcePack(std::unique_ptr<CPackForLobby> pack);
bool passHost(int toConnectionId); bool passHost(int toConnectionId);
void announceTxt(const std::string & txt, const std::string & playerName = "system"); void announceTxt(const std::string & txt, const std::string & playerName = "system");
void announceMessage(const std::string & txt);
void addToAnnounceQueue(std::unique_ptr<CPackForLobby> pack);
void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const; void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const;
void updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpt = {}); void updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpt = {});
@ -101,6 +115,11 @@ public:
void clientDisconnected(std::shared_ptr<CConnection> c); void clientDisconnected(std::shared_ptr<CConnection> c);
void reconnectPlayer(int connId); void reconnectPlayer(int connId);
public:
void announceMessage(const std::string & txt);
void handleReceivedPack(std::unique_ptr<CPackForLobby> pack);
void updateAndPropagateLobbyState(); void updateAndPropagateLobbyState();
void setState(EServerState value); void setState(EServerState value);

View File

@ -13,11 +13,9 @@
#include "CVCMIServer.h" #include "CVCMIServer.h"
#include "CGameHandler.h" #include "CGameHandler.h"
#include "../lib/serializer/Connection.h"
#include "../lib/StartInfo.h" #include "../lib/StartInfo.h"
// Campaigns
#include "../lib/campaign/CampaignState.h" #include "../lib/campaign/CampaignState.h"
#include "../lib/serializer/Connection.h"
void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack) void ClientPermissionsCheckerNetPackVisitor::visitForLobby(CPackForLobby & pack)
{ {
@ -38,67 +36,18 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitForLobby(CPackForLobby & pac
void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
{ {
if(srv.gh)
{
for(auto & connection : srv.hangingConnections)
{
if(connection->uuid == pack.uuid)
{
{
result = true;
return;
}
}
}
}
if(srv.getState() == EServerState::LOBBY) if(srv.getState() == EServerState::LOBBY)
{ {
result = true; result = true;
return; return;
} }
//disconnect immediately and ignore this client
srv.connections.erase(pack.c);
if(pack.c && pack.c->isOpen())
{
pack.c->close();
pack.c->connected = false;
}
{
result = false; result = false;
return; return;
}
} }
void ApplyOnServerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack) void ApplyOnServerNetPackVisitor::visitLobbyClientConnected(LobbyClientConnected & pack)
{ {
if(srv.gh)
{
for(auto & connection : srv.hangingConnections)
{
if(connection->uuid == pack.uuid)
{
logNetwork->info("Reconnection player");
pack.c->connectionID = connection->connectionID;
for(auto & playerConnection : srv.gh->connections)
{
for(auto & existingConnection : playerConnection.second)
{
if(existingConnection == connection)
{
playerConnection.second.erase(existingConnection);
playerConnection.second.insert(pack.c);
break;
}
}
}
srv.hangingConnections.erase(connection);
break;
}
}
}
srv.clientConnected(pack.c, pack.names, pack.uuid, pack.mode); srv.clientConnected(pack.c, pack.names, pack.uuid, pack.mode);
// Server need to pass some data to newly connected client // Server need to pass some data to newly connected client
pack.clientId = pack.c->connectionID; pack.clientId = pack.c->connectionID;
@ -121,7 +70,7 @@ void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientConnected(LobbyCl
startGameForReconnectedPlayer->initializedStartInfo = srv.si; startGameForReconnectedPlayer->initializedStartInfo = srv.si;
startGameForReconnectedPlayer->initializedGameState = srv.gh->gameState(); startGameForReconnectedPlayer->initializedGameState = srv.gh->gameState();
startGameForReconnectedPlayer->clientId = pack.c->connectionID; startGameForReconnectedPlayer->clientId = pack.c->connectionID;
srv.addToAnnounceQueue(std::move(startGameForReconnectedPlayer)); srv.announcePack(std::move(startGameForReconnectedPlayer));
} }
} }
@ -154,38 +103,28 @@ void ClientPermissionsCheckerNetPackVisitor::visitLobbyClientDisconnected(LobbyC
void ApplyOnServerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) void ApplyOnServerNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{ {
srv.clientDisconnected(pack.c); srv.clientDisconnected(pack.c);
pack.c->close();
pack.c->connected = false;
result = true; result = true;
} }
void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{ {
if(pack.c && pack.c->isOpen())
{
boost::unique_lock<boost::mutex> lock(*pack.c->mutexWrite);
pack.c->close();
pack.c->connected = false;
}
if(pack.shutdownServer) if(pack.shutdownServer)
{ {
logNetwork->info("Client requested shutdown, server will close itself..."); logNetwork->info("Client requested shutdown, server will close itself...");
srv.setState(EServerState::SHUTDOWN); srv.setState(EServerState::SHUTDOWN);
return; return;
} }
else if(srv.connections.empty()) else if(srv.activeConnections.empty())
{ {
logNetwork->error("Last connection lost, server will close itself..."); logNetwork->error("Last connection lost, server will close itself...");
srv.setState(EServerState::SHUTDOWN); srv.setState(EServerState::SHUTDOWN);
} }
else if(pack.c == srv.hostClient) else if(pack.c->connectionID == srv.hostClientId)
{ {
auto ph = std::make_unique<LobbyChangeHost>(); auto ph = std::make_unique<LobbyChangeHost>();
auto newHost = *RandomGeneratorUtil::nextItem(srv.connections, CRandomGenerator::getDefault()); auto newHost = srv.activeConnections.front();
ph->newHostConnectionId = newHost->connectionID; ph->newHostConnectionId = newHost->connectionID;
srv.addToAnnounceQueue(std::move(ph)); srv.announcePack(std::move(ph));
} }
srv.updateAndPropagateLobbyState(); srv.updateAndPropagateLobbyState();
@ -270,8 +209,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack)
void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyEndGame(LobbyEndGame & pack)
{ {
boost::unique_lock<boost::mutex> stateLock(srv.stateMutex); for(auto & c : srv.activeConnections)
for(auto & c : srv.connections)
{ {
c->enterLobbyConnectionMode(); c->enterLobbyConnectionMode();
c->disableStackSendingByID(); c->disableStackSendingByID();
@ -312,10 +250,10 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) void ApplyOnServerAfterAnnounceNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
{ {
if(pack.clientId == -1) //do not restart game for single client only if(pack.clientId == -1) //do not restart game for single client only
srv.startGameImmidiately(); srv.startGameImmediately();
else else
{ {
for(auto & c : srv.connections) for(auto & c : srv.activeConnections)
{ {
if(c->connectionID == pack.clientId) if(c->connectionID == pack.clientId)
{ {

View File

@ -20,11 +20,11 @@
#include "../lib/IGameCallback.h" #include "../lib/IGameCallback.h"
#include "../lib/mapObjects/CGTownInstance.h" #include "../lib/mapObjects/CGTownInstance.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/gameState/CGameState.h" #include "../lib/gameState/CGameState.h"
#include "../lib/battle/IBattleState.h" #include "../lib/battle/IBattleState.h"
#include "../lib/battle/BattleAction.h" #include "../lib/battle/BattleAction.h"
#include "../lib/battle/Unit.h" #include "../lib/battle/Unit.h"
#include "../lib/serializer/Connection.h"
#include "../lib/spells/CSpellHandler.h" #include "../lib/spells/CSpellHandler.h"
#include "../lib/spells/ISpellMechanics.h" #include "../lib/spells/ISpellMechanics.h"
#include "../lib/serializer/Cast.h" #include "../lib/serializer/Cast.h"

View File

@ -13,7 +13,6 @@
#include "../CGameHandler.h" #include "../CGameHandler.h"
#include "../CVCMIServer.h" #include "../CVCMIServer.h"
#include "../../lib/serializer/Connection.h"
#include "../../lib/CGeneralTextHandler.h" #include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h" #include "../../lib/CHeroHandler.h"
#include "../../lib/modding/IdentifierStorage.h" #include "../../lib/modding/IdentifierStorage.h"
@ -22,11 +21,13 @@
#include "../../lib/StartInfo.h" #include "../../lib/StartInfo.h"
#include "../../lib/gameState/CGameState.h" #include "../../lib/gameState/CGameState.h"
#include "../../lib/mapObjects/CGTownInstance.h" #include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/modding/IdentifierStorage.h" #include "../../lib/modding/IdentifierStorage.h"
#include "../../lib/modding/ModScope.h" #include "../../lib/modding/ModScope.h"
#include "../../lib/mapping/CMap.h" #include "../../lib/mapping/CMap.h"
#include "../../lib/networkPacks/PacksForClient.h" #include "../../lib/networkPacks/PacksForClient.h"
#include "../../lib/networkPacks/StackLocation.h" #include "../../lib/networkPacks/StackLocation.h"
#include "../../lib/serializer/Connection.h"
PlayerMessageProcessor::PlayerMessageProcessor() PlayerMessageProcessor::PlayerMessageProcessor()
:gameHandler(nullptr) :gameHandler(nullptr)
@ -62,10 +63,7 @@ bool PlayerMessageProcessor::handleHostCommand(PlayerColor player, const std::st
std::vector<std::string> words; std::vector<std::string> words;
boost::split(words, message, boost::is_any_of(" ")); boost::split(words, message, boost::is_any_of(" "));
bool isHost = false; bool isHost = gameHandler->gameLobby()->isPlayerHost(player);
for(auto & c : gameHandler->connections[player])
if(gameHandler->gameLobby()->isClientHost(c->connectionID))
isHost = true;
if(!isHost || words.size() < 2 || words[0] != "game") if(!isHost || words.size() < 2 || words[0] != "game")
return false; return false;