Як я можу пов’язати key_callback з екземпляром класу обгортки?


11

Я намагаюся зв'язати свої дзвінки GLFW3 в один клас:

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

І я намагаюся налаштувати однокласний клавіатурний клас, який збирає натискання клавіш під час виконання. У GLFW я можу встановити key_callbackфункцію, яка знаходиться поза визначенням класу (вільна функція):

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

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

Як я можу пов’язати зворотний виклик та мій WindowManagerекземпляр, щоб я могла встановити keyboard_значення об'єкта? Я не можу зробити функцію key_callbackчлена, WindowManagerтому що це не буде працювати, оскільки ця функція буде членом класу WindowManager, а в C ++-члені функції класу отримують їхні імена розвішуються.

Відповіді:


11

У мене була схожа проблема з цією. Прикро, що так мало документації щодо використання glfwSetWindowUserPointer та glfGetWindowUserPointer. Ось моє рішення вашої проблеми:

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

У будь-якому випадку, оскільки це один з найкращих результатів використання GLFW з класами C ++, я також запропоную свій метод інкапсуляції glfwWindow у клас C ++. Я думаю, що це найелегантніший спосіб зробити це, оскільки це дозволяє уникнути використання глобулів, синглів або unique_ptrs, дозволяє програмісту маніпулювати вікном у набагато більшому стилі OO / C ++ - y та дозволяє підкласифікувати (ціною трохи більш захаращений файл заголовка).

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

І для:

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

Можливо, це може бути досить легко інтегруватися з класом WindowManager / InputManager, але я думаю, що простіше просто керувати кожним вікном.


Я повернувся через кілька років і побачив оновлену відповідь. Дійсно добре дякую
ArmenB

У статичних функціях ви створюєте новий екземпляр класу (тобто Window *window ). Як це вирішує проблему?
CroCo

Я помітив, що відповідь змінилася для підтримки деяких нових функцій C ++. Чи є якась користь встановити тип повернення функції в автоматичний режим, а потім введіть натяк, використовуючи -> void?
АрменБ

5

Зворотні дзвінки повинні бути вільними або статичними функціями, як ви з'ясували. GLFWwindow*Першим аргументом є зворотний виклик замість автоматичного thisвказівника.

За допомогою GLFW ви можете використовувати glwSetWindowUserPointerта glfwGetWindowUserPointerзберігати та отримувати посилання на екземпляр WindowManagerза вікном Window.

Пам'ятайте, що GLFW не використовує віртуальних функцій жодного виду прямого поліморфізму, оскільки це чистий C API. Такі API завжди беруть на себе вільні функції (у C взагалі немає класів або функцій-членів, віртуально чи іншим чином) і передають явні "об'єктні екземпляри" як параметри (як правило, як перший параметр; C не має this). Хороші API API також включають функціональні функції вказівника користувача (іноді їх називають серед інших речей "користувачами даних"), тому вам не доведеться використовувати глобальні дані.

стара відповідь:

Якщо вам потрібно отримати доступ до інших даних у вашій WindowManager(або інших системах), можливо, вам доведеться мати доступ до них у всьому світі, якщо ви хочете отримати доступ до них з зворотного зв'язку. Наприклад, є глобальний, std::unique_ptr<Engine>який ви можете використовувати для доступу до свого менеджера вікон, або просто зробити глобальний std::unique_ptr<WindowManager>(замініть std::unique_ptrна щось "краще для одиночних", якщо хочете).

Якщо ви хочете підтримати декілька вікон, ви також маєте WindowManagerмістити структуру даних, щоб зіставити GLFWwindow*' values to your ownвікно 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 * `, яке вони отримали для пошуку необхідних даних.


Дякую за твою допомогу. У подібному сценарії це нормально обробляється (використовуючи глобальний унікальний_птр для відстеження входів клавіатури)? Я хотів уникнути будь-яких глобальних змінних, як це, і вважав за краще обходити покажчики клавіатури на когось, кому це потрібно, але це звучить так, як це неможливо, я прав?
ArmenB

1
Зазвичай унікальний_ptr, але не рідкість використання синглтона. GLFW також має встановлену функцію даних користувачів для Windows, яка може уникнути потреби в глобальному масштабі. У більшості "хороших" API API є щось подібне. Можна оновити відповідь, щоб підказати, коли я повернусь до справжнього комп’ютера.
Sean Middleditch
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.