Como associar um key_callback a uma instância da classe wrapper?

11

Estou tentando agrupar minhas chamadas GLFW3 em uma única classe:

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

E estou tentando configurar uma classe de teclado único que coleta as teclas pressionadas durante a execução. No GLFW, posso definir uma key_callbackfunção que está fora da definição de classe (uma função livre):

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

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

Como posso associar meu retorno de chamada e minha WindowManagerinstância para que eu possa definir os keyboard_valores do objeto? Não consigo fazer a key_callbackfunção membro WindowManagerporque isso não funcionaria, pois essa função seria membro da classe WindowManager e, na função membro C ++ de uma classe, os nomes seriam alterados.

ArmenB
fonte

Respostas:

11

Eu tive um problema semelhante a isso. É irritante que haja tão pouca documentação sobre o uso de glfwSetWindowUserPointer e glfGetWindowUserPointer. Aqui está a minha solução para o seu problema:

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;
     }
}

De qualquer forma, como esse é um dos principais resultados do uso do GLFW com classes C ++, também fornecerei meu método de encapsular um glfwWindow em uma classe C ++. Eu acho que essa é a maneira mais elegante de fazer isso, pois evita o uso de globais, singletons ou unique_ptrs, permite que o programador manipule a janela em um estilo muito mais OO / C ++ - y e permite subclassificação (ao custo de um arquivo de cabeçalho um pouco mais confuso).

// 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
}

E para:

// 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);
}

Provavelmente, isso pode ser facilmente integrado a uma classe WindowManager / InputManager, mas acho que é mais fácil fazer com que cada janela se gerencie.

Burtonageo
fonte
Voltei depois de vários anos e vi a resposta atualizada. Realmente bom obrigado
ArmenB
Nas funções estáticas, você está criando uma nova instância de uma classe (ou seja Window *window ). Como isso resolve o problema?
CroCo 4/12
Notei que a resposta mudou para oferecer suporte a alguns novos recursos do C ++. Existe algum benefício em definir o tipo de retorno da função como automático e digite dicas usando -> void?
precisa saber é o seguinte
5

Os retornos de chamada devem ser funções livres ou estáticas, como você descobriu. Os retornos de chamada tomam GLFWwindow*como o primeiro argumento no lugar de um thisponteiro automático .

Com o GLFW, você pode usar glwSetWindowUserPointere glfwGetWindowUserPointerarmazenar e recuperar uma referência WindowManagerou uma Windowinstância por janela .

Lembre-se de que o GLFW não usa funções virtuais como polimorfismo direto, pois é uma API C pura. Essas APIs sempre assumem funções livres (C não possui classes ou funções membro, virtuais ou não) e passam "instâncias de objetos" explícitas como parâmetros (geralmente como o primeiro parâmetro; C não possui this). As boas APIs C também incluem a funcionalidade de ponteiro do usuário (às vezes chamada de "dados do usuário" entre outras coisas), para que você não precise usar globais.

resposta antiga:

Se você precisar acessar outros dados em seu WindowManager(ou em outros sistemas), pode ser necessário que eles estejam acessíveis globalmente, se desejar acessá-los a partir de retornos de chamada. Por exemplo, tenha um global std::unique_ptr<Engine>que você possa usar para acessar seu gerenciador de janelas ou apenas faça um global std::unique_ptr<WindowManager>(substitua std::unique_ptrpor algo "melhor para singletons", se desejar).

Se você deseja suporte a várias janelas, também terá WindowManageruma estrutura de dados para mapear o 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 * `que eles receberam para procurar os dados de que precisam.

Sean Middleditch
fonte
Obrigado pela ajuda. Em um cenário como este, é assim que é normalmente tratado (usando um unique_ptr global para acompanhar as entradas do teclado)? Eu queria evitar quaisquer variáveis ​​globais como essa e preferi repassar const ponteiros do teclado para quem precisar, mas parece que isso não é possível, estou certo?
precisa saber é o seguinte
1
Geralmente não é um unique_ptr, mas não é incomum usar um singleton. O GLFW também possui uma função de dados do usuário definida para janelas que pode evitar a necessidade de um global. A maioria das APIs C "boas" tem algo semelhante. Pode atualizar a resposta para sugerir isso quando eu voltar a um computador real.
Sean Middleditch