Explorar el Código

Switch client-server communication to new API

Ivan Savenko hace 2 años
padre
commit
0a1153e1c6

+ 82 - 102
client/CServerHandler.cpp

@@ -45,17 +45,17 @@
 #include "../lib/mapping/CMapInfo.h"
 #include "../lib/mapObjects/MiscObjects.h"
 #include "../lib/modding/ModIncompatibility.h"
+#include "../lib/network/NetworkClient.h"
 #include "../lib/rmg/CMapGenOptions.h"
+#include "../lib/serializer/Connection.h"
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
-#include "../lib/serializer/Connection.h"
 #include "../lib/serializer/CMemorySerializer.h"
 #include "../lib/UnlockGuard.h"
 
 #include <boost/uuid/uuid.hpp>
 #include <boost/uuid/uuid_io.hpp>
 #include <boost/uuid/uuid_generators.hpp>
-#include <boost/asio.hpp>
 #include "../lib/serializer/Cast.h"
 #include "LobbyClientNetPackVisitors.h"
 
@@ -131,8 +131,16 @@ public:
 static const std::string NAME_AFFIX = "client";
 static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
 
+CServerHandler::~CServerHandler() = default;
+
 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()());
 	//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());
 }
 
-void CServerHandler::startLocalServerAndConnect()
+void CServerHandler::startLocalServerAndConnect(const std::function<void()> & onConnected)
 {
 	if(threadRunLocalServer)
 		threadRunLocalServer->join();
@@ -169,17 +177,13 @@ void CServerHandler::startLocalServerAndConnect()
 	th->update();
 	
 	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");
 		CInfoWindow::showInfoDialog(errorMsg, {});
 		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)
 	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
 
-	justConnectToServer(localhostAddress, 0);
+	justConnectToServer(localhostAddress, 0, onConnected);
 
 	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;
-	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())
 	{
@@ -286,13 +267,39 @@ void CServerHandler::justConnectToServer(const std::string & addr, const ui16 po
 		Settings serverPort = settings.write["server"]["port"];
 		serverPort->Integer() = port;
 	}
+
+	networkClient->start(addr.size() ? addr : getHostAddress(), port ? port : getHostPort());
 }
 
-void CServerHandler::applyPacksOnLobbyScreen()
+void CServerHandler::onConnectionFailed(const std::string & errorMessage)
 {
-	if(!c || !c->handler)
+	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()
+{
 	boost::unique_lock<boost::recursive_mutex> lock(*mx);
 	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()
 {
 	return clientHumanColors(c->connectionID);
@@ -421,7 +418,6 @@ void CServerHandler::sendClientDisconnecting()
 	{
 		// Network thread might be applying network pack at this moment
 		auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
-		c->close();
 		c.reset();
 	}
 }
@@ -814,7 +810,7 @@ void CServerHandler::restoreLastSession()
 			myNames.push_back(name.String());
 		resetStateForLobby(StartInfo::LOAD_GAME, &myNames);
 		screenType = ESelectionScreen::loadGame;
-		justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer());
+		justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer(), {});
 	};
 	
 	auto cleanUpSession = []()
@@ -844,9 +840,9 @@ void CServerHandler::debugStartTest(std::string filename, bool save)
 		screenType = ESelectionScreen::newGame;
 	}
 	if(settings["session"]["donotstartserver"].Bool())
-		justConnectToServer(localhostAddress, 3030);
+		justConnectToServer(localhostAddress, 3030, {});
 	else
-		startLocalServerAndConnect();
+		startLocalServerAndConnect({});
 
 	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");
-	c->enterLobbyConnectionMode();
-
-	try
+	CPack * pack = c->retrievePack(message);
+	if(state == EClientState::DISCONNECTING)
 	{
-		sendClientConnecting();
-		while(c && c->connected)
-		{
-			while(state == EClientState::STARTING)
-				boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
+		// 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);
+	}
+}
 
-			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);
-			}
-		}
+void CServerHandler::onDisconnected()
+{
+	if(state == EClientState::DISCONNECTING)
+	{
+		logNetwork->info("Successfully closed connection to server, ending listening thread!");
 	}
-	//catch only asio exceptions
-	catch(const boost::system::system_error & e)
+	else
 	{
-		if(state == EClientState::DISCONNECTING)
+		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
 		{
-			if (e.code() == boost::asio::error::eof)
-				logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed");
-			else
-				logNetwork->error("Lost connection to server, ending listening thread! Reason: %s", e.what());
-
-			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);
-			}
+			auto lcd = new LobbyClientDisconnected();
+			lcd->clientId = c->connectionID;
+			boost::unique_lock<boost::recursive_mutex> lock(*mx);
+			packsForLobbyScreen.push_back(lcd);
 		}
 	}
 }

+ 14 - 6
client/CServerHandler.h

@@ -11,6 +11,7 @@
 
 #include "../lib/CStopWatch.h"
 
+#include "../lib/network/NetworkListener.h"
 #include "../lib/StartInfo.h"
 #include "../lib/CondSh.h"
 
@@ -80,8 +81,10 @@ public:
 };
 
 /// 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;
 	
 	std::shared_ptr<CApplier<CBaseForLobbyApply>> applier;
@@ -95,12 +98,18 @@ class CServerHandler : public IServerAPI, public LobbyInfo
 
 	std::shared_ptr<HighScoreCalculation> highScoreCalc;
 
-	void threadHandleConnection();
 	void threadRunServer();
 	void onServerFinished();
 	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:
+	std::shared_ptr<CConnection> c;
+
 	std::atomic<EClientState> state;
 	////////////////////
 	// FIXME: Bunch of crutches to glue it all together
@@ -115,7 +124,6 @@ public:
 	std::unique_ptr<CStopWatch> th;
 	std::shared_ptr<boost::thread> threadRunLocalServer;
 
-	std::shared_ptr<CConnection> c;
 	CClient * client;
 
 	CondSh<bool> campaignServerRestartLock;
@@ -123,15 +131,15 @@ public:
 	static const std::string localhostAddress;
 
 	CServerHandler();
+	~CServerHandler();
 	
 	std::string getHostAddress() const;
 	ui16 getHostPort() const;
 
 	void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr);
-	void startLocalServerAndConnect();
-	void justConnectToServer(const std::string & addr, const ui16 port);
+	void startLocalServerAndConnect(const std::function<void()> & onConnected);
+	void justConnectToServer(const std::string & addr, const ui16 port, const std::function<void()> & onConnected);
 	void applyPacksOnLobbyScreen();
-	void stopServerConnection();
 
 	// Helpers for lobby state access
 	std::set<PlayerColor> getHumanColors();

+ 2 - 1
client/Client.cpp

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

+ 1 - 1
client/NetPacksClient.cpp

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

+ 0 - 2
client/NetPacksLobbyClient.cpp

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

+ 1 - 1
client/lobby/CSelectionBase.cpp

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

+ 0 - 1
client/lobby/SelectionTab.cpp

@@ -43,7 +43,6 @@
 #include "../../lib/mapping/CMapHeader.h"
 #include "../../lib/mapping/MapFormat.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)
 {

+ 24 - 29
client/mainmenu/CMainMenu.cpp

@@ -45,7 +45,6 @@
 #include "../../lib/CGeneralTextHandler.h"
 #include "../../lib/JsonNode.h"
 #include "../../lib/campaign/CampaignHandler.h"
-#include "../../lib/serializer/Connection.h"
 #include "../../lib/serializer/CTypeList.h"
 #include "../../lib/filesystem/Filesystem.h"
 #include "../../lib/filesystem/CCompressedStream.h"
@@ -559,7 +558,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(bool host)
 	{
 		textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverConnecting"));
 		buttonOk->block(true);
-		startConnectThread();
+		startConnection();
 	}
 	else
 	{
@@ -582,7 +581,7 @@ void CSimpleJoinScreen::connectToServer()
 	buttonOk->block(true);
 	GH.stopTextInput();
 
-	startConnectThread(inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
+	startConnection(inputAddress->getText(), boost::lexical_cast<ui16>(inputPort->getText()));
 }
 
 void CSimpleJoinScreen::leaveScreen()
@@ -603,7 +602,7 @@ void CSimpleJoinScreen::onChange(const std::string & newText)
 	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)
 	// 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
 	CVCMIServer::reuseClientJNIEnv(SDL_AndroidGetJNIEnv());
 #endif
-	boost::thread connector(&CSimpleJoinScreen::connectThread, this, addr, port);
 
-	connector.detach();
-}
-
-void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port)
-{
-	setThreadName("connectThread");
-	if(!addr.length())
-		CSH->startLocalServerAndConnect();
-	else
-		CSH->justConnectToServer(addr, port);
+	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"), {});
 
-	// 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);
+			}
 
-			textTitle->setText(CGI->generaltexth->translate("vcmi.mainMenu.serverAddressEnter"));
-			GH.startTextInput(inputAddress->pos);
-			buttonOk->block(false);
-		}
+			if(GH.windows().isTopWindow(this))
+			{
+				close();
+			}
+		});
+	};
 
-		if(GH.windows().isTopWindow(this))
-		{
-			close();
-		}
-	});
+	if(addr.empty())
+		CSH->startLocalServerAndConnect(onConnected);
+	else
+		CSH->justConnectToServer(addr, port, onConnected);
 }
 
 CLoadingScreen::CLoadingScreen()

+ 1 - 2
client/mainmenu/CMainMenu.h

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

+ 20 - 5
client/serverLobby/LobbyWindow.cpp

@@ -18,12 +18,12 @@
 
 #include "../../lib/MetaString.h"
 #include "../../lib/CConfigHandler.h"
+#include "../../lib/network/NetworkClient.h"
 
 LobbyClient::LobbyClient(LobbyWindow * window)
-	: window(window)
-{
-
-}
+	: networkClient(std::make_unique<NetworkClient>(*this))
+	, window(window)
+{}
 
 static std::string getCurrentTimeFormatted(int timeOffsetSeconds = 0)
 {
@@ -100,7 +100,22 @@ void LobbyClient::sendMessage(const JsonNode & data)
 
 	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)

+ 7 - 2
client/serverLobby/LobbyWindow.h

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

+ 1 - 0
cmake_modules/VCMI_lib.cmake

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

+ 0 - 2
lib/IGameCallback.cpp

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

+ 6 - 1
lib/StartInfo.cpp

@@ -123,7 +123,12 @@ bool LobbyInfo::isClientHost(int clientId) const
 	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;
 	for(auto & elem : si->playerInfos)

+ 2 - 2
lib/StartInfo.h

@@ -197,7 +197,6 @@ struct DLL_LINKAGE LobbyState
 
 struct DLL_LINKAGE LobbyInfo : public LobbyState
 {
-	boost::mutex stateMutex;
 	std::string uuid;
 
 	LobbyInfo() {}
@@ -205,7 +204,8 @@ struct DLL_LINKAGE LobbyInfo : public LobbyState
 	void verifyStateBeforeStart(bool ignoreNoHuman = false) 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;
 
 	// Helpers for lobby state access

+ 23 - 6
lib/network/NetworkClient.cpp

@@ -13,10 +13,27 @@
 
 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)
 	, 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)
 	{
-		onConnectionFailed(ec.message());
+		listener.onConnectionFailed(ec.message());
 		return;
 	}
 
 	connection = std::make_shared<NetworkConnection>(socket, *this);
 	connection->start();
 
-	onConnectionEstablished();
+	listener.onConnectionEstablished();
 }
 
 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)
 {
-	onDisconnected();
+	listener.onDisconnected();
 }
 
 void NetworkClient::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
 {
-	onPacketReceived(message);
+	listener.onPacketReceived(message);
 }
 
 

+ 10 - 9
lib/network/NetworkClient.h

@@ -10,9 +10,14 @@
 #pragma once
 
 #include "NetworkDefines.h"
+#include "NetworkListener.h"
 
 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 DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionListener
@@ -20,24 +25,20 @@ class DLL_LINKAGE NetworkClient : boost::noncopyable, public INetworkConnectionL
 	std::shared_ptr<NetworkService> io;
 	std::shared_ptr<NetworkSocket> socket;
 	std::shared_ptr<NetworkConnection> connection;
-	std::shared_ptr<NetworkTimer> timer;
+
+	INetworkClientListener & listener;
 
 	void onConnected(const boost::system::error_code & ec);
 
 	void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) override;
 	void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) override;
 
-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;
-
-	void sendPacket(const std::vector<uint8_t> & message);
 public:
-	NetworkClient();
+	NetworkClient(INetworkClientListener & listener);
 	virtual ~NetworkClient() = default;
 
+	void sendPacket(const std::vector<uint8_t> & message);
+
 	void start(const std::string & host, uint16_t port);
 	void run();
 	void poll();

+ 1 - 0
lib/network/NetworkConnection.h

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

+ 0 - 12
lib/network/NetworkDefines.h

@@ -17,17 +17,5 @@ using NetworkService = boost::asio::io_service;
 using NetworkSocket = boost::asio::basic_stream_socket<boost::asio::ip::tcp>;
 using NetworkAcceptor = boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>;
 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

+ 50 - 0
lib/network/NetworkListener.h

@@ -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

+ 18 - 2
lib/network/NetworkServer.cpp

@@ -13,6 +13,12 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+NetworkServer::NetworkServer(INetworkServerListener & listener)
+	:listener(listener)
+{
+
+}
+
 void NetworkServer::start(uint16_t port)
 {
 	io = std::make_shared<boost::asio::io_service>();
@@ -32,6 +38,11 @@ void NetworkServer::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)
 {
 	if(ec)
@@ -43,7 +54,7 @@ void NetworkServer::connectionAccepted(std::shared_ptr<NetworkSocket> upcomingCo
 	auto connection = std::make_shared<NetworkConnection>(upcomingConnection, *this);
 	connections.insert(connection);
 	connection->start();
-	onNewConnection(connection);
+	listener.onNewConnection(connection);
 	startAsyncAccept();
 }
 
@@ -56,7 +67,12 @@ void NetworkServer::onDisconnected(const std::shared_ptr<NetworkConnection> & co
 {
 	assert(connections.count(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

+ 7 - 6
lib/network/NetworkServer.h

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

+ 1 - 1
lib/serializer/CSerializer.h

@@ -51,7 +51,7 @@ struct VectorizedObjectInfo
 };
 
 /// 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>
 	static int32_t idToNumber(const Numeric &t)

+ 47 - 2
lib/serializer/Connection.cpp

@@ -10,9 +10,52 @@
 #include "StdInc.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
 
@@ -362,3 +405,5 @@ std::string CConnection::toString() const
 }
 
 VCMI_LIB_NAMESPACE_END
+
+#endif

+ 55 - 94
lib/serializer/Connection.h

@@ -9,123 +9,84 @@
  */
 #pragma once
 
-#include "BinaryDeserializer.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
-
+#include "CSerializer.h"
 
 VCMI_LIB_NAMESPACE_BEGIN
 
+class BinaryDeserializer;
+class BinarySerializer;
 struct CPack;
 struct ConnectionBuffers;
+class NetworkConnection;
 
 /// Main class for network communication
 /// Allows establishing connection and bidirectional read-write
-class DLL_LINKAGE CConnection
-	: public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
+class DLL_LINKAGE CConnection : public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
 {
-	void init();
-	void reportState(vstd::CLoggerBase * out) override;
+	/// Non-owning pointer to underlying connection
+	std::weak_ptr<NetworkConnection> networkConnection;
 
+//	void init();
+//	void reportState(vstd::CLoggerBase * out) override;
+//
 	int write(const void * data, unsigned size) override;
 	int read(void * data, unsigned size) override;
-	void flushBuffers();
-
-	std::shared_ptr<boost::asio::io_service> io_service; //can be empty if connection made from socket
-
-	bool enableBufferedWrite;
-	bool enableBufferedRead;
-	std::unique_ptr<ConnectionBuffers> connectionBuffers;
+//	void flushBuffers();
+//
+//	bool enableBufferedWrite;
+//	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:
-	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;
-
 	int connectionID;
-	std::shared_ptr<boost::thread> handler;
-
-	CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID);
-	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
 
-	void close();
-	bool isOpen() const;
-	template<class T>
-	CConnection &operator&(const T&);
-	virtual ~CConnection();
+	CConnection(std::weak_ptr<NetworkConnection> networkConnection);
+//	CConnection(const std::string & host, ui16 port, std::string Name, std::string UUID);
+//	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();
 
-	CPack * retrievePack();
+//	void close();
+//	bool isOpen() const;
+//
+//	CPack * retrievePack();
 	void sendPack(const CPack * pack);
 
+	CPack * retrievePack(const std::vector<uint8_t> & data);
+//	std::vector<uint8_t> serializePack(const CPack * pack);
+//
 	void disableStackSendingByID();
-	void enableStackSendingByID();
-	void disableSmartPointerSerialization();
-	void enableSmartPointerSerialization();
-	void disableSmartVectorMemberSerialization();
-	void enableSmartVectorMemberSerializatoin();
-
+//	void enableStackSendingByID();
+//	void disableSmartPointerSerialization();
+//	void enableSmartPointerSerialization();
+//	void disableSmartVectorMemberSerialization();
+//	void enableSmartVectorMemberSerializatoin();
+//
 	void enterLobbyConnectionMode();
 	void enterGameplayConnectionMode(CGameState * gs);
-
-	std::string toString() const;
-
-	template<class T>
-	CConnection & operator>>(T &t)
-	{
-		iser & t;
-		return * this;
-	}
-
-	template<class T>
-	CConnection & operator<<(const T &t)
-	{
-		oser & t;
-		return * this;
-	}
+//
+//	std::string toString() const;
+//
+//	template<class T>
+//	CConnection & operator>>(T &t)
+//	{
+//		iser & t;
+//		return * this;
+//	}
+//
+//	template<class T>
+//	CConnection & operator<<(const T &t)
+//	{
+//		oser & t;
+//		return * this;
+//	}
 };
 
 VCMI_LIB_NAMESPACE_END

+ 13 - 2
lobby/LobbyServer.cpp

@@ -104,14 +104,14 @@ void LobbyServer::sendMessage(const std::shared_ptr<NetworkConnection> & target,
 
 	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::onConnectionLost(const std::shared_ptr<NetworkConnection> & connection)
+void LobbyServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
 {
 	activeAccounts.erase(connection);
 }
@@ -169,9 +169,20 @@ void LobbyServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & co
 
 LobbyServer::LobbyServer()
 	: 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[])
 {
 	LobbyServer server;

+ 6 - 2
lobby/LobbyServer.h

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

+ 8 - 19
server/CGameHandler.cpp

@@ -61,6 +61,7 @@
 
 #include "../lib/serializer/CSaveFile.h"
 #include "../lib/serializer/CLoadFile.h"
+#include "../lib/serializer/Connection.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
 }
 
-void CGameHandler::run(bool resume)
+void CGameHandler::start(bool resume)
 {
 	LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
 
-	for (auto cc : lobby->connections)
+	for (auto cc : lobby->activeConnections)
 	{
 		auto players = lobby->getAllClientPlayers(cc->connectionID);
 		std::stringstream sbuffer;
@@ -1004,18 +1005,11 @@ void CGameHandler::run(bool resume)
 		events::GameResumed::defaultExecute(serverEventBus.get());
 
 	turnOrder->onGameStarted();
+}
 
-	//wait till game is done
-	auto clockLast = std::chrono::steady_clock::now();
-	while(lobby->getState() == EServerState::GAMEPLAY)
-	{
-		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::tick(int millisecondsPassed)
+{
+	turnTimerHandler.update(millisecondsPassed);
 }
 
 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)
 {
 	logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name());
-	for (auto c : lobby->connections)
-	{
-		if(!c->isOpen())
-			continue;
-
+	for (auto c : lobby->activeConnections)
 		c->sendPack(pack);
-	}
 }
 
 void CGameHandler::sendAndApply(CPackForClient * pack)

+ 2 - 1
server/CGameHandler.h

@@ -261,7 +261,8 @@ public:
 
 	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);
 	void spawnWanderingMonsters(CreatureID creatureID);
 

+ 152 - 267
server/CVCMIServer.cpp

@@ -8,12 +8,11 @@
  *
  */
 #include "StdInc.h"
-#include <boost/asio.hpp>
+#include <boost/program_options.hpp>
 
 #include "../lib/filesystem/Filesystem.h"
 #include "../lib/campaign/CampaignState.h"
 #include "../lib/CThreadHelper.h"
-#include "../lib/serializer/Connection.h"
 #include "../lib/CArtHandler.h"
 #include "../lib/CGeneralTextHandler.h"
 #include "../lib/CHeroHandler.h"
@@ -37,12 +36,15 @@
 #include "CGameHandler.h"
 #include "processors/PlayerMessageProcessor.h"
 #include "../lib/mapping/CMapInfo.h"
+#include "../lib/network/NetworkServer.h"
+#include "../lib/network/NetworkClient.h"
 #include "../lib/GameConstants.h"
 #include "../lib/logging/CBasicLogConfigurator.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/ScopeGuard.h"
 #include "../lib/serializer/CMemorySerializer.h"
 #include "../lib/serializer/Cast.h"
+#include "../lib/serializer/Connection.h"
 
 #include "../lib/UnlockGuard.h"
 
@@ -81,11 +83,8 @@ public:
 
 		if(checker.getResult())
 		{
-			boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);
 			ApplyOnServerNetPackVisitor applier(*srv);
-			
 			ptr->visit(applier);
-
 			return applier.getResult();
 		}
 		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 = GameConstants::VCMI_VERSION + std::string(" (") + SERVER_NAME_AFFIX + ')';
 
 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()());
 	logNetwork->trace("CVCMIServer created! UUID: %s", uuid);
 	applier = std::make_shared<CApplier<CBaseForServerApply>>();
 	registerTypesLobbyPacks(*applier);
 
+	uint16_t port = 3030;
 	if(cmdLineOptions.count("port"))
-		port = cmdLineOptions["port"].as<ui16>();
+		port = cmdLineOptions["port"].as<uint16_t>();
 	logNetwork->info("Port %d will be used", port);
-	try
-	{
-		acceptor = std::make_shared<TAcceptor>(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 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();
-	}
+
+	networkServer = std::make_unique<NetworkServer>(*this);
+	networkServer->start(port);
 	logNetwork->info("Listening for connections at port %d", port);
 }
 
-CVCMIServer::~CVCMIServer()
+CVCMIServer::~CVCMIServer() = default;
+
+void CVCMIServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
+{
+	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)
 {
-	announceQueue.clear();
+	//TODO: handle pack received from lobby
+}
 
-	if(announceLobbyThread)
-		announceLobbyThread->join();
+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)
@@ -171,99 +223,58 @@ EServerState CVCMIServer::getState() const
 	return state.load();
 }
 
+std::shared_ptr<CConnection> CVCMIServer::findConnection(const std::shared_ptr<NetworkConnection> & netConnection)
+{
+	//TODO
+	assert(0);
+	return nullptr;
+}
+
 void CVCMIServer::run()
 {
+#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
 	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;
 		vmHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
-#endif
-#endif
 	}
+#endif
 
-	while(state == EServerState::LOBBY || state == EServerState::GAMEPLAY_STARTING)
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
-
-	logNetwork->info("Thread handling connections ended");
+	static const int serverUpdateIntervalMilliseconds = 50;
+	auto clockInitial = std::chrono::steady_clock::now();
+	int64_t msPassedLast = 0;
 
-	if(state == EServerState::GAMEPLAY)
+	while(state != EServerState::SHUTDOWN)
 	{
-		gh->run(si->mode == StartInfo::LOAD_GAME);
+		networkServer->run(std::chrono::milliseconds(serverUpdateIntervalMilliseconds));
+
+		const auto clockNow = std::chrono::steady_clock::now();
+		const auto clockPassed = clockNow - clockInitial;
+		const int64_t msPassedNow = std::chrono::duration_cast<std::chrono::milliseconds>(clockPassed).count();
+		const int64_t msDelta = msPassedNow - msPassedLast;
+		msPassedLast = msPassedNow;
+
+		if (state == EServerState::GAMEPLAY)
+			gh->tick(msDelta);
 	}
-	while(state == EServerState::GAMEPLAY_ENDED)
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
 }
 
-void CVCMIServer::establishRemoteConnections()
+void CVCMIServer::establishOutgoingConnection()
 {
-	setThreadName("establishConnection");
+	if(!cmdLineOptions.count("lobby"))
+		return;
 
-	//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);
-	}
-}
+	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);
 
-void CVCMIServer::threadAnnounceLobby()
-{
-	setThreadName("announceLobby");
-	while(state != EServerState::SHUTDOWN)
-	{
-		{
-			boost::unique_lock<boost::recursive_mutex> myLock(mx);
-			while(!announceQueue.empty())
-			{
-				announcePack(std::move(announceQueue.front()));
-				announceQueue.pop_front();
-			}
+	outgoingConnection = std::make_unique<NetworkClient>(*this);
 
-			if(acceptor)
-			{
-				io->reset();
-				io->poll();
-			}
-		}
+	outgoingConnection->start(address, port);//, SERVER_NAME, uuid);
 
-		boost::this_thread::sleep_for(boost::chrono::milliseconds(50));
-	}
+//	connections.insert(c);
+//	remoteConnections.insert(c);
 }
 
 void CVCMIServer::prepareToRestart()
@@ -280,11 +291,9 @@ void CVCMIServer::prepareToRestart()
 			campaignMap = si->campState->currentScenario().value_or(CampaignScenarioID(0));
 			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->disableStackSendingByID();
@@ -306,10 +315,11 @@ bool CVCMIServer::prepareToStartGame()
 		{
 			if(progressTracking.get() != currentProgress)
 			{
+				//FIXME: UNGUARDED MULTITHREADED ACCESS!!!
 				currentProgress = progressTracking.get();
 				std::unique_ptr<LobbyLoadProgress> loadProgress(new LobbyLoadProgress);
 				loadProgress->progress = currentProgress;
-				addToAnnounceQueue(std::move(loadProgress));
+				announcePack(std::move(loadProgress));
 			}
 			boost::this_thread::sleep(boost::posix_time::milliseconds(50));
 		}
@@ -355,149 +365,55 @@ bool CVCMIServer::prepareToStartGame()
 	return true;
 }
 
-void CVCMIServer::startGameImmidiately()
+void CVCMIServer::startGameImmediately()
 {
-	for(auto c : connections)
+	for(auto c : activeConnections)
 		c->enterGameplayConnectionMode(gh->gs);
 
+	gh->start(si->mode == StartInfo::LOAD_GAME);
 	state = EServerState::GAMEPLAY;
 }
 
-void CVCMIServer::startAsyncAccept()
-{
-	assert(!upcomingConnection);
-	assert(acceptor);
-
-#if BOOST_VERSION >= 107000  // Boost version >= 1.70
-	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)
-{
-	if(ec)
-	{
-		if(state != EServerState::SHUTDOWN)
-			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)
+void CVCMIServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
 {
-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));
-	}
+	logNetwork->error("Network error receiving a pack. Connection has been closed");
 
-	virtual void visitForServer(CPackForServer & serverPack) override
-	{
-		if (gh)
-			gh->handleReceivedPack(&serverPack);
-		else
-			logNetwork->error("Received pack for game server while in lobby!");
-	}
+	std::shared_ptr<CConnection> c = findConnection(connection);
 
-	virtual void visitForClient(CPackForClient & clientPack) override
-	{
-	}
-};
+	inactiveConnections.push_back(c);
+	vstd::erase(activeConnections, c);
 
-void CVCMIServer::threadHandleClient(std::shared_ptr<CConnection> c)
-{
-	setThreadName("handleClient");
-	c->enterLobbyConnectionMode();
+	if(activeConnections.empty() || hostClientId == c->connectionID)
+		state = EServerState::SHUTDOWN;
 
-	while(c->connected)
+	if(gh && state == EServerState::GAMEPLAY)
 	{
-		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);
+		gh->handleClientDisconnection(c);
 	}
 
 	boost::unique_lock<boost::recursive_mutex> queueLock(mx);
 
-	if(c->connected)
-	{
-		auto lcd = std::make_unique<LobbyClientDisconnected>();
-		lcd->c = c;
-		lcd->clientId = c->connectionID;
-		handleReceivedPack(std::move(lcd));
-	}
-
-	logNetwork->info("Thread listening for %s ended", c->toString());
-	c->handler.reset();
+//	if(c->connected)
+//	{
+//		auto lcd = std::make_unique<LobbyClientDisconnected>();
+//		lcd->c = c;
+//		lcd->clientId = c->connectionID;
+//		handleReceivedPack(std::move(lcd));
+//	}
+//
+//	logNetwork->info("Thread listening for %s ended", c->toString());
 }
 
 void CVCMIServer::handleReceivedPack(std::unique_ptr<CPackForLobby> pack)
 {
 	CBaseForServerApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack.get()));
 	if(apply->applyOnServerBefore(this, pack.get()))
-		addToAnnounceQueue(std::move(pack));
+		announcePack(std::move(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
 		// 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);
 	auto cm = std::make_unique<LobbyShowMessage>();
 	cm->message = txt;
-	addToAnnounceQueue(std::move(cm));
+	announcePack(std::move(cm));
 }
 
 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>();
 	cm->playerName = playerName;
 	cm->message = txt;
-	addToAnnounceQueue(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));
+	announcePack(std::move(cm));
 }
 
 bool CVCMIServer::passHost(int toConnectionId)
 {
-	for(auto c : connections)
+	for(auto c : activeConnections)
 	{
 		if(isClientHost(c->connectionID))
 			continue;
 		if(c->connectionID != toConnectionId)
 			continue;
 
-		hostClient = c;
 		hostClientId = c->connectionID;
 		announceTxt(boost::str(boost::format("Pass host to connection %d") % toConnectionId));
 		return true;
@@ -555,9 +464,8 @@ void CVCMIServer::clientConnected(std::shared_ptr<CConnection> c, std::vector<st
 	if(state == EServerState::LOBBY)
 		c->connectionID = currentClientId++;
 
-	if(!hostClient)
+	if(hostClientId == -1)
 	{
-		hostClient = c;
 		hostClientId = c->connectionID;
 		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)
 {
-	connections -= c;
-	if(connections.empty() || hostClient == c)
+	vstd::erase(activeConnections, c);
+
+	if(activeConnections.empty() || hostClientId == c->connectionID)
 	{
 		state = EServerState::SHUTDOWN;
 		return;
@@ -626,7 +535,7 @@ void CVCMIServer::clientDisconnected(std::shared_ptr<CConnection> c)
 		if(gh && si && state == EServerState::GAMEPLAY)
 		{
 			gh->playerMessages->broadcastMessage(playerSettings->color, playerLeftMsgText);
-			gh->connections[playerSettings->color].insert(hostClient);
+	//		gh->connections[playerSettings->color].insert(hostClient);
 			startAiPack.players.push_back(playerSettings->color);
 		}
 	}
@@ -753,7 +662,6 @@ void CVCMIServer::updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo,
 
 void CVCMIServer::updateAndPropagateLobbyState()
 {
-	boost::unique_lock<boost::mutex> stateLock(stateMutex);
 	// Update player settings for RMG
 	// TODO: find appropriate location for this code
 	if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
@@ -772,7 +680,7 @@ void CVCMIServer::updateAndPropagateLobbyState()
 
 	auto lus = std::make_unique<LobbyUpdateState>();
 	lus->state = *this;
-	addToAnnounceQueue(std::move(lus));
+	announcePack(std::move(lus));
 }
 
 void CVCMIServer::setPlayer(PlayerColor clickedColor)
@@ -1188,32 +1096,9 @@ int main(int argc, const char * argv[])
 	cond->notify_one();
 #endif
 
-	try
-	{
-		boost::asio::io_service io_service;
-		CVCMIServer server(opts);
+	CVCMIServer server(opts);
+	server.run();
 
-		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
 	CAndroidVMHelper envHelper;
 	envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");

+ 45 - 26
server/CVCMIServer.h

@@ -9,10 +9,10 @@
  */
 #pragma once
 
-#include "../lib/serializer/Connection.h"
+#include "../lib/network/NetworkListener.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)
 #define VCMI_ANDROID_DUAL_PROCESS 1
@@ -24,6 +24,7 @@ class CMapInfo;
 
 struct CPackForLobby;
 
+class CConnection;
 struct StartInfo;
 struct LobbyInfo;
 struct PlayerSettings;
@@ -46,53 +47,66 @@ enum class EServerState : ui8
 	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::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;
 	std::shared_ptr<CApplier<CBaseForServerApply>> applier;
-	std::unique_ptr<boost::thread> announceLobbyThread;
 	std::unique_ptr<boost::thread> remoteConnectionsThread;
 	std::atomic<EServerState> state;
 
-public:
-	std::shared_ptr<CGameHandler> gh;
-	ui16 port;
+	// INetworkServerListener impl
+	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 onNewConnection(const std::shared_ptr<NetworkConnection> &) override;
+
+	// INetworkClientListener impl
+	void onPacketReceived(const std::vector<uint8_t> & message) override;
+	void onConnectionFailed(const std::string & errorMessage) override;
+	void onConnectionEstablished() override;
+	void onDisconnected() override;
+
+	void establishOutgoingConnection();
+
+	std::shared_ptr<CConnection> findConnection(const std::shared_ptr<NetworkConnection> &);
 
-	boost::program_options::variables_map cmdLineOptions;
-	std::set<std::shared_ptr<CConnection>> connections;
-	std::set<std::shared_ptr<CConnection>> remoteConnections;
-	std::set<std::shared_ptr<CConnection>> hangingConnections; //keep connections of players disconnected during the game
-	
 	std::atomic<int> currentClientId;
 	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();
+
 	void run();
+
 	bool prepareToStartGame();
 	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 threadAnnounceLobby();
-	void handleReceivedPack(std::unique_ptr<CPackForLobby> pack);
 
 	void announcePack(std::unique_ptr<CPackForLobby> pack);
 	bool passHost(int toConnectionId);
 
 	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 updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpt = {});
@@ -101,6 +115,11 @@ public:
 	void clientDisconnected(std::shared_ptr<CConnection> c);
 	void reconnectPlayer(int connId);
 
+public:
+	void announceMessage(const std::string & txt);
+
+	void handleReceivedPack(std::unique_ptr<CPackForLobby> pack);
+
 	void updateAndPropagateLobbyState();
 
 	void setState(EServerState value);

+ 9 - 71
server/NetPacksLobbyServer.cpp

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

+ 1 - 1
server/NetPacksServer.cpp

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

+ 3 - 5
server/processors/PlayerMessageProcessor.cpp

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