Add: make modal windows update more smooth

Basically, modal windows had their own thread-locking for what
drawing was possible. This is a bit nonsense now we have a
game-thread. And it makes much more sense to do things like
NewGRFScan and GenerateWorld in the game-thread, and not in a
thread next to the game-thread.

This commit changes that: it removes the threads for NewGRFScan
and GenerateWorld, and just runs the code in the game-thread.
On regular intervals it allows the draw-thread to do a tick,
which gives a much smoother look and feel.

It does slow down NewGRFScan and GenerateWorld ever so slightly
as it spends more time on drawing. But the slowdown is not
measureable on my machines (with 700+ NewGRFs / 4kx4k map and
a Debug build).

Running without a game-thread means NewGRFScan and GenerateWorld
are now blocking.
pull/238/head
Patric Stout 3 years ago committed by Patric Stout
parent 098d5b2239
commit 970fedd78c

@ -338,6 +338,7 @@ void GenerateClearTile()
TileIndex tile_new; TileIndex tile_new;
SetClearGroundDensity(tile, CLEAR_ROCKS, 3); SetClearGroundDensity(tile, CLEAR_ROCKS, 3);
MarkTileDirtyByTile(tile);
do { do {
if (--j == 0) goto get_out; if (--j == 0) goto get_out;
tile_new = tile + TileOffsByDiagDir((DiagDirection)GB(Random(), 0, 2)); tile_new = tile + TileOffsByDiagDir((DiagDirection)GB(Random(), 0, 2));

@ -1340,7 +1340,7 @@ DEF_CONSOLE_CMD(ConRescanNewGRF)
return true; return true;
} }
ScanNewGRFFiles(nullptr); RequestNewGRFScan();
return true; return true;
} }

@ -59,18 +59,10 @@ GenWorldInfo _gw;
/** Whether we are generating the map or not. */ /** Whether we are generating the map or not. */
bool _generating_world; bool _generating_world;
/** class AbortGenerateWorldSignal { };
* Tells if the world generation is done in a thread or not.
* @return the 'threaded' status
*/
bool IsGenerateWorldThreaded()
{
return _gw.threaded && !_gw.quit_thread;
}
/** /**
* Clean up the 'mess' of generation. That is, show windows again, reset * Generation is done; show windows again and delete the progress window.
* thread variables, and delete the progress window.
*/ */
static void CleanupGeneration() static void CleanupGeneration()
{ {
@ -78,11 +70,10 @@ static void CleanupGeneration()
SetMouseCursorBusy(false); SetMouseCursorBusy(false);
/* Show all vital windows again, because we have hidden them */ /* Show all vital windows again, because we have hidden them */
if (_gw.threaded && _game_mode != GM_MENU) ShowVitalWindows(); if (_game_mode != GM_MENU) ShowVitalWindows();
SetModalProgress(false); SetModalProgress(false);
_gw.proc = nullptr; _gw.proc = nullptr;
_gw.abortp = nullptr; _gw.abortp = nullptr;
_gw.threaded = false;
DeleteWindowByClass(WC_MODAL_PROGRESS); DeleteWindowByClass(WC_MODAL_PROGRESS);
ShowFirstError(); ShowFirstError();
@ -97,10 +88,8 @@ static void _GenerateWorld()
/* Make sure everything is done via OWNER_NONE. */ /* Make sure everything is done via OWNER_NONE. */
Backup<CompanyID> _cur_company(_current_company, OWNER_NONE, FILE_LINE); Backup<CompanyID> _cur_company(_current_company, OWNER_NONE, FILE_LINE);
std::unique_lock<std::mutex> lock(_modal_progress_work_mutex, std::defer_lock);
try { try {
_generating_world = true; _generating_world = true;
lock.lock();
if (_network_dedicated) DEBUG(net, 1, "Generating map, please wait..."); if (_network_dedicated) DEBUG(net, 1, "Generating map, please wait...");
/* Set the Random() seed to generation_seed so we produce the same map with the same seed */ /* Set the Random() seed to generation_seed so we produce the same map with the same seed */
if (_settings_game.game_creation.generation_seed == GENERATE_NEW_SEED) _settings_game.game_creation.generation_seed = _settings_newgame.game_creation.generation_seed = InteractiveRandom(); if (_settings_game.game_creation.generation_seed == GENERATE_NEW_SEED) _settings_game.game_creation.generation_seed = _settings_newgame.game_creation.generation_seed = InteractiveRandom();
@ -136,14 +125,7 @@ static void _GenerateWorld()
/* Only generate towns, tree and industries in newgame mode. */ /* Only generate towns, tree and industries in newgame mode. */
if (_game_mode != GM_EDITOR) { if (_game_mode != GM_EDITOR) {
if (!GenerateTowns(_settings_game.economy.town_layout)) { if (!GenerateTowns(_settings_game.economy.town_layout)) {
_cur_company.Restore();
HandleGeneratingWorldAbortion(); HandleGeneratingWorldAbortion();
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP);
if (_network_dedicated) {
/* Exit the game to prevent a return to main menu. */
DEBUG(net, 0, "Generating map failed, aborting");
_exit_game = true;
}
return; return;
} }
GenerateIndustries(); GenerateIndustries();
@ -200,7 +182,6 @@ static void _GenerateWorld()
IncreaseGeneratingWorldProgress(GWP_GAME_START); IncreaseGeneratingWorldProgress(GWP_GAME_START);
CleanupGeneration(); CleanupGeneration();
lock.unlock();
ShowNewGRFError(); ShowNewGRFError();
@ -212,11 +193,19 @@ static void _GenerateWorld()
seprintf(name, lastof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date); seprintf(name, lastof(name), "dmp_cmds_%08x_%08x.sav", _settings_game.game_creation.generation_seed, _date);
SaveOrLoad(name, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false); SaveOrLoad(name, SLO_SAVE, DFT_GAME_FILE, AUTOSAVE_DIR, false);
} }
} catch (...) { } catch (AbortGenerateWorldSignal&) {
CleanupGeneration();
BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP, true); BasePersistentStorageArray::SwitchMode(PSM_LEAVE_GAMELOOP, true);
if (_cur_company.IsValid()) _cur_company.Restore(); if (_cur_company.IsValid()) _cur_company.Restore();
_generating_world = false;
throw; if (_network_dedicated) {
/* Exit the game to prevent a return to main menu. */
DEBUG(net, 0, "Generating map failed, aborting");
_exit_game = true;
} else {
SwitchToMode(_switch_mode);
}
} }
} }
@ -240,23 +229,6 @@ void GenerateWorldSetAbortCallback(GWAbortProc *proc)
_gw.abortp = proc; _gw.abortp = proc;
} }
/**
* This will wait for the thread to finish up his work. It will not continue
* till the work is done.
*/
void WaitTillGeneratedWorld()
{
if (!_gw.thread.joinable()) return;
_modal_progress_work_mutex.unlock();
_modal_progress_paint_mutex.unlock();
_gw.quit_thread = true;
_gw.thread.join();
_gw.threaded = false;
_modal_progress_work_mutex.lock();
_modal_progress_paint_mutex.lock();
}
/** /**
* Initializes the abortion process * Initializes the abortion process
*/ */
@ -284,11 +256,7 @@ void HandleGeneratingWorldAbortion()
if (_gw.abortp != nullptr) _gw.abortp(); if (_gw.abortp != nullptr) _gw.abortp();
CleanupGeneration(); throw AbortGenerateWorldSignal();
if (_gw.thread.joinable() && _gw.thread.get_id() == std::this_thread::get_id()) throw OTTDThreadExitSignal();
SwitchToMode(_switch_mode);
} }
/** /**
@ -308,8 +276,6 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti
_gw.abort = false; _gw.abort = false;
_gw.abortp = nullptr; _gw.abortp = nullptr;
_gw.lc = _local_company; _gw.lc = _local_company;
_gw.quit_thread = false;
_gw.threaded = true;
/* This disables some commands and stuff */ /* This disables some commands and stuff */
SetLocalCompany(COMPANY_SPECTATOR); SetLocalCompany(COMPANY_SPECTATOR);
@ -328,28 +294,16 @@ void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_setti
SetupColoursAndInitialWindow(); SetupColoursAndInitialWindow();
SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0); SetObjectToPlace(SPR_CURSOR_ZZZ, PAL_NONE, HT_NONE, WC_MAIN_WINDOW, 0);
if (_gw.thread.joinable()) _gw.thread.join();
if (!UseThreadedModelProgress() || !VideoDriver::GetInstance()->HasGUI() || !StartNewThread(&_gw.thread, "ottd:genworld", &_GenerateWorld)) {
DEBUG(misc, 1, "Cannot create genworld thread, reverting to single-threaded mode");
_gw.threaded = false;
_modal_progress_work_mutex.unlock();
_GenerateWorld();
_modal_progress_work_mutex.lock();
return;
}
UnshowCriticalError(); UnshowCriticalError();
/* Remove any open window */
DeleteAllNonVitalWindows(); DeleteAllNonVitalWindows();
/* Hide vital windows, because we don't allow to use them */
HideVitalWindows(); HideVitalWindows();
/* Don't show the dialog if we don't have a thread */
ShowGenerateWorldProgress(); ShowGenerateWorldProgress();
/* Centre the view on the map */ /* Centre the view on the map */
if (FindWindowById(WC_MAIN_WINDOW, 0) != nullptr) { if (FindWindowById(WC_MAIN_WINDOW, 0) != nullptr) {
ScrollMainWindowToTile(TileXY(MapSizeX() / 2, MapSizeY() / 2), true); ScrollMainWindowToTile(TileXY(MapSizeX() / 2, MapSizeY() / 2), true);
} }
_GenerateWorld();
} }

@ -52,15 +52,12 @@ typedef void GWAbortProc(); ///< Called when genworld is aborted
/** Properties of current genworld process */ /** Properties of current genworld process */
struct GenWorldInfo { struct GenWorldInfo {
bool abort; ///< Whether to abort the thread ASAP bool abort; ///< Whether to abort the thread ASAP
bool quit_thread; ///< Do we want to quit the active thread
bool threaded; ///< Whether we run _GenerateWorld threaded
GenWorldMode mode; ///< What mode are we making a world in GenWorldMode mode; ///< What mode are we making a world in
CompanyID lc; ///< The local_company before generating CompanyID lc; ///< The local_company before generating
uint size_x; ///< X-size of the map uint size_x; ///< X-size of the map
uint size_y; ///< Y-size of the map uint size_y; ///< Y-size of the map
GWDoneProc *proc; ///< Proc that is called when done (can be nullptr) GWDoneProc *proc; ///< Proc that is called when done (can be nullptr)
GWAbortProc *abortp; ///< Proc that is called when aborting (can be nullptr) GWAbortProc *abortp; ///< Proc that is called when aborting (can be nullptr)
std::thread thread; ///< The thread we are in (joinable if a thread was created)
}; };
/** Current stage of world generation process */ /** Current stage of world generation process */
@ -81,10 +78,8 @@ enum GenWorldProgress {
}; };
/* genworld.cpp */ /* genworld.cpp */
bool IsGenerateWorldThreaded();
void GenerateWorldSetCallback(GWDoneProc *proc); void GenerateWorldSetCallback(GWDoneProc *proc);
void GenerateWorldSetAbortCallback(GWAbortProc *proc); void GenerateWorldSetAbortCallback(GWAbortProc *proc);
void WaitTillGeneratedWorld();
void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_settings = true); void GenerateWorld(GenWorldMode mode, uint size_x, uint size_y, bool reset_settings = true);
void AbortGeneratingWorld(); void AbortGeneratingWorld();
bool IsGeneratingWorldAborted(); bool IsGeneratingWorldAborted();

@ -29,6 +29,7 @@
#include "error.h" #include "error.h"
#include "newgrf_townname.h" #include "newgrf_townname.h"
#include "townname_type.h" #include "townname_type.h"
#include "video/video_driver.hpp"
#include "widgets/genworld_widget.h" #include "widgets/genworld_widget.h"
@ -1312,10 +1313,10 @@ static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uin
static_assert(lengthof(percent_table) == GWP_CLASS_COUNT + 1); static_assert(lengthof(percent_table) == GWP_CLASS_COUNT + 1);
assert(cls < GWP_CLASS_COUNT); assert(cls < GWP_CLASS_COUNT);
/* Do not run this function if we aren't in a thread */ if (IsGeneratingWorldAborted()) {
if (!IsGenerateWorldThreaded() && !_network_dedicated) return; HandleGeneratingWorldAbortion();
return;
if (IsGeneratingWorldAborted()) HandleGeneratingWorldAbortion(); }
if (total == 0) { if (total == 0) {
assert(_gws.cls == _generation_class_table[cls]); assert(_gws.cls == _generation_class_table[cls]);
@ -1328,10 +1329,6 @@ static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uin
_gws.percent = percent_table[cls]; _gws.percent = percent_table[cls];
} }
/* Don't update the screen too often. So update it once in every once in a while... */
if (!_network_dedicated && std::chrono::steady_clock::now() < _gws.next_update) return;
_gws.next_update = std::chrono::steady_clock::now() + std::chrono::milliseconds(MODAL_PROGRESS_REDRAW_TIMEOUT);
/* Percentage is about the number of completed tasks, so 'current - 1' */ /* Percentage is about the number of completed tasks, so 'current - 1' */
_gws.percent = percent_table[cls] + (percent_table[cls + 1] - percent_table[cls]) * (_gws.current == 0 ? 0 : _gws.current - 1) / _gws.total; _gws.percent = percent_table[cls] + (percent_table[cls + 1] - percent_table[cls]) * (_gws.current == 0 ? 0 : _gws.current - 1) / _gws.total;
@ -1350,21 +1347,12 @@ static void _SetGeneratingWorldProgress(GenWorldProgress cls, uint progress, uin
DEBUG(net, 1, "Map generation percentage complete: %d", _gws.percent); DEBUG(net, 1, "Map generation percentage complete: %d", _gws.percent);
last_percent = _gws.percent; last_percent = _gws.percent;
/* Don't continue as dedicated never has a thread running */
return; return;
} }
SetWindowDirty(WC_MODAL_PROGRESS, 0); SetWindowDirty(WC_MODAL_PROGRESS, 0);
MarkWholeScreenDirty();
VideoDriver::GetInstance()->GameLoopPause();
/* Release the rights to the map generator, and acquire the rights to the
* paint thread. The 'other' thread already has the paint thread rights so
* this ensures us that we are waiting until the paint thread is done
* before we reacquire the mapgen rights */
_modal_progress_work_mutex.unlock();
_modal_progress_paint_mutex.lock();
_modal_progress_work_mutex.lock();
_modal_progress_paint_mutex.unlock();
} }
/** /**

@ -1470,26 +1470,6 @@ void DrawDirtyBlocks()
int x; int x;
int y; int y;
if (HasModalProgress()) {
/* We are generating the world, so release our rights to the map and
* painting while we are waiting a bit. */
_modal_progress_paint_mutex.unlock();
_modal_progress_work_mutex.unlock();
/* Wait a while and hope the modal gives us a bit of time to draw the GUI. */
if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT);
/* Modal progress thread may need blitter access while we are waiting for it. */
_modal_progress_paint_mutex.lock();
_modal_progress_work_mutex.lock();
/* When we ended with the modal progress, do not draw the blocks.
* Simply let the next run do so, otherwise we would be loading
* the new state (and possibly change the blitter) when we hold
* the drawing lock, which we must not do. */
if (_switch_mode != SM_NONE && !HasModalProgress()) return;
}
y = 0; y = 0;
do { do {
x = 0; x = 0;

@ -1062,6 +1062,7 @@ static bool MakeLake(TileIndex tile, void *user_data)
TileIndex t2 = tile + TileOffsByDiagDir(d); TileIndex t2 = tile + TileOffsByDiagDir(d);
if (IsWaterTile(t2)) { if (IsWaterTile(t2)) {
MakeRiver(tile, Random()); MakeRiver(tile, Random());
MarkTileDirtyByTile(tile);
/* Remove desert directly around the river tile. */ /* Remove desert directly around the river tile. */
TileIndex t = tile; TileIndex t = tile;
CircularTileSearch(&t, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr); CircularTileSearch(&t, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr);
@ -1135,6 +1136,7 @@ static void River_FoundEndNode(AyStar *aystar, OpenListNode *current)
TileIndex tile = path->node.tile; TileIndex tile = path->node.tile;
if (!IsWaterTile(tile)) { if (!IsWaterTile(tile)) {
MakeRiver(tile, Random()); MakeRiver(tile, Random());
MarkTileDirtyByTile(tile);
/* Remove desert directly around the river tile. */ /* Remove desert directly around the river tile. */
CircularTileSearch(&tile, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr); CircularTileSearch(&tile, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr);
} }
@ -1247,6 +1249,7 @@ static bool FlowRiver(TileIndex spring, TileIndex begin)
DistanceManhattan(spring, lakeCenter) > _settings_game.game_creation.min_river_length) { DistanceManhattan(spring, lakeCenter) > _settings_game.game_creation.min_river_length) {
end = lakeCenter; end = lakeCenter;
MakeRiver(lakeCenter, Random()); MakeRiver(lakeCenter, Random());
MarkTileDirtyByTile(lakeCenter);
/* Remove desert directly around the river tile. */ /* Remove desert directly around the river tile. */
CircularTileSearch(&lakeCenter, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr); CircularTileSearch(&lakeCenter, RIVER_OFFSET_DESERT_DISTANCE, RiverModifyDesertZone, nullptr);
lakeCenter = end; lakeCenter = end;
@ -1368,8 +1371,11 @@ void GenerateLandscape(byte mode)
/* Do not call IncreaseGeneratingWorldProgress() before FixSlopes(), /* Do not call IncreaseGeneratingWorldProgress() before FixSlopes(),
* it allows screen redraw. Drawing of broken slopes crashes the game */ * it allows screen redraw. Drawing of broken slopes crashes the game */
FixSlopes(); FixSlopes();
MarkWholeScreenDirty();
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);
ConvertGroundTilesIntoWaterTiles(); ConvertGroundTilesIntoWaterTiles();
MarkWholeScreenDirty();
IncreaseGeneratingWorldProgress(GWP_LANDSCAPE); IncreaseGeneratingWorldProgress(GWP_LANDSCAPE);
if (_settings_game.game_creation.landscape == LT_TROPIC) CreateDesertOrRainForest(); if (_settings_game.game_creation.landscape == LT_TROPIC) CreateDesertOrRainForest();

@ -15,6 +15,7 @@
#include "../ai/ai.hpp" #include "../ai/ai.hpp"
#include "../game/game.hpp" #include "../game/game.hpp"
#include "../base_media_base.h" #include "../base_media_base.h"
#include "../openttd.h"
#include "../sortlist_type.h" #include "../sortlist_type.h"
#include "../stringfilter_type.h" #include "../stringfilter_type.h"
#include "../querystring_gui.h" #include "../querystring_gui.h"
@ -236,7 +237,7 @@ public:
break; break;
case CONTENT_TYPE_NEWGRF: case CONTENT_TYPE_NEWGRF:
ScanNewGRFFiles(nullptr); RequestNewGRFScan();
break; break;
case CONTENT_TYPE_SCENARIO: case CONTENT_TYPE_SCENARIO:

@ -635,20 +635,12 @@ bool GRFFileScanner::AddFile(const std::string &filename, size_t basepath_length
} }
this->num_scanned++; this->num_scanned++;
if (std::chrono::steady_clock::now() >= this->next_update) {
this->next_update = std::chrono::steady_clock::now() + std::chrono::milliseconds(MODAL_PROGRESS_REDRAW_TIMEOUT);
_modal_progress_work_mutex.unlock(); const char *name = nullptr;
_modal_progress_paint_mutex.lock(); if (c->name != nullptr) name = GetGRFStringFromGRFText(c->name);
if (name == nullptr) name = c->filename;
const char *name = nullptr; UpdateNewGRFScanStatus(this->num_scanned, name);
if (c->name != nullptr) name = GetGRFStringFromGRFText(c->name); VideoDriver::GetInstance()->GameLoopPause();
if (name == nullptr) name = c->filename;
UpdateNewGRFScanStatus(this->num_scanned, name);
_modal_progress_work_mutex.lock();
_modal_progress_paint_mutex.unlock();
}
if (!added) { if (!added) {
/* File couldn't be opened, or is either not a NewGRF or is a /* File couldn't be opened, or is either not a NewGRF or is a
@ -676,8 +668,6 @@ static bool GRFSorter(GRFConfig * const &c1, GRFConfig * const &c2)
*/ */
void DoScanNewGRFFiles(NewGRFScanCallback *callback) void DoScanNewGRFFiles(NewGRFScanCallback *callback)
{ {
std::unique_lock<std::mutex> lock_work(_modal_progress_work_mutex);
ClearGRFConfigList(&_all_grfs); ClearGRFConfigList(&_all_grfs);
TarScanner::DoScan(TarScanner::NEWGRF); TarScanner::DoScan(TarScanner::NEWGRF);
@ -709,9 +699,6 @@ void DoScanNewGRFFiles(NewGRFScanCallback *callback)
NetworkAfterNewGRFScan(); NetworkAfterNewGRFScan();
} }
lock_work.unlock();
std::lock_guard<std::mutex> lock_paint(_modal_progress_paint_mutex);
/* Yes... these are the NewGRF windows */ /* Yes... these are the NewGRF windows */
InvalidateWindowClassesData(WC_SAVELOAD, 0, true); InvalidateWindowClassesData(WC_SAVELOAD, 0, true);
InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_NEWGRF_STATE, GOID_NEWGRF_RESCANNED, true); InvalidateWindowData(WC_GAME_OPTIONS, WN_GAME_OPTIONS_NEWGRF_STATE, GOID_NEWGRF_RESCANNED, true);
@ -733,15 +720,7 @@ void ScanNewGRFFiles(NewGRFScanCallback *callback)
/* Only then can we really start, especially by marking the whole screen dirty. Get those other windows hidden!. */ /* Only then can we really start, especially by marking the whole screen dirty. Get those other windows hidden!. */
MarkWholeScreenDirty(); MarkWholeScreenDirty();
if (!UseThreadedModelProgress() || !VideoDriver::GetInstance()->HasGUI() || !StartNewThread(nullptr, "ottd:newgrf-scan", &DoScanNewGRFFiles, (NewGRFScanCallback *)callback)) { // Without the seemingly superfluous cast, strange compiler errors ensue. DoScanNewGRFFiles(callback);
_modal_progress_work_mutex.unlock();
_modal_progress_paint_mutex.unlock();
DoScanNewGRFFiles(callback);
_modal_progress_paint_mutex.lock();
_modal_progress_work_mutex.lock();
} else {
UpdateNewGRFScanStatus(0, nullptr);
}
} }
/** /**

@ -1127,7 +1127,7 @@ struct NewGRFWindow : public Window, NewGRFScanCallback {
case WID_NS_RESCAN_FILES: case WID_NS_RESCAN_FILES:
case WID_NS_RESCAN_FILES2: case WID_NS_RESCAN_FILES2:
ScanNewGRFFiles(this); RequestNewGRFScan(this);
break; break;
} }
} }

@ -91,6 +91,8 @@ extern void ShowOSErrorBox(const char *buf, bool system);
extern std::string _config_file; extern std::string _config_file;
bool _save_config = false; bool _save_config = false;
bool _request_newgrf_scan = false;
NewGRFScanCallback *_request_newgrf_scan_callback = nullptr;
/** /**
* Error handling for fatal user errors. * Error handling for fatal user errors.
@ -345,7 +347,6 @@ static void LoadIntroGame(bool load_newgrfs = true)
/* Load the default opening screen savegame */ /* Load the default opening screen savegame */
if (SaveOrLoad("opntitle.dat", SLO_LOAD, DFT_GAME_FILE, BASESET_DIR) != SL_OK) { if (SaveOrLoad("opntitle.dat", SLO_LOAD, DFT_GAME_FILE, BASESET_DIR) != SL_OK) {
GenerateWorld(GWM_EMPTY, 64, 64); // if failed loading, make empty world. GenerateWorld(GWM_EMPTY, 64, 64); // if failed loading, make empty world.
WaitTillGeneratedWorld();
SetLocalCompany(COMPANY_SPECTATOR); SetLocalCompany(COMPANY_SPECTATOR);
} else { } else {
SetLocalCompany(COMPANY_FIRST); SetLocalCompany(COMPANY_FIRST);
@ -559,9 +560,6 @@ int openttd_main(int argc, char *argv[])
extern bool _dedicated_forks; extern bool _dedicated_forks;
_dedicated_forks = false; _dedicated_forks = false;
std::unique_lock<std::mutex> modal_work_lock(_modal_progress_work_mutex, std::defer_lock);
std::unique_lock<std::mutex> modal_paint_lock(_modal_progress_paint_mutex, std::defer_lock);
_game_mode = GM_MENU; _game_mode = GM_MENU;
_switch_mode = SM_MENU; _switch_mode = SM_MENU;
@ -828,30 +826,17 @@ int openttd_main(int argc, char *argv[])
if (musicdriver.empty() && !_ini_musicdriver.empty()) musicdriver = _ini_musicdriver; if (musicdriver.empty() && !_ini_musicdriver.empty()) musicdriver = _ini_musicdriver;
DriverFactoryBase::SelectDriver(musicdriver, Driver::DT_MUSIC); DriverFactoryBase::SelectDriver(musicdriver, Driver::DT_MUSIC);
/* Take our initial lock on whatever we might want to do! */
try {
modal_work_lock.lock();
modal_paint_lock.lock();
} catch (const std::system_error&) {
/* If there is some error we assume that threads aren't usable on the system we run. */
extern bool _use_threaded_modal_progress; // From progress.cpp
_use_threaded_modal_progress = false;
}
GenerateWorld(GWM_EMPTY, 64, 64); // Make the viewport initialization happy GenerateWorld(GWM_EMPTY, 64, 64); // Make the viewport initialization happy
WaitTillGeneratedWorld();
LoadIntroGame(false); LoadIntroGame(false);
CheckForMissingGlyphs(); CheckForMissingGlyphs();
/* ScanNewGRFFiles now has control over the scanner. */ /* ScanNewGRFFiles now has control over the scanner. */
ScanNewGRFFiles(scanner.release()); RequestNewGRFScan(scanner.release());
VideoDriver::GetInstance()->MainLoop(); VideoDriver::GetInstance()->MainLoop();
WaitTillSaved(); WaitTillSaved();
WaitTillGeneratedWorld(); // Make sure any generate world threads have been joined.
/* only save config if we have to */ /* only save config if we have to */
if (_save_config) { if (_save_config) {
@ -1460,6 +1445,19 @@ static void DoAutosave()
} }
} }
/**
* Request a new NewGRF scan. This will be executed on the next game-tick.
* This is mostly needed to ensure NewGRF scans (which are blocking) are
* done in the game-thread, and not in the draw-thread (which most often
* triggers this request).
* @param callback Optional callback to call when NewGRF scan is completed.
*/
void RequestNewGRFScan(NewGRFScanCallback *callback)
{
_request_newgrf_scan = true;
_request_newgrf_scan_callback = callback;
}
void GameLoop() void GameLoop()
{ {
if (_game_mode == GM_BOOTSTRAP) { if (_game_mode == GM_BOOTSTRAP) {
@ -1468,6 +1466,12 @@ void GameLoop()
return; return;
} }
if (_request_newgrf_scan) {
ScanNewGRFFiles(_request_newgrf_scan_callback);
_request_newgrf_scan = false;
_request_newgrf_scan_callback = nullptr;
}
ProcessAsyncSaveFinish(); ProcessAsyncSaveFinish();
/* autosave game? */ /* autosave game? */

@ -81,4 +81,6 @@ void HandleExitGameRequest();
void SwitchToMode(SwitchMode new_mode); void SwitchToMode(SwitchMode new_mode);
void RequestNewGRFScan(struct NewGRFScanCallback *callback = nullptr);
#endif /* OPENTTD_H */ #endif /* OPENTTD_H */

@ -14,33 +14,12 @@
/** Are we in a modal progress or not? */ /** Are we in a modal progress or not? */
bool _in_modal_progress = false; bool _in_modal_progress = false;
bool _first_in_modal_loop = false;
/** Threading usable for modal progress? */
bool _use_threaded_modal_progress = true;
/** Rights for the performing work. */
std::mutex _modal_progress_work_mutex;
/** Rights for the painting. */
std::mutex _modal_progress_paint_mutex;
/** /**
* Set the modal progress state. * Set the modal progress state.
* @note Makes IsFirstModalProgressLoop return true for the next call.
* @param state The new state; are we modal or not? * @param state The new state; are we modal or not?
*/ */
void SetModalProgress(bool state) void SetModalProgress(bool state)
{ {
_in_modal_progress = state; _in_modal_progress = state;
_first_in_modal_loop = true;
}
/**
* Check whether this is the first modal progress loop.
* @note Set by SetModalProgress, unset by calling this method.
* @return True if this is the first loop.
*/
bool IsFirstModalProgressLoop()
{
bool ret = _first_in_modal_loop;
_first_in_modal_loop = false;
return ret;
} }

@ -10,10 +10,6 @@
#ifndef PROGRESS_H #ifndef PROGRESS_H
#define PROGRESS_H #define PROGRESS_H
#include <mutex>
static const uint MODAL_PROGRESS_REDRAW_TIMEOUT = 200; ///< Timeout between redraws
/** /**
* Check if we are currently in a modal progress state. * Check if we are currently in a modal progress state.
* @return Are we in the modal state? * @return Are we in the modal state?
@ -24,20 +20,6 @@ static inline bool HasModalProgress()
return _in_modal_progress; return _in_modal_progress;
} }
/**
* Check if we can use a thread for modal progress.
* @return Threading usable?
*/
static inline bool UseThreadedModelProgress()
{
extern bool _use_threaded_modal_progress;
return _use_threaded_modal_progress;
}
bool IsFirstModalProgressLoop();
void SetModalProgress(bool state); void SetModalProgress(bool state);
extern std::mutex _modal_progress_work_mutex;
extern std::mutex _modal_progress_paint_mutex;
#endif /* PROGRESS_H */ #endif /* PROGRESS_H */

@ -14,10 +14,6 @@
#include <system_error> #include <system_error>
#include <thread> #include <thread>
/** Signal used for signalling we knowingly want to end the thread. */
class OTTDThreadExitSignal { };
/** /**
* Sleep on the current thread for a defined time. * Sleep on the current thread for a defined time.
* @param milliseconds Time to sleep for in milliseconds. * @param milliseconds Time to sleep for in milliseconds.
@ -54,7 +50,6 @@ inline bool StartNewThread(std::thread *thr, const char *name, TFn&& _Fx, TArgs&
try { try {
/* Call user function with the given arguments. */ /* Call user function with the given arguments. */
F(A...); F(A...);
} catch (OTTDThreadExitSignal&) {
} catch (...) { } catch (...) {
NOT_REACHED(); NOT_REACHED();
} }

@ -164,6 +164,7 @@ static void PlaceTree(TileIndex tile, uint32 r)
if (tree != TREE_INVALID) { if (tree != TREE_INVALID) {
PlantTreesOnTile(tile, tree, GB(r, 22, 2), std::min<byte>(GB(r, 16, 3), 6)); PlantTreesOnTile(tile, tree, GB(r, 22, 2), std::min<byte>(GB(r, 16, 3), 6));
MarkTileDirtyByTile(tile);
/* Rerandomize ground, if neither snow nor shore */ /* Rerandomize ground, if neither snow nor shore */
TreeGround ground = GetTreeGround(tile); TreeGround ground = GetTreeGround(tile);

@ -251,19 +251,16 @@ void VideoDriver_Dedicated::MainLoop()
/* If SwitchMode is SM_LOAD_GAME, it means that the user used the '-g' options */ /* If SwitchMode is SM_LOAD_GAME, it means that the user used the '-g' options */
if (_switch_mode != SM_LOAD_GAME) { if (_switch_mode != SM_LOAD_GAME) {
StartNewGameWithoutGUI(GENERATE_NEW_SEED); StartNewGameWithoutGUI(GENERATE_NEW_SEED);
SwitchToMode(_switch_mode);
_switch_mode = SM_NONE;
} else { } else {
_switch_mode = SM_NONE;
/* First we need to test if the savegame can be loaded, else we will end up playing the /* First we need to test if the savegame can be loaded, else we will end up playing the
* intro game... */ * intro game... */
if (!SafeLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.detail_ftype, GM_NORMAL, BASE_DIR)) { if (SaveOrLoad(_file_to_saveload.name, _file_to_saveload.file_op, _file_to_saveload.detail_ftype, BASE_DIR) == SL_ERROR) {
/* Loading failed, pop out.. */ /* Loading failed, pop out.. */
DEBUG(net, 0, "Loading requested map failed, aborting"); DEBUG(net, 0, "Loading requested map failed, aborting");
_networking = false; return;
} else { } else {
/* We can load this game, so go ahead */ /* We can load this game, so go ahead */
SwitchToMode(SM_LOAD_GAME); _switch_mode = SM_LOAD_GAME;
} }
} }
@ -271,11 +268,6 @@ void VideoDriver_Dedicated::MainLoop()
/* Done loading, start game! */ /* Done loading, start game! */
if (!_networking) {
DEBUG(net, 0, "Dedicated server could not be started, aborting");
return;
}
while (!_exit_game) { while (!_exit_game) {
if (!_dedicated_forks) DedicatedHandleKeyInput(); if (!_dedicated_forks) DedicatedHandleKeyInput();

@ -56,6 +56,25 @@ void VideoDriver::GameThread()
} }
} }
/**
* Pause the game-loop for a bit, releasing the game-state lock. This allows,
* if the draw-tick requested this, the drawing to happen.
*/
void VideoDriver::GameLoopPause()
{
/* If we are not called from the game-thread, ignore this request. */
if (std::this_thread::get_id() != this->game_thread.get_id()) return;
this->game_state_mutex.unlock();
{
/* See GameThread() for more details on this lock. */
std::lock_guard<std::mutex> lock(this->game_thread_wait_mutex);
}
this->game_state_mutex.lock();
}
/* static */ void VideoDriver::GameThreadThunk(VideoDriver *drv) /* static */ void VideoDriver::GameThreadThunk(VideoDriver *drv)
{ {
drv->GameThread(); drv->GameThread();

@ -179,6 +179,8 @@ public:
this->change_blitter = new_blitter; this->change_blitter = new_blitter;
} }
void GameLoopPause();
/** /**
* Get the currently active instance of the video driver. * Get the currently active instance of the video driver.
*/ */

Loading…
Cancel
Save