Come posso associare un key_callback a un'istanza della classe wrapper?


11

Sto cercando di racchiudere le mie chiamate GLFW3 in una singola classe:

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

E sto cercando di impostare una classe di tastiera singleton che raccoglie i tasti premuti durante l'esecuzione. In GLFW posso impostare una key_callbackfunzione che è al di fuori della definizione di classe (una funzione gratuita):

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

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

Come posso associare il mio callback e la mia WindowManageristanza in modo da poter impostare i keyboard_valori dell'oggetto? Non riesco a rendere la key_callbackfunzione membro a WindowManagerperché non funzionerebbe poiché quella funzione sarebbe un membro della classe WindowManager e nella funzione membro C ++ di una classe i loro nomi sarebbero stati appesi.

Risposte:


11

Ho avuto un problema simile a questo. È fastidioso che ci sia così poca documentazione sull'uso di glfwSetWindowUserPointer e glfGetWindowUserPointer. Ecco la mia soluzione al tuo 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;
     }
}

Comunque, poiché questo è uno dei migliori risultati per l'uso di GLFW con classi C ++, fornirò anche il mio metodo di incapsulamento di una finestra glfw in una classe C ++. Penso che questo sia il modo più elegante per farlo, poiché evita di usare globali, singleton o unique_ptrs, consente al programmatore di manipolare la finestra in uno stile molto più OO / C ++ - y e consente la sottoclasse (a costo di un file di intestazione leggermente più disordinato).

// 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 per:

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

Probabilmente questo può essere facilmente integrato con una classe WindowManager / InputManager, ma penso che sia più semplice gestire ciascuna finestra da sola.


Sono tornato dopo diversi anni e ho visto la risposta aggiornata. Davvero buono grazie
ArmenB,

Nelle funzioni statiche, stai creando una nuova istanza di una classe (ad es Window *window .). Come questo risolve il problema?
CroCo,

Ho notato che la risposta è cambiata per supportare alcune nuove funzionalità di C ++. C'è qualche vantaggio nell'impostare il tipo di ritorno della funzione su auto, e quindi digitare il suggerimento usando -> void?
ArmenB,

5

I callback devono essere funzioni libere o statiche, come hai scoperto. I callback prendono GLFWwindow*come primo argomento al posto di un thispuntatore automatico .

Con GLFW è possibile utilizzare glwSetWindowUserPointere glfwGetWindowUserPointerarchiviare e recuperare un riferimento WindowManagero Windowun'istanza per finestra .

Ricorda che GLFW non utilizza funzioni virtuali alcun tipo di polimorfismo diretto in quanto è un'API C pura. Tali API assumono sempre funzioni libere (C non ha affatto classi o funzioni membro, virtuali o di altro tipo) e passano esplicite "istanze di oggetto" come parametri (di solito come primo parametro; C non ha this). Le buone API C includono anche la funzionalità del puntatore utente (a volte chiamato "dati utente" tra le altre cose), quindi non è necessario utilizzare i globuli.

vecchia risposta:

Se è necessario accedere ad altri dati nel proprio WindowManager(o in altri sistemi), potrebbe essere necessario averli accessibili a livello globale se si desidera accedervi dai callback. Ad esempio, disporre di un globale std::unique_ptr<Engine>che è possibile utilizzare per accedere al proprio gestore di finestre o semplicemente crearne uno globale std::unique_ptr<WindowManager>( std::unique_ptrse si desidera sostituirlo con qualcosa di "migliore per i singoli").

Se vuoi il supporto di più finestre, avrai anche bisogno di WindowManagercontenere una struttura di dati per mappare 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 * `che hanno ricevuto per cercare i dati di cui hanno bisogno.


Grazie per l'aiuto. In uno scenario come questo, è così che viene normalmente gestito (usando un unico global_ptr per tenere traccia degli input da tastiera)? Volevo evitare qualsiasi variabile globale come questa e preferivo passare intorno ai puntatori const della tastiera a chiunque ne avesse bisogno ma sembra che questo non sia possibile, giusto?
ArmenB,

1
Di solito non è unique_ptr, ma non è raro usare un singleton. GLFW ha anche una funzione dati utente impostata per Windows che può evitare la necessità di un globale. La maggior parte delle API C "buone" hanno qualcosa di simile. Potrebbe aggiornare la risposta per suggerire che quando torno a un computer reale.
Sean Middleditch,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.