1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-27 22:49:25 +02:00

Merge pull request #2635 from Nordsoft91/loading-bar

Loading progress bar
This commit is contained in:
Nordsoft91
2023-08-23 17:28:19 +04:00
committed by GitHub
21 changed files with 229 additions and 43 deletions

View File

@@ -181,7 +181,10 @@ void CClient::newGame(CGameState * initializedGameState)
gs->preInit(VLC);
logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
if(!initializedGameState)
gs->init(&mapService, CSH->si.get(), settings["general"]["saveRandomMaps"].Bool());
{
Load::ProgressAccumulator progressTracking;
gs->init(&mapService, CSH->si.get(), progressTracking, settings["general"]["saveRandomMaps"].Bool());
}
logNetwork->trace("Initializing GameState (together): %d ms", CSH->th->getDiff());
initMapHandler();

View File

@@ -53,6 +53,7 @@ public:
virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override;
virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override;
virtual void visitLobbyStartGame(LobbyStartGame & pack) override;
virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) override;
virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override;
virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override;
};

View File

@@ -59,6 +59,9 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClient
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{
if(auto w = GH.windows().topWindow<CLoadingScreen>())
GH.windows().popWindow(w);
if(GH.windows().count() > 0)
GH.windows().popWindows(1);
}
@@ -122,16 +125,31 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pac
handler.si = pack.initializedStartInfo;
handler.si->mode = modeBackup;
}
if(settings["session"]["headless"].Bool())
handler.startGameplay(pack.initializedGameState);
handler.startGameplay(pack.initializedGameState);
}
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
{
if(pack.clientId != -1 && pack.clientId != handler.c->connectionID)
return;
if(auto w = GH.windows().topWindow<CLoadingScreen>())
{
w->finish();
w->tick(0);
w->redraw();
}
else
GH.windows().createAndPushWindow<CLoadingScreen>();
}
GH.windows().createAndPushWindow<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, &handler, pack.initializedGameState));
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyLoadProgress(LobbyLoadProgress & pack)
{
if(auto w = GH.windows().topWindow<CLoadingScreen>())
{
w->set(pack.progress);
w->tick(0);
w->redraw();
}
else
GH.windows().createAndPushWindow<CLoadingScreen>();
}
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & pack)

View File

@@ -582,36 +582,67 @@ void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port)
});
}
CLoadingScreen::CLoadingScreen(std::function<void()> loader)
: CWindowObject(BORDERED, getBackground()), loadingThread(loader)
CLoadingScreen::CLoadingScreen()
: CWindowObject(BORDERED, getBackground())
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
addUsedEvents(TIME);
CCS->musich->stopMusic(5000);
const auto & conf = CMainMenuConfig::get().getConfig()["loading"];
if(conf.isStruct())
{
const int posx = conf["x"].Integer(), posy = conf["y"].Integer();
const int blockSize = conf["size"].Integer();
const int blocksAmount = conf["amount"].Integer();
for(int i = 0; i < blocksAmount; ++i)
{
progressBlocks.push_back(std::make_shared<CAnimImage>(conf["name"].String(), i, 0, posx + i * blockSize, posy));
progressBlocks.back()->deactivate();
progressBlocks.back()->visible = false;
}
}
}
CLoadingScreen::~CLoadingScreen()
{
loadingThread.join();
}
void CLoadingScreen::showAll(Canvas & to)
void CLoadingScreen::tick(uint32_t msPassed)
{
//FIXME: filling screen with transparency? BLACK intended?
//Rect rect(0, 0, to->w, to->h);
//CSDL_Ext::fillRect(to, rect, Colors::TRANSPARENCY);
if(!progressBlocks.empty())
{
int status = float(get()) / 255.f * progressBlocks.size();
CWindowObject::showAll(to);
for(int i = 0; i < status; ++i)
{
progressBlocks.at(i)->activate();
progressBlocks.at(i)->visible = true;
}
}
}
std::string CLoadingScreen::getBackground()
{
const auto & conf = CMainMenuConfig::get().getConfig()["loading"].Vector();
std::string fname = "loadbar";
const auto & conf = CMainMenuConfig::get().getConfig()["loading"];
if(conf.empty())
if(conf.isStruct())
{
return "loadbar";
}
else
{
return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String();
if(conf["background"].isVector())
return RandomGeneratorUtil::nextItem(conf["background"].Vector(), CRandomGenerator::getDefault())->String();
if(conf["background"].isString())
return conf["background"].String();
return fname;
}
if(conf.isVector() && !conf.Vector().empty())
return RandomGeneratorUtil::nextItem(conf.Vector(), CRandomGenerator::getDefault())->String();
return fname;
}

View File

@@ -11,6 +11,7 @@
#include "../windows/CWindowObject.h"
#include "../../lib/JsonNode.h"
#include "../../lib/LoadProgress.h"
VCMI_LIB_NAMESPACE_BEGIN
@@ -22,9 +23,11 @@ class CTextInput;
class CGStatusBar;
class CTextBox;
class CTabbedInt;
class CAnimImage;
class CAnimation;
class CButton;
class CFilledTexture;
class CLabel;
// TODO: Find new location for these enums
@@ -178,17 +181,17 @@ public:
CSimpleJoinScreen(bool host = true);
};
class CLoadingScreen : public CWindowObject
class CLoadingScreen : virtual public CWindowObject, virtual public Load::Progress
{
boost::thread loadingThread;
std::vector<std::shared_ptr<CAnimImage>> progressBlocks;
std::string getBackground();
public:
CLoadingScreen(std::function<void()> loader);
CLoadingScreen();
~CLoadingScreen();
void showAll(Canvas & to) override;
void tick(uint32_t msPassed) override;
};
extern std::shared_ptr<CMainMenu> CMM;

View File

@@ -2,7 +2,11 @@
//images used in game selection screen
"game-select" : ["gamselb0", "gamselb1"],
"loading" : ["loadbar"],
"loading" :
{
"background" : ["loadbar"],
"x": 395, "y": 548, "size": 18, "amount": 20, "name": "loadprog"
},
//Main menu window, consists of several sub-menus aka items
"window":

View File

@@ -18,6 +18,11 @@ Progress::Progress(): _progress(std::numeric_limits<Type>::min())
setupSteps(100);
}
Progress::Progress(int steps): _progress(std::numeric_limits<Type>::min())
{
setupSteps(steps);
}
Type Progress::get() const
{
if(_step >= _maxSteps)
@@ -82,3 +87,49 @@ void Progress::step(int count)
_step += count;
}
}
void ProgressAccumulator::include(const Progress & p)
{
boost::unique_lock<boost::mutex> guard(_mx);
_progress.emplace_back(p);
}
void ProgressAccumulator::exclude(const Progress & p)
{
boost::unique_lock<boost::mutex> guard(_mx);
for(auto i = _progress.begin(); i != _progress.end(); ++i)
{
if(&i->get() == &p)
{
_accumulated += static_cast<long long>(p.get()) * p._maxSteps;
_steps += p._maxSteps;
_progress.erase(i);
return;
}
}
}
bool ProgressAccumulator::finished() const
{
boost::unique_lock<boost::mutex> guard(_mx);
for(auto i : _progress)
if(!i.get().finished())
return false;
return true;
}
Type ProgressAccumulator::get() const
{
boost::unique_lock<boost::mutex> guard(_mx);
auto sum = _accumulated;
auto totalSteps = _steps;
for(auto p : _progress)
{
sum += static_cast<long long>(p.get().get()) * p.get()._maxSteps;
totalSteps += p.get()._maxSteps;
}
if(totalSteps)
sum /= totalSteps;
return static_cast<Type>(sum);
}

View File

@@ -18,6 +18,8 @@ namespace Load
using Type = unsigned char;
class ProgressAccumulator;
/*
* Purpose of that class is to track progress of computations
* Derive from this class if you want to translate user or system
@@ -29,8 +31,9 @@ class DLL_LINKAGE Progress
public:
//Sets current state to 0.
//Amount of steps to finish progress will be equal to 100
//Amount of steps to finish progress will be equal to 100 for default constructor
Progress();
Progress(int steps);
virtual ~Progress() = default;
//Returns current state of the progress
@@ -67,5 +70,25 @@ public:
private:
std::atomic<Type> _progress, _target;
std::atomic<int> _step, _maxSteps;
friend class ProgressAccumulator;
};
class DLL_LINKAGE ProgressAccumulator
{
public:
ProgressAccumulator() = default;
void include(const Progress &);
void exclude(const Progress &);
bool finished() const;
Type get() const;
private:
mutable boost::mutex _mx;
long long _accumulated = 0, _steps = 0;
std::vector<std::reference_wrapper<const Progress>> _progress;
};
}

View File

@@ -145,6 +145,7 @@ public:
virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) {}
virtual void visitLobbyChatMessage(LobbyChatMessage & pack) {}
virtual void visitLobbyGuiAction(LobbyGuiAction & pack) {}
virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) {}
virtual void visitLobbyEndGame(LobbyEndGame & pack) {}
virtual void visitLobbyStartGame(LobbyStartGame & pack) {}
virtual void visitLobbyChangeHost(LobbyChangeHost & pack) {}

View File

@@ -688,6 +688,11 @@ void LobbyGuiAction::visitTyped(ICPackVisitor & visitor)
visitor.visitLobbyGuiAction(*this);
}
void LobbyLoadProgress::visitTyped(ICPackVisitor & visitor)
{
visitor.visitLobbyLoadProgress(*this);
}
void LobbyEndGame::visitTyped(ICPackVisitor & visitor)
{
visitor.visitLobbyEndGame(*this);

View File

@@ -99,6 +99,18 @@ struct DLL_LINKAGE LobbyGuiAction : public CLobbyPackToPropagate
}
};
struct DLL_LINKAGE LobbyLoadProgress : public CLobbyPackToPropagate
{
unsigned char progress;
virtual void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & progress;
}
};
struct DLL_LINKAGE LobbyEndGame : public CLobbyPackToPropagate
{
bool closeConnection = false, restart = false;

View File

@@ -404,7 +404,7 @@ void CGameState::preInit(Services * services)
this->services = services;
}
void CGameState::init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap)
void CGameState::init(const IMapService * mapService, StartInfo * si, Load::ProgressAccumulator & progressTracking, bool allowSavingRandomMap)
{
preInitAuto();
logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed);
@@ -416,7 +416,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
switch(scenarioOps->mode)
{
case StartInfo::NEW_GAME:
initNewGame(mapService, allowSavingRandomMap);
initNewGame(mapService, allowSavingRandomMap, progressTracking);
break;
case StartInfo::CAMPAIGN:
initCampaign();
@@ -535,7 +535,7 @@ void CGameState::preInitAuto()
}
}
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap)
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
{
if(scenarioOps->createRandomMap())
{
@@ -544,8 +544,10 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
// Gen map
CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed);
progressTracking.include(mapGenerator);
std::unique_ptr<CMap> randomMap = mapGenerator.generate();
progressTracking.exclude(mapGenerator);
if(allowSavingRandomMap)
{

View File

@@ -11,6 +11,7 @@
#include "bonuses/CBonusSystemNode.h"
#include "IGameCallback.h"
#include "LoadProgress.h"
namespace boost
{
@@ -89,7 +90,7 @@ public:
void preInit(Services * services);
void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false);
void init(const IMapService * mapService, StartInfo * si, Load::ProgressAccumulator &, bool allowSavingRandomMap = false);
void updateOnLoad(StartInfo * si);
ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
@@ -166,7 +167,7 @@ public:
private:
// ----- initialization -----
void preInitAuto();
void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
void initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking);
void checkMapChecksum();
void initGlobalBonuses();
void initGrailPosition();

View File

@@ -379,6 +379,7 @@ void registerTypesLobbyPacks(Serializer &s)
s.template registerType<CLobbyPackToPropagate, LobbyChatMessage>();
// Only host client send
s.template registerType<CLobbyPackToPropagate, LobbyGuiAction>();
s.template registerType<CLobbyPackToPropagate, LobbyLoadProgress>();
s.template registerType<CLobbyPackToPropagate, LobbyEndGame>();
s.template registerType<CLobbyPackToPropagate, LobbyStartGame>();
s.template registerType<CLobbyPackToPropagate, LobbyChangeHost>();

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

View File

@@ -579,7 +579,7 @@ void CGameHandler::reinitScripting()
#endif
}
void CGameHandler::init(StartInfo *si)
void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTracking)
{
if (si->seedToBeUsed == 0)
{
@@ -589,7 +589,7 @@ void CGameHandler::init(StartInfo *si)
gs = new CGameState();
gs->preInit(VLC);
logGlobal->info("Gamestate created!");
gs->init(&mapService, si);
gs->init(&mapService, si, progressTracking);
logGlobal->info("Gamestate initialized!");
// reset seed, so that clients can't predict any following random values

View File

@@ -13,6 +13,7 @@
#include "../lib/IGameCallback.h"
#include "../lib/battle/CBattleInfoCallback.h"
#include "../lib/LoadProgress.h"
#include "../lib/ScriptHandler.h"
#include "TurnTimerHandler.h"
@@ -201,7 +202,7 @@ public:
void expGiven(const CGHeroInstance *hero); //triggers needed level-ups, handles also commander of this hero
//////////////////////////////////////////////////////////////////////////
void init(StartInfo *si);
void init(StartInfo *si, Load::ProgressAccumulator & progressTracking);
void handleClientDisconnection(std::shared_ptr<CConnection> c);
void handleReceivedPack(CPackForServer * pack);
PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;

View File

@@ -279,6 +279,26 @@ void CVCMIServer::prepareToRestart()
bool CVCMIServer::prepareToStartGame()
{
Load::ProgressAccumulator progressTracking;
Load::Progress current(1);
progressTracking.include(current);
Load::Type currentProgress = std::numeric_limits<Load::Type>::max();
auto progressTrackingThread = boost::thread([this, &progressTracking, &currentProgress]()
{
while(!progressTracking.finished())
{
if(progressTracking.get() != currentProgress)
{
currentProgress = progressTracking.get();
std::unique_ptr<LobbyLoadProgress> loadProgress(new LobbyLoadProgress);
loadProgress->progress = currentProgress;
addToAnnounceQueue(std::move(loadProgress));
}
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
}
});
gh = std::make_shared<CGameHandler>(this);
switch(si->mode)
{
@@ -286,18 +306,22 @@ bool CVCMIServer::prepareToStartGame()
logNetwork->info("Preparing to start new campaign");
si->campState->setCurrentMap(campaignMap);
si->campState->setCurrentMapBonus(campaignBonus);
gh->init(si.get());
gh->init(si.get(), progressTracking);
break;
case StartInfo::NEW_GAME:
logNetwork->info("Preparing to start new game");
gh->init(si.get());
gh->init(si.get(), progressTracking);
break;
case StartInfo::LOAD_GAME:
logNetwork->info("Preparing to start loaded game");
if(!gh->load(si->mapname))
{
current.finish();
progressTrackingThread.join();
return false;
}
break;
default:
logNetwork->error("Wrong mode in StartInfo!");
@@ -305,7 +329,9 @@ bool CVCMIServer::prepareToStartGame()
break;
}
state = EServerState::GAMEPLAY_STARTING;
current.finish();
progressTrackingThread.join();
return true;
}

View File

@@ -289,6 +289,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
result = false;
return;
}
// Server will prepare gamestate and we announce StartInfo to clients
if(!srv.prepareToStartGame())
{
@@ -299,6 +300,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
pack.initializedStartInfo = std::make_shared<StartInfo>(*srv.gh->getStartInfo(true));
pack.initializedGameState = srv.gh->gameState();
srv.state = EServerState::GAMEPLAY_STARTING;
result = true;
}

View File

@@ -178,7 +178,8 @@ public:
}
gameState->init(&mapService, &si, false);
Load::ProgressAccumulator progressTracker;
gameState->init(&mapService, &si, progressTracker, false);
ASSERT_NE(map, nullptr);
ASSERT_EQ(map->heroesOnMap.size(), 2);