Ver código fonte

Initial version of antilag support

Ivan Savenko 3 meses atrás
pai
commit
3576efc3f3

+ 213 - 0
client/AntilagServer.cpp

@@ -0,0 +1,213 @@
+/*
+ * AntilagServer.cpp, 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
+ *
+ */
+#include "StdInc.h"
+#include "AntilagServer.h"
+
+#include "GameEngine.h"
+
+#include "../server/CGameHandler.h"
+#include "../lib/gameState/CGameState.h"
+#include "../lib/mapObjects/CGHeroInstance.h"
+#include "../lib/serializer/GameConnection.h"
+
+int ConnectionPackWriter::write(const std::byte * data, unsigned size)
+{
+	buffer.insert(buffer.end(), data, data + size);
+	return size;
+}
+
+void AntilagFakeConnection::sendPack(const CPack & pack)
+{
+	logGlobal->info("Prediction: pack '%s'", typeid(pack).name());
+
+	ConnectionPackWriter packWriter;
+	BinarySerializer serializer(&packWriter);
+	serializer & &pack;
+	writtenPacks.push_back(std::move(packWriter));
+}
+
+std::unique_ptr<CPack> AntilagFakeConnection::retrievePack(const std::vector<std::byte> & data)
+{
+	throw std::runtime_error("AntilagFakeConnection::retrievePack not implemented");
+}
+
+int AntilagFakeConnection::getConnectionID() const
+{
+	return 0;
+}
+
+void AntilagRollbackGeneratorVisitor::visitTryMoveHero(TryMoveHero & pack)
+{
+	auto rollbackMove = std::make_unique<TryMoveHero>();
+	auto rollbackFow = std::make_unique<FoWChange>();
+	const auto * movedHero = gs.getHero(pack.id);
+
+	rollbackMove->id = pack.id;
+	rollbackMove->movePoints = movedHero->movementPointsRemaining();
+	rollbackMove->result = pack.result;
+	if (pack.result == TryMoveHero::EMBARK)
+		rollbackMove->result = TryMoveHero::DISEMBARK;
+	if (pack.result == TryMoveHero::DISEMBARK)
+		rollbackMove->result = TryMoveHero::EMBARK;
+	rollbackMove->start = pack.end;
+	rollbackMove->end = pack.start;
+
+	rollbackFow->mode = ETileVisibility::HIDDEN;
+	rollbackFow->player = movedHero->getOwner();
+	rollbackFow->tiles = pack.fowRevealed;
+
+	rollbackPacks.push_back(std::move(rollbackMove));
+	rollbackPacks.push_back(std::move(rollbackFow));
+	success = true;
+}
+
+bool AntilagRollbackGeneratorVisitor::canBeRolledBack() const
+{
+	return success;
+}
+
+std::vector<std::unique_ptr<CPackForClient>> AntilagRollbackGeneratorVisitor::getRollbackPacks()
+{
+	return std::move(rollbackPacks);
+}
+
+AntilagReplyPredictionVisitor::AntilagReplyPredictionVisitor() = default;
+
+void AntilagReplyPredictionVisitor::visitMoveHero(MoveHero & pack)
+{
+	canBeAppliedValue = true;
+}
+
+bool AntilagReplyPredictionVisitor::canBeApplied() const
+{
+	return canBeAppliedValue;
+}
+
+AntilagServer::AntilagServer(INetworkHandler & network, const std::shared_ptr<CGameState> & gs)
+	: gameHandler(std::make_unique<CGameHandler>(*this, gs))
+{
+	antilagNetConnection = network.createAsyncConnection(*this);
+	antilagGameConnection = std::make_shared<GameConnection>(antilagNetConnection);
+}
+
+AntilagServer::~AntilagServer() = default;
+
+void AntilagServer::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
+{
+	// should never be called
+	throw std::runtime_error("AntilagServer::onDisconnected called!");
+}
+
+void AntilagServer::onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message)
+{
+	std::scoped_lock interfaceLock(ENGINE->interfaceMutex);
+
+	auto basePack = antilagGameConnection->retrievePack(message);
+	auto * serverPack = dynamic_cast<CPackForServer*>(basePack.get());
+
+	AntilagReplyPredictionVisitor packVisitor;
+	serverPack->visit(packVisitor);
+	if (!packVisitor.canBeApplied())
+		return;
+
+	logGlobal->info("Predicting effects of pack '%s'", typeid(*serverPack).name());
+
+	auto newConnection = std::make_shared<AntilagFakeConnection>();
+	newConnection->requestID = serverPack->requestID;
+	newConnection->senderID = serverPack->player;
+	predictedReplies.push_back(std::move(newConnection));
+
+	gameHandler->handleReceivedPack(predictedReplies.back(), *serverPack);
+}
+
+void AntilagServer::tryPredictReply(const CPackForServer & request)
+{
+	antilagGameConnection->sendPack(request);
+	logGlobal->info("Scheduled prediction of effects of pack '%s'", typeid(request).name());
+}
+
+bool AntilagServer::verifyReply(const CPackForClient & pack)
+{
+	logGlobal->info("Verifying reply: received pack '%s'", typeid(pack).name());
+
+	const auto * packageReceived = dynamic_cast<const PackageReceived*>(&pack);
+	const auto * packageApplied = dynamic_cast<const PackageApplied*>(&pack);
+
+	if (packageReceived)
+	{
+		assert(currentPackageID == invalidPackageID);
+		assert(!predictedReplies.empty());
+		const auto & nextPrediction = predictedReplies.front();
+		assert(nextPrediction->senderID == packageReceived->player);
+		assert(nextPrediction->requestID == packageReceived->requestID);
+		currentPackageID = packageReceived->requestID;
+	}
+
+	if (currentPackageID == invalidPackageID)
+	{
+		// this is system package or reply to actions of another player
+		// TODO: consider reapplying all our predictions, in case if this event invalidated our prediction
+		return false;
+	}
+
+	ConnectionPackWriter packWriter;
+	BinarySerializer serializer(&packWriter);
+	serializer & &pack;
+
+	if (packWriter.buffer == predictedReplies.front()->writtenPacks.front().buffer)
+		predictedReplies.front()->writtenPacks.erase(predictedReplies.front()->writtenPacks.begin());
+	else
+		throw std::runtime_error("TODO: IMPLEMENT PACK ROLLBACK");
+
+	if (packageApplied)
+	{
+		assert(currentPackageID == packageApplied->requestID);
+		assert(!predictedReplies.empty());
+		assert(currentPackageID == predictedReplies.front()->requestID);
+		assert(predictedReplies.front()->writtenPacks.empty());
+		predictedReplies.erase(predictedReplies.begin());
+		currentPackageID = invalidPackageID;
+	}
+
+	return true;
+}
+
+void AntilagServer::setState(EServerState value)
+{
+	// no-op
+}
+
+EServerState AntilagServer::getState() const
+{
+	return EServerState::GAMEPLAY;
+}
+
+bool AntilagServer::isPlayerHost(const PlayerColor & color) const
+{
+	return false; // TODO?
+}
+
+bool AntilagServer::hasPlayerAt(PlayerColor player, const std::shared_ptr<IGameConnection> & c) const
+{
+	return true; // TODO?
+}
+
+bool AntilagServer::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
+{
+	return false; // TODO?
+}
+
+void AntilagServer::broadcastPack(CPackForClient & pack)
+{
+	AntilagReplyPredictionVisitor visitor;
+	pack.visit(visitor);
+	predictedReplies.back()->sendPack(pack);
+}
+

+ 213 - 0
client/AntilagServer.h

@@ -0,0 +1,213 @@
+/*
+ * AntilagServer.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
+
+#include "../lib/networkPacks/NetPackVisitor.h"
+#include "../server/IGameServer.h"
+#include "../lib/network/NetworkInterface.h"
+#include "../lib/serializer/IGameConnection.h"
+#include "../lib/serializer/CSerializer.h"
+#include "../lib/serializer/BinarySerializer.h"
+
+VCMI_LIB_NAMESPACE_BEGIN
+struct CPackForServer;
+class IGameConnection;
+VCMI_LIB_NAMESPACE_END
+
+class CGameHandler;
+
+class ConnectionPackWriter final : public IBinaryWriter
+{
+public:
+	std::vector<std::byte> buffer;
+
+	int write(const std::byte * data, unsigned size) final;
+};
+
+class AntilagFakeConnection final : public IGameConnection
+{
+public:
+	void sendPack(const CPack & pack) override;
+	std::unique_ptr<CPack> retrievePack(const std::vector<std::byte> & data) override;
+	int getConnectionID() const override;
+
+	PlayerColor senderID;
+	uint32_t requestID;
+	std::vector<ConnectionPackWriter> writtenPacks;
+};
+
+class AntilagReplyPredictionVisitor final : public VCMI_LIB_WRAP_NAMESPACE(ICPackVisitor)
+{
+	bool canBeAppliedValue = false;
+
+	//void visitSaveGame(SaveGame & pack) override;
+	//void visitGamePause(GamePause & pack) override;
+	//void visitEndTurn(EndTurn & pack) override;
+	//void visitDismissHero(DismissHero & pack) override;
+	void visitMoveHero(MoveHero & pack) override;
+	//void visitCastleTeleportHero(CastleTeleportHero & pack) override;
+	//void visitArrangeStacks(ArrangeStacks & pack) override;
+	//void visitBulkMoveArmy(BulkMoveArmy & pack) override;
+	//void visitBulkSplitStack(BulkSplitStack & pack) override;
+	//void visitBulkMergeStacks(BulkMergeStacks & pack) override;
+	//void visitBulkSplitAndRebalanceStack(BulkSplitAndRebalanceStack & pack) override;
+	//void visitDisbandCreature(DisbandCreature & pack) override;
+	//void visitBuildStructure(BuildStructure & pack) override;
+	//void visitSpellResearch(SpellResearch & pack) override;
+	//void visitVisitTownBuilding(VisitTownBuilding & pack) override;
+	//void visitRecruitCreatures(RecruitCreatures & pack) override;
+	//void visitUpgradeCreature(UpgradeCreature & pack) override;
+	//void visitGarrisonHeroSwap(GarrisonHeroSwap & pack) override;
+	//void visitExchangeArtifacts(ExchangeArtifacts & pack) override;
+	//void visitBulkExchangeArtifacts(BulkExchangeArtifacts & pack) override;
+	//void visitManageBackpackArtifacts(ManageBackpackArtifacts & pack) override;
+	//void visitManageEquippedArtifacts(ManageEquippedArtifacts & pack) override;
+	//void visitAssembleArtifacts(AssembleArtifacts & pack) override;
+	//void visitEraseArtifactByClient(EraseArtifactByClient & pack) override;
+	//void visitBuyArtifact(BuyArtifact & pack) override;
+	//void visitTradeOnMarketplace(TradeOnMarketplace & pack) override;
+	//void visitSetFormation(SetFormation & pack) override;
+	//void visitHireHero(HireHero & pack) override;
+	//void visitBuildBoat(BuildBoat & pack) override;
+	//void visitQueryReply(QueryReply & pack) override;
+	//void visitMakeAction(MakeAction & pack) override;
+	//void visitDigWithHero(DigWithHero & pack) override;
+	//void visitCastAdvSpell(CastAdvSpell & pack) override;
+	//void visitPlayerMessage(PlayerMessage & pack) override;
+	//void visitSaveLocalState(SaveLocalState & pack) override;
+
+public:
+	AntilagReplyPredictionVisitor();
+
+	bool canBeApplied() const;
+};
+
+class AntilagRollbackGeneratorVisitor final : public ICPackVisitor
+{
+private:
+	const CGameState & gs;
+	std::vector<std::unique_ptr<CPackForClient>> rollbackPacks;
+	bool success = false;
+
+	//void visitSetResources(SetResources & pack) override;
+	//void visitSetPrimarySkill(SetPrimarySkill & pack) override;
+	//void visitSetHeroExperience(SetHeroExperience & pack) override;
+	//void visitGiveStackExperience(GiveStackExperience & pack) override;
+	//void visitSetSecSkill(SetSecSkill & pack) override;
+	//void visitHeroVisitCastle(HeroVisitCastle & pack) override;
+	//void visitSetMana(SetMana & pack) override;
+	//void visitSetMovePoints(SetMovePoints & pack) override;
+	//void visitSetResearchedSpells(SetResearchedSpells & pack) override;
+	//void visitFoWChange(FoWChange & pack) override;
+	//void visitChangeStackCount(ChangeStackCount & pack) override;
+	//void visitSetStackType(SetStackType & pack) override;
+	//void visitEraseStack(EraseStack & pack) override;
+	//void visitSwapStacks(SwapStacks & pack) override;
+	//void visitInsertNewStack(InsertNewStack & pack) override;
+	//void visitRebalanceStacks(RebalanceStacks & pack) override;
+	//void visitBulkRebalanceStacks(BulkRebalanceStacks & pack) override;
+	//void visitGrowUpArtifact(GrowUpArtifact & pack) override;
+	//void visitPutArtifact(PutArtifact & pack) override;
+	//void visitBulkEraseArtifacts(BulkEraseArtifacts & pack) override;
+	//void visitBulkMoveArtifacts(BulkMoveArtifacts & pack) override;
+	//void visitAssembledArtifact(AssembledArtifact & pack) override;
+	//void visitDisassembledArtifact(DisassembledArtifact & pack) override;
+	//void visitDischargeArtifact(DischargeArtifact & pack) override;
+	//void visitHeroVisit(HeroVisit & pack) override;
+	//void visitNewTurn(NewTurn & pack) override;
+	//void visitGiveBonus(GiveBonus & pack) override;
+	//void visitChangeObjPos(ChangeObjPos & pack) override;
+	//void visitPlayerEndsTurn(PlayerEndsTurn & pack) override;
+	//void visitPlayerEndsGame(PlayerEndsGame & pack) override;
+	//void visitPlayerReinitInterface(PlayerReinitInterface & pack) override;
+	//void visitRemoveBonus(RemoveBonus & pack) override;
+	//void visitRemoveObject(RemoveObject & pack) override;
+	void visitTryMoveHero(TryMoveHero & pack) override;
+	//void visitNewStructures(NewStructures & pack) override;
+	//void visitRazeStructures(RazeStructures & pack) override;
+	//void visitSetAvailableCreatures(SetAvailableCreatures & pack) override;
+	//void visitSetHeroesInTown(SetHeroesInTown & pack) override;
+	//void visitHeroRecruited(HeroRecruited & pack) override;
+	//void visitGiveHero(GiveHero & pack) override;
+	//void visitSetObjectProperty(SetObjectProperty & pack) override;
+	//void visitHeroLevelUp(HeroLevelUp & pack) override;
+	//void visitCommanderLevelUp(CommanderLevelUp & pack) override;
+	//void visitBattleStart(BattleStart & pack) override;
+	//void visitBattleSetActiveStack(BattleSetActiveStack & pack) override;
+	//void visitBattleTriggerEffect(BattleTriggerEffect & pack) override;
+	//void visitBattleAttack(BattleAttack & pack) override;
+	//void visitBattleSpellCast(BattleSpellCast & pack) override;
+	//void visitSetStackEffect(SetStackEffect & pack) override;
+	//void visitStacksInjured(StacksInjured & pack) override;
+	//void visitBattleUnitsChanged(BattleUnitsChanged & pack) override;
+	//void visitBattleObstaclesChanged(BattleObstaclesChanged & pack) override;
+	//void visitBattleStackMoved(BattleStackMoved & pack) override;
+	//void visitCatapultAttack(CatapultAttack & pack) override;
+	//void visitPlayerStartsTurn(PlayerStartsTurn & pack) override;
+	//void visitNewObject(NewObject & pack) override;
+	//void visitSetAvailableArtifacts(SetAvailableArtifacts & pack) override;
+	//void visitEntitiesChanged(EntitiesChanged & pack) override;
+	//void visitSetCommanderProperty(SetCommanderProperty & pack) override;
+	//void visitAddQuest(AddQuest & pack) override;
+	//void visitChangeFormation(ChangeFormation & pack) override;
+	//void visitChangeSpells(ChangeSpells & pack) override;
+	//void visitSetAvailableHero(SetAvailableHero & pack) override;
+	//void visitChangeObjectVisitors(ChangeObjectVisitors & pack) override;
+	//void visitChangeArtifactsCostume(ChangeArtifactsCostume & pack) override;
+	//void visitNewArtifact(NewArtifact & pack) override;
+	//void visitBattleUpdateGateState(BattleUpdateGateState & pack) override;
+	//void visitPlayerCheated(PlayerCheated & pack) override;
+	//void visitDaysWithoutTown(DaysWithoutTown & pack) override;
+	//void visitStartAction(StartAction & pack) override;
+	//void visitSetRewardableConfiguration(SetRewardableConfiguration & pack) override;
+	//void visitBattleSetStackProperty(BattleSetStackProperty & pack) override;
+	//void visitBattleNextRound(BattleNextRound & pack) override;
+	//void visitBattleCancelled(BattleCancelled & pack) override;
+	//void visitBattleResultsApplied(BattleResultsApplied & pack) override;
+	//void visitBattleResultAccepted(BattleResultAccepted & pack) override;
+	//void visitTurnTimeUpdate(TurnTimeUpdate & pack) override;
+
+public:
+	AntilagRollbackGeneratorVisitor(const CGameState & gs)
+		: gs(gs)
+	{}
+
+	bool canBeRolledBack() const;
+	std::vector<std::unique_ptr<CPackForClient>> getRollbackPacks();
+};
+
+// Fake server that is used by client to make a quick prediction on what real server would reply without waiting for network latency
+class AntilagServer final : public IGameServer, public INetworkConnectionListener, boost::noncopyable
+{
+	std::vector<std::shared_ptr<AntilagFakeConnection>> predictedReplies;
+	std::shared_ptr<INetworkConnection> antilagNetConnection;
+	std::shared_ptr<IGameConnection> antilagGameConnection;
+	std::unique_ptr<CGameHandler> gameHandler;
+
+	static constexpr uint32_t invalidPackageID = std::numeric_limits<uint32_t>::max();
+	uint32_t currentPackageID = invalidPackageID;
+
+	// IGameServer impl
+	void setState(EServerState value) override;
+	EServerState getState() const override;
+	bool isPlayerHost(const PlayerColor & color) const override;
+	bool hasPlayerAt(PlayerColor player, const std::shared_ptr<IGameConnection> & c) const override;
+	bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
+	void broadcastPack(CPackForClient & pack) override;
+	void onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage) override;
+	void onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message) override;
+
+public:
+	AntilagServer(INetworkHandler & network, const std::shared_ptr<CGameState> & gs);
+	~AntilagServer();
+
+	void tryPredictReply(const CPackForServer & request);
+	bool verifyReply(const CPackForClient & reply);
+};

+ 2 - 0
client/CMakeLists.txt

@@ -184,6 +184,7 @@ set(vcmiclientcommon_SRCS
 
 	xBRZ/xbrz.cpp
 
+	AntilagServer.cpp
 	ArtifactsUIController.cpp
 	GameEngine.cpp
 	GameInstance.cpp
@@ -407,6 +408,7 @@ set(vcmiclientcommon_HEADERS
 	xBRZ/xbrz.h
 	xBRZ/xbrz_tools.h
 
+	AntilagServer.h
 	ArtifactsUIController.h
 	CMT.h
 	CPlayerInterface.h

+ 16 - 2
client/CServerHandler.cpp

@@ -8,8 +8,9 @@
  *
  */
 #include "StdInc.h"
-
 #include "CServerHandler.h"
+
+#include "AntilagServer.h"
 #include "Client.h"
 #include "ServerRunner.h"
 #include "GameChatHandler.h"
@@ -616,6 +617,9 @@ void CServerHandler::startGameplay(std::shared_ptr<CGameState> gameState)
 	if(GAME->mainmenu())
 		GAME->mainmenu()->disable();
 
+//	if (isGuest())
+	antilagServer = std::make_unique<AntilagServer>(getNetworkHandler(), gameState);
+
 	switch(si->mode)
 	{
 	case EStartMode::NEW_GAME:
@@ -940,10 +944,12 @@ void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
 
 void CServerHandler::visitForClient(CPackForClient & clientPack)
 {
+	if (antilagServer && antilagServer->verifyReply(clientPack))
+		return;
+
 	client->handlePack(clientPack);
 }
 
-
 void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
 {
 	if(getState() != EClientState::STARTING)
@@ -959,3 +965,11 @@ bool CServerHandler::inGame() const
 {
 	return logicConnection != nullptr;
 }
+
+void CServerHandler::sendGamePack(const CPackForServer & pack) const
+{
+	if (antilagServer)
+		antilagServer->tryPredictReply(pack);
+
+	logicConnection->sendPack(pack);
+}

+ 5 - 0
client/CServerHandler.h

@@ -26,12 +26,14 @@ class CMapInfo;
 class CGameState;
 struct ClientPlayer;
 struct CPackForLobby;
+struct CPackForServer;
 struct CPackForClient;
 
 class HighScoreParameter;
 
 VCMI_LIB_NAMESPACE_END
 
+class AntilagServer;
 class CClient;
 class CBaseForLobbyApply;
 class GlobalLobbyClient;
@@ -100,6 +102,7 @@ class CServerHandler final : public IServerAPI, public LobbyInfo, public INetwor
 	std::unique_ptr<GlobalLobbyClient> lobbyClient;
 	std::unique_ptr<GameChatHandler> gameChat;
 	std::unique_ptr<IServerRunner> serverRunner;
+	std::unique_ptr<AntilagServer> antilagServer;
 	std::shared_ptr<CMapInfo> mapToStart;
 	std::vector<std::string> localPlayerNames;
 
@@ -214,4 +217,6 @@ public:
 
 	void visitForLobby(CPackForLobby & lobbyPack);
 	void visitForClient(CPackForClient & clientPack);
+
+	void sendGamePack(const CPackForServer & pack) const;
 };

+ 1 - 1
client/Client.cpp

@@ -366,7 +366,7 @@ int CClient::sendRequest(const CPackForServer & request, PlayerColor player, boo
 	waitingRequest.pushBack(requestID);
 	request.requestID = requestID;
 	request.player = player;
-	GAME->server().logicConnection->sendPack(request);
+	GAME->server().sendGamePack(request);
 	if(vstd::contains(playerint, player))
 		playerint[player]->requestSent(&request, requestID);
 

+ 1 - 0
client/Client.h

@@ -126,6 +126,7 @@ class CClient : public Environment, public IClient
 {
 	std::shared_ptr<CGameState> gamestate;
 	int requestCounter = 1;
+
 public:
 	std::map<PlayerColor, std::shared_ptr<CGameInterface>> playerint;
 	std::map<PlayerColor, std::shared_ptr<CBattleGameInterface>> battleints;

+ 4 - 4
lib/network/NetworkConnection.cpp

@@ -208,15 +208,15 @@ void NetworkConnection::close()
 	//NOTE: ignoring error code, intended
 }
 
-InternalConnection::InternalConnection(INetworkConnectionListener & listener, NetworkContext & context)
-	: io(context)
+InternalConnection::InternalConnection(INetworkConnectionListener & listener, NetworkStrand & strand)
+	: strand(strand)
 	, listener(listener)
 {
 }
 
 void InternalConnection::receivePacket(const std::vector<std::byte> & message)
 {
-	boost::asio::post(io, [self = std::static_pointer_cast<InternalConnection>(shared_from_this()), message](){
+	strand.post([self = std::static_pointer_cast<InternalConnection>(shared_from_this()), message](){
 		if (self->connectionActive)
 			self->listener.onPacketReceived(self, message);
 	});
@@ -224,7 +224,7 @@ void InternalConnection::receivePacket(const std::vector<std::byte> & message)
 
 void InternalConnection::disconnect()
 {
-	boost::asio::post(io, [self = std::static_pointer_cast<InternalConnection>(shared_from_this())](){
+	strand.post([self = std::static_pointer_cast<InternalConnection>(shared_from_this())](){
 		self->listener.onDisconnected(self, "Internal connection has been terminated");
 		self->otherSideWeak.reset();
 		self->connectionActive = false;

+ 2 - 2
lib/network/NetworkConnection.h

@@ -49,11 +49,11 @@ public:
 class InternalConnection final : public IInternalConnection, public std::enable_shared_from_this<InternalConnection>
 {
 	std::weak_ptr<IInternalConnection> otherSideWeak;
-	NetworkContext & io;
+	NetworkStrand & strand;
 	INetworkConnectionListener & listener;
 	bool connectionActive = false;
 public:
-	InternalConnection(INetworkConnectionListener & listener, NetworkContext & context);
+	InternalConnection(INetworkConnectionListener & listener, NetworkStrand & strand);
 
 	void receivePacket(const std::vector<std::byte> & message) override;
 	void disconnect() override;

+ 1 - 0
lib/network/NetworkDefines.h

@@ -20,6 +20,7 @@ using NetworkContext = boost::asio::io_context;
 #else
 using NetworkContext = boost::asio::io_service;
 #endif
+using NetworkStrand = NetworkContext::strand;
 using NetworkSocket = boost::asio::ip::tcp::socket;
 using NetworkAcceptor = boost::asio::ip::tcp::acceptor;
 using NetworkBuffer = boost::asio::streambuf;

+ 10 - 2
lib/network/NetworkHandler.cpp

@@ -22,11 +22,19 @@ std::unique_ptr<INetworkHandler> INetworkHandler::createHandler()
 
 NetworkHandler::NetworkHandler()
 	: io(std::make_unique<NetworkContext>())
+	, strand(std::make_unique<NetworkStrand>(*io))
 {}
 
 std::unique_ptr<INetworkServer> NetworkHandler::createServerTCP(INetworkServerListener & listener)
 {
-	return std::make_unique<NetworkServer>(listener, *io);
+	return std::make_unique<NetworkServer>(listener, *io, *strand);
+}
+
+std::shared_ptr<INetworkConnection> NetworkHandler::createAsyncConnection(INetworkConnectionListener & listener)
+{
+	auto loopbackConnection = std::make_shared<InternalConnection>(listener, *strand);
+	loopbackConnection->connectTo(loopbackConnection);
+	return loopbackConnection;
 }
 
 void NetworkHandler::connectToRemote(INetworkClientListener & listener, const std::string & host, uint16_t port)
@@ -75,7 +83,7 @@ void NetworkHandler::createTimer(INetworkTimerListener & listener, std::chrono::
 
 void NetworkHandler::createInternalConnection(INetworkClientListener & listener, INetworkServer & server)
 {
-	auto localConnection = std::make_shared<InternalConnection>(listener, *io);
+	auto localConnection = std::make_shared<InternalConnection>(listener, *strand);
 
 	server.receiveInternalConnection(localConnection);
 

+ 3 - 1
lib/network/NetworkHandler.h

@@ -13,9 +13,10 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class NetworkHandler : public INetworkHandler
+class NetworkHandler final : public INetworkHandler
 {
 	std::unique_ptr<NetworkContext> io;
+	std::unique_ptr<NetworkStrand> strand;
 
 public:
 	NetworkHandler();
@@ -23,6 +24,7 @@ public:
 	std::unique_ptr<INetworkServer> createServerTCP(INetworkServerListener & listener) override;
 	void connectToRemote(INetworkClientListener & listener, const std::string & host, uint16_t port) override;
 	void createInternalConnection(INetworkClientListener & listener, INetworkServer & server) override;
+	std::shared_ptr<INetworkConnection> createAsyncConnection(INetworkConnectionListener & listener) override;
 	void createTimer(INetworkTimerListener & listener, std::chrono::milliseconds duration) override;
 
 	void run() override;

+ 3 - 0
lib/network/NetworkInterface.h

@@ -108,6 +108,9 @@ public:
 	/// On success INetworkTimerListener::onConnectionEstablished() will be called asynchronously, established connection provided as parameter
 	virtual void createInternalConnection(INetworkClientListener & listener, INetworkServer & server) = 0;
 
+	/// Creates one-way connection that allows sending messages to listener in async form
+	virtual std::shared_ptr<INetworkConnection> createAsyncConnection(INetworkConnectionListener & listener) = 0;
+
 	/// Creates a timer that will be called once, after specified interval has passed
 	/// On success: INetworkTimerListener::onTimer() will be called
 	/// On failure: no-op

+ 3 - 2
lib/network/NetworkServer.cpp

@@ -13,8 +13,9 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-NetworkServer::NetworkServer(INetworkServerListener & listener, NetworkContext & context)
+NetworkServer::NetworkServer(INetworkServerListener & listener, NetworkContext & context, NetworkStrand & strand)
 	: io(context)
+	, strand(strand)
 	, listener(listener)
 {
 }
@@ -59,7 +60,7 @@ void NetworkServer::onDisconnected(const std::shared_ptr<INetworkConnection> & c
 
 void NetworkServer::receiveInternalConnection(std::shared_ptr<IInternalConnection> remoteConnection)
 {
-	auto localConnection = std::make_shared<InternalConnection>(*this, io);
+	auto localConnection = std::make_shared<InternalConnection>(*this, strand);
 
 	connections.insert(localConnection);
 

+ 2 - 1
lib/network/NetworkServer.h

@@ -16,6 +16,7 @@ VCMI_LIB_NAMESPACE_BEGIN
 class NetworkServer : public INetworkConnectionListener, public INetworkServer
 {
 	NetworkContext & io;
+	NetworkStrand & strand;
 	std::shared_ptr<NetworkAcceptor> acceptor;
 	std::set<std::shared_ptr<INetworkConnection>> connections;
 
@@ -27,7 +28,7 @@ class NetworkServer : public INetworkConnectionListener, public INetworkServer
 	void onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage) override;
 	void onPacketReceived(const std::shared_ptr<INetworkConnection> & connection, const std::vector<std::byte> & message) override;
 public:
-	NetworkServer(INetworkServerListener & listener, NetworkContext & context);
+	NetworkServer(INetworkServerListener & listener, NetworkContext & context, NetworkStrand & strand);
 
 	void receiveInternalConnection(std::shared_ptr<IInternalConnection> remoteConnection) override;
 

+ 2 - 2
lib/serializer/GameConnection.cpp

@@ -19,7 +19,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 
-class DLL_LINKAGE ConnectionPackWriter final : public IBinaryWriter
+class ConnectionPackWriter final : public IBinaryWriter
 {
 public:
 	std::vector<std::byte> buffer;
@@ -27,7 +27,7 @@ public:
 	int write(const std::byte * data, unsigned size) final;
 };
 
-class DLL_LINKAGE ConnectionPackReader final : public IBinaryReader
+class ConnectionPackReader final : public IBinaryReader
 {
 public:
 	const std::vector<std::byte> * buffer;

+ 1 - 1
lib/serializer/GameConnection.h

@@ -50,7 +50,7 @@ public:
 
 	void sendPack(const CPack & pack) override;
 	int getConnectionID() const override;
-	std::unique_ptr<CPack> retrievePack(const std::vector<std::byte> & data);
+	std::unique_ptr<CPack> retrievePack(const std::vector<std::byte> & data) override;
 
 	void enterLobbyConnectionMode();
 	void setCallback(IGameInfoCallback & cb);

+ 1 - 0
lib/serializer/IGameConnection.h

@@ -17,6 +17,7 @@ class DLL_LINKAGE IGameConnection : boost::noncopyable
 {
 public:
 	virtual void sendPack(const CPack & pack) = 0;
+	virtual std::unique_ptr<CPack> retrievePack(const std::vector<std::byte> & data) = 0;
 	virtual int getConnectionID() const = 0;
 };
 

+ 1 - 0
lib/serializer/RegisterTypes.h

@@ -291,6 +291,7 @@ void registerTypes(Serializer &s)
 	s.template registerType<GiveStackExperience>(248);
 	s.template registerType<TimesStackSizeUpdater>(249);
 	s.template registerType<TimesArmySizeUpdater>(250);
+	s.template registerType<PackageReceived>(251);
 }
 
 VCMI_LIB_NAMESPACE_END

+ 2 - 1
server/CGameHandler.cpp

@@ -488,6 +488,7 @@ void CGameHandler::handleReceivedPack(std::shared_ptr<IGameConnection> connectio
 		connection->sendPack(applied);
 	};
 
+	std::this_thread::sleep_for(std::chrono::milliseconds(100));
 	PackageReceived received(
 		pack.player,
 		pack.requestID,
@@ -1505,7 +1506,7 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
 	}
 }
 
-void CGameHandler::sendToAllClients(const CPackForClient & pack)
+void CGameHandler::sendToAllClients(CPackForClient & pack)
 {
 	logNetwork->trace("\tSending to all clients: %s", typeid(pack).name());
 	gameServer().broadcastPack(pack);

+ 1 - 1
server/CGameHandler.h

@@ -276,7 +276,7 @@ public:
 #endif
 	}
 
-	void sendToAllClients(const CPackForClient & pack);
+	void sendToAllClients(CPackForClient & pack);
 	void sendAndApply(CPackForClient & pack) override;
 	void sendAndApply(CGarrisonOperationPack & pack);
 	void sendAndApply(SetResources & pack);

+ 1 - 1
server/CVCMIServer.cpp

@@ -1158,7 +1158,7 @@ bool CVCMIServer::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor r
 	return false;
 }
 
-void CVCMIServer::broadcastPack(const CPackForClient & pack)
+void CVCMIServer::broadcastPack(CPackForClient & pack)
 {
 	for (const auto & c : activeConnections)
 		c->sendPack(pack);

+ 1 - 1
server/CVCMIServer.h

@@ -64,7 +64,7 @@ public:
 	bool isPlayerHost(const PlayerColor & color) const override;
 	bool hasPlayerAt(PlayerColor player, const std::shared_ptr<IGameConnection> & c) const override;
 	bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
-	void broadcastPack(const CPackForClient & pack) override;
+	void broadcastPack(CPackForClient & pack) override;
 
 	/// List of all active connections
 	std::vector<std::shared_ptr<GameConnection>> activeConnections;

+ 1 - 1
server/IGameServer.h

@@ -33,5 +33,5 @@ public:
 	virtual bool isPlayerHost(const PlayerColor & color) const = 0;
 	virtual bool hasPlayerAt(PlayerColor player, const std::shared_ptr<IGameConnection> & c) const = 0;
 	virtual bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const = 0;
-	virtual void broadcastPack(const CPackForClient & pack) = 0;
+	virtual void broadcastPack(CPackForClient & pack) = 0;
 };