Continuing on multithreading exploration, I can't figure out where the GUI should live in a nice way. Most GUI libraries just avoid threading entirely, the two I am aware of and suited for games, CEGUI, and ImGui included. At least with the provided rendering implementations.
I wasn't able to find any example code or really any questions on the topic (a few about multi-threading the UI itself, not what I want), and am wondering if I missed a simple solution to this?
Should the entire GUI (along with handling mouse/keyboard/etc. input) be with the rendering? Or with the update? Or should I somehow split my UI differently, maybe it lives separately from both somehow?
GUI in the render thread
Given that the OGL/D3D implementations in the libraries I looked at don't provide a simple means to split the logic from the actual rendering, the most obvious solution seems to be the entire thing should live in the render thread (including input handling).
Actions that will affect the game world are then sent over to the update thread to enact (which seems good practice anyway rather than directly manipulating game object/entity state, and this input layer can also be used for recordings, networking, etc.).
struct UnitAttackCommand // as part of some sort of union/variant collection
{
std::vector<EntityId> selection;
EntityId target;
};
The big catch is while implementing this into an actual project, I see that various aspects of the GUI (widgets/windows themselves, HUD stuff, game world overlays or floating elements like HP bars, labels, etc.) can end up wanting to read all sorts of state.
struct ProjectileRenderState
{
const Sprite *spr; // actual image / pre-backed atlas entry, size, etc.
Vector2F prev_pos, next_pos; // 2 points for interpolation in each state set
float prev_dir, next_dir;
// Note that there is nothing here about it's owner, target, AI (guided target prediction etc.). For more complex objects their is a lot of stuff referencing other entities, dynamically allocated collections, etc.
};
I wonder if I am just creating this entire issue for myself here where most games don't have one by trying to “over-optimise”.?
Would it be normal to clone the entire game state over to the render thread on every update? Even information that would generally never be "visible” like stuff in different map regions, AI/command data, etc. just in case the UI needs it for something?
I avoided doing this because such a copy seemed pretty complex and expensive, especially with a lot of things containing dynamically sized collections and handling any references/pointers.
GUI in the update thread
The other idea was that the GUI lives in the update thread where it can read anything in the game, and sends over it's render data.
The catch this time being that most GUI libraries seem completely not designed to do this (and want to touch font, texture/image objects, etc. in many places, e.g. on creation, and then later for both layout and rendering), so I wonder if this is just a terrible idea? If this is a done thing, is there any open source example implementation of doing so?
Especially fonts/text, as well as complete stuff like ImGui, neither the DirectWrite or FreeType (both supporting at least some Unicode, loading glyphs on demand) text implementations I personally have around will handle the font issues easily it seems (maybe putting mutexes around such stuff, I suspect I will regret that however).
struct UiDrawText // and similar for images, gradients, etc. in a variant/union container
{
Font *font;
Vector2I pos;
RectI clip;
TextAlign align;
Colour col;
std::string text; // UTF-8, possibly need to do full layout in update thread and just pass a list of glyph positions, but thread saftey on that "Font" object seems a big issue
};
While seemingly simpler and better initially, having now made it mostly work, I am wondering if this was truly the wrong direction?