Wie kann ich einer Wrapper-Klasseninstanz einen key_callback zuordnen?

11

Ich versuche, meine GLFW3-Aufrufe in eine einzelne Klasse zu packen:

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

Und ich versuche, eine Singleton-Tastaturklasse einzurichten, die die Tastendrücke während der Ausführung sammelt. In GLFW kann ich eine key_callbackFunktion festlegen , die außerhalb der Klassendefinition liegt (eine freie Funktion):

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

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

Wie kann ich meinen Rückruf und meine WindowManagerInstanz verknüpfen , um die keyboard_Objektwerte festzulegen? Ich kann die key_callbacka-Member-Funktion WindowManagernicht festlegen, da dies nicht funktionieren würde, da diese Funktion ein Mitglied der WindowManager-Klasse wäre und in der C ++ - Member-Funktion einer Klasse ihre Namen baumeln würden.

ArmenB
quelle

Antworten:

10

Ich hatte ein ähnliches Problem. Es ist ärgerlich, dass es so wenig Dokumentation zur Verwendung von glfwSetWindowUserPointer und glfGetWindowUserPointer gibt. Hier ist meine Lösung für Ihr Problem:

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

Da dies eines der besten Ergebnisse für die Verwendung von GLFW mit C ++ - Klassen ist, werde ich auch meine Methode zum Einkapseln eines glfwWindow in eine C ++ - Klasse bereitstellen. Ich denke, dass dies die eleganteste Art ist, dies zu tun, da keine Globals, Singletons oder unique_ptrs verwendet werden müssen, der Programmierer das Fenster in einem viel OO / C ++ - y-Stil manipulieren kann und Unterklassen zulässt (auf Kosten von eine etwas überladenere Header-Datei).

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

Und für:

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

Dies kann wahrscheinlich recht einfach in eine WindowManager / InputManager-Klasse integriert werden, aber ich denke, es ist einfacher, jedes Fenster selbst verwalten zu lassen.

Burtontono
quelle
Ich kam nach einigen Jahren zurück und sah die aktualisierte Antwort. Wirklich gut, danke
ArmenB
In statischen Funktionen erstellen Sie eine neue Instanz einer Klasse (dh Window *window ). Wie löst dies das Problem?
CroCo
Ich habe festgestellt, dass sich die Antwort geändert hat, um einige neue C ++ - Funktionen zu unterstützen. Gibt es einen Vorteil, wenn Sie den Funktionsrückgabetyp auf auto setzen und dann mit -> void einen Hinweis eingeben?
ArmenB
5

Die Rückrufe müssen freie oder statische Funktionen sein, wie Sie herausgefunden haben. Die Rückrufe verwenden a GLFWwindow*als erstes Argument anstelle eines automatischen thisZeigers.

Mit GLFW können Sie einen Verweis auf oder eine Instanz pro Fenster verwenden glwSetWindowUserPointerund glfwGetWindowUserPointerspeichern und abrufen .WindowManagerWindow

Denken Sie daran, dass GLFW keine direkten Funktionen für virtuelle Funktionen verwendet, da es sich um eine reine C-API handelt. Solche APIs übernehmen immer freie Funktionen (C hat überhaupt keine Klassen oder Elementfunktionen, weder virtuell noch anderweitig) und übergeben explizite "Objektinstanzen" als Parameter (normalerweise als erster Parameter; C hat keine this). Gute C-APIs enthalten auch die Benutzerzeigerfunktionalität (manchmal auch als "Benutzerdaten" bezeichnet), sodass Sie keine globalen Zeichen verwenden müssen.

alte Antwort:

Wenn Sie auf andere Daten in Ihrem WindowManager(oder anderen Systemen) zugreifen müssen, müssen Sie möglicherweise global auf diese zugreifen können, wenn Sie über Rückrufe darauf zugreifen möchten. Haben std::unique_ptr<Engine>Sie zum Beispiel eine globale , mit der Sie auf Ihren Fenstermanager zugreifen können, oder erstellen Sie einfach eine globale std::unique_ptr<WindowManager>(ersetzen Sie sie std::unique_ptrdurch etwas "Besseres für Singletons", wenn Sie dies wünschen).

Wenn Sie die Unterstützung mehrerer Fenster wünschen, müssen Sie auch eine WindowManagerDatenstruktur zum Zuordnen von 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 * `enthalten, die sie erhalten haben, um die benötigten Daten nachzuschlagen.

Sean Middleditch
quelle
Danke für deine Hilfe. Wird dies in einem solchen Szenario normalerweise so gehandhabt (Verwenden eines globalen unique_ptr zum Verfolgen von Tastatureingaben)? Ich wollte solche globalen Variablen vermeiden und zog es vor, konstante Zeiger der Tastatur an jeden weiterzugeben, der sie benötigt, aber es klingt so, als wäre dies nicht möglich, oder?
ArmenB
1
Normalerweise kein unique_ptr, aber es ist nicht ungewöhnlich, einen Singleton zu verwenden. GLFW verfügt außerdem über eine festgelegte Benutzerdatenfunktion für Fenster, mit der die Notwendigkeit einer globalen Funktion vermieden werden kann. Die meisten "guten" C-APIs haben etwas Ähnliches. Könnte die Antwort aktualisieren, um darauf hinzuweisen, dass ich zu einem echten Computer zurückkehre.
Sean Middleditch