Input on different thread

Started by
7 comments, last by haegarr 10 years, 3 months ago

Hello everyone.

I googled for a while now, and am probably missing some keywords to find the answers to my question(s).

I'm planning on splitting my code so input is gathered in one thread, and the game logic is on another thread, so i can timestamp input on the main thread, and use these timestamped inputs in the game logic thread. This is on win32, and is influenced by L.Spiros posts.

The question i'm having is how to take the input messages on the game thread.

I'm completely new to multithreading, so i thought i'd ask first before trying any of the solution versions i have in mind, as i have no idea if what i would do is horribly wrong.

The basic idea are the two threads, main/input thread and the game thread (not yet bothering with render thread). Main thread receives the messages from the OS, timestamps them and stores them somewhere (this is the question), while the game thread only uses messages that happen up to the current time.

One way i thought i would do this is by having a container of events (std::vector? std::deque? std::list?) in the main thread, and events are always put at the end of the container by push_back() or emplace_back(). The game thread would then ask for events either one by one or in bulk, and would do this by looking to the front of the container and doing pop_front() for each event that happened before the current time. From my googling i found this is called single producer-single consumer, and i'm wondering if there's something in the STL that i can use for this instead of taking random/potentially unsafe code from the net.

Another way is to have the main thread send each event to the game thread as it is received and timestamped, and let the game thread store the event and use them when needed. I guess this is basically the same as the first one, only the container of events is owned by the game thread instead of the main thread.

Do i need to implement locking of any kind for this? Can i use the basic STL containers if i can guarantee i have exactly one thread doing a push/emplace_back(), and the other doing a pop_front()? Is yes, which container is best suited for this?

I appreciate any help :)

devstropo.blogspot.com - Random stuff about my gamedev hobby

Advertisement

It is not exactly the traditional producer/consumer problem, because in this case the consumer does not stop until consumables are available again. However, the synchronization mechanisms suitable for and described with the traditional producer/consumer issue should work as long as they don't block the consumer (i.e. there are trial methods or the lock-free version is used).

C++11 adds MT support. I doubt that the STL is MT safe by itself, but I donÄt know whether MT safe classes exist as well.

I made a small test for this, and this seems to work ok:


bool Window::peek(uint64_t time, WindowEvent& outEvent)
{
    bool eventExists = false;
    if(!m_events.empty() && m_events.front().m_timestamp <= time)
    {
        eventExists = true;
        outEvent = m_events.front();
        m_events.pop_front();
    }
    return eventExists;
}

This function is called from the game thread until it returns false. The main thread runs the winProc loop and parses the WM_* messages into the WindowEvent structure, and is emplaced_back() into a std::deque. Deque docs say it has no data races as far as emplace_back() and pop_front() go, only the iterators are invalidated, but i'm not using iterators anyway, so...

This seems to work fine, but i'm probably missing something, seeing this is my first time playing with more than one thread.

devstropo.blogspot.com - Random stuff about my gamedev hobby

I'd recommend something simple. Using C++11, create an InputQueue class that contains a std::queue and a std::mutex and wraps each push()/pop()/top() with a std::lock_guard.

This code is untested but illustrative.


#include <mutex>
#include <queue>
 
class InputQueue
{
  bool empty() const
  { std::lock_guard<std::mutex>(mutex_); return queue_.empty(); }

  InputEvent const& front() const
  { std::lock_guard<std::mutex>(mutex_); return queue_.front(); }
 
  void pop()
  { std::lock_guard<std::mutex>(mutex_); queue_.pop(); }
 
  void push(InputEvent const& event)
  { std::lock_guard<std::mutex>(mutex_); queue_.push(event); }
 
private:
  std::queue<InputEvent> queue_;
  std::mutex             mutex_;
};

Yes, there's a race condition between empty() and front() but if you're polling the queue in a producer-consumer and doing busy work between polls, it won't cause a deadlock and is unlikely to cause delay problems.

Stephen M. Webb
Professional Free Software Developer

The real question is, why would you want to do that? Input handling is like one of the cheapest operation to perform in a game, i dont see any reason to use a thread for that, all you'll get in return is something wayyyy more complicated, with no real benefit. Multithreading is hard to get right, and all the lock/unlock you're gonna do constantly will just kill any benefit you'll gain from it.

But that's my opinion, feel free to try it if you wish.

The real question is, why would you want to do that? Input handling is like one of the cheapest operation to perform in a game, i dont see any reason to use a thread for that, all you'll get in return is something wayyyy more complicated, with no real benefit. Multithreading is hard to get right, and all the lock/unlock you're gonna do constantly will just kill any benefit you'll gain from it.

But that's my opinion, feel free to try it if you wish.

Because i want accurate timestamps on my input?
Because of this?

devstropo.blogspot.com - Random stuff about my gamedev hobby

Input uses same thread as rendering logically.

S T O P C R I M E !

Visual Pro 2005 C++ DX9 Cubase VST 3.70 Working on : LevelContainer class & LevelEditor

Your player reacting to the screen, is nothing more then logic.

I mean like this :

if( ACTIONBUTTON )player->action();

Maybe Directx fills data in another thread for the input, is that why someone -1 me above ?,

else what would be the reason to have your input in another thread then the rendering ? :

Bacause your frames per second is to low.

S T O P C R I M E !

Visual Pro 2005 C++ DX9 Cubase VST 3.70 Working on : LevelContainer class & LevelEditor

An own thread for input helps in avoiding input misses because the thread is much more often executed with a much shorter round trip time than the game loop is. It further allows to create finer timestamps if necessary.

Even graphical rendering is not necessarily a single thread's job. At the same time when you see the scene in state N on the screen, the GPU and the CPU within the render thread can process a render command queue to produce scene N+1, and the CPU within the main working thread (where the game loop is living) can write the commands for scene N+2 into the 2nd command queue (i.e. a double buffered CPU side render command queue is used).

An own thread for resource streaming from mass storage helps to avoid blocking on file I/O, of course.

Whether multi-threaded or not, this ...


if( ACTIONBUTTON )player->action();

… is definitely the way we do not want to go because there is a direct coupling of raw input to actions, no input configuration, no input sequencing, hence no possibility for input combo detection, no support for multiple input devices, no support for input transitions (instead of states), and perhaps more no's.

Okay, a simple game needs simple input and the above method may be fine. But more elaborate games up to engines require some more thinking.

This topic is closed to new replies.

Advertisement