Анимации на лямбдах в C++11

d122f54cce1487eef2b553cf5671f8c6.pngКомпании-разработчики, как правило, не особо спешат переходить на новый Си++. Главным образом из-за поддержки его компиляторами, а точнее ее полного или частичного отсутствия. Недавно я решил узнать, что же есть новенького в плане поддержки 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 actor_func;

// Handler is called after the animation is complete typedef std: function handler_func;

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 (std: chrono: system_clock: now () — timeStart).count (); if (actor (timeDelta — lastInterval)) { handler (); break; } lastInterval = timeDelta; } }); t.detach (); }

} // 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: можно ввести дополнительный функционал, который бы позволил прерывать анимации. Сделать это несложно (да хотя бы введением флага, который бы все потоки с анимациями проверяли при работе), но, опять же, код примера распух бы от этого.

Вполне вероятно, что вы знаете более лаконичный способ решения подобной задачи — будет круто, если вы изложите его в комментариях. Или вы знаете более интересный кейс, где лямбды упростили бы жизнь… в общем, вы знаете, что делать.

Спасибо за внимание!

© Habrahabr.ru