瀏覽代碼

Merge pull request #2635 from Nordsoft91/loading-bar

Loading progress bar
Nordsoft91 2 年之前
父節點
當前提交
1bad0e96ef

+ 4 - 1
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();

+ 1 - 0
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;
 };

+ 24 - 6
client/NetPacksLobbyClient.cpp

@@ -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;
-	
-	GH.windows().createAndPushWindow<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, &handler, pack.initializedGameState));
+	if(auto w = GH.windows().topWindow<CLoadingScreen>())
+	{
+		w->finish();
+		w->tick(0);
+		w->redraw();
+	}
+	else
+		GH.windows().createAndPushWindow<CLoadingScreen>();
+}
+
+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)

+ 47 - 16
client/mainmenu/CMainMenu.cpp

@@ -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);
-
-	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())
-	{
-		return "loadbar";
-	}
-	else
+	if(conf.isStruct())
 	{
-		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;
 }

+ 9 - 6
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<std::shared_ptr<CAnimImage>> progressBlocks;
+	
 	std::string getBackground();
 
-public:
-	CLoadingScreen(std::function<void()> loader);
+public:	
+	CLoadingScreen();
 	~CLoadingScreen();
 
-	void showAll(Canvas & to) override;
+	void tick(uint32_t msPassed) override;
 };
 
 extern std::shared_ptr<CMainMenu> CMM;

+ 5 - 1
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":

+ 51 - 0
lib/LoadProgress.cpp

@@ -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);
+}

+ 24 - 1
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<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;
+};
+
 }

+ 1 - 0
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) {}

+ 5 - 0
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);

+ 12 - 0
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 <typename Handler> void serialize(Handler &h, const int version)
+	{
+		h & progress;
+	}
+};
+
 struct DLL_LINKAGE LobbyEndGame : public CLobbyPackToPropagate
 {
 	bool closeConnection = false, restart = false;

+ 5 - 3
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<CMap> randomMap = mapGenerator.generate();
+		progressTracking.exclude(mapGenerator);
 
 		if(allowSavingRandomMap)
 		{

+ 3 - 2
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<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();

+ 1 - 0
lib/registerTypes/RegisterTypes.h

@@ -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>();

二進制
mapeditor/icons/lock-closed.png


二進制
mapeditor/icons/lock-open.png


+ 2 - 2
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

+ 2 - 1
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<CConnection> c);
 	void handleReceivedPack(CPackForServer * pack);
 	PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;

+ 29 - 3
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<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;
 }
 

+ 2 - 0
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<StartInfo>(*srv.gh->getStartInfo(true));
 	pack.initializedGameState = srv.gh->gameState();
 
+	srv.state = EServerState::GAMEPLAY_STARTING;
 	result = true;
 }
 

+ 2 - 1
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);