Jak mogę powiązać funkcję key_callback z instancją klasy opakowania?

11

Próbuję zawinąć swoje wywołania GLFW3 w jedną klasę:

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

Próbuję ustawić klasę klawiatury singleton, która zbiera naciśnięcia klawiszy podczas wykonywania. W GLFW mogę ustawić key_callbackfunkcję, która jest poza definicją klasy (funkcja bezpłatna):

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

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

Jak mogę powiązać moje wywołanie zwrotne i moją WindowManagerinstancję, aby móc ustawić keyboard_wartości obiektu? Nie mogę utworzyć key_callbackfunkcji członka, WindowManagerponieważ to nie zadziałałoby, ponieważ funkcja ta byłaby członkiem klasy WindowManager, aw funkcji członka klasy C ++ zmieniono nazwy.

ArmenB
źródło

Odpowiedzi:

11

Miałem podobny problem do tego. To denerwujące, że jest tak mało dokumentacji na temat korzystania z glfwSetWindowUserPointer i glfGetWindowUserPointer. Oto moje rozwiązanie twojego problemu:

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

W każdym razie, ponieważ jest to jeden z najlepszych wyników dla używania GLFW z klasami C ++, przedstawię również moją metodę enkapsulacji glfwWindow w klasie C ++. Myślę, że jest to najbardziej elegancki sposób, aby to zrobić, ponieważ pozwala uniknąć konieczności korzystania z globałów, singletonów lub unikatowych_ptrs, pozwala programiście manipulować oknem w znacznie większym stylu OO / C ++ - i umożliwia podklasowanie (kosztem nieco bardziej zaśmiecony plik nagłówka).

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

I dla:

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

Prawdopodobnie można to dość łatwo zintegrować z klasą WindowManager / InputManager, ale myślę, że łatwiej jest po prostu zarządzać każdym oknem.

burtonageo
źródło
Wróciłem po kilku latach i zobaczyłem zaktualizowaną odpowiedź. Naprawdę dobre, dziękuję
ArmenB
W funkcjach statycznych tworzysz nową instancję klasy (tj Window *window .). Jak to rozwiązać problem?
CroCo,
Zauważyłem, że odpowiedź uległa zmianie w celu obsługi niektórych nowych funkcji C ++. Czy jest jakaś korzyść z ustawienia typu zwracanej funkcji na auto, a następnie podpowiedzi za pomocą -> void?
ArmenB
5

Jak już się przekonałeś, wywołania zwrotne muszą być funkcjami swobodnymi lub funkcjami statycznymi. Oddzwonienia przyjmują GLFWwindow*jako swój pierwszy argument zamiast automatycznegothis wskaźnika.

Z GLFW możesz używać glwSetWindowUserPointeri glfwGetWindowUserPointerdo przechowywania i pobierania referencji WindowManagerlub Windowinstancji dla okna .

Pamiętaj, że GLFW nie korzysta z funkcji wirtualnych żadnego bezpośredniego polimorfizmu, ponieważ jest to czysty interfejs API języka C. Takie interfejsy API zawsze przyjmują funkcje bezpłatne (C nie ma żadnych klas lub funkcji składowych, wirtualnych lub innych) i przekazują jawne „instancje obiektów” jako parametry (zwykle jako pierwszy parametr; C nie ma this). Dobre interfejsy API C zawierają również funkcję wskaźnika użytkownika (czasami nazywaną między innymi „danymi użytkownika”), więc nie musisz używać globałów.

stara odpowiedź:

Jeśli chcesz uzyskać dostęp do innych danych w swoim WindowManager(lub innych systemach), może być konieczne, aby były one globalnie dostępne, jeśli chcesz uzyskać dostęp do nich z oddzwaniania. Na przykład, miej globalny std::unique_ptr<Engine>, którego możesz użyć, aby uzyskać dostęp do menedżera okien, lub po prostu stwórz globalny std::unique_ptr<WindowManager>(zamień na std::unique_ptrcoś „lepszego dla singletonów”, jeśli chcesz).

Jeśli chcesz obsługiwać wiele okien, będziesz także WindowManagerzawierał strukturę danych do mapowania 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 * `, które otrzymali, aby wyszukać potrzebne dane.

Sean Middleditch
źródło
Dziękuję za pomoc Czy w takim scenariuszu jest to normalnie obsługiwane (przy użyciu globalnego pliku Unique_ptr do śledzenia wprowadzania danych z klawiatury)? Chciałem uniknąć takich globalnych zmiennych i wolałem przekazywać stałe wskaźniki klawiatury komukolwiek, kto tego potrzebuje, ale wygląda na to, że nie jest to możliwe, prawda?
ArmenB
1
Zwykle nie jest to unikalny_ptr, ale często zdarza się, że używa się singletonu. GLFW posiada również ustawioną funkcję danych użytkownika dla systemu Windows, która pozwala uniknąć potrzeby globalnej. Większość „dobrych” API C ma coś podobnego. Może zaktualizować odpowiedź, aby zasugerować, że kiedy wrócę do prawdziwego komputera.
Sean Middleditch