Bagaimana saya bisa mengaitkan key_callback dengan instance kelas wrapper?

11

Saya mencoba untuk membungkus panggilan GLFW3 saya ke dalam satu kelas:

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

Dan saya mencoba untuk mengatur kelas keyboard tunggal yang mengumpulkan penekanan tombol selama eksekusi. Di GLFW saya dapat mengatur key_callbackfungsi yang berada di luar definisi kelas (fungsi bebas):

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

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

Bagaimana saya bisa menghubungkan callback dan WindowManagerinstance saya sehingga saya bisa mengatur nilai keyboard_objek? Saya tidak bisa membuat key_callbackfungsi anggota WindowManagerkarena itu tidak akan berfungsi karena fungsi itu akan menjadi anggota kelas WindowManager dan di C ++ fungsi anggota kelas mendapatkan nama mereka menggantung.

ArmenB
sumber

Jawaban:

11

Saya punya masalah yang mirip dengan ini. Sangat menjengkelkan bahwa ada sedikit dokumentasi tentang penggunaan glfwSetWindowUserPointer dan glfGetWindowUserPointer. Inilah solusi saya untuk masalah Anda:

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

Lagi pula, karena ini adalah salah satu hasil teratas untuk menggunakan GLFW dengan kelas C ++, saya juga akan memberikan metode saya mengenkapsulasi glfwWindow dalam kelas C ++. Saya pikir ini adalah cara yang paling elegan untuk melakukannya, karena ia tidak harus menggunakan global, singleton atau unique_ptrs, memungkinkan programmer memanipulasi jendela dengan gaya OO / C ++ - y yang jauh lebih banyak, dan memungkinkan subklasifikasi (dengan mengorbankan file header yang sedikit lebih berantakan).

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

Dan untuk:

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

Ini mungkin dapat dengan mudah diintegrasikan dengan kelas WindowManager / InputManager, tapi saya pikir akan lebih mudah untuk mengatur setiap jendela itu sendiri.

burtonageo
sumber
Saya kembali setelah beberapa tahun dan melihat jawaban yang diperbarui. Terima kasih sekali
ArmenB
Dalam fungsi statis, Anda membuat instance kelas baru (yaitu Window *window ). Bagaimana ini menyelesaikan masalah?
CroCo
Saya perhatikan jawabannya telah berubah untuk mendukung beberapa fitur C ++ baru. Apakah ada manfaat mengatur jenis fungsi kembali ke otomatis, dan kemudian mengetik petunjuk menggunakan -> void?
ArmenB
5

Panggilan balik harus berupa fungsi bebas atau fungsi statis, seperti yang Anda ketahui. Callback mengambil GLFWwindow*sebagai argumen pertama mereka sebagai pengganti thispenunjuk otomatis .

Dengan GLFW Anda dapat menggunakan glwSetWindowUserPointerdan glfwGetWindowUserPointeruntuk menyimpan dan mengambil referensi ke WindowManageratau contoh per-jendela Window.

Ingatlah bahwa GLFW tidak menggunakan fungsi virtual segala jenis polimorfisme langsung karena ini adalah API C murni. API semacam itu selalu menganggap fungsi bebas (C tidak memiliki fungsi kelas atau anggota sama sekali, virtual atau lainnya) dan meneruskan "instance objek" eksplisit sebagai parameter (biasanya sebagai parameter pertama; C tidak memiliki this). API C yang bagus juga menyertakan fungsi pointer pengguna (kadang-kadang disebut "data pengguna" di antara hal-hal lain) sehingga Anda tidak harus menggunakan global.

jawaban lama:

Jika Anda perlu mengakses data lain di WindowManager(atau sistem lain) Anda, Anda mungkin harus membuatnya dapat diakses secara global jika Anda ingin mengaksesnya dari callback. Misalnya, memiliki global std::unique_ptr<Engine>yang dapat Anda gunakan untuk mengakses window manager Anda, atau hanya membuat global std::unique_ptr<WindowManager>(ganti std::unique_ptrdengan sesuatu yang "lebih baik untuk lajang" jika Anda mau).

Jika Anda ingin dukungan beberapa jendela, Anda juga harus memiliki WindowManagerbeberapa struktur data untuk memetakan 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 theGLFWwow * * yang mereka terima untuk mencari data yang mereka butuhkan.

Sean Middleditch
sumber
Terima kasih untuk bantuannya. Dalam skenario seperti ini, apakah ini cara biasanya ditangani (menggunakan global unique_ptr untuk melacak input keyboard)? Saya ingin menghindari variabel global seperti ini dan lebih suka membagikan pointer const keyboard ke siapa pun yang membutuhkannya tetapi sepertinya ini tidak mungkin, apakah saya benar?
ArmenB
1
Biasanya bukan unique_ptr, tetapi tidak jarang menggunakan singleton. GLFW juga memiliki fungsi data pengguna untuk windows yang dapat menghindari kebutuhan global. Sebagian besar API "baik" memiliki sesuatu yang serupa. Mungkin pembaruan menjawab untuk menyarankan bahwa ketika saya kembali ke komputer nyata.
Sean Middleditch