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:
@@ -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);
|
||||
}
|
@@ -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
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
201
client/netlag/NetworkLagCompensator.cpp
Normal file
201
client/netlag/NetworkLagCompensator.cpp
Normal 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);
|
||||
}
|
52
client/netlag/NetworkLagCompensator.h
Normal file
52
client/netlag/NetworkLagCompensator.h
Normal 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);
|
||||
};
|
30
client/netlag/NetworkLagPredictedPack.h
Normal file
30
client/netlag/NetworkLagPredictedPack.h
Normal 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;
|
||||
}
|
||||
};
|
67
client/netlag/NetworkLagPredictionTestVisitor.h
Normal file
67
client/netlag/NetworkLagPredictionTestVisitor.h
Normal 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;
|
||||
}
|
||||
};
|
28
client/netlag/NetworkLagReplyPrediction.h
Normal file
28
client/netlag/NetworkLagReplyPrediction.h
Normal 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;
|
||||
};
|
126
client/netlag/PackRollbackGeneratorVisitor.cpp
Normal file
126
client/netlag/PackRollbackGeneratorVisitor.cpp
Normal 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);
|
||||
}
|
@@ -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();
|
||||
};
|
Reference in New Issue
Block a user