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

в 21:13, , рубрики: c++, c++11, lambda expressions, Блог компании Ivideon, замыкания, функциональное программирование

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

Компании-разработчики, как правило, не особо спешат переходить на новый Си++. Главным образом из-за поддержки его компиляторами, а точнее ее полного или частичного отсутствия. Недавно я решил узнать, что же есть новенького в плане поддержки C++11 компилятором GCC, и понял, что пора начинать. Благо, у нас в Ivideon лояльно относятся к новым технологиям и дают пробовать что-то новое.
Начал, конечно же, с самого вкусного — с лямбда-выражений! И с потоков.

Хочу представить читателю небольшую зарисовку на тему того, как лямбды могут помочь сделать код более читаемым и лаконичным. Такой код можно использовать всюду, где надо вызывать периодически какую-нибудь функцию, при этом передавая ей время, прошедшее с предыдущего вызова — например, в анимациях.

Заголовок:

#ifndef ANIMATION_ENGINE_H
#define ANIMATION_ENGINE_H

#include <functional>

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<bool(float)> actor_func;

// Handler is called after the animation is complete
typedef std::function<void(void)> 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 <thread>

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

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

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

Автор: vsabadazh

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js