Jump to content

  • Log In with Google      Sign In   
  • Create Account


Input on different thread


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
8 replies to this topic

#1 Strewya   Members   -  Reputation: 1169

Like
0Likes
Like

Posted 29 December 2013 - 05:10 AM

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


Sponsor:

#2 haegarr   Crossbones+   -  Reputation: 3779

Like
0Likes
Like

Posted 29 December 2013 - 06:15 AM

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.



#3 Strewya   Members   -  Reputation: 1169

Like
0Likes
Like

Posted 29 December 2013 - 07:08 AM

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


#4 Bregma   Crossbones+   -  Reputation: 4770

Like
0Likes
Like

Posted 29 December 2013 - 08:08 AM

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

#5 Vortez   Crossbones+   -  Reputation: 2688

Like
1Likes
Like

Posted 29 December 2013 - 08:51 AM

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.


Edited by Vortez, 29 December 2013 - 08:53 AM.


#6 Strewya   Members   -  Reputation: 1169

Like
0Likes
Like

Posted 29 December 2013 - 10:00 AM

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


#7 the incredible smoker   Members   -  Reputation: 263

Like
-1Likes
Like

Posted 29 December 2013 - 11:14 AM

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


#8 the incredible smoker   Members   -  Reputation: 263

Like
0Likes
Like

Posted 31 December 2013 - 06:27 AM

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.


Edited by the incredible smoker, 31 December 2013 - 06:29 AM.

S T O P   C R I M E !

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


#9 haegarr   Crossbones+   -  Reputation: 3779

Like
0Likes
Like

Posted 31 December 2013 - 07:46 AM

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.






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS