diff --git a/client/Client.cpp b/client/Client.cpp index eec33cb18..c650d3f4e 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -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(); diff --git a/client/LobbyClientNetPackVisitors.h b/client/LobbyClientNetPackVisitors.h index 7ea105b02..276250455 100644 --- a/client/LobbyClientNetPackVisitors.h +++ b/client/LobbyClientNetPackVisitors.h @@ -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; }; diff --git a/client/NetPacksLobbyClient.cpp b/client/NetPacksLobbyClient.cpp index 8329e5e31..ab6f7e2c0 100644 --- a/client/NetPacksLobbyClient.cpp +++ b/client/NetPacksLobbyClient.cpp @@ -59,6 +59,9 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClient void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) { + if(auto w = GH.windows().topWindow()) + 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; - - GH.windows().createAndPushWindow(std::bind(&CServerHandler::startGameplay, &handler, pack.initializedGameState)); + if(auto w = GH.windows().topWindow()) + { + w->finish(); + w->tick(0); + w->redraw(); + } + else + GH.windows().createAndPushWindow(); +} + +void ApplyOnLobbyScreenNetPackVisitor::visitLobbyLoadProgress(LobbyLoadProgress & pack) +{ + if(auto w = GH.windows().topWindow()) + { + w->set(pack.progress); + w->tick(0); + w->redraw(); + } + else + GH.windows().createAndPushWindow(); } void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & pack) diff --git a/client/mainmenu/CMainMenu.cpp b/client/mainmenu/CMainMenu.cpp index aabff9076..ea2bdb93f 100644 --- a/client/mainmenu/CMainMenu.cpp +++ b/client/mainmenu/CMainMenu.cpp @@ -582,36 +582,67 @@ void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port) }); } -CLoadingScreen::CLoadingScreen(std::function 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(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); - - CWindowObject::showAll(to); + if(!progressBlocks.empty()) + { + int status = float(get()) / 255.f * progressBlocks.size(); + + 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; } diff --git a/client/mainmenu/CMainMenu.h b/client/mainmenu/CMainMenu.h index ffc17b67f..7e632fe6f 100644 --- a/client/mainmenu/CMainMenu.h +++ b/client/mainmenu/CMainMenu.h @@ -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> progressBlocks; + std::string getBackground(); -public: - CLoadingScreen(std::function loader); +public: + CLoadingScreen(); ~CLoadingScreen(); - void showAll(Canvas & to) override; + void tick(uint32_t msPassed) override; }; extern std::shared_ptr CMM; diff --git a/config/mainmenu.json b/config/mainmenu.json index 9cd4a6923..e9e357ecf 100644 --- a/config/mainmenu.json +++ b/config/mainmenu.json @@ -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": diff --git a/lib/LoadProgress.cpp b/lib/LoadProgress.cpp index aa24a4c2e..70eadbdbb 100644 --- a/lib/LoadProgress.cpp +++ b/lib/LoadProgress.cpp @@ -18,6 +18,11 @@ Progress::Progress(): _progress(std::numeric_limits::min()) setupSteps(100); } +Progress::Progress(int steps): _progress(std::numeric_limits::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 guard(_mx); + _progress.emplace_back(p); +} + +void ProgressAccumulator::exclude(const Progress & p) +{ + boost::unique_lock guard(_mx); + for(auto i = _progress.begin(); i != _progress.end(); ++i) + { + if(&i->get() == &p) + { + _accumulated += static_cast(p.get()) * p._maxSteps; + _steps += p._maxSteps; + _progress.erase(i); + return; + } + } +} + +bool ProgressAccumulator::finished() const +{ + boost::unique_lock guard(_mx); + for(auto i : _progress) + if(!i.get().finished()) + return false; + return true; +} + +Type ProgressAccumulator::get() const +{ + boost::unique_lock guard(_mx); + auto sum = _accumulated; + auto totalSteps = _steps; + for(auto p : _progress) + { + sum += static_cast(p.get().get()) * p.get()._maxSteps; + totalSteps += p.get()._maxSteps; + } + + if(totalSteps) + sum /= totalSteps; + return static_cast(sum); +} diff --git a/lib/LoadProgress.h b/lib/LoadProgress.h index 2911dab2a..1c3c21b0d 100644 --- a/lib/LoadProgress.h +++ b/lib/LoadProgress.h @@ -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 _progress, _target; std::atomic _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> _progress; +}; + } diff --git a/lib/NetPackVisitor.h b/lib/NetPackVisitor.h index 2d6c57165..f0c82b290 100644 --- a/lib/NetPackVisitor.h +++ b/lib/NetPackVisitor.h @@ -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) {} diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 2f04e48c0..2f1cd86a5 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -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); diff --git a/lib/NetPacksLobby.h b/lib/NetPacksLobby.h index 245b72353..8c19d87c9 100644 --- a/lib/NetPacksLobby.h +++ b/lib/NetPacksLobby.h @@ -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 void serialize(Handler &h, const int version) + { + h & progress; + } +}; + struct DLL_LINKAGE LobbyEndGame : public CLobbyPackToPropagate { bool closeConnection = false, restart = false; diff --git a/lib/gameState/CGameState.cpp b/lib/gameState/CGameState.cpp index 1fd3a696c..898243378 100644 --- a/lib/gameState/CGameState.cpp +++ b/lib/gameState/CGameState.cpp @@ -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 randomMap = mapGenerator.generate(); + progressTracking.exclude(mapGenerator); if(allowSavingRandomMap) { diff --git a/lib/gameState/CGameState.h b/lib/gameState/CGameState.h index 0281aa18e..ca906f6ff 100644 --- a/lib/gameState/CGameState.h +++ b/lib/gameState/CGameState.h @@ -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 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(); diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 0fb09b64d..88d43967c 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -379,6 +379,7 @@ void registerTypesLobbyPacks(Serializer &s) s.template registerType(); // Only host client send s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/mapeditor/icons/lock-closed.png b/mapeditor/icons/lock-closed.png new file mode 100644 index 000000000..9d7e19c17 Binary files /dev/null and b/mapeditor/icons/lock-closed.png differ diff --git a/mapeditor/icons/lock-open.png b/mapeditor/icons/lock-open.png new file mode 100644 index 000000000..d382de4c4 Binary files /dev/null and b/mapeditor/icons/lock-open.png differ diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index f0ec0cc1a..152ef5480 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -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 diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 446970f59..8d5f354ab 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -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 c); void handleReceivedPack(CPackForServer * pack); PlayerColor getPlayerAt(std::shared_ptr c) const; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 83a711fe9..4028ecc2d 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -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::max(); + + auto progressTrackingThread = boost::thread([this, &progressTracking, ¤tProgress]() + { + while(!progressTracking.finished()) + { + if(progressTracking.get() != currentProgress) + { + currentProgress = progressTracking.get(); + std::unique_ptr loadProgress(new LobbyLoadProgress); + loadProgress->progress = currentProgress; + addToAnnounceQueue(std::move(loadProgress)); + } + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + } + }); + gh = std::make_shared(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; } diff --git a/server/NetPacksLobbyServer.cpp b/server/NetPacksLobbyServer.cpp index 70c3a326b..54c31236a 100644 --- a/server/NetPacksLobbyServer.cpp +++ b/server/NetPacksLobbyServer.cpp @@ -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(*srv.gh->getStartInfo(true)); pack.initializedGameState = srv.gh->gameState(); + srv.state = EServerState::GAMEPLAY_STARTING; result = true; } diff --git a/test/game/CGameStateTest.cpp b/test/game/CGameStateTest.cpp index ae9bd2af0..32f0cd176 100644 --- a/test/game/CGameStateTest.cpp +++ b/test/game/CGameStateTest.cpp @@ -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);