4
\$\begingroup\$

I'm trying to recreate a rhythm game. However trying to get the key-presses with SDL2 I ran into the problem, that you can only get an event every time a frame has been successfully rendered (I can't poll the events from another thread than the main thread). As you could separate game-logic from rendering, I thought I could run the game logic (including the polling of the events) for every 1ms and render the screen for every 16ms (60fps).

I read some articles stating that it doesn't make much sense to use multi-threading for that usage, but 16ms are quite a lot of latency for a rhythm game (which has about 30ms to determine a better rating for a better high-score). (The problem is also described here: Accuracy and frame-rate in a rhythm game)

EDIT: Here It's being said that multi-threading shouldn't be used:

What is the best way to handle event with SDL/C++

Now my question is:"Should I really not separate game-logic from render logic, as I don't need visual feedback for this kind of game and accuracy is more important"

\$\endgroup\$
  • \$\begingroup\$ Yes, you should not create a separate thread. What's wrong with a single-threaded approach? \$\endgroup\$ – HolyBlackCat Jun 20 '16 at 15:08
  • \$\begingroup\$ @HolyBlackCat if you use the single-threaded approach, you can only capture keyboard input as fast as the render loop can run (which is 60 FPS usually if you have VSYNC turned on). Ideally, for this application, you should be able to record what keys are being pressed/released (and when), even between frames. Now, let's say you run at 1 FPS, and your game requires you to press A followed by B within 500ms... You simply can't do it if your render loop takes 1000ms per iteration between checking the event queue. \$\endgroup\$ – Breakthrough Jun 20 '16 at 15:49
  • \$\begingroup\$ @Breakthrough When SDL application receives an event, it can get a timestamp (when this event was sent). If one take timestamps into account, single-threaded method becomes viable. \$\endgroup\$ – HolyBlackCat Jun 20 '16 at 20:07
  • \$\begingroup\$ @HolyBlackCat although indeed events do have a timestamp field, the accuracy of those timestamps are still limited by the speed of the render loop. As a test, I wrote a quick input loop with a 2-second delay, printing out the timestamps of any keypresses recieved, and even if I leave a 1-second delay between pressing two keys, the timestamps for both presses are always within +/- 2ms of eachother, instead of the expected 1000ms difference. \$\endgroup\$ – Breakthrough Jun 20 '16 at 23:31
  • \$\begingroup\$ @Breakthrough Oops. Looks like I was unaware of how it really works. \$\endgroup\$ – HolyBlackCat Jun 21 '16 at 0:34
2
\$\begingroup\$

TL,DR: The SDL2 event/render system is not yet thread-safe, but you can overcome this limitation by using OpenGL to decouple the renderer system from your application (and then perform all rendering in a separate thread using only OpenGL functions). In this method, you still have a single event queue.

Another way is to use multiple processes (e.g. using PySDL2 and Python's multiprocessing module, or a C/C++ program that launches two sub-processes), and follow the same dual-window method outlined below (where each window has it's own event queue, but one window is hidden and is only polled to check for user input). One process will handle drawing and the other input, and application state changes and events are communicated between both processes using a pipe or thread-safe queue.


It's generally not recommended to separate render/input/game logic into different threads since it greatly complicates things under the hood, and you need to ensure data concurrency and thread synchronization (although SDL2 does indeed provide what you need to accomplish this). That being said, if you do need to separate input/rendering - and in this case, I agree that would be the most logical thing to do - it is possible to do using SDL2 threads (as well as Python threads if you're using PySDL2) and OpenGL.

There are two approaches to accomplishing this, one with a single-window and another with a double-window.

With a single window, create your OpenGL context/window with SDL2, then pass the OpenGL context to a new thread that will perform only rendering. It needs to safely notify the main thread when to call SDL_GL_SwapWindow and block until it's done (you can still keep polling events for the window even if you don't call GL_SwapWindow), but so long as you ensure only one thread is calling OpenGL functions at once, there is no problem doing this, even cross platform.

Using the double-window approach allows you to fully decouple all OpenGL API/rendering calls from the main loop, by using the main loop to create a hidden overlay window that captures the keyboard/mouse input, and continually polls for input/window events. This way you don't need to stop for any OpenGL calls, and so long as your application state is thread-safe, you can pass any relevant window events to the GL window in a queue.

\$\endgroup\$
  • \$\begingroup\$ This is a really good answer. However I didn't really plan to use OpenGL. But I guess there is not really a different way except for doing the rendering in the main thread using SDL and doing the logic including button probing inside another thread (If you don't want to use OGL) \$\endgroup\$ – The19thFighter Jun 21 '16 at 14:51
  • \$\begingroup\$ @The19thFighter if your familiar with Python, you might have some luck with the multiprocessing module, handling two entirely separate applications/render loops, communicating between eachother using a multithreaded Queue/Pipe. \$\endgroup\$ – Breakthrough Jun 21 '16 at 18:51

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.