1
\$\begingroup\$

I am considering using the following game engine design pattern, but I am unsure if it's a good idea or not:

  • Each major task (drawing, physics, logic, networking, disk I/O) will have its own isolated thread or subprocess
  • Each of these threads can be put to sleep if it is running too quickly, so as to not consume an excess of CPU resources
  • Threads can only talk to each other through some well-defined interface to avoid messy, buggy, thread-unsafe conditions from occurring
  • This approach enables parallelism and scalability, but causes a thread safety problem: what if one thread is accessing data that another thread is currently processing?
  • We could introduce fine-grained locking, but it still can't be guaranteed that the collective states of all of a thread's data objects are coherent.
  • To solve this, collections of objects are maintained inside state objects, and state objects are double-buffered. The intent of this is to ensure that other threads only see a valid, coherent set of data. If one thread happens to be processing data that another thread wants to read, then the other thread reads from a buffered state, which is guaranteed not to be written to.
  • However, buffering these states introduces a delay where one thread sees old data from the last cycle of the other thread.
  • Another problem might occur when a reading thread has a slower "tick rate" than a writing thread: the writing thread may have flipped the state buffer multiple times, while the reading thread is still processing one "tick" using the same state. However this can be solved using OO references.
  • Pipelining should be achievable using this approach, because the draw thread could be drawing information that entered the pipeline 30 milliseconds ago, while more information is being processed
  • Ultimately, what I'd like to see is completely decoupled processing for all of the tasks in a game, implemented in a way that helps leverage multi-core environments, and maybe leaves some future expansion room for scaling the server side of things amongst multiple machines. I am aware that this is probably overkill for most purposes :-)

What are the criticisms or pitfalls of this approach? Is this the "right" way to do things? Am I thinking about this backwards?

\$\endgroup\$
2
  • 5
    \$\begingroup\$ My general advice is that if you have to ask, then you're probably better off not using a thread-intensive architecture. It really makes everything a thousand times harder to implement and debug, usually to no benefit. If your goal is to make a game, rather than to teach yourself to manage huge numbers of threads, then I advice against this course of action. (If the goal is to learn to wrangle threads, then absolutely go for it, and don't worry about whether or not it's the "right" way) \$\endgroup\$ Commented Jan 17, 2013 at 3:44
  • \$\begingroup\$ I really think you should make a game rather than getting all tied up in a load of threads. Unless you enjoy that kind of kinky game. It sounds a bit masochistic to me. \$\endgroup\$
    – MarkR
    Commented Jan 17, 2013 at 15:04

2 Answers 2

5
\$\begingroup\$

The benefit to pushing your rendering to its own thread separate from the game logic is to give the player the illusion that they are getting higher FPS but reality is that in most cases, you're really just drawing the same state over and over more often by doing so since you should be batching rendering commands to the rendering thread through a set interval of your logic thread's fixed time-step.

For File IO and even Network IO, I would consider using asynchronous IO. Whether you opt to use a library for this or not, there is serious advantages to using Asyncronous IO. I wouldn't offload this to another thread however, I'd make use of OS-centric constructs to perform non-blocking IO. This provides you the means to swap between blocking & non-blocking where appropriate for IO without having to concern oneself with threading.

For game logic, I use the notion where I update logical systems in sequential/serial order but the update loop itself operates in a batched parallel_for() manor where I operate on small buckets of components for a given operation across multiple threads.

Thread 1 - Components 1 through 50
Thread 2 - Components 51 through 100
Thread 3 - Components 101 through 150
Thread 4 - Components 151 through 200

What I prefer about all these approaches is that if you simply double buffer the command queue between the render thread and the logic thread, you can render as fast as possible, you can update on a set interval and you can manage all the above multi-threading goodness with nearly little to no need for locks, making the execution speed as robust as possible.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks, I'll consider this approach. I am assuming that you use a worker thread pool model for your parallelizable game logic? \$\endgroup\$
    – okw
    Commented Jan 18, 2013 at 1:42
2
\$\begingroup\$

I don't prefer multiple threads for each process. I use one thread for logic and rendering and a thread for each sound (say asynchronously) so that the sound system doesn't prevent logic and rendering thread. Though I use the same thread for logic and rendering, I separate them so that the game renders only after the logic has completed. Here's my game loop.

private void gameLoop(){
    long now;
    long gameTime;
    long ups = 33;
    long frameTime = 1000/ups; // 30ms for each frame
    while (running){
        now = getCurrentTime();
        while (now > gameTime){
            updateFrame(frameTime);
            gameTime += frameTime;
        }
        renderGame();
    }
}

This implementation is getting me 90-100 fps on my 2.6 GHz duel core machine with 1 GB RAM. If you are using java, please see my game engine here

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .