Saturday, October 12, 2013

Progress and std::function<>

Here's another update, against all odds (that are against blogging).

Can a coder sleep at night and wake up in the morning ?
I think so ! I recently set myself to do that, and on top of it, I'm also running about 2 km each morning. Not much, but infinitely more than nothing 8)

For the past 3 years, since I started my independent development activity, I've been postponing physical activity. I relegated that to something that I'd do eventually, once I'd reach some sort of economic stability. But that clearly takes more time than one would hope, so something had to give.

On the development side, there are really no new games, but that's pretty much how it's supposed to be, given the situation with mobile. Meaning that while released games haven't be making me and my partners rich, they are still an important source of income.
It's important to keep the old games up to date and try to make any possible gains off things such as free-with-ads editions, adding new features to keep new users coming or for cross promotions with other companies (e.g. add support for a certain gamepad).
It's also a good way to push forward the development of the common libraries, and to generally learn by refactoring. In short, updating current games, makes it simpler to eventually work on new ones, when the day will come..

Another thing to consider is that the games that we make tend to stand out. So, in the mobile market they are sort of timeless, although economically they are mediocre, but there is some degree of stability and when it comes to income, any sort of stability is a win.

Talking about development, I finally started diving into C++11. Some of the features are really useful, they definitely help writing better code.
The most important feature to me in C++11 is the lambdas, or generally the concept of anonymous functions and the ability to wrap a whole context in std::function<> (see this article for an introduction).
Lambdas are a great way to improve loops without performance penalty. Capturing code with std::function<> and using it a different context instead does add some extra work (and possibly heap allocation), but it's extremely valuable to handle all that kind of code that needs to be deferred.
Basically, C++ finally has callbacks !
It was possible to do callbacks in C++ before, but it was painful, it required extra code, type casting and so it just wasn't practical enough.
With callbacks now, component-based objects are easier to make.. gone is that OOP madness that wanted to push polymorphism at every cost. Polymorphism is often confusing and it's not very flexible either.

Callbacks do have their drawbacks (ah !) however.
For example this code (only illustrative, not tested):

std::function<void()> makeFunPrint( int a )
    // BAD ! Should use [=] or [a]
    return [&](){print( "%i\n", a ); };

void main()
    auto funPrint2 = makeFunPrint( 2 );

..will compile fine, but it probably won't print "2". Because "a" is captured by reference, and basically when the function is called, it will look for that place in memory, which is somewhere on the stack where "a" used to be.. ouch !

So, it's definitely important to be careful with that syntax of closures. But to me that's not the trickiest part. What's more tricky is the general concept of callbacks, which can very easily break the synchronization of logic in the code.
It's not nearly as bad as multithreading, but the concept is similar and there are dangers that can be overlooked.

One simple example is that of a gamepad handler: when callbacks are reacting to gamepad buttons' changes, a game may find itself with different states for the same buttons within the same frame of animation, depending on how things are structured and when events are triggered.
It's something that can be prevented, but situations like these can be overlooked, and they can definitely bite back.
So, callbacks are great, but it's also important to be aware of the fact that they can easily break determinism.. and that's a huge price to pay.

What I ended up doing for the controller and touch input messages, is to cache them in a list of events, and flush that list only at the beginning of each animation frame of the main loop, effectively deferring all user input at a fixed location in the code and in the main flow.

All in all, C++11 lambdas are a great addition. They can improve quality of code in loops without performance penalty and, when captured with std::function<>, can greatly improve the experience of writing higher level code... but there are pitfalls, and it's better to be prepared.


No comments:

Post a Comment