Question

I know 100% that there already is a solution out there for what I am asking but with all the research I have done, I can't seem to find it.

Problem: Currently, a CS student trying to do independent studies to stay ahead. Recently, I have been wondering how different languages/frameworks implement Event Handling and I have found some information on it such as there typically is an Event loop running synchronously with the main thread.

My question is, how is such a thing implemented when wanting to check for multiple events such as game events. Would you want to create a thread for each event and have those threads loop synchronously with the main thread or would you want to have a single thread? I would only assume it is better to open a new thread for each event you want to handle but there has to be a better solution.

Was it helpful?

Solution

My question is, how is such a thing implemented when wanting to check for multiple events such as game events

The event loop ("main loop", "game loop") runs on a single thread. In games, most runtime processing is done on every update (so, it happens very fast in order to maintain a decent framerate). More rarely, things are spread out over multiple frames, and in some cases, certain types of processing happen only every other or third frame (e.g., if game designers feel that AI doesn't need to do updates as often as the renderer, they may deliberately reduce the rate of AI updates).

If some computation is too expensive to be performed in this way, it is done offline, if possible . I.e., most of it is precomputed/baked in some way - and the results are stored as data, and used at runtime in a simpler computation (and there's often some kind of approximation involved - to get something that's good enough, rather then strictly accurate).

Under the hood, the events are pulled from some sort of an event queue. These are typically things like user input, which come in asynchronously (essentially, under the hood, even though the CPU runs in a single thread, there's other hardware (peripherals) that runs independently (in parallel) and communicates with the CPU on the OS level). So multiple events are just entries in the queue, and as the loop "spins", when the time comes for the next update, the game just removes these events from the queue and then does something with them. This is not unique to games - this is how window systems work.

Actual games may add a level of abstraction on top of that. When it comes to pushing and handling those events, things may be arranged in a different way. For example, instead of you pulling events, you may derive from some class and override some method that will get called when some event happens. In this case, these are probably some higher level events (like "Timer Expired", "Player Triggered [TriggerName]"). Under the hood, there's still the game loop, but what's different is, before the next update, the game would do some processing to figure out what has actually happened in terms of these higher-level events, and then it would call the appropriate functions (event handlers). If there are several events, they are executed sequentially, but you don't notice because it all happens many times per second. Depending on how things are arranged, you may also be able to push your own custom events to be handled on the next update; but mostly you are just manipulating the data structures that represent the state of the game.

See this for more info.

That said, sometimes you need to perform a long-running computation (in games or in other kinds of applications) that would block the main thread and make the application unresponsive; in that case you do it on a separate thread. (E.g., you could be dynamically loading data from the disk.) However, there's a cost to threading; in a "normal" application, you can spin up a bunch of threads, but in an interactive/game-y application, it's just not feasible to spawn threads all the time as that cost quickly accumulates.

So, with games, you have to be rather deliberate about the use of threads. Since modern CPUs are multicore, games can make use of that to do certain kinds of processing in parallel and gain a certain amount of speed-up (as multiple cores mean that a couple of threads of execution can run truly simultaneously). But that's not done to process individual events, but rather to do bulk processing of data that can be processed independently in some way. Unless I'm mistaken, this is usually done to achieve something "fancy" (e.g., some kind of specialized geometry processing, specialized physics, simulations, etc.) and is by no means something all games do.

BTW, that's all on the CPU; most games, of course, assume/require a GPU, and a GPU is essentially a massively parallel data processor. Besides for rendering, it can be used by games for other kinds of computations due to programmable shaders.

OTHER TIPS

Most events are handled quickly.

If you have an event that is time consuming, you do the work on a background thread, or your app will be unresponsive.

Note that if you return from the event handler on the main thread, the caller will assume that the event is “handled”, and if it isn’t handled, you may be in trouble. So how does this work with background threads? Simple. If you have a “print” button for example, you define that it’s action isn’t “print 100 page document” but “put a task to print a 100 page document onto my to-do list”. The latter is an action that you can handle on the main thread.

Many things in a computer can run asynchronously without involving additional threads. For example, the operating system can asynchronously ask for data from a disk, because the disk can perform that operation independently.

Consider the following example written in C#:

async Task<int> AccessTheWebAsync()
{
    var client = new HttpClient();

    // Start an operation to get a string from the Internet.
    // This method returns immediately.
    Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com/dotnet");

    // While we're waiting for the string to arrive, go do something else.
    DoIndependentWork();

    // Retrieve the string from the Task<string> returned above.
    string urlContents = await getStringTask;

    return urlContents.Length;
}

The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.

Further Reading
The Task asynchronous programming model in C#

In most cases thread creation is a rather expensive operation. So it's often a bad idea to use newly created thread for each incoming event. Tou should prefer to reuse existing threads. Better take a look at thread pools.

Licensed under: CC-BY-SA with attribution
scroll top