7
\$\begingroup\$

Simple programs that collect data from only one system need only one (blocking) event loop. For example, Windows applications have the message loop, POSIX network programs usually have a select/epoll/etc. loop at their core, pure SDL games use SDL's event loop. But what if you need to collect events from several subsystems? Such as an SDL game which doesn't use SDL_net for networking.

I can think of several solutions:

  1. Polling (ugh)
  2. Put each event loop in its own thread, and:

    1. Send messages to the main thread, which collects and processes the events, or
    2. Place the event-processing code of each thread in a critical section, so that the threads can wait for events asynchronously but process them synchronously
  3. Choose one subsystem for the main event loop, and pass events from other subsystems via that subsystem as custom messages (for example, the Windows message loop and custom messages, or a socket select() loop and passing events via a loopback connection).

Note that I'm talking about an event loop that blocks execution until an event arrives. This allows writing event-based games that don't pointlessly burn 100% CPU, and can process events even during drawing or performing a logic update. Checking if a subsystem has events and continuing execution otherwise is what I'd call polling.

Option 2.1 is more interesting on platforms where message-passing is a well-developed threading primitive (e.g. in the D programming language), but 2.2 looks like the best option to me.

\$\endgroup\$
2
  • \$\begingroup\$ How does 2.1 differ from 3? It would appear that 2.1 is the simplest case of 3, merely designating the system on the main thread as the authority. \$\endgroup\$
    – Kylotan
    Feb 19, 2011 at 20:04
  • \$\begingroup\$ Hi. I explained that in a comment to homebrew's answer. \$\endgroup\$ Feb 19, 2011 at 21:32

4 Answers 4

2
\$\begingroup\$

So, the tech that I'm working with right now has a notion of a central message stream shared by all threads via subscription and duplication (threads install handlers which may or may not duplicate consumed messages).

This stream is fed by a thread dedicated to translating/polling different event queues/devices. If need be, because event sources all expose the same API to this thread, I can (in, say, the case of XInput which seems to be polling-only) create a class containing a thread devoted to handling a particular sort of input; this lets me handle things as diverse as 360 controllers and asynchronous social media querying.

One nice thing about having this unified stream is also that I can do weird things like have a handler that consumes events and pattern matches them with a state-machine to handle things like attack combos for a fighting game and then emit a game event that says "Haruken!"

Keep in mind that the end goal for something like this is to combine a diverse range of input events into a single stream some tech is guaranteed to handle correctly (Quake had a good take on this).

\$\endgroup\$
3
  • 1
    \$\begingroup\$ To amend, an event stream for an application shouldn't even be a thread, ideally; it should just be a queue (lockless or protected with mutices) that external threads manipulate. We use a dedicated thread just to open up the option of strange hacks. \$\endgroup\$
    – ChrisE
    Feb 18, 2011 at 22:49
  • \$\begingroup\$ That sounds like a lot of threads. My plan is to use a single thread which handles all events, and defer slow processing to separate threads (task completion is signalled as another event). \$\endgroup\$ Feb 19, 2011 at 1:16
  • 1
    \$\begingroup\$ We're a pretty heavily threaded engine (targeting PCs, so this is acceptable). One thread is used to service normal event loops, one to do rendering (with the way OpenGL handles contexts, this is somewhat necessary), and then one more to handle logic, feeding some number of worker threads, and so forth. \$\endgroup\$
    – ChrisE
    Feb 19, 2011 at 21:16
1
\$\begingroup\$

I remember looking through the code base of ChucK http://chuck.cs.princeton.edu/ and noticing that the whole program uses just one thread. ChucK is an interesting example because it fakes concurrency using one thread in a "strongly-timed" model (that's my best explanation ;) ). However, there are threads for different event sources (MIDI events, Open Sound Control events, or socket messages) and these queue the events for processing by the main loop.

So...I'd pick option 2.1. which seems the same as option 3.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks. The difference between options 2.1 and 3 is that all the main thread does in 2.1 is collect messages sent by other threads, using native thread message-passing. 3 is simpler, but more of a hack since you [ab]use another subsystem to send messages to yourself. \$\endgroup\$ Feb 18, 2011 at 22:09
1
\$\begingroup\$

Adding threads to the system creates bugs at worst and inefficiency at best (due to the necessary synchronisation) unless you have a clear example of the parallelism helping, so I wouldn't use it in this case. Note that your option 2.1 has left out the synchronisation aspect - you're ok on Windows since PostMessage is synchronised, but this is something you need to check on a per-API basis.

I wouldn't choose to route several subsystems into another via custom messages because this could require significant rewriting should you change that subsystem - eg. port from Win32 to SDL or whatever.

So, I usually go with polling where the subsystems make it possible and efficient. eg. It's rarely a problem to poll your network library, because either there is data ready to read or there is not - you're not going to slow down your program by checking for it if the library is well made.

\$\endgroup\$
16
  • \$\begingroup\$ Sorry, I think this is a mis-informed answer. I don't see where any synchronization problems could appear on 2.1, unless your platform's thread messaging is poor/nonexistant and you need to write your own thread-safe event queue (and even then this couldn't have anything to do with PostMessage etc.) "Significant rewriting" is not a problem unless you're Doing It Wrong - remember that you are communicating with code within the same process, so you can easily just send a pointer to a subsystem-agnostic structure with the data. \$\endgroup\$ Feb 19, 2011 at 21:12
  • 1
    \$\begingroup\$ As for polling at 'arbitrary points', what's the problem? We render at arbitrary points too. We often drain the event queue at arbitrary points. All points are arbitrary unless your process is responding directly to hardware interrupts, which very few user level programs are these days. \$\endgroup\$
    – Kylotan
    Feb 20, 2011 at 0:03
  • 1
    \$\begingroup\$ As for not being able to process things between frames, handling the input in another thread isn't going to help because the player's latency will still be limited by their frame rate, as they have no other feedback to show them the results of their actions. And even if your players are so fast that the 20ms latency per frame at 50fps is significant then again being threaded and event driven won't help because your input thread is unlikely to be scheduled at a much higher granularity than that. The best you can hope for is timestamped events - which can be polled for as usual. \$\endgroup\$
    – Kylotan
    Feb 20, 2011 at 0:08
  • 2
    \$\begingroup\$ @CyberShadow: They can, but they won't get any feedback on it. Even if they push the button perfectly, you can't render the frame or do the physics to actually update that action in time. \$\endgroup\$
    – DeadMG
    Feb 20, 2011 at 11:07
  • 1
    \$\begingroup\$ @CyberShadow: By default Windows won't switch between threads more rapidly than every 15/16ms or so, unless you change it with timeBeginPeriod. Other operating systems are similar. Context switches are expensive so most OSes try to minimise them. As for the D thread messaging, that may be fine but there is nothing like that as part of C++. There is also nothing like that in SDL. To compare pros and cons further will require a concrete example. \$\endgroup\$
    – Kylotan
    Feb 20, 2011 at 14:03
1
\$\begingroup\$

If the event loop is cleanly written, there is no reason that a primary thread cannot have two event loops. Windows, for example, has PeekMessage. First you process all Windows messages, then you move on and process all whatever messages. As long as you're not blocking on a call to receive a message, then there's no reason that you can't call two message querying functions in a single loop instead of one.

\$\endgroup\$
3
  • \$\begingroup\$ That's what I meant by "polling", basically. You can't gracefully wait until an event comes from either event source. It's quite inefficient. \$\endgroup\$ Feb 19, 2011 at 21:07
  • \$\begingroup\$ @CyberShadow: No game loop blocks until a new message is available, they go render a new frame or do a game logic update. \$\endgroup\$
    – DeadMG
    Feb 19, 2011 at 21:45
  • \$\begingroup\$ Oh, I see what you mean now. Please see my comments to Kylotan's answer why I don't consider this a good solution. I'll amend my question to make this clearer. \$\endgroup\$ Feb 19, 2011 at 21:50

You must log in to answer this question.

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