3

I'm trying my hand at multi-thread C programming and decided to start with a simple ncurses program to move a character across the terminal screen and change direction based on user input (think classic snake game). I decided that I needed to create a separate thread to handle moving the character across the screen, while the main thread handles capturing user input. My question is as follows:

Is there any benefit one way or the other to having the user input captured in the main thread, or the child thread? Does it matter at all which thread controls coordinate placement and which one controls capturing user input? Is there a best practice?

Pseudo code:

int main()
{
    pthread_create(blah....)
    while(ch=getch())
    /*
        get user input to change direction
    */
}

void *thread (void *arg)
{
    while(true)
    /*
        update placement of character on screen
    */
}

// is there a best practice for which thread
// handles which function?

EDIT:

Thank you to everyone for your responses. In case it's useful for other's in a similar situation, ncurses' function nodelay(stdscreen, TRUE) will put getch() into "No Delay" mode. This will constantly poll the keyboard, allowing you to put all of your logic in a single, main thread. Thank you to everyone that helped guide me to this design.

My original question focused on an issue that I had during multi-threading. While I believe it is valid and a great learning opportunity, some of the other responses guided me down a completely different approach to my program. The correct answer for the original question comes from:

@Blrfl

The correct answer for the question that morphed out of my original ("What is the best way to write a game loop accepting user input"), came from:

@Dragon Energy

1

In my blunt opinion this is like a ridiculous way to use threads because what does user input involve? It involves checking to see if the user pressed/release some key on the keyboard or pushed/release some button on a mouse or joystick (and normally I wouldn't use a blocking function like getch for this). Which means the thread has little work to do besides just waiting for an event/interrupt to occur or, worse, just polling and doing nothing until the user actually does the something with his/her input devices. And it's not such a big deal if a thread is mostly sleeping and doesn't have much to do, but it becomes a bigger deal if there's a latency involved in communicating from one thread to the next when a very real-time, consistently-timed response is needed.

That's just not very hefty for a dedicated thread to devote itself to doing while the latency requirements are very steep. Moreover in this design case, you don't necessarily want user input to be asynchronous of what the user is actually seeing displayed on the screen. If the user hits the contra code on his joystick/keypad really rapidly, then you don't want the display to lag behind the response of pressing those buttons.

Gamedev

This is very specific to a game development context but input response and display response aren't necessarily something you want to be so independent of each other in a real-time game (it's not necessarily that useful to be able to handle 1000 button pushes per second in a game like this that only displays 60 frames per second). If the frame rate is slow, that's something to fix on its own (because it's hardly practical for a user to play, say, a platformer if he can't see what he's doing fast enough), but not something to necessarily try to enhance by making input asynchronous of display/output. It's not necessarily that useful in a game to allow the user to do things in the middle of seeing the output of what he has done so far. In games like Snake it's very important to keep the frame rates interactive, and when the output is interactive, then there's not a practical need to devote a separate thread for user input. The formula of the 'main loop' common in games of this sort as they were written historically are more than adequate for the task and arguably even ideal for the task. If you want to use state-of-the-art graphics then the main loop still isn't necessarily obsolete if you can devote external threads to the heftier aspects of computation (and that doesn't include a thread dedicated to user input).

Take a classic game like Super Mario Bros. If you were to port to modern hardware and separate input handling in a separate thread from rendering, then chances are that it would feel no better or very possibly even worse when the control input response is no longer so precisely and consistently timed to frame output. It's either going to feel as right or worse (as though the frame display is somewhat a bit off from what the user is pushing on his gamepad) since we're basically just separating a thread doing very little only to communicate the results of what the user inputs to other threads just to basically cause Mario to jump or something of this sort. Even if the cost of that can be made dirt cheap from the time a button is pushed to starting to show the results of that in a frame, it's still jumping through hurdles for no real practical reason when you can make a game like this very interactive while the basic frame output and user input are being done in the same thread*.

  • This is not to suggest that you do very hefty work like manually rotating huge sprites on CPU in the same thread as opposed to distributing that work across a parallel loop, for example. It's just an argument in favor of tying the task responsible for, say, blitting the final results to the frame buffer together with the task responsible for detecting user input together in favor of games that "feel right". For GUI environments there are far stronger arguments against separating the event processing thread from the GUI output thread due to the complexities of getting such implementations correct and not just responsive.

So this is somewhat controversial, maybe, and I'm afraid I can't get so scientific here in favor of "feeling right" for a game, but it's hard to beat the response and interactivity of old school games like Super Mario which didn't separate an external thread just to detect what button a user pushed. It's not something that's "enhanced" with separate threads (and even from a productivity standpoint) and I could very easily see cases where the end product "feels" worse in terms of control response. In the interest of keeping the control closely tied to the frame rates as much as possible in a video game context and given that input is hardly expensive as far as computation (including async polling if it gets to that which isn't even that bad in a game that has to constantly utilize CPU to keep up a frame rate of 60+ FPS anyway), I'd suggest to generally favor putting the "raw output" and "raw input" code together in the same thread and favor non-blocking input functions or events (I'd avoid any blocking input function like getch like the plague for anything but a turn-based game which can wait on the user to do something before displaying the output for the next move).

The simplicity of the game might also factor in here. If we're talking Snake that might be a bit different from Unreal Engine 4 (and no sane developer should apply the same engineering practices for both if he's interested in shipping), though I'd be surprised if the developers at Epic dedicated a separate thread just for detecting user input that's completely separate from any sort of frame output.

If you have hefty computations to make in between those like checking for collision detection for physics, then you might devote a thread to that sort of thing which might have to do very hefty work for every single frame displayed. But user input is something I'd generally lean against dedicating a thread towards, and even more so in a multitasking GUI environment as opposed to like a fullscreen game.

Of course if you have like a slider which drags some integer value and each change to that integer requires some very hefty computation in the middle before an external output can be displayed, that might be something to seek to devote to a separate thread. This way the user can drag and see the integer values update in the GUI output without sacrificing interactivity of the GUI control while the external output sort of lags behind. But there the display of the control output and the user input for dragging the control with the mouse are still synchronized together on a basic level, it's just this kind of external output which requires more hefty computation which is being done asynchronously (to some extent or another) in a separate thread to avoid reducing the interactivity of the GUI input/output thread. That's a sort of design for a "pseudo-realtime" application that isn't fast enough (and together in a single thread) to output at interactive frame rates, so you keep the GUI input/output fast enough (say 30+ FPS) while the external output might only display at 1 frame per second. It's not a practical design for a video game. In a video game you want the display of all relevant output at a fast enough frame rate, at which point it starts to become impractical to devote a separate thread to user input.

Typing Game

All right, just to contradict myself a bit for exceptional cases and yield to any who might disagree with this opinion, I could see more of a case for a separate user input thread in even a simple typing game: those games where you have to type what you see on screen as fast as possible. In those cases the nature of the game input might not be so directly tied to frame output. Even if the game can display its frames at 30+ FPS, a user super fast on the keyboard might type faster than this or hit two or more keys in between the display of one frame to the next, and there it might be useful to queue up those results in some producer thread and have a consumer pop it off and handle all the keys pressed in between those two frames. That's a very different game from Snake though or Super Mario or something where it's not necessarily useful to queue up what the user has pressed in between frames (those games tend to benefit more from just knowing when a button is down/up for a particular frame as opposed to queuing up key presses like an input field). It's like if the user presses down and then up key in between one frame to another, there's nothing for the game to do except ignore one of those two keys and start moving the snake in one of those directions when it comes time to handle the game logic and display the frame. I'm taking very much into account the specific nature of the type of game being developed in my answer and also what I believe is the simplest, most practical solution for such an application that's likely to yield desired results with minimal fuss.

  • 1
    Thank you for this comment! The other answer more directly related to the question, HOWEVER this answer is what I really needed. In the absence of a way to "merge" the answers, I accepted one and upvoted the other. Thank you, and I'll rethink my design before going down the path of threading. – bgregs Dec 3 '18 at 12:26
  • 1
    Cheers. I got a bit specific and biased there as a former gamedev, but if I get very contextual with your question and not answer it so generally, then I'd generally look to not separating user input from user output threads in this type of real-time interactive context of a video game. And mainly I wouldn't use getch or any other blocking input function for anything but a turn-based game. – Dragon Energy Dec 3 '18 at 12:29
  • 1
    Makes perfect sense after reading through your response. Again, thank you for the detailed answer, exactly what I needed to hear! – bgregs Dec 3 '18 at 12:31
  • 1
    This is a really good answer and in my opinion deserves the green tick, especially for pointing out that in the times of old all games were single threaded input/output loops. – pjc50 Dec 3 '18 at 13:37
  • I hate to bounce previously accepted answers, but with the recent updates to this one, I think it has deserved the answered tag. – bgregs Dec 3 '18 at 13:55
4

From a purely conceptual standpoint, main() is no different than any other thread in the same process. It just happens to be the one that was started first but still shares code and data with every other thread within the process.

Under the hood, there may be differences in how threads are implemented. For example, early threading in Linux was based around the process as the schedulable unit of execution but didn't separate address space, file descriptors or the signal table. Instead of calling fork(2), you'd call a Linux-specific function called clone(2)that did the job. As time progressed, the implementation morphed into one where the process abstraction still exists but anything involving execution is all threads. When the kernel runs a program, it creates a process and an initial thread that eventually calls your main(). Even if clone(2) is never called, that one thread still operates the same as if there were others.

Unless you're in the business of implementing threading libraries, none of this should matter to you. The P in POSIX stands for portable, and all of it, the thread library included, is there to hide the underlying implementation and give you a standard API that will work anywhere.

  • 1
    My apologies for pulling the "accepted" status, as this answer does completely answer the question. However, I think the other response more specifically targeted the issue I was having. If these could be merged together, that would be the perfect answer! Thank you for your help, I really do appreciate the background you provided. – bgregs Dec 3 '18 at 13:56
  • @bgregs The other answer covers the question implied by the title ("Is this a decent thread architecture for a game?"); this one answers the one actually asked in the body ("Is there any benefit one way or the other to having the user input captured in the main thread, or the child thread?"). This may be a sign that the question needs improvement. If your goal is getting your feet wet with concurrent programming, 85% of the other answer is what you worry about after you've mastered the base concepts. Whether or not this answer is the accepted one is no skin off my nose. – Blrfl Dec 3 '18 at 14:21
1

Is there any benefit one way or the other to having the user input captured in the main thread, or the child thread? Does it matter at all which thread controls coordinate placement and which one controls capturing user input? Is there a best practice?

Addressing this specifically: in a lot of UI systems it is either required or strongly advised to do all user input and GUI output on the main thread. Get a message from the operating system informing you of input, update the coordinates, draw the result. Simple.

If you find you need to do some intensive processing, then you need to start building an architecture of background threads to do this in, and a means of queueing work items to those threads and reading back the results. Don't be tempted to do it all through shared variables: either you don't lock them and end up with inconsistency, or you do lock them and find that you have multiple threads but only one can run at any one time.

But remember that computers are fast. If you do need to delegate a quantity of maths to somewhere, remember that a GTX 1060 can do four trillion arithmetic operations every second.

in the times of old all games were single threaded input/output loops

Mentioned in my comment on the other answer, because user Dragon Energy is right here: this was common practice right up until the mid-2000s when multicore systems started becoming common. They require use of multiple threads to make full use of their resources.

The old approach relied on very acute awareness of the speed of the system processors, combined with the knowledge that video is output as a series of lines across the screen in a series of frames at a fixed rate. This allowed the creating of video effects that relied on changing the output while a frame was in progress. See the excellent book: https://mitpress.mit.edu/books/racing-beam

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.