Well, bored and can’t think of anything else to do, so lets talk about Gail’s event programming system! It came up on IRC a few days ago, so thought it might be a good thing to talk about.
So whats event driven programming? Basically its where all of your code is driven by events instead of being strictly linear. A good example of this is a GUI program : it spends most of its time waiting. When you click the mouse on a button or type a key, it responds to the event with an event handler, then goes back to waiting for the next event.
Gail originally had a standard main loop (see some of the really old examples and GAIL_CORE_DEPRECATED_GAILMAIN_WRAPPER_WITH_NAME). The basics of it are still usable since Gail’s insanely good with backwards compatibility, but not much uses it anymore (one day i’ll remove it).
On a side note, best warning ever from clang:
Main.cpp:89:1: warning: 'DeprecatedApplication' is deprecated [-Wdeprecated-declarations]
So anyways, main loop. The big problem with the standard main loop is for any complicated program, you end up putting everything in the main loop (or you write tiny main loops everywhere). If this do these things, if this do this thing, for a couple thousand lines. Becomes a huge mess. So to solve this, you standardize the way stuff is added to the main loop, and have everything else drive itself based on a set of events (generally with Gail that is signals, file descriptors, and timers). So how do you handle events? Theirs a few ways to solve this : the two main ways i’ll mention are event passing and signaling (which is what gail uses).
Event passing basically allows objects to register for events and they got notified when they occur. A good example of this is WinAPI which every event comes in this form. WM_PAINT is when your app needs to be repainted, WM_LMOUSEDOWN is when the mouse is pressed down, etc. This system can work, but has some issues for me. Namely three things.
- The event system requires you to add a new identifier every time you want to post a new event. If you only have a few events or limit it to a specific set, it works well enough, but if you have lots of events it quickly can add up.
- Tricky to do event storage. Every event type has its own data it needs to store, whether its numbers, strings, whatever. Probably i would have done something like InterpolationTimer’s userdata pointer which takes any gail object if i went this route, but you have to package/unpackage the data every time you plan to use it. Not fun.
- An event system like this is inherently global. If thats what you need (like the window example), great! Sometimes you want one object to be able to notify an object indirectly though, such as a TextGroup getting notified when Text’s color change (which by the way was added today for all you TextGroup users). While you probably could shoehorn local messages into a system, its a mess.
text->signalColorChanged().connect (Gail::Core::functionPointer (this, &TextGroup::childTextNodeChangedColor));
This line says to get the signal for coloring changing from text, and add the function for this->TextGroup::childTextNodeChangedColor to it. When the signal is emitted (the color was changed), it’ll automatically call childTextNodeChangedColor() which in this case, tells the text group it will need to rebuild.
Now notice something important: text has no knowledge of text group. All text knows is when its color changes. it emits the color changed signal. Anyone who cares about when that occurs can register their own functions. This is a very powerful concept.
Now some of you might be saying ‘what about delegates?’. Delegates serve the same purpose, and Gail uses them heavily too. Delegates though force a specific protocol between the two classes, and generally is limited to a single delegate per object. Works great in a lot of places, but signals are a bit more flexible. Generally, I use delegates when an object would be interested in a lot of different events, providing data to the host object, or would be easier. Signals are a lot better for one off events where an object might only care about one or two, or not at all.
So one more thing : function pointers! You may have noticed when i connected the signal to my function, I used a weird function in between. Here’s the important part:
Gail::Core::functionPointer (this, &TextGroup::childTextNodeChangedColor)
Gail::Core::functionPointer() is a template meta function. Essentially what it does is it converts any given object/method pair into a common format that can be easily called. So why do we need such an object? First you need to remember how function pointers work.
So lets start with C function pointers. For all of these examples, i’m using the function:
double pow(double)
as an example function. In C, a function pointer is simply a pointer to a function. Its represented by the weird type ‘return (*name) (params)’ so for our example, its of type:
double (*p) (double)
Not the nicest format, but it gets the job done. The address is directly pointing to the function, so to use it, you would just do the following:
a = (*p)(20) OR a = p(20);
So to c++! With C++, a member function is relative to the object its a part of, and NOT an absolute address. So first, the type is encoded in the pointer type. Lets say our pow() is a member of the class Math. So the pointer type would be:
double (Math::*p) (double)
Now as i mentioned, this pointer is relative to an object. Which means to use it, you need an instance to call it on. So assuming with had Math* m; you could call it like:
a = m->*p (12)
Starting to get really really messy. Now notice that the type is encoded directly in the pointer. Which means we cant for example, use a ‘double (*) (double)’ there or even a ‘double (SomeOtherObject::*) (double)’ there. It has to be for Math, and Math only. Which considering the signaling i mentioned earlier has no class dependency, is a huge problem. Plus if you want to connect to a static function, you cant since thats a normal C function pointer, which isn’t compatible with C++ member pointers! So how do we get around this?
Theirs two major parts to this, Gail::Core::FunctionPointerX<> and Gail::Core::functionPointer (notice the case difference). The first is a class that can handle the differences between various pointer types, and puts them in a common format. So for our pow example, our FunctionPointer would be of type:
Gail::Core::FunctionPointer1<double, double> p;
This is true if its a global function, or if its a member function. In the case of a member function, it records the object tied to it, otherwise it remembers its a global function. FunctionPointer has a few helper methods on it, namely being able to run() it and compare two pointers. So to run our pointer, we just do:
a = p.run(20);
Nice and simple as it should be. Of course having to define the entire type of a function pointer every time is annoying (especially since under the hood it might use multiple classes such as GlobalFunctionPointer0 and MemberFunctionPointer0). So thats where Gail::Core::functionPointer comes in. This template function automatically determines the type of pointer given, and passes it on correctly to the FunctionPointer constructor, making any function pointer as simple as:
Gail::Core::functionPointer (NULL, &pow)
Once you can represent a function pointer easily, a signal then just takes a list of these. connect() adds to the list, disconnect() removes from the list, and emit() calls .run() on every function pointer of the list.
So an example of why this is awesome! Introducing, Gail::Core::InterpolationTimer! Basically, InterpolationTimer is a timer that calls a function over time for a given range. So for example, you want a smooth animation of an object moving from one place to another. Normally you’d need to listen every loop, figure out how much to move, move by that much, see if you finished, etc. With signals and InterpolationTimer though, it becomes really easy! From a current project:
// Interpolate current UI Gail::Core::InterpolationTimer<Gail::Core::Point2D>* timer = new Gail::Core::InterpolationTimer<Gail::Core::Point2D>; timer->setStartValue (position()); timer->setEndValue (newPosition); timer->signalSetValue().connect (Gail::Core::functionPointer (this, &UI::setPosition)); timer->setDuration (duration); timer->setAnimationCurve(animation); timer->signalFinished().connect (Gail::Core::FunctionPointer1<void, Gail::Core::InterpolationTimer<Gail::Core::Point2D>* >(this, &UI::enableUIInput)); timer->start(); timer->release();
Every frame, the timer will call UI::setPosition with the new position, smoothly animating. Once its finished, it’ll call enableUIInput. 9 lines of code, compared to the few hundred some of my previous animations took that did the same thing.
So thats essentially Gail’s event system (+ function pointers). Little long, but its a very simple concept that makes a lot of things easier!
–thothonegan
Thanks for the share!
Hellen
I suggest adding a facebook like button for the blog!
Strongly suggest adding a “google+” button for the blog!