¿Cómo puedo asociar un key_callback con una instancia de clase wrapper?

11

Estoy tratando de ajustar mis llamadas GLFW3 en una sola clase:

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

Y estoy tratando de configurar una clase de teclado singleton que recolecta las pulsaciones de teclas durante la ejecución. En GLFW puedo establecer una key_callbackfunción que está fuera de la definición de clase (una función libre):

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

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

¿Cómo puedo asociar mi devolución de llamada y mi WindowManagerinstancia para poder establecer los keyboard_valores de los objetos? No puedo hacer que la key_callbackfunción un miembro sea WindowManagerporque eso no funcionaría ya que esa función sería un miembro de la clase WindowManager y en la función miembro de C ++ de una clase se cuelgan sus nombres.

ArmenB
fuente

Respuestas:

11

Tuve un problema similar a esto. Es molesto que haya tan poca documentación sobre el uso de glfwSetWindowUserPointer y glfGetWindowUserPointer. Aquí está mi solución a su 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 todos modos, como este es uno de los mejores resultados para usar GLFW con clases de C ++, también proporcionaré mi método de encapsular un glfwWindow en una clase de C ++. Creo que esta es la forma más elegante de hacerlo, ya que evita tener que usar globals, singletons o unique_ptrs, permite que el programador manipule la ventana en un estilo mucho más OO / C ++ y permite subclases (a costa de un archivo de encabezado un poco más desordenado).

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

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

Probablemente esto se pueda integrar fácilmente con una clase WindowManager / InputManager, pero creo que es más fácil hacer que cada ventana se administre sola.

burtonageo
fuente
Regresé después de varios años y vi la respuesta actualizada. Muy bueno, gracias
ArmenB
En funciones estáticas, está creando una nueva instancia de una clase (es decir Window *window ). ¿Cómo resuelve esto el problema?
CroCo
Noté que la respuesta ha cambiado para admitir algunas características nuevas de C ++. ¿Hay algún beneficio de configurar el tipo de retorno de función en auto y luego escribir sugerencias usando -> void?
ArmenB
5

Las devoluciones de llamada deben ser funciones libres o funciones estáticas, como ha descubierto. Las devoluciones de llamada toman a GLFWwindow*como su primer argumento en lugar de un thispuntero automático .

Con GLFW puede usar glwSetWindowUserPointery glfwGetWindowUserPointeralmacenar y recuperar una referencia WindowManagero una Windowinstancia por ventana .

Recuerde que GLFW no utiliza funciones virtuales de ningún tipo de polimorfismo directo, ya que es una API C pura. Dichas API siempre asumen funciones libres (C no tiene clases ni funciones miembro, virtuales o de otro tipo) y pasan "instancias de objeto" explícitas como parámetros (generalmente como el primer parámetro; C no tiene this). Las buenas API de C también incluyen la funcionalidad de puntero de usuario (a veces llamada "datos de usuario", entre otras cosas) para que no tenga que usar globales.

vieja respuesta:

Si necesita acceder a otros datos en su WindowManager(u otros sistemas), es posible que deba tener acceso global a ellos si desea acceder a ellos desde devoluciones de llamada. Por ejemplo, tenga un global std::unique_ptr<Engine>que pueda usar para acceder a su administrador de ventanas, o simplemente haga un global std::unique_ptr<WindowManager>(reemplace std::unique_ptrcon algo "mejor para singletons" si lo desea).

Si desea compatibilidad con múltiples ventanas, también deberá WindowManagercontener alguna estructura de datos para asignar GLFWwindow*' values to your ownWindow classes in some way, e.g. using astd :: or the like. Your callback could then access the global and query the datastructure using theunordered_map GLFWwindow * `que recibieron para buscar los datos que necesitan.

Sean Middleditch
fuente
Gracias por tu ayuda. En un escenario como este, ¿es así como se maneja normalmente (usando un unique_ptr global para realizar un seguimiento de las entradas del teclado)? Quería evitar cualquier variable global como esta y preferí pasar los punteros constantes del teclado a quien lo necesite, pero parece que esto no es posible, ¿estoy en lo cierto?
ArmenB
1
No suele ser un unique_ptr, pero no es raro usar un singleton. GLFW también tiene una función de datos de usuario establecida para Windows que puede evitar la necesidad de un global. La mayoría de las "buenas" API de C tienen algo similar. Podría actualizar la respuesta para sugerir que cuando regrese a una computadora real.
Sean Middleditch