Door: Thijs Zumbrink
While refactoring TaZCrash I took another look at GLUT window creation. Originally set up in one-window only mode, I had some trouble in converting this to clean OOP code. In particular, the library only accepts static callbacks with no way to identify the object you are using it for. After some trial and error I found a clean solution.
When I originally encountered this implementation issue, I had a GameView class that opens a FreeGLUT window to render itself in. This was a regular class with regular member methods. The issue arose when I wanted to implement callbacks, for example to take action when a window is closed. The function signature to register a close callback looks like this:
In other words, provide it with a pointer to a plain C-style function or static C++ class function. That function takes no arguments and returns nothing.
At first I was puzzled: "how do I access my object from this callback function? I want to shut down my GameView instance when its window is closed." In my experience, libraries usually add a argument to callbacks registration and callback parameters, which you can use to pass or anything else you like. In the callback you could then cast it back to before accessing its non-static member method. Since this was not supported, I decided to make GameView a singleton so I could access it statically. Callback code would look like this:
The downside to this solution is obviously that there cannot be multiple instances. This hinders testing and gives a decidedly static feel to the class. It also litters the class with a wrapper per callback type. Furthermore while refactoring code to a Window class, it would seem silly to again make this a singleton and limit the use to just one window.
My preferred way to actually use this library is to pass any std::function in to give the user maximum freedom. Something like:
However this would not work, since the generated callbacks do not compile into a function with zero arguments. After all, at least one argument is needed to pass so the member method can work on the instance.
During refactoring into a Window class, I realized that glut does support multiple windows. So intuitively there is some sort of instancing going on, which could help lift the need for static in my GameView. As it turns out, creating a Window returns an ID (int) and any callback can query the active Window via to determine where the event happened.
So now I have a Window class that is used by GameView. It contains a small static map that registers which Window objects belong to which GLUT ID's. The Window class stores the closure in a member variable and executes it after finding the correct Window instance in the map:
And finally the cleaned up code in GameView:
So what's the takeaway from this? At first I had blamed GLUT and FreeGLUT for not providing at least a to get around the static problem. I had read the docs and knew that existed, but creating a singleton out of GameView seemed the logical solution. It was only after a refactoring into a Window class, with clearly defined concerns and being agnostic to the game code, that the real solution became clear.
Refactoring to separate concerns, splitting code into smaller classes (occasionally value objects) in my case greatly reduced code size and complexity, and removed technical limitations. Things start to fall into place with proper class modeling.