New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add async threads #2219
Add async threads #2219
Conversation
The code is stored in a thread-safe work queue. It is executed before the next frame is rendered.
Particularly in light of that last commit that was just added, it seems like this should probably be labeled as "testing requested" and/or "work in progress" |
This should be "work in progress" until someone decides what to do with unhandled exceptions in async threads, which is not my decision to make. For now I'm just adding a line to ErrorLogger(). As for the last commit. I added is_loading code when I was reverting changes to trace messages before pushing this PR. I though I had tested it at the time but it seems like I only tried compiling... won't happen again, sorry. :( |
client/human/HumanClientFSM.h
Outdated
//! Update UI in the GUI thread. | ||
void ProcessUI(bool is_new_game, const SaveGameUIData& ui_data); | ||
|
||
volatile bool is_loading = false; //!< true from GameStart to DoneLoading |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure you know what you're doing with volatile
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Atomic access is not needed here, so volatile is enough. Do you prefer std::atomic
?
It is written in one thread and read in another, but the access is sequential (not concurrent). I just need to make sure it is not reading a cached value, therefore volatile is enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If your answer is 'yes', that's fine.
client/human/HumanClientFSM.cpp
Outdated
@@ -782,29 +783,61 @@ WaitingForGameStart::WaitingForGameStart(my_context ctx) : | |||
} | |||
|
|||
WaitingForGameStart::~WaitingForGameStart() | |||
{ TraceLogger(FSM) << "(HumanClientFSM) ~WaitingForGameStart"; } | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{
to previous line
UI/ClientUI.cpp
Outdated
@@ -1044,6 +1044,29 @@ std::vector<std::shared_ptr<GG::Texture>> ClientUI::GetPrefixedTextures(const bo | |||
return prefixed_textures_and_dist.first; | |||
} | |||
|
|||
bool ClientUI::PushWork(std::function<void()> work) | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same adjustment to {
in various other non-template places with a single line function name and parameters
UI/ClientUI.h
Outdated
/** Add work for the GUI thread. */ | ||
bool PushWork(std::function<void()> work); | ||
|
||
/** Get and remove work for the GUI thread. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does it mean to "remove work for the GUI thread" ?
client/human/HumanClientFSM.h
Outdated
boost::statechart::result react(const GameStart& msg); | ||
|
||
CLIENT_ACCESSOR | ||
|
||
//! Load data in an asyc thread. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what data? explain what this does / how / why.
should these functions be private?
@@ -36,6 +37,9 @@ struct TurnEnded : boost::statechart::event<TurnEnded> {}; | |||
// Posted to advance the turn, including when auto-advancing the first turn | |||
struct AdvanceTurn : boost::statechart::event<AdvanceTurn> {}; | |||
|
|||
// Indicates that everything has been loaded. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what does "loaded" mean?
client/human/HumanClientFSM.cpp
Outdated
@@ -815,6 +848,13 @@ boost::statechart::result WaitingForGameStart::react(const GameStart& msg) { | |||
GetGameRules().SetFromStrings(Client().GetGalaxySetupData().GetGameRules()); | |||
|
|||
bool is_new_game = !(loaded_game_data && ui_data_available); | |||
Client().GetClientUI().PushWork( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not clear about what's going on / why. ProcessData
is called within launch_async
but then it calls the PushWork
function... why in that order?
client/human/HumanClientFSM.cpp
Outdated
@@ -826,7 +866,7 @@ boost::statechart::result WaitingForGameStart::react(const GameStart& msg) { | |||
|
|||
Client().GetClientUI().GetPlayerListWnd()->Refresh(); | |||
|
|||
return transit<PlayingTurn>(); | |||
context<HumanClientFSM>().process_event(DoneLoading()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does this function generate the DoneLoading
event and not the caller? It and ProcessData
and ProcessUI
don't seem to be used elsewhere.
@geoffthemedio Currently the main thread is used as GUI thread and processing thread, causing visible freezes every time processing takes a bit longer than an instant (for me it drops to 0/1 FPS quite often). In OpenGL, almost every interaction must happen in the thread that created the OpenGL context. It is possible to create additional contexts but that would only complicate things. If I want to separate processing from the GUI thread, then the processing code must have some way to tell the GUI thread that it can execute the UI code that depends on the processed data. In this approach the processing code can add work (arbitrary code as a In short, GUI work is arbitrary code to be executed before rendering a frame, and any thread can add GUI work. Some renaming might be needed to make it clearer, but I can't think of better names... Now then, let's explain the split in WaitingForGameStart... When the GameStart message is received, The code here modifies Universe-related data and then updates the UI. What I did was split that into two functions, This is the new sequence:
Ideally the FSM would run in it's own thread, allowing you to post events from anywhere, but that would require splitting all UI code from the react functions of the FSM and that is not what this PR is about. Since |
Too busy at the moment to go through all you wrote, but I particularly wanted any non-clears bits of that explained in the code with comments. |
They are in a struct but they don't need to be public.
@geoffthemedio I tried to answer all your questions with comments in the code. It would be nice if you guys documented the preferred code style. |
I've tested this now, in comparison with master, with a 650 star galaxy and about 10 AIs, medium or high on most settings. I don't see much difference... There's a less-than-half second pause for the mid-turn update, and a slightly longer pause for the main turn update, during which the UI seems to hang. Seems quite similar for both. Do you have a better test case or expect to see a more substantial difference with this pull request applied? |
I'm not gonna be around to continue this so maybe it should be closed? I hate seeing software freeze. From a UI perspective, not giving feedback is bad. |
@geoffthemedio, so do I understand correctly, even if @flaviojs isn't going to work on this anymore, you want that to keep it open and continue working on it/wait for someone to pick it up? |
This PR introduces a way to split loading/processing stuff and GUI stuff.
An example split is done in the WaitingForGameStart state of the human client.
The data is loaded in an async thread. When done it adds the UI updates to the GUI work queue, which is executed before the next frame is rendered.
As a consequence, when you continue a game from the intro menu, instead of being frozen you will be able to move the mouse during the initial load (before the PlayingTurn state).
Topic: http://www.freeorion.org/forum/viewtopic.php?f=9&t=11009