Как спроектировать цикл рендеринга окна? - PullRequest
0 голосов
/ 04 февраля 2019

Я пытаюсь сконструировать объект окна, работа которого заключается в том, чтобы обрабатывать все функциональные возможности окна GLFW (инициализация, обратные вызовы, обработка ввода ...)

Одна из наиболее важных вещей - это цикл рендеринга,Самый наивный дизайн, который я могу себе представить, это иметь метод renderloop, который бы брал указатель на функцию без аргументов и затем вызывал его внутри метода цикла, как показано ниже:

class Window
{
public:
    Window();
    ~Window();

    void WindowLoop(void (*f) (void));
protected:
    GLFWwindow* window;
};


void Window::WindowLoop(void (*f) (void))
{
    while(!glfwWindowShouldClose(window))
    {
        f();
        glfwPollEvents();
    }
}

Это, однако, накладывает многоограничения.С одной стороны, это означает, что функция не может принимать никаких аргументов.Что может или не может быть проблемой.

Я провел некоторое исследование, и, очевидно, вы можете передавать указатели на функции, которые принимают произвольное число параметров, но это кажется трудным и не рекомендуется.

Другим вариантом может быть общий функтор, а затем параметры можно определить как часть класса / структуры, избегая необходимости обрабатывать это.

Возможно, есть другие потенциальные конструкции, о которых я не знаю, какхорошо.

Что было бы хорошим дизайном в C ++ для цикла рендеринга, пытаясь определить приоритеты первой универсальности использования, а второй - скорости выполнения?

1 Ответ

0 голосов
/ 04 февраля 2019

Мой короткий ответ был бы:

Использовать std::function вместо необработанного указателя функции.Это дает вам гораздо большую гибкость, так как может содержать:

  • указатели на функции
  • указатели на метод (с объектом, конечно)
  • функторы
  • лямбды (с перехватами или без них, которые фактически повторяют одно или другое выше).

Вы по-прежнему ограничены определением подписи для вызова, но вы можете указать свой контекст обратного вызова, который, вероятно, является всем, чем вынужно.

Итак, вот как это могло бы выглядеть:

#include <functional>

class Window
{
public:
    Window();
    ~Window();

    void WindowLoop(std::function<void()> f);
protected:
    GLFWwindow* window;
};


void Window::WindowLoop(std::function<void()> f)
{
    while(!glfwWindowShouldClose(window))
    {
        f();
        glfwPollEvents();
    }
}

(выглядит не сильно отличающимся от оригинального образца OP.)


Мышлениедважды я нашел, что стоит упомянуть наборы виджетов и какие решения они предоставляют (как решение одной и той же проблемы).

Два основных решения - это

Сигнал, по сути, является не чем иным, как контейнером с указателями на функции (или std::function или что-то сопоставимое),Сигнал испускается (т.е. вызываются указатели сохраненных функций) в определенных ситуациях.Таким образом, другие объекты могут получать уведомления в этих ситуациях (путем регистрации своего обработчика сигнала в сигнале).Таким образом, сигнал фактически аналогичен приведенному выше, за исключением того, что указатель (и) функции не предоставляются временно, а хранятся в переменных-членах.

Альтернативой является вызов метода virtual в определенных ситуациях.Чтобы добавить пользовательское поведение, соотв.базовый класс должен быть получен, а методы virtual в квесте должны быть переопределены.

В случае OP это может выглядеть так:

class Window
{
public:
    Window();
    ~Window();

    void WindowLoop();
protected:
    virtual void step();
protected:
    GLFWwindow* window;
};


void Window::WindowLoop()
{
    while(!glfwWindowShouldClose(window))
    {
        step();
        glfwPollEvents();
    }
}

void Window::step() { /* empty placeholder */ }

Чтобы использовать это в приложениипроизводный класс Window является обязательным:

class GameWindow: public Window {
    protected:
        virtual void step() override;
};

void GameWindow::step()
{
    // Do the game step stuff (e.g. rendering)
    // where this (of type GameWindow) can provide the necessary context.
}

Относительно Qt , существуют сигналы для различных ситуаций и virtual методы, например, для обработчиков событий в виджетах.В основном есть выбор либо / или - я не могу вспомнить, что оба доступны для чего-то.Например, для QPushButton::clicked() может быть зарегистрирован обработчик сигнала, но чтобы настроить обработчики событий как mousePressEvent(), необходимо перегрузить виджет Qt, чтобы переопределить метод обработчика событий.(Существует также концепция фильтров событий, но, по-моему, это не совсем то же самое.) Вместо этого

gtkmm (по крайней мере, в версии 2, которую я использовал в прошлом) предоставлено все, что я могупомните virtual методы и сигналы.Таким образом, всегда был выбор: извлечь виджет gtkmm или изменить / расширить поведение виджета gtkmm, просто зарегистрировав обработчик сигнала.Это может привести к небольшим дополнительным затратам на производительность, но это было очень удобно для программирования приложений.

...