Browse Source

Fix crash on closing game during background image upscaling

Ivan Savenko 7 months ago
parent
commit
96d691b40c

+ 6 - 7
AI/Nullkiller/AIGateway.cpp

@@ -10,6 +10,7 @@
 #include "StdInc.h"
 
 #include "../../lib/ArtifactUtils.h"
+#include "../../lib/AsyncRunner.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/StartInfo.h"
 #include "../../lib/entities/building/CBuilding.h"
@@ -32,8 +33,6 @@
 #include "AIGateway.h"
 #include "Goals/Goals.h"
 
-static tbb::task_arena executeActionAsyncArena;
-
 namespace NKAI
 {
 
@@ -73,7 +72,7 @@ AIGateway::AIGateway()
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
 	nullkiller.reset(new Nullkiller());
-	asyncTasks = std::make_unique<tbb::task_group>();
+	asyncTasks = std::make_unique<AsyncRunner>();
 }
 
 AIGateway::~AIGateway()
@@ -593,11 +592,11 @@ void AIGateway::yourTurn(QueryID queryID)
 
 	nullkiller->makingTurnInterrupption.reset();
 
-	executeActionAsyncArena.enqueue(asyncTasks->defer([this]()
+	asyncTasks->run([this]()
 	{
 		ScopedThreadName guard("NKAI::makingTurn");
 		makeTurn();
-	}));
+	});
 }
 
 void AIGateway::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
@@ -1608,13 +1607,13 @@ void AIGateway::executeActionAsync(const std::string & description, const std::f
 	if (!asyncTasks)
 		throw std::runtime_error("Attempt to execute task on shut down AI state!");
 
-	executeActionAsyncArena.enqueue(asyncTasks->defer([this, description, whatToDo]()
+	asyncTasks->run([this, description, whatToDo]()
 	{
 		ScopedThreadName guard("NKAI::" + description);
 		SET_GLOBAL_STATE(this);
 		std::shared_lock gsLock(CGameState::mutex);
 		whatToDo();
-	}));
+	});
 }
 
 void AIGateway::lostHero(HeroPtr h)

+ 4 - 3
AI/Nullkiller/AIGateway.h

@@ -22,8 +22,9 @@
 #include "Pathfinding/AIPathfinder.h"
 #include "Engine/Nullkiller.h"
 
-#include <tbb/task_group.h>
-#include <tbb/task_arena.h>
+VCMI_LIB_NAMESPACE_BEGIN
+class AsyncRunner;
+VCMI_LIB_NAMESPACE_END
 
 namespace NKAI
 {
@@ -74,7 +75,7 @@ public:
 	AIStatus status;
 	std::string battlename;
 	std::shared_ptr<CCallback> myCb;
-	std::unique_ptr<tbb::task_group> asyncTasks;
+	std::unique_ptr<AsyncRunner> asyncTasks;
 
 public:
 	ObjectInstanceID selectedObject;

+ 6 - 7
AI/VCAI/VCAI.cpp

@@ -15,6 +15,7 @@
 #include "Goals/Goals.h"
 
 #include "../../lib/ArtifactUtils.h"
+#include "../../lib/AsyncRunner.h"
 #include "../../lib/CThreadHelper.h"
 #include "../../lib/UnlockGuard.h"
 #include "../../lib/StartInfo.h"
@@ -37,8 +38,6 @@
 
 #include "AIhelper.h"
 
-static tbb::task_arena executeActionAsyncArena;
-
 extern FuzzyHelper * fh;
 
 const double SAFE_ATTACK_CONSTANT = 1.5;
@@ -78,7 +77,7 @@ struct SetGlobalState
 VCAI::VCAI()
 {
 	LOG_TRACE(logAi);
-	asyncTasks = std::make_unique<tbb::task_group>();
+	asyncTasks = std::make_unique<AsyncRunner>();
 	destinationTeleport = ObjectInstanceID();
 	destinationTeleportPos = int3(-1);
 
@@ -653,11 +652,11 @@ void VCAI::yourTurn(QueryID queryID)
 	status.startedTurn();
 
 	makingTurnInterrupption.reset();
-	executeActionAsyncArena.enqueue(asyncTasks->defer([this]()
+	asyncTasks->run([this]()
 	{
 		ScopedThreadName guard("VCAI::makingTurn");
 		makeTurn();
-	}));
+	});
 }
 
 void VCAI::heroGotLevel(const CGHeroInstance * hero, PrimarySkill pskill, std::vector<SecondarySkill> & skills, QueryID queryID)
@@ -2510,13 +2509,13 @@ void VCAI::executeActionAsync(const std::string & description, const std::functi
 	if (!asyncTasks)
 		throw std::runtime_error("Attempt to execute task on shut down AI state!");
 
-	executeActionAsyncArena.enqueue(asyncTasks->defer([this, description, whatToDo]()
+	asyncTasks->run([this, description, whatToDo]()
 	{
 		ScopedThreadName guard("VCAI::" + description);
 		SET_GLOBAL_STATE(this);
 		std::shared_lock gsLock(CGameState::mutex);
 		whatToDo();
-	}));
+	});
 }
 
 void VCAI::lostHero(HeroPtr h)

+ 2 - 4
AI/VCAI/VCAI.h

@@ -23,13 +23,11 @@
 #include "../../lib/spells/CSpellHandler.h"
 #include "Pathfinding/AIPathfinder.h"
 
-#include <tbb/task_group.h>
-#include <tbb/task_arena.h>
-
 VCMI_LIB_NAMESPACE_BEGIN
 
 struct QuestInfo;
 class PathfinderCache;
+class AsyncRunner;
 
 VCMI_LIB_NAMESPACE_END
 
@@ -108,7 +106,7 @@ public:
 
 	std::shared_ptr<CCallback> myCb;
 
-	std::unique_ptr<tbb::task_group> asyncTasks;
+	std::unique_ptr<AsyncRunner> asyncTasks;
 	ThreadInterruption makingTurnInterrupption;
 
 public:

+ 3 - 0
client/GameEngine.cpp

@@ -36,6 +36,7 @@
 #include "GameEngineUser.h"
 #include "battle/BattleInterface.h"
 
+#include "../lib/AsyncRunner.h"
 #include "../lib/CThreadHelper.h"
 #include "../lib/CConfigHandler.h"
 
@@ -86,6 +87,8 @@ GameEngine::GameEngine()
 	sound().setVolume((ui32)settings["general"]["sound"].Float());
 	music().setVolume((ui32)settings["general"]["music"].Float());
 	cursorHandlerInstance = std::make_unique<CursorHandler>();
+
+	asyncTasks = std::make_unique<AsyncRunner>();
 }
 
 void GameEngine::handleEvents()

+ 3 - 0
client/GameEngine.h

@@ -11,6 +11,7 @@
 
 VCMI_LIB_NAMESPACE_BEGIN
 class Point;
+class AsyncRunner;
 class Rect;
 VCMI_LIB_NAMESPACE_END
 
@@ -52,6 +53,7 @@ private:
 	std::unique_ptr<IMusicPlayer> musicPlayerInstance;
 	std::unique_ptr<CursorHandler> cursorHandlerInstance;
 	std::unique_ptr<IVideoPlayer> videoPlayerInstance;
+	std::unique_ptr<AsyncRunner> asyncTasks;
 
 	IGameEngineUser *engineUser = nullptr;
 
@@ -68,6 +70,7 @@ public:
 	EventDispatcher & events();
 	InputHandler & input();
 
+	AsyncRunner & async() { return *asyncTasks; }
 	IGameEngineUser & user() { return *engineUser; }
 	ISoundPlayer & sound() { return *soundPlayerInstance; }
 	IMusicPlayer & music() { return *musicPlayerInstance; }

+ 2 - 3
client/renderSDL/SDLImage.cpp

@@ -20,6 +20,7 @@
 #include "../GameEngine.h"
 #include "../render/IScreenHandler.h"
 
+#include "../../lib/AsyncRunner.h"
 #include "../../lib/CConfigHandler.h"
 
 #include <tbb/parallel_for.h>
@@ -256,8 +257,6 @@ std::shared_ptr<const ISharedImage> SDLImageShared::scaleInteger(int factor, SDL
 
 SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFactor, EScalingAlgorithm algorithm)
 {
-	static tbb::task_arena upscalingArena;
-
 	upscalingInProgress = true;
 
 	auto scaler = std::make_shared<SDLImageScaler>(from->surf, Rect(from->margins, from->fullSize), true);
@@ -273,7 +272,7 @@ SDLImageShared::SDLImageShared(const SDLImageShared * from, int integerScaleFact
 	};
 
 	if(settings["video"]["asyncUpscaling"].Bool())
-		upscalingArena.enqueue(scalingTask);
+		ENGINE->async().run(scalingTask);
 	else
 		scalingTask();
 }

+ 4 - 0
clientapp/EntryPoint.cpp

@@ -27,6 +27,7 @@
 #include "../client/windows/CMessage.h"
 #include "../client/windows/InfoWindows.h"
 
+#include "../lib/AsyncRunner.h"
 #include "../lib/CConsoleHandler.h"
 #include "../lib/CConfigHandler.h"
 #include "../lib/CThreadHelper.h"
@@ -417,6 +418,9 @@ int main(int argc, char * argv[])
 		vstd::clear_pointer(graphics);
 	}
 
+	// must be executed before reset - since unique_ptr resets pointer to null before calling destructor
+	ENGINE->async().wait();
+
 	ENGINE.reset();
 
 	vstd::clear_pointer(LIBRARY);

+ 40 - 0
lib/AsyncRunner.h

@@ -0,0 +1,40 @@
+/*
+ * AsyncRunner.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 <tbb/task_group.h>
+#include <tbb/task_arena.h>
+
+VCMI_LIB_NAMESPACE_BEGIN
+
+class AsyncRunner : boost::noncopyable
+{
+	tbb::task_arena arena;
+	tbb::task_group taskGroup;
+
+public:
+	template <typename Functor>
+	void run(Functor && f)
+	{
+		arena.enqueue(taskGroup.defer(std::forward<Functor>(f)));
+	}
+
+	void wait()
+	{
+		taskGroup.wait();
+	}
+
+	~AsyncRunner()
+	{
+		wait();
+	}
+};
+
+VCMI_LIB_NAMESPACE_END

+ 1 - 0
lib/CMakeLists.txt

@@ -693,6 +693,7 @@ set(lib_MAIN_HEADERS
 
 	AI_Base.h
 	ArtifactUtils.h
+	AsyncRunner.h
 	BattleFieldHandler.h
 	CAndroidVMHelper.h
 	CArtHandler.h