Как я могу связать key_callback с экземпляром класса оболочки?

11

Я пытаюсь объединить мои вызовы GLFW3 в один класс:

class WindowManager {
private:
    GLFWwindow* window_;
    GLFWmonitor* monitor_;
    Keyboard* keyboard_;
...
}

И я пытаюсь настроить класс одноэлементной клавиатуры, который собирает нажатия клавиш во время выполнения. В GLFW я могу установить key_callbackфункцию, которая находится вне определения класса (свободная функция):

WindowManager::WindowManager() {
    ...
    glfwSetKeyCallback(window_, key_callback);
    ...
}

// not a class function
void key_callback(GLFWwindow* window, int key, int scan code, int action, int mods) {
    ...
}

Как я могу связать свой обратный вызов и мой WindowManagerэкземпляр, чтобы я мог установить keyboard_значения объекта? Я не могу сделать key_callbackфункцию-член из- WindowManagerза того, что она не будет работать, так как эта функция будет членом класса WindowManager, а в функции-члене C ++ класса их имена будут зависать.

Armenb
источник

Ответы:

11

У меня была похожая проблема с этим. Раздражает, что так мало документации по использованию glfwSetWindowUserPointer и glfGetWindowUserPointer. Вот мое решение вашей проблемы:

WindowManager::WindowManager() {
    // ...
    glfwSetUserPointer(window_, this);
    glfwSetKeyCallback(window_, key_callback_);
    // ...
}

void WindowManager::key_callback(GLFWwindow *window, int, int ,int, int) {
    WindowManager *windowManager =
      static_cast<WindowManager*>(glfwGetUserPointer(window));
    Keyboard *keyboard = windowManager->keyboard_;

    switch(key) {
        case GLFW_KEY_ESCAPE:
             keyboard->reconfigure();
             break;
     }
}

В любом случае, поскольку это один из лучших результатов использования GLFW с классами C ++, я также предоставлю свой метод инкапсуляции glfwWindow в класс C ++. Я думаю, что это самый элегантный способ сделать это, так как он избегает необходимости использовать globals, singletons или unique_ptrs, позволяет программисту манипулировать окном в гораздо более OO / C ++ - y стиле и позволяет создавать подклассы (за счет немного более загроможденный заголовочный файл).

// Window.hpp
#include <GLFW/glfw3.h>
class Window {
public:
    Window();
    auto ViewportDidResize(int w, int h)             -> void;
    // Make virtual you want to subclass so that windows have 
    // different contents. Another strategy is to split the
    // rendering calls into a renderer class.
    (virtual) auto RenderScene(void)                 -> void;
    (virtual) auto UpdateScene(double ms)            -> void;
    // etc for input, quitting
private:
    GLFWwindow *m_glfwWindow;

    // Here are our callbacks. I like making them inline so they don't take up
    // any of the cpp file
    inline static auto WindowResizeCallback(
        GLFWwindow *win,
        int w,
        int h) -> void {
            Window *window = static_cast<Window*>(glfwGetUserPointer(win));
            window->ViewportDidResize(w, h);
    }
    inline static auto WindowRefreshCallback(
        void) -> void {
            Window *window = static_cast<Window*>(glfwGetUserPointer(win));
            window->RenderScene(void);
    }
    // same for input, quitting
}

И для:

// Window.cpp
#include <GLFW/glfw3.h>
#include "Window.hpp"
Window::Window() {
    // initialise glfw and m_glfwWindow,
    // create openGL context, initialise any other c++ resources
    glfwInit();
    m_glfwWindow = glfwCreateWindow(800, 600, "GL", NULL, NULL);        

    // needed for glfwGetUserPointer to work
    glfwSetWindowUserPointer(m_glfwWindow, this);

    // set our static functions as callbacks
    glfwSetFramebufferSizeCallback(m_glfwWindow, WindowResizeCallback);
    glfwSetWindowRefreshCallback(m_glfwWindow, WindowRefreshCallback);
}

// Standard window methods are called for each window
auto
Window::ViewportDidResize(int w, int h) -> void
{
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glMatrixMode(GL_MODELVIEW);
}

Вероятно, это может быть довольно легко интегрировано с классом WindowManager / InputManager, но я думаю, что проще просто управлять каждым окном самостоятельно.

burtonageo
источник
Я вернулся через несколько лет и увидел обновленный ответ. Действительно хороший спасибо
АрменБ
В статических функциях вы создаете новый экземпляр класса (т.е. Window *window ). Как это решить проблему?
CroCo
Я заметил, что ответ изменился, чтобы поддержать некоторые новые функции C ++. Есть ли какая-то польза от установки типа возвращаемого значения функции auto, а затем подсказки типа с помощью -> void?
АрменБ
5

Как вы выяснили, обратные вызовы должны быть свободными или статическими функциями. Обратные вызовы принимают в GLFWwindow*качестве первого аргумента вместо автоматического thisуказателя.

С GLFW вы можете использовать glwSetWindowUserPointerи glfwGetWindowUserPointerдля хранения и извлечения ссылки WindowManagerна Windowэкземпляр для каждого окна .

Помните, что GLFW не использует виртуальные функции любого вида прямого полиморфизма, поскольку это чистый C API. Такие API всегда предполагают свободные функции (C не имеет классов или функций-членов вообще, виртуальные или иные) и передают явные «экземпляры объекта» в качестве параметров (обычно в качестве первого параметра; C не имеет this). Хорошие API C также включают в себя функциональность указателя пользователя (иногда называемую «пользовательскими данными»), поэтому вам не нужно использовать глобальные переменные.

старый ответ:

Если вам нужен доступ к другим данным в вашей WindowManager(или других системах), вам может потребоваться, чтобы они были глобально доступны, если вы хотите получить доступ к ним из обратных вызовов. Например, имейте глобал, std::unique_ptr<Engine>который вы можете использовать для доступа к своему оконному менеджеру, или просто сделайте глобал std::unique_ptr<WindowManager>(замените std::unique_ptrчто-нибудь «лучше для одиночных игр», если хотите).

Если вам нужна поддержка нескольких окон, вы также должны будете WindowManagerсодержать некоторую структуру данных для сопоставления GLFWwindow*' values to your ownWindow classes in some way, e.g. using astd :: unordered_map or the like. Your callback could then access the global and query the datastructure using theGLFWwindow *, которую они получили, для поиска нужных им данных.

Шон Миддледич
источник
Спасибо за помощь. В таком сценарии, как это обычно обрабатывается (используя глобальный unique_ptr для отслеживания ввода с клавиатуры)? Я хотел избежать любых глобальных переменных, подобных этой, и предпочел передавать константные указатели клавиатуры тому, кому это нужно, но, похоже, это невозможно, я прав?
АрменБ
1
Обычно это не unique_ptr, но использование синглтона не редкость. GLFW также имеет установленную функцию пользовательских данных для окон, которая может избежать необходимости в глобальном. Большинство "хороших" C API имеют нечто подобное. Могу обновить ответ, чтобы предложить, когда я вернусь к реальному компьютеру.
Шон Мидлдич