#8 Lina Engine: Input System - Part 2
Hey there, today I would like to talk about the second portion of my input system in Lina Engine. If you have not read the first part, I advise you to read it first before keeping up with this one, here.
So in the Part 1 post, we had talked about event polling and state reading to process input handling throughout the engine. We had summarized that reading key & mouse states would not be enough on its own, we would need a hybrid system between events & direct update-related state readings. Then we talked about the event mechanism, the most plausible one: Signals & Slots from Qt. So let's dive more into it.
Signals & Slots
Signals & Slots is one of the biggest features of the Qt software development framework. It's smartly implemented and can be counted as a distinguishing feature of Qt from other libraries. Below is a basic structure of the pattern, taken from the Qt documentation.
As can be seen from the illustration, by using a pattern similar to Qt's Signals & Slots, we can create events that can be carried out in any object. And any other instance, whether the same typed object or different, can receive these events. In order to achieve that, there are 3 ways we can go:
Connect the signals to desired slots.
Connect the slots as listeners to the signals.
When this type of a pattern is implemented, it provides fast and accurate event handling. Any class, whether low-level or high-level can carry their own events, transfer them around the systems, or any system can listen to a particular event in any other. There would be a direct relation between the signals and slots, thus event producing be processed relatively faster than other types of patterns we can use to implement. So it's a great way of efficient communication. However, just as with everything that is good, there is a problem, and it's dependencies. So, let's talk about the drawbacks that we can have using this type of an event handling pattern.
Drawbacks of Signals & Slots
So you see, in order to listen to signals, or connect particular signals into slots, we need to have dependencies. Take a look at the figure above again, you would see that in order to connect Signal2 in Object1, Object1 needs to know about Object4. A more application based example, let's assume the rendering engine has a signal that is called "Frame Rendered", and I do want to take some actions on the physics engine upon this signal. It has to be one of three ways to go, either the physics engine would depend on the rendering engine for listening to that particular signal, or the rendering engine would depend on the physics engine to connect a particular signal to a slot on it, or both of the cases at the same time. This has a high chance of causing problems, assume that something went wrong with one of the systems and signal listening or slot connection has failed, this would effect the other system, and in order to handle problems, we would have to write a lot of exception handling, that will clutter up our systems and general code architecture. And things get even messier if we are talking about signal & slot communication between high-level game classes and low-level engine classes. We would not want our game classes to directly connect with the core engine classes. We don't prefer a single class that handles for instance translation of a cube during the gameplay, to know about the input engine and be able to reach to it directly. In order to prevent that, we would have to write some interfaces.
Writing Interfaces for Signals & Slots
Of course, the most logical option is to write some API calls and interfaces between these classes that bears the signals, and the slots that receives them. However, we might end up with a lot of different interfaces, or a single huge interfaces that is tend to cause problems as the number of systems and sub-systems grow. This is because we would design our pattern in such a way that any object would be able to carry it's own signals, that is the one of the biggest flexibility of Signals & Slots right? We do not have to rely on a single central structure of events, an object responsible for in game time mechanics can have gameplay timer related events that it can hold, and other game classes can listen to this. A low-level system like the rendering engine can have signals, and everybody can listen to this. So, since the number of signals on various differently typed objects are expected to be a lot, writing interfaces for communicating with these events will destroy the whole purpose, because it would be an extra-layer of communication between the signals and slots, thus will slow down the communication, destroying the whole point of achieving fast results.
What to Do?
It seems to me is that, we are kinda stuck in a decision tree right now. Obviously a pattern similar to signals and slots would provide us some great flexibility of different types of events in any object and fast communication to any listener, however it is not plausible to use this kind of a system for the low-level systems, because of the reasons explained above. However, again, this pattern would be perfect for high-level game object communications, they would send signals to each other in an accurate and reliable way, for the gameplay classes themselves, we do not worry about the dependencies as much as we would for the core classes, since the gameplay can be handled independently from the core engine. Then, we need another type of event handling for our core classes. And we will definitely go into that in the Part3, but for now, let's wrap it up. And as a closure, you can take a look at the code below to see and implementation of event handling with a Signals & Slots pattern in mind that we will be using for our high-level classes in Lina Engine.