typedef struct
{
WindowData data;
bool vsync;
} Window;
typedef struct
{
Window window;
GLFWwindow *native_window;
} WindowsWindow;
Window *windowCreate(const WindowProperties props, Handle id)
{
WindowsWindow *windows_window = malloc(sizeof(WindowsWindow));
*windows_window = windowswindowCreate(props);
glfwSetWindowUserPointer(windows_window->native_window, &windows_window->window.data);
Window *window = (Window *)windows_window;
window->data.id = id;
return window;
}
void windowSetContextCurrent(Window *window)
{
//log_info("Window Context current, %s", window->data.props.name);
glfwMakeContextCurrent(((WindowsWindow *)window)->native_window);
}
So This is a little snippet that hopefully conveys what I was doing before. I was casting the platform-specific window to the generic window pointer then would convert the generic pointer when needed to the platform-specific one to access its platform-specific data (just the native window currently) and I have run into the issue now of my alignment being off now specifically in my Nuklear GUI layer causing an error when deleting the window the ID of the window accesses the width of the window now instead of the windows ID (its index in my array of all windows) so I access the index at 800 instead of 0.
So lesson learned don't have a pointer point to a pointer of a different type (maybe some good use cases but definitely not here). Is there a better method to do this?
The canonical approach to this is to have a struct containing all the data that's common to the types, and a union inside it that has one of the OS-specific versions.
There are two main ways to let it know which version it's looking at: first is to have a type
field in the struct that other functions can look at to decide which element of the union to use. The other is to have all the functionality that's specific to one OS wrapped in a function, and then set up function pointers to the correct versions when you initialize the struct.
And you can always combine the two: function pointers for the differences that are worth having in their own function, and a type var for the rest.
enum Window_Type { WINDOWS, X11 };
typedef struct {
/* enum to tell us which element of the union to use. All functions that operate on a Window
* will check this first to see which branch to take/auxiliary function to call.
*/
enum Window_Type type;
//common data
unsigned id;
bool vsync;
//OS-specific data
union {
GLFWindow *windows;
X11Window *x11;
} win;
} Window;
Window *windowsWindowCreate(const WindowProperties props, Handle id) {
Window *win = malloc(sizeof(Window));
assert(win);
win->type = WINDOWS;
win->vsync = 1;
win->id = id;
win->win.windows = create_glf_window(props);
return win;
}
bool window_display(Window *win) {
// windows_window_display will look at the common fields and win.windows.
// unix_window_display will look at the common fields and win.x11.
return (WINDOWS == win->type) ? windows_window_display(win)
: (X11 == win->type) ? unix_window_display(win)
: 0;
}
typedef struct Window {
//common data
unsigned id;
bool vsync;
//Store the pointers to the OS-specific functions
bool (*windows_display)(struct Window *win);
//OS-specific data
union {
GLFWindow *windows;
X11Window *x11;
} win;
} Window;
Window *windowsWindowCreate(const WindowProperties props, Handle id) {
Window *win = malloc(sizeof(Window));
assert(win);
win->vsync = 1;
win->id = id;
win->win.windows = create_glf_window(props);
//Set the function pointers to the correct versions
win->windows_display = windows_window_display;
return win;
}