Làm cách nào tôi có thể liên kết một key_callback với một thể hiện của lớp bao bọc?


11

Tôi đang cố gắng thực hiện các cuộc gọi GLFW3 của mình thành một lớp duy nhất:

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

Và tôi đang cố gắng thiết lập một lớp bàn phím đơn thu thập các phím bấm trong khi thực hiện. Trong GLFW tôi có thể đặt một key_callbackhàm nằm ngoài định nghĩa lớp (một hàm miễn phí):

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

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

Làm cách nào tôi có thể liên kết cuộc gọi lại của tôi và WindowManagercá thể của tôi để tôi có thể đặt các keyboard_giá trị đối tượng? Tôi không thể tạo key_callbackhàm thành viên WindowManagervì nó sẽ không hoạt động vì hàm đó sẽ là thành viên của lớp WindowManager và trong hàm thành viên C ++ của một lớp bị treo tên.

Câu trả lời:


10

Tôi đã có một vấn đề tương tự như thế này. Thật khó chịu khi có quá ít tài liệu về việc sử dụng glfwSetWindowUserPulum và glfGetWindowUserPulum. Đây là giải pháp của tôi cho vấn đề của bạn:

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

Dù sao, vì đây là một trong những kết quả hàng đầu để sử dụng GLFW với các lớp C ++, tôi cũng sẽ cung cấp phương pháp đóng gói glfwWindow trong lớp C ++. Tôi nghĩ rằng đây là cách thức thanh lịch nhất để làm điều đó, vì nó tránh phải sử dụng toàn cầu, singletons hoặc unique_ptrs, cho phép lập trình viên điều khiển cửa sổ theo kiểu OO / C ++ - y hơn và cho phép phân lớp (với chi phí một tập tin tiêu đề lộn xộn hơn một chút).

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

Va cho:

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

Điều này có thể được tích hợp khá dễ dàng với lớp WindowManager / InputManager, nhưng tôi nghĩ việc mỗi cửa sổ tự quản lý sẽ dễ dàng hơn.


Tôi đã trở lại sau vài năm và thấy câu trả lời cập nhật. Thực sự rất tốt cảm ơn bạn
ArmenB

Trong các hàm tĩnh, bạn đang tạo một thể hiện mới của một lớp (tức là Window *window ). Làm thế nào điều này giải quyết vấn đề?
CroCo

Tôi nhận thấy câu trả lời đã thay đổi để hỗ trợ một số tính năng mới của C ++. Có bất kỳ lợi ích nào của việc đặt loại trả về chức năng thành tự động, sau đó nhập gợi ý bằng cách sử dụng -> void?
ArmenB

5

Các cuộc gọi lại phải là các hàm miễn phí hoặc các hàm tĩnh, như bạn đã tìm ra. Các cuộc gọi lại lấy một GLFWwindow*đối số đầu tiên của chúng thay cho một thiscon trỏ tự động .

Với GLFW, bạn có thể sử dụng glwSetWindowUserPointerglfwGetWindowUserPointerlưu trữ và truy xuất một tham chiếu đến WindowManagerhoặc một thể hiện trên mỗi cửa sổ Window.

Hãy nhớ rằng GLFW không sử dụng các hàm ảo bất kỳ loại đa hình trực tiếp nào vì đây là API C thuần túy. Các API như vậy luôn đảm nhận các hàm miễn phí (C hoàn toàn không có lớp hoặc hàm thành viên, ảo hoặc mặt khác) và truyền "các thể hiện đối tượng" rõ ràng làm tham số (thường là tham số đầu tiên; C không có this). API C tốt cũng bao gồm chức năng con trỏ người dùng (đôi khi được gọi là "dữ liệu người dùng" trong số những thứ khác) để bạn không phải sử dụng toàn cầu.

câu trả lời cũ:

Nếu bạn cần truy cập dữ liệu khác trong WindowManager(hoặc các hệ thống khác), bạn có thể cần phải truy cập chúng trên toàn cầu nếu bạn muốn truy cập dữ liệu đó từ các cuộc gọi lại. Chẳng hạn, có một toàn cầu std::unique_ptr<Engine>mà bạn có thể sử dụng để truy cập trình quản lý cửa sổ của mình hoặc chỉ tạo một toàn cầu std::unique_ptr<WindowManager>(thay thế std::unique_ptrbằng một cái gì đó "tốt hơn cho singletons" nếu bạn muốn).

Nếu bạn muốn hỗ trợ nhiều cửa sổ, thì bạn cũng sẽ WindowManagerchứa một số cấu trúc dữ liệu để ánh xạ 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 * `họ đã nhận được để tra cứu dữ liệu họ cần.


Cảm ơn sự giúp đỡ của bạn. Trong một kịch bản như thế này, đây có phải là cách nó được xử lý thông thường (sử dụng unique_ptr toàn cầu để theo dõi các đầu vào bàn phím)? Tôi muốn tránh bất kỳ biến toàn cục nào như thế này và ưu tiên chuyển xung quanh các con trỏ bàn phím cho bất kỳ ai cần nó nhưng có vẻ như điều này là không thể, phải không?
ArmenB

1
Không thường là unique_ptr, nhưng không có gì lạ khi sử dụng singleton. GLFW cũng có chức năng dữ liệu người dùng được thiết lập cho các cửa sổ có thể tránh được nhu cầu toàn cầu. Hầu hết các API C "tốt" đều có thứ tương tự. Có thể cập nhật câu trả lời để đề xuất rằng khi tôi quay lại máy tính thật.
Sean Middleditch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.