1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-09-16 09:26:28 +02:00

Add comments, rename entities to more logical names, reorganize code

This commit is contained in:
Ivan Savenko
2025-07-28 18:13:41 +03:00
parent 6a22b042ac
commit 44cc3a0214
14 changed files with 584 additions and 459 deletions

View File

@@ -1,334 +0,0 @@
/*
* 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 "CServerHandler.h"
#include "Client.h"
#include "GameEngine.h"
#include "../server/CGameHandler.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/serializer/GameConnection.h"
#include "GameInstance.h"
class AntilagRollbackNotSupportedException : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
int ConnectionPackWriter::write(const std::byte * data, unsigned size)
{
buffer.insert(buffer.end(), data, data + size);
return size;
}
void AntilagRollbackGeneratorVisitor::visitPackageReceived(PackageReceived & pack)
{
success = true;
// no-op rollback?
}
void AntilagRollbackGeneratorVisitor::visitPackageApplied(PackageApplied & pack)
{
success = true;
// no-op rollback?
}
void AntilagRollbackGeneratorVisitor::visitPlayerBlocked(PlayerBlocked & pack)
{
success = true;
// no-op rollback?
}
void AntilagRollbackGeneratorVisitor::visitSwapStacks(SwapStacks & pack)
{
auto rollbackSwap = std::make_unique<SwapStacks>();
rollbackSwap->srcArmy = pack.dstArmy;
rollbackSwap->dstArmy = pack.srcArmy;
rollbackSwap->srcSlot = pack.dstSlot;
rollbackSwap->dstSlot = pack.srcSlot;
rollbackPacks.push_back(std::move(rollbackSwap));
success = true;
}
void AntilagRollbackGeneratorVisitor::visitRebalanceStacks(RebalanceStacks & pack)
{
const auto * srcObject = gs.getObjInstance(pack.srcArmy);
const auto * dstObject = gs.getObjInstance(pack.dstArmy);
const auto * srcArmy = dynamic_cast<const CArmedInstance*>(srcObject);
const auto * dstArmy = dynamic_cast<const CArmedInstance*>(dstObject);
if (srcArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
dstArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
srcArmy->getStack(pack.srcSlot).getSlot(ArtifactPosition::CREATURE_SLOT)->artifactID.hasValue())
{
// TODO: rollback creature artifacts & stack experience
return;
}
auto rollbackRebalance = std::make_unique<RebalanceStacks>();
rollbackRebalance->srcArmy = pack.dstArmy;
rollbackRebalance->dstArmy = pack.srcArmy;
rollbackRebalance->srcSlot = pack.dstSlot;
rollbackRebalance->dstSlot = pack.srcSlot;
rollbackRebalance->count = pack.count;
rollbackPacks.push_back(std::move(rollbackRebalance));
success = true;
}
void AntilagRollbackGeneratorVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
{
for (auto & subpack : pack.moves)
visitRebalanceStacks(subpack);
success = true;
}
void AntilagRollbackGeneratorVisitor::visitHeroVisitCastle(HeroVisitCastle & pack)
{
auto rollbackVisit = std::make_unique<HeroVisitCastle>();
rollbackVisit->startVisit = !pack.startVisit;
rollbackVisit->tid = pack.tid;
rollbackVisit->hid = pack.hid;
rollbackPacks.push_back(std::move(rollbackVisit));
success = true;
}
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;
}
void AntilagReplyPredictionVisitor::visitArrangeStacks(ArrangeStacks & pack)
{
canBeAppliedValue = true;
}
bool AntilagReplyPredictionVisitor::canBeApplied() const
{
return canBeAppliedValue;
}
AntilagServer::AntilagServer(INetworkHandler & network, const std::shared_ptr<CGameState> & gs)
: gameState(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());
AntilagReplyPrediction newPrediction;
newPrediction.requestID = serverPack->requestID;
newPrediction.senderID = serverPack->player;
predictedReplies.push_back(std::move(newPrediction));
try
{
CGameHandler gameHandler(*this, gameState);
gameHandler.handleReceivedPack(GameConnectionID::FIRST_CONNECTION, *serverPack);
}
catch (const AntilagRollbackNotSupportedException & )
{
return;
}
}
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);
if (!predictedReplies.empty() && predictedReplies.front().requestID == packageReceived->requestID)
{
[[maybe_unused]] 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)
{
// Our prediction was sucessful - drop rollback information for this pack
predictedReplies.front().writtenPacks.erase(predictedReplies.front().writtenPacks.begin());
if (predictedReplies.front().writtenPacks.empty())
{
predictedReplies.erase(predictedReplies.begin());
currentPackageID = invalidPackageID;
return true;
}
}
else
{
// Prediction was incorrect - rollback everything that is left in this prediction and use real server packs
for (auto & prediction : boost::adaptors::reverse(predictedReplies.front().writtenPacks))
{
for (auto & pack : prediction.rollbackPacks)
GAME->server().client->handlePack(*pack);
}
predictedReplies.erase(predictedReplies.begin());
currentPackageID = invalidPackageID;
return false;
}
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, GameConnectionID c) const
{
return true; // TODO?
}
bool AntilagServer::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
{
return false; // TODO?
}
void AntilagServer::applyPack(CPackForClient & pack)
{
AntilagRollbackGeneratorVisitor visitor(*gameState);
pack.visit(visitor);
if (!visitor.canBeRolledBack())
{
logGlobal->info("Prediction not possible: pack '%s'", typeid(pack).name());
throw AntilagRollbackNotSupportedException(std::string("Prediction not possible ") + typeid(pack).name());
}
logGlobal->info("Prediction: pack '%s'", typeid(pack).name());
ConnectionPackWriter packWriter;
BinarySerializer serializer(&packWriter);
serializer & &pack;
packWriter.rollbackPacks = visitor.getRollbackPacks();
predictedReplies.back().writtenPacks.push_back(std::move(packWriter));
GAME->server().client->handlePack(pack);
}
void AntilagServer::sendPack(CPackForClient & pack, GameConnectionID connectionID)
{
applyPack(pack);
}

View File

@@ -88,6 +88,9 @@ set(vcmiclientcommon_SRCS
media/CSoundHandler.cpp
media/CVideoHandler.cpp
netlag/NetworkLagCompensator.cpp
netlag/PackRollbackGeneratorVisitor.cpp
render/AssetGenerator.cpp
render/CAnimation.cpp
render/CBitmapHandler.cpp
@@ -184,7 +187,6 @@ set(vcmiclientcommon_SRCS
xBRZ/xbrz.cpp
AntilagServer.cpp
ArtifactsUIController.cpp
GameEngine.cpp
GameInstance.cpp
@@ -301,6 +303,12 @@ set(vcmiclientcommon_HEADERS
media/ISoundPlayer.h
media/IVideoPlayer.h
netlag/NetworkLagCompensator.h
netlag/NetworkLagPredictedPack.h
netlag/NetworkLagPredictionTestVisitor.h
netlag/NetworkLagReplyPrediction.h
netlag/PackRollbackGeneratorVisitor.h
render/AssetGenerator.h
render/CAnimation.h
render/CBitmapHandler.h
@@ -408,7 +416,6 @@ set(vcmiclientcommon_HEADERS
xBRZ/xbrz.h
xBRZ/xbrz_tools.h
AntilagServer.h
ArtifactsUIController.h
CMT.h
CPlayerInterface.h

View File

@@ -10,21 +10,24 @@
#include "StdInc.h"
#include "CServerHandler.h"
#include "AntilagServer.h"
#include "Client.h"
#include "ServerRunner.h"
#include "GameChatHandler.h"
#include "CPlayerInterface.h"
#include "Client.h"
#include "GameChatHandler.h"
#include "GameEngine.h"
#include "GameInstance.h"
#include "gui/WindowHandler.h"
#include "LobbyClientNetPackVisitors.h"
#include "ServerRunner.h"
#include "globalLobby/GlobalLobbyClient.h"
#include "gui/WindowHandler.h"
#include "lobby/CSelectionBase.h"
#include "lobby/CLobbyScreen.h"
#include "lobby/CBonusSelection.h"
#include "windows/InfoWindows.h"
#include "windows/GUIClasses.h"
#include "netlag/NetworkLagCompensator.h"
#include "media/CMusicHandler.h"
#include "media/IVideoPlayer.h"
@@ -32,6 +35,9 @@
#include "mainmenu/CPrologEpilogVideo.h"
#include "mainmenu/CHighScoreScreen.h"
#include "windows/InfoWindows.h"
#include "windows/GUIClasses.h"
#include "../lib/CConfigHandler.h"
#include "../lib/GameLibrary.h"
#include "../lib/texts/CGeneralTextHandler.h"
@@ -55,7 +61,6 @@
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include "LobbyClientNetPackVisitors.h"
#include <vcmi/events/EventBus.h>
#include <SDL_thread.h>
@@ -612,13 +617,21 @@ void CServerHandler::startMapAfterConnection(std::shared_ptr<CMapInfo> to)
mapToStart = to;
}
void CServerHandler::enableLagCompensation(bool on)
{
if (on)
networkLagCompensator = std::make_unique<NetworkLagCompensator>(getNetworkHandler(), client->gameStatePtr());
else
networkLagCompensator.reset();
}
void CServerHandler::startGameplay(std::shared_ptr<CGameState> gameState)
{
if(GAME->mainmenu())
GAME->mainmenu()->disable();
//if (isGuest())
networkLagCompensator = std::make_unique<AntilagServer>(getNetworkHandler(), gameState);
if (isGuest())
networkLagCompensator = std::make_unique<NetworkLagCompensator>(getNetworkHandler(), gameState);
switch(si->mode)
{

View File

@@ -33,7 +33,7 @@ class HighScoreParameter;
VCMI_LIB_NAMESPACE_END
class AntilagServer;
class NetworkLagCompensator;
class CClient;
class CBaseForLobbyApply;
class GlobalLobbyClient;
@@ -102,7 +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> networkLagCompensator;
std::unique_ptr<NetworkLagCompensator> networkLagCompensator;
std::shared_ptr<CMapInfo> mapToStart;
std::vector<std::string> localPlayerNames;
@@ -150,6 +150,7 @@ public:
void resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode serverMode, const std::vector<std::string> & playerNames);
void startLocalServerAndConnect(bool connectToLobby);
void connectToServer(const std::string & addr, const ui16 port);
void enableLagCompensation(bool on);
GameChatHandler & getGameChat();
GlobalLobbyClient & getGlobalLobby();

View File

@@ -144,6 +144,7 @@ public:
vstd::CLoggerBase * logger() const override;
events::EventBus * eventBus() const override;
std::shared_ptr<CGameState> gameStatePtr() { return gamestate; }
CGameState & gameState() { return *gamestate; }
const CGameState & gameState() const { return *gamestate; }
IGameInfoCallback & gameInfo();

View File

@@ -339,6 +339,29 @@ void ClientCommandManager::handleGetConfigCommand()
printCommandMessage("Extracted files can be found in " + outPath.string() + " directory\n");
}
void ClientCommandManager::handleAntilagCommand(std::istringstream& singleWordBuffer)
{
std::string commandName;
singleWordBuffer >> commandName;
if (commandName == "on")
{
GAME->server().enableLagCompensation(true);
printCommandMessage("Network lag compensation is now enabled.\n");
}
else if (commandName == "off")
{
GAME->server().enableLagCompensation(true);
printCommandMessage("Network lag compensation is now disabled.\n");
}
else
{
printCommandMessage("Unexpected syntax. Supported forms:\n");
printCommandMessage("'antilag on'\n");
printCommandMessage("'antilag off'\n");
}
}
void ClientCommandManager::handleGetScriptsCommand()
{
#if SCRIPTING_ENABLED
@@ -596,6 +619,9 @@ void ClientCommandManager::processCommand(const std::string & message, bool call
else if(commandName == "setBattleAI")
handleSetBattleAICommand(singleWordBuffer);
else if(commandName == "antilag")
handleAntilagCommand(singleWordBuffer);
else if(commandName == "redraw")
handleRedrawCommand();

View File

@@ -45,6 +45,9 @@ class ClientCommandManager //take mantis #2292 issue about account if thinking a
// Redraw the current screen
void handleRedrawCommand();
// Enable or disable network lag compensation
void handleAntilagCommand(std::istringstream& singleWordBuffer);
// Extracts all translateable game texts into Translation directory, separating files on per-mod basis
void handleTranslateGameCommand(bool onlyMissing);

View File

@@ -0,0 +1,201 @@
/*
* NetworkLagCompensator.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 "NetworkLagCompensator.h"
#include "NetworkLagPredictionTestVisitor.h"
#include "NetworkLagReplyPrediction.h"
#include "PackRollbackGeneratorVisitor.h"
#include "../CServerHandler.h"
#include "../Client.h"
#include "../GameEngine.h"
#include "../GameInstance.h"
#include "../../lib/serializer/BinarySerializer.h"
#include "../../lib/serializer/GameConnection.h"
#include "../../server/CGameHandler.h"
class RollbackNotSupportedException : public std::runtime_error
{
public:
using std::runtime_error::runtime_error;
};
NetworkLagCompensator::NetworkLagCompensator(INetworkHandler & network, const std::shared_ptr<CGameState> & gs)
: gameState(gs)
{
antilagNetConnection = network.createAsyncConnection(*this);
antilagGameConnection = std::make_shared<GameConnection>(antilagNetConnection);
}
NetworkLagCompensator::~NetworkLagCompensator() = default;
void NetworkLagCompensator::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
{
// should never be called
throw std::runtime_error("AntilagServer::onDisconnected called!");
}
void NetworkLagCompensator::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());
NetworkLagPredictionTestVisitor packVisitor;
serverPack->visit(packVisitor);
if(!packVisitor.canBeApplied())
return;
logGlobal->info("Predicting effects of pack '%s'", typeid(*serverPack).name());
NetworkLagReplyPrediction newPrediction;
newPrediction.requestID = serverPack->requestID;
newPrediction.senderID = serverPack->player;
predictedReplies.push_back(std::move(newPrediction));
try
{
CGameHandler gameHandler(*this, gameState);
gameHandler.handleReceivedPack(GameConnectionID::FIRST_CONNECTION, *serverPack);
}
catch(const RollbackNotSupportedException &)
{
return;
}
}
void NetworkLagCompensator::tryPredictReply(const CPackForServer & request)
{
antilagGameConnection->sendPack(request);
logGlobal->info("Scheduled prediction of effects of pack '%s'", typeid(request).name());
}
bool NetworkLagCompensator::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);
if(!predictedReplies.empty() && predictedReplies.front().requestID == packageReceived->requestID)
{
[[maybe_unused]] 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;
}
NetworkLagPredictedPack packWriter;
BinarySerializer serializer(&packWriter);
serializer & &pack;
if(packWriter.predictedPackData == predictedReplies.front().writtenPacks.front().predictedPackData)
{
// Our prediction was sucessful - drop rollback information for this pack
predictedReplies.front().writtenPacks.erase(predictedReplies.front().writtenPacks.begin());
if(predictedReplies.front().writtenPacks.empty())
{
predictedReplies.erase(predictedReplies.begin());
currentPackageID = invalidPackageID;
return true;
}
}
else
{
// Prediction was incorrect - rollback everything that is left in this prediction and use real server packs
for(auto & prediction : boost::adaptors::reverse(predictedReplies.front().writtenPacks))
{
for(auto & pack : prediction.rollbackPacks)
GAME->server().client->handlePack(*pack);
}
predictedReplies.erase(predictedReplies.begin());
currentPackageID = invalidPackageID;
return false;
}
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 NetworkLagCompensator::setState(EServerState value)
{
// no-op
}
EServerState NetworkLagCompensator::getState() const
{
return EServerState::GAMEPLAY;
}
bool NetworkLagCompensator::isPlayerHost(const PlayerColor & color) const
{
return false; // TODO?
}
bool NetworkLagCompensator::hasPlayerAt(PlayerColor player, GameConnectionID c) const
{
return true; // TODO?
}
bool NetworkLagCompensator::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
{
return false; // TODO?
}
void NetworkLagCompensator::applyPack(CPackForClient & pack)
{
PackRollbackGeneratorVisitor visitor(*gameState);
pack.visit(visitor);
if(!visitor.canBeRolledBack())
{
logGlobal->info("Prediction not possible: pack '%s'", typeid(pack).name());
throw RollbackNotSupportedException(std::string("Prediction not possible ") + typeid(pack).name());
}
logGlobal->info("Prediction: pack '%s'", typeid(pack).name());
NetworkLagPredictedPack packWriter;
BinarySerializer serializer(&packWriter);
serializer & &pack;
packWriter.rollbackPacks = visitor.acquireRollbackPacks();
predictedReplies.back().writtenPacks.push_back(std::move(packWriter));
GAME->server().client->handlePack(pack);
}
void NetworkLagCompensator::sendPack(CPackForClient & pack, GameConnectionID connectionID)
{
applyPack(pack);
}

View File

@@ -0,0 +1,52 @@
/*
* NetworkLagCompensator.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/network/NetworkInterface.h"
#include "../../server/IGameServer.h"
VCMI_LIB_NAMESPACE_BEGIN
struct CPackForServer;
class CGameState;
class GameConnection;
VCMI_LIB_NAMESPACE_END
class NetworkLagReplyPrediction;
/// Fake server that is used by client to make a quick prediction on what real server would reply without waiting for network latency
class NetworkLagCompensator final : public IGameServer, public INetworkConnectionListener, boost::noncopyable
{
std::vector<NetworkLagReplyPrediction> predictedReplies;
std::shared_ptr<INetworkConnection> antilagNetConnection;
std::shared_ptr<GameConnection> antilagGameConnection;
std::shared_ptr<CGameState> gameState;
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, GameConnectionID connection) const override;
bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
void applyPack(CPackForClient & pack) override;
void sendPack(CPackForClient & pack, GameConnectionID connectionID) 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:
NetworkLagCompensator(INetworkHandler & network, const std::shared_ptr<CGameState> & gs);
~NetworkLagCompensator();
void tryPredictReply(const CPackForServer & request);
bool verifyReply(const CPackForClient & reply);
};

View File

@@ -0,0 +1,30 @@
/*
* NetworkLagPredictedPack.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/NetPacksBase.h"
#include "../../lib/serializer/CSerializer.h"
/// Class that contains data for a single pack that client expects to receive from server
class NetworkLagPredictedPack final : public IBinaryWriter
{
public:
/// Serialized data of predicted pack, for comparison with version from server
std::vector<std::byte> predictedPackData;
/// List of packs that must be applied on client in case of failed prediction
std::vector<std::unique_ptr<CPackForClient>> rollbackPacks;
int write(const std::byte * data, unsigned size) final
{
predictedPackData.insert(predictedPackData.end(), data, data + size);
return size;
}
};

View File

@@ -0,0 +1,67 @@
/*
* 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"
/// Class that filters which client request to server can be predicted, at least partially
class NetworkLagPredictionTestVisitor final : public 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
{
canBeAppliedValue = true;
}
//void visitCastleTeleportHero(CastleTeleportHero & pack) override;
void visitArrangeStacks(ArrangeStacks & pack) override
{
canBeAppliedValue = true;
}
//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:
/// Returns true if game should try to predict results of pack on which this class was applied
bool canBeApplied() const
{
return canBeAppliedValue;
}
};

View File

@@ -0,0 +1,28 @@
/*
* NetworkLagReplyPrediction.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 "NetworkLagPredictedPack.h"
#include "../../lib/constants/EntityIdentifiers.h"
/// Class that contains data for a single pack that client sent to server
class NetworkLagReplyPrediction
{
public:
/// Sender that sent this reply. Generally - this should be only local client color
PlayerColor senderID;
/// Unique request ID that was written into sent pack
uint32_t requestID;
/// One or more packs that client expects to receive from server as a reply
std::vector<NetworkLagPredictedPack> writtenPacks;
};

View File

@@ -0,0 +1,126 @@
/*
* PackRollbackGeneratorVisitor.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 "PackRollbackGeneratorVisitor.h"
#include "../lib/gameState/CGameState.h"
#include "../lib/mapObjects/CGHeroInstance.h"
void PackRollbackGeneratorVisitor::visitPackageReceived(PackageReceived & pack)
{
success = true;
// no-op rollback?
}
void PackRollbackGeneratorVisitor::visitPackageApplied(PackageApplied & pack)
{
success = true;
// no-op rollback?
}
void PackRollbackGeneratorVisitor::visitPlayerBlocked(PlayerBlocked & pack)
{
success = true;
// no-op rollback?
}
void PackRollbackGeneratorVisitor::visitSwapStacks(SwapStacks & pack)
{
auto rollbackSwap = std::make_unique<SwapStacks>();
rollbackSwap->srcArmy = pack.dstArmy;
rollbackSwap->dstArmy = pack.srcArmy;
rollbackSwap->srcSlot = pack.dstSlot;
rollbackSwap->dstSlot = pack.srcSlot;
rollbackPacks.push_back(std::move(rollbackSwap));
success = true;
}
void PackRollbackGeneratorVisitor::visitRebalanceStacks(RebalanceStacks & pack)
{
const auto * srcObject = gs.getObjInstance(pack.srcArmy);
const auto * dstObject = gs.getObjInstance(pack.dstArmy);
const auto * srcArmy = dynamic_cast<const CArmedInstance *>(srcObject);
const auto * dstArmy = dynamic_cast<const CArmedInstance *>(dstObject);
if (srcArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
dstArmy->getStack(pack.srcSlot).getTotalExperience() != 0 ||
srcArmy->getStack(pack.srcSlot).getSlot(ArtifactPosition::CREATURE_SLOT)->artifactID.hasValue())
{
// TODO: rollback creature artifacts & stack experience
return;
}
auto rollbackRebalance = std::make_unique<RebalanceStacks>();
rollbackRebalance->srcArmy = pack.dstArmy;
rollbackRebalance->dstArmy = pack.srcArmy;
rollbackRebalance->srcSlot = pack.dstSlot;
rollbackRebalance->dstSlot = pack.srcSlot;
rollbackRebalance->count = pack.count;
rollbackPacks.push_back(std::move(rollbackRebalance));
success = true;
}
void PackRollbackGeneratorVisitor::visitBulkRebalanceStacks(BulkRebalanceStacks & pack)
{
for(auto & subpack : pack.moves)
visitRebalanceStacks(subpack);
success = true;
}
void PackRollbackGeneratorVisitor::visitHeroVisitCastle(HeroVisitCastle & pack)
{
auto rollbackVisit = std::make_unique<HeroVisitCastle>();
rollbackVisit->startVisit = !pack.startVisit;
rollbackVisit->tid = pack.tid;
rollbackVisit->hid = pack.hid;
rollbackPacks.push_back(std::move(rollbackVisit));
success = true;
}
void PackRollbackGeneratorVisitor::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 PackRollbackGeneratorVisitor::canBeRolledBack() const
{
return success;
}
std::vector<std::unique_ptr<CPackForClient>> PackRollbackGeneratorVisitor::acquireRollbackPacks()
{
return std::move(rollbackPacks);
}

View File

@@ -1,5 +1,5 @@
/*
* AntilagServer.h, part of VCMI engine
* PackRollbackGeneratorVisitor.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@@ -9,83 +9,13 @@
*/
#pragma once
#include "../lib/networkPacks/NetPackVisitor.h"
#include "../server/IGameServer.h"
#include "../lib/network/NetworkInterface.h"
#include "../lib/serializer/CSerializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../../lib/networkPacks/NetPackVisitor.h"
VCMI_LIB_NAMESPACE_BEGIN
struct CPackForServer;
class GameConnection;
VCMI_LIB_NAMESPACE_END
class CGameHandler;
class ConnectionPackWriter final : public IBinaryWriter
{
public:
std::vector<std::byte> buffer;
std::vector<std::unique_ptr<CPackForClient>> rollbackPacks;
int write(const std::byte * data, unsigned size) final;
};
class AntilagReplyPrediction
{
public:
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
/// Class that generates data for rollback
/// on success, canBeRolledBack() method will return true
/// and rollbackPacks will contain list of packs that can be applied to rollback provided pack
/// Note that it is legal for rollbackPacks list to be empty for some trivial packs
class PackRollbackGeneratorVisitor final : public ICPackVisitor
{
private:
const CGameState & gs;
@@ -173,41 +103,15 @@ private:
//void visitTurnTimeUpdate(TurnTimeUpdate & pack) override;
public:
AntilagRollbackGeneratorVisitor(const CGameState & gs)
PackRollbackGeneratorVisitor(const CGameState & gs)
: gs(gs)
{}
{
}
/// Returns true if tested pack can be rolled back
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<AntilagReplyPrediction> predictedReplies;
std::shared_ptr<INetworkConnection> antilagNetConnection;
std::shared_ptr<GameConnection> antilagGameConnection;
std::shared_ptr<CGameState> gameState;
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, GameConnectionID connection) const override;
bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
void applyPack(CPackForClient & pack) override;
void sendPack(CPackForClient & pack, GameConnectionID connectionID) 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);
/// Acquires list of packs that can be used to rollback tested pack
/// (!) non-reentrable
std::vector<std::unique_ptr<CPackForClient>> acquireRollbackPacks();
};