Comment puis-je associer un key_callback à une instance de classe wrapper?

11

J'essaie d'encapsuler mes appels GLFW3 dans une seule classe:

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

Et j'essaie de configurer une classe de clavier singleton qui collecte les touches enfoncées pendant l'exécution. Dans GLFW, je peux définir une key_callbackfonction qui est en dehors de la définition de classe (une fonction 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) {
    ...
}

Comment puis-je associer mon rappel et mon WindowManagerinstance afin de pouvoir définir les keyboard_valeurs des objets? Je ne peux pas rendre la key_callbackfonction membre de WindowManagercar cela ne fonctionnerait pas car cette fonction serait membre de la classe WindowManager et dans la fonction membre C ++ d'une classe, leurs noms seraient suspendus.

ArmenB
la source

Réponses:

11

J'ai eu un problème similaire à cela. Il est ennuyeux qu'il y ait si peu de documentation sur l'utilisation de glfwSetWindowUserPointer et glfGetWindowUserPointer. Voici ma solution à votre problème:

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

Quoi qu'il en soit, comme c'est l'un des meilleurs résultats pour l'utilisation de GLFW avec des classes C ++, je vais également fournir ma méthode d'encapsulation d'un glfwWindow dans une classe C ++. Je pense que c'est la façon la plus élégante de le faire, car cela évite d'avoir à utiliser des globaux, des singletons ou des uniques_ptrs, permet au programmeur de manipuler la fenêtre dans un style beaucoup plus OO / C ++ - y, et autorise le sous-classement (au prix de un fichier d'en-tête légèrement plus encombré).

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

Et pour:

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

Cela peut probablement être assez facilement intégré à une classe WindowManager / InputManager, mais je pense qu'il est plus facile de faire gérer chaque fenêtre par elle-même.

burtonageo
la source
Je suis revenu après plusieurs années et j'ai vu la réponse mise à jour. Vraiment bien merci
ArmenB
Dans les fonctions statiques, vous créez une nouvelle instance d'une classe (ie Window *window ). Comment cela résout-il le problème?
CroCo
J'ai remarqué que la réponse a changé pour prendre en charge de nouvelles fonctionnalités C ++. Y a-t-il un avantage à définir le type de retour de la fonction sur auto, puis à taper l'indice à l'aide de -> void?
ArmenB
5

Les rappels doivent être des fonctions libres ou des fonctions statiques, comme vous l'avez découvert. Les rappels prennent un GLFWwindow*comme premier argument à la place d'un thispointeur automatique .

Avec GLFW, vous pouvez utiliser glwSetWindowUserPointeret glfwGetWindowUserPointerpour stocker et récupérer une référence à WindowManagerou une Windowinstance par fenêtre .

N'oubliez pas que GLFW n'utilise pas de fonctions virtuelles de polymorphisme direct car c'est une pure API C. Ces API supposent toujours des fonctions libres (C n'a aucune classe ou fonction membre du tout, virtuelle ou autre) et transmettent des "instances d'objet" explicites en tant que paramètres (généralement comme premier paramètre; C n'en a pas this). Les bonnes API C incluent également la fonctionnalité de pointeur utilisateur (parfois appelée «données utilisateur», entre autres), vous n'avez donc pas à utiliser de globaux.

ancienne réponse:

Si vous devez accéder à d'autres données de votre WindowManager(ou d'autres systèmes), vous devrez peut-être les rendre accessibles globalement si vous souhaitez y accéder à partir de rappels. Par exemple, ayez un global std::unique_ptr<Engine>que vous pouvez utiliser pour accéder à votre gestionnaire de fenêtres, ou créez simplement un global std::unique_ptr<WindowManager>(remplacez-le std::unique_ptrpar quelque chose de "meilleur pour les singletons" si vous le souhaitez).

Si vous souhaitez prendre en charge plusieurs fenêtres, vous devrez également WindowManagercontenir une structure de données pour mapper la GLFWwindow*' values to your ownfenêtre 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 * `qu'ils ont reçue pour rechercher les données dont ils ont besoin.

Sean Middleditch
la source
Merci de votre aide. Dans un scénario comme celui-ci, est-ce normalement ainsi (en utilisant un global unique_ptr pour garder une trace des entrées clavier)? Je voulais éviter toutes les variables globales comme celle-ci et j'ai préféré passer les pointeurs const du clavier à qui il en avait besoin, mais il semble que ce ne soit pas possible, ai-je raison?
ArmenB
1
Ce n'est généralement pas un unique_ptr, mais il n'est pas rare d'utiliser un singleton. GLFW a également une fonction de définition de données utilisateur pour les fenêtres qui peut éviter le besoin d'un global. La plupart des «bonnes» API C ont quelque chose de similaire. Pourrait mettre à jour la réponse pour suggérer que lorsque je reviens à un vrai ordinateur.
Sean Middleditch