Анимации на лямбдах в C++11
Компании-разработчики, как правило, не особо спешат переходить на новый Си++. Главным образом из-за поддержки его компиляторами, а точнее ее полного или частичного отсутствия. Недавно я решил узнать, что же есть новенького в плане поддержки C++11 компилятором GCC, и понял, что пора начинать. Благо, у нас в Ivideon лояльно относятся к новым технологиям и дают пробовать что-то новое.Начал, конечно же, с самого вкусного — с лямбда-выражений! И с потоков.
Хочу представить читателю небольшую зарисовку на тему того, как лямбды могут помочь сделать код более читаемым и лаконичным. Такой код можно использовать всюду, где надо вызывать периодически какую-нибудь функцию, при этом передавая ей время, прошедшее с предыдущего вызова — например, в анимациях.
Заголовок:
#ifndef ANIMATION_ENGINE_H #define ANIMATION_ENGINE_H
#include
namespace animation {
// Actor function has one parameter: time delta since previous frame
// It should return true if animation has finished,
// or false if it should go on
typedef std: function
// Handler is called after the animation is complete
typedef std: function
const handler_func doNothing = [] {};
void start (unsigned intervalMs, actor_func, handler_func = doNothing);
} // end of namespace animation
#endif // ANIMATION_ENGINE_H Интерфейс простой: в start () мы передаём интервал вызовов, функцию и необязательный обработчик завершения.
И реализация:
#include «animation_engine.h»
#include
namespace animation {
void start (unsigned intervalMs, actor_func actor, handler_func handler)
{
std: thread t ([=]() {
auto timeStart = std: chrono: system_clock: now ();
float lastInterval = 0;
while (1) {
std: this_thread: sleep_for (std: chrono: milliseconds (intervalMs));
float timeDelta = std: chrono: duration_cast
} // end of namespace animation Вот здесь уже интереснее: на основе функций, которые передали аргументами, и интервала, строится новая функция (замыкание), куда переданные аргументы копируются ([=] означает захват по значению всех доступных переменных). Дальше эта новая функция запускается в отдельном потоке, где она в бесконечном цикле гоняет нашу несчастную функцию actor, покуда она не вернет true. Ну, или приложение не закроется. Далее start () завершается, оставляя анимацию крутиться в своём потоке.
Ниже пример использования. Допустим, мы пишем игрушку наподобие пинг-понга или арканоида, и нам нужно привести в действие шарик. Вот как можно это реализовать:
animation: start (30, [=] (float dt) { this→adjustBallDirection (dt); // bouncing, maybe gravitation this→updateBallPosition (dt); return (this→ballMissed ()) // if player missed the ball → stop }, [this] { resetBall (); } ); Компилировать это всё, как и водится, надо с ключом -std=c++0x или -std=c++0x. Кроме того, на этапе линковки нужно передать ключ -pthread. Если вы, как и я, работаете с QMake, просто добавьте эти две строчки в .pro-файл:
CONFIG += c++11 QMAKE_LFLAGS += -pthread Возможное улучшение №1: для реальных задач неплохо бы улучшить расчет интервала между вызовами. Для этого следует учитывать время, пока отрабатывает функция-актор. А то сейчас выходит, что интервал становится чуть больше. Но я не стал заморачиваться на этот раз с этим: просто не об этом заметка.
Возможное улучшение №2: можно ввести дополнительный функционал, который бы позволил прерывать анимации. Сделать это несложно (да хотя бы введением флага, который бы все потоки с анимациями проверяли при работе), но, опять же, код примера распух бы от этого.
Вполне вероятно, что вы знаете более лаконичный способ решения подобной задачи — будет круто, если вы изложите его в комментариях. Или вы знаете более интересный кейс, где лямбды упростили бы жизнь… в общем, вы знаете, что делать.
Спасибо за внимание!
