Jump to content
  • Advertisement
Aressera

Thread Architecture for Editor/Engine

Recommended Posts

I am about 60-70% done with my game engine editor and have some questions about how to best design the threading architecture of my editor.

Currently, I have 2 separate threads: one that only handles the OS events (call this the UI thread), and another that is the "main" thread of the editor that updates in a loop at 60Hz and handles all of the rendering/simulation updates. My editor uses a custom GUI that is rendered using OpenGL, and all of the updates/rendering of the GUI also happen on the main thread. Events (mouse,keyboard) received on the UI thread are double-buffered and sent to the main thread. The events are then dispatched on the main thread to the hierarchy of GUI widgets. This used to work fine until I started adding more features.

Now, recently I have implemented drag and drop, and it requires handling everything on the UI thread so that it can inform the OS whether a drop can be performed. At the moment, I have mostly ignored thread saftey. When I drag objects around the editor (say from a list into a scene viewport), it can sometimes cause those dropped objects to have their graphics data initialized from the UI thread, which causes a crash since there is no OpenGL context on the UI thread. The same problem occurs when I use native OS menus - selecting a menu item (e.g. creating a new mesh) sends a callback from the UI thread. If I directly create the object in that callback, it can crash when the graphics data is initialized.

I can synchronize these callbacks, but it is quite a lot of work to do since I have dozens of menus that would need to safely communicate to the main thread what item(s) were selected. Drag and drop is more problematic since I have to immediately return the drag operation that can happen from the UI thread callback.

 

So, I ask for your advice for how I should proceed to fix these problems. I see 2 possible paths:

  • Merge the UI/main threads into a single thread that does everything. This will be safest of all, but could cause other problems with responsiveness. I am worried about what happens when I add VR support and need to have precise control over the frame rate. On OS X (my main development platform), I don't have control over the UI thread's event loop so I'm not sure how to ensure that I get a callback every 60Hz or 90Hz to keep up the frame rate. To do this, I have to rewrite a lot of the windowing/buffer swapping code to work from a UI thread callback instead of a simple loop.
  • Keep it as is, but synchronize the hell out of it. This is LOT of work (probably adds 20-40 man hours, which I'd rather avoid). Every single menu in the very large editor will have to have threadsafe event communication. Drag and drop becomes more difficult to implement (I have to mutex with the main thread, do everything on the UI thread, and then communicate the dropped objects to the main thread when a drop operation occurs). This sync/communicate would have to be done at every place in the GUI that accepts a drag operation (dozens of locations). BUT - this way gives me more control over the frame rate (e.g. I can drop to 10Hz when the editor is not the foreground app), VR timing becomes easier. I also don't have to worry about stalling the UI thread if my rendering takes too long.

How do existing game engine editors (Unity, Unreal, etc.) manage their threads? I believe that Unity editor all runs on the UI thread (including rendering), but I may be wrong. What is the best overall architecture that is versatile, safe, performant, and future-proof?

Edited by Aressera

Share this post


Link to post
Share on other sites
Advertisement

AFAIK engines dont pay that much attention to editor's performance. Runtime performance (as in for the end-user) on the other hand is a big problem.

For the editor they just throw hardware at it, since it's cheaper to buy beefy workstations than to dedicate months of man-hours to tune everything.

Somewhere in this talk this stuff is talked about, cant recall exactly when, might give you some pointers:

 

Share this post


Link to post
Share on other sites

Exact same talk came to my mind when I read the OP, you just beat me to it.  Overall it's a good talk to listen to I think in general so I would second the recommendation of watching it.

Share this post


Link to post
Share on other sites

I'm not talking about how to multithread the engine, that's a separate concern. I will have a job system/thread pool where necessary on the internals. I'm more asking about how people interface the engine/editor with the OS event thread, and especially how that impacts latency-sensitive applications like running VR within the editor.

Share this post


Link to post
Share on other sites

Can you give a specific example of the OS messages that are causing headaches and how the communication works? The only kind I can think would be a problem are if: the OS calls your message handler callback and requires it to respond to the message immediately via a return value / etc. In that case, you would need to post a message to the real time thread and block until it responds,which probably isn't too bad seeing as it's going to respond it real time :)

You should be able to solve that without excessive manual labour, in a similar way that you already share the mouse cursor position with the real time thread. The real time thread should be able to have a single sync point that reads mouse update messages and posts back mouse-over state messages to the OS thread. 

Share this post


Link to post
Share on other sites

If you want multi threading, you need synchronization. Otherwise you'll get spurious nondeterministic errors. Anything shared between threads needs to be synchronized. And for an engine you probably do want multi threading support so that you can make use of multiple cores.

Share this post


Link to post
Share on other sites

I'd rather not share any data between the 2 threads. Instead, I'd send a message through a threadsafe message queue to the main thread and have it respond asap with another message (should be ok to have the 1st thread wait on the reply for max. 1/60 second in rare cases this is necesssary).

Share this post


Link to post
Share on other sites

Thanks everyone for the replies, but I don't think anyone has really come close to answering the question I posed twice, so I'll try again:

What is people's experience with how engines/editors split tasks between the UI thread and the rendering thread? Particularly, how would rendering to VR on the UI thread impact the ability to consistently hit 90fps?

I'm not looking for tips on how to write multithreaded software at a low level, I already know how to do that. I am more looking to see if there are any pitfalls in either of the two possible thread architectures I could use (described in the OP). Which way would be best if you were writing a new engine from scratch?

Share this post


Link to post
Share on other sites

Why are you limited yourself to 2 threads? I'm currently working on my editor using the Sony ATF framework ( WinForms version ) and with that application idle message pumps the 'main thread' as this is the thread that does all the rendering. The UI does not block and should not block unless you like being frustrated. Whenever a drag and drop operation is performed a loading task is issue which perform the resource loading. Resource loading happens in different phases and is fully threaded.
-File I/O. If the resource is a graphics resource then dispatch additional task to load the graphics resource. There is nothing stopping you from creating additional OpenGL context on other threads to do the loading.
-If the task does not require graphics resources then a callback is scheduled to indication its completion.
There is a lot of specific details that I left out as they are more or less related to my current specific architecture. I do admit it was a little tricky getting the drag and drop behavior to work ( most sync issues ).
However, without a proper thread/multithread/task system in place you are going to find yourself just hacking stuff to pieces.

Like others have mentioned though, editors for the most part are not realtime in the sense that runtime performance should be a mode of the editor vs being the actual editor. If that is not the case then what you seem to be aiming at is an in-engine editor which you alluded to having a separate design plan for that. Why would you want them different ? Wouldn't that be doing twice the work ?

My approach that I touched on above utilized the same framework that the actual application built using the editor would use and so far I cannot complain.

Share this post


Link to post
Share on other sites

You mentioned several times that you're familiar with writing multi-threaded software, so you already know what there is to gain by taking advantage of additional processing power that would otherwise remain idle. That's the answer to your question, but to be perfectly honest, it seems to me you already knew that. I feel the real issue is that you're having trouble accepting the amount of time and effort involved in implementing proper thread synchronization. At the end of the OP you ask, "What is the best overall architecture that is versatile, safe, performant, and future-proof?". But considering how much emphasis you place on wanting to minimize any additional time or effort (even going so far as to quantify the manhours), the question reads in my mind as "What is the best overall architecture that is versatile, safe, performant, future-proof... and least amount of work to implement?".

Other engines/studios handle this by acknowledging the complexity and scope of the problem and designing for it from the beginning. They don't treat it as just another feature that can be implemented at some late stage in the project, and then look for ways to shoehorn it into their existing architecture with minimal impact on everything else. That's not to say it can't be done, but there isn't going to be a quick fix or magic bullet to get you to where you want to be. You either have to accept that there's no way around the work that has to be done to achieve the versatile, safe, performant, and future-proof implementation you're looking for, or throw it all out and accept the issues and limitations that come with a single-threaded runtime environment. Once you commit to doing the work (or not) the way forward will become clear.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!