Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to glfwSetKeyCallback for different classes?

Tags:

c++

opengl

glfw

I use GLFW and I have different classes representing the different states in my Application and one state-managing class. I want to use GLFW to receive the key-input and pass it to the current state (so to another class).

The only way I could think of was giving each class an own keycallback-function and use glfwSetKeyCallback(window, keyCallback);

keycallback(GLFWwindow *window, int key, int scancode, int action, int mods)

It didn't work that way

cannot convert 'IntroState::handleKeyEvent' from type 'void (IntroState::)(GLFWwindow*, int, int, int, int)' to type 'GLFWkeyfun {aka void (*)(GLFWwindow*, int, int, int, int)}'
     glfwSetKeyCallback(m_manager->window, handleKeyEvent);

People recommended something like that:

void GLFWCALL functionname( int key, int action );

But the GLFWCALL-macro has been removed from GLFW (Official Note).


I have read that you can use a static function.

static void keyCallback(GLFWwindow*, int, int, int);

But I need to access non-static member functions and get some problems when the callback-function is static.


The actual question:

How can I make the current "state"-class get the key-input from GLFW? like

states.back()->handlekeyEvent(int, int, int);
like image 792
Dunkelbunt27 Avatar asked Nov 30 '22 19:11

Dunkelbunt27


1 Answers

Things get much easier when you step back for a moment and don't think in classes at all. Also a class is a type and types don't really describe a specific state. I think what you actually mean are instances if a class with different internal state.

So your problem then becomes to associate a callback with your state. In your post you wrote the following incomplete callback signature:

keycallback(GLFWwindow *window, int key, int scancode, int action, int mods);

There is missing something, namely the type it returns. Which is void so the full signature is

void GLFWCALL keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);

Where GLFWCALL is a not further specified macro that expands into additional type signature qualifiers. You don't have to care about it anymore, than that this is the only type signature you can validly pass as a callback. Now image you'd write something like

class FooState
{
    void GLFWCALL keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods);  
};

What would be the type signature of this? Well, when you actually implement it, you're writing it down

void GLFWCALL FooState::keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);  

Look at this additional FooState::. This is not just some addition to the name or a namespace. It's actually a type modifier. It's like as if you added another parameter to the function (which is what in fact happens), and that parameter is a reference or a pointer (a pointer in the case of C++) to the class instance. This pointer is usually called this. So if you were to look at this function through the eyes of a C compiler (which doesn't know classes) and GLFW is written in C, the signature in fact looks like

void GLFWCALL keycallback(
    FooState *this,
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);  

And it's pretty obvious that this doesn't fit the needs of GLFW. So what you need is some kind of wrapper that gets this additional parameter there. And where do you get that additional parameter from? Well that would be another variable in your program you have to manage.

Now we can use static class members for this. Within the scope of a class static means a global variable shared by all instances of the class and functions that are within the namespace of the class, but don't work on instances of it (so from a C compiler's perspective they're just regular functions with no additional parameter).

First a base class that gives us a unified interface

// statebase.h
class StateBase
{
    virtual void keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods) = 0; /* purely abstract function */

    static StateBase *event_handling_instance;
    // technically setEventHandling should be finalized so that it doesn't
    // get overwritten by a descendant class.
    virtual void setEventHandling() { event_handling_instance = this; }

    static void GLFWCALL keycallback_dispatch(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods)
    {
        if(event_handling_instance)
            event_handling_instance->keycallback(window,key,scancode,action,mods);
    }    
};

// statebase.cpp
#include "statebase.h"
// funny thing that you have to omit `static` here. Learn about global scope
// type qualifiers to understand why.
StateBase * StateBase::event_handling_instance;

Now the purely abstract virtual function is the trick here: Whatever kind of class derived from StateBase is referenced by the event_handling_instance by use of a virtual dispatch it will end up being called with the function implementation of the class type of that instance.

So we can now declare FooState being derived from StateBase and implement the virtual callback function as usual (but omit that GLFWCALL macro)

class FooState : BaseState
{
    virtual void keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods);  
};
void FooState::keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods)
{
    /* ... */
}

To use this with GLFW register the static dispatcher as callback

glfwSetKeyCallback(window, StateBase::keycallback_dispatcher);

And to select a specific instance as active callback handler you call its inherited void StateBase::setEventHandling() function.

FooState state;
state.setEventHandling();

A few parting words of advice:

If you've not been familiar with these concepts of OOP in C++ and the intricate how types and instances interact you should not do any OOP programming, yet. OOP was once claimed to make things easier to understand, while in fact the shrewd ways it got implemented in C++ actually makes one brain hurt and proliferates very bad style.

Here's an exercise for you: Try to implement what you try to achieve without using OOP. Just use structs without member functions in them (so that they don't become classes in disguise), try to think up flat data structures and how to work with them. It's a really important skill which people who jump directly into OOP don't train a lot.

like image 147
datenwolf Avatar answered Dec 03 '22 08:12

datenwolf