Release-1 Overhaul #23

Open
opened 2024-12-10 19:57:42 -05:00 by josh · 10 comments
Owner

The main RWindow class will be refactored with the "Pimpl" idiom.
User-input will be abstracted as much as possible, ideally into a separate ReInput project that interlinks with ReWindow.
The ReWindow project files will be reorganized as well.
RenderingAPI will be expanded to a Renderer class interface, with default implementations provided for OpenGL (GLX, WGL), Vulkan, and WebGL backends.


// Pure virtual class that defines the Interface for what RWindow's pImpl
class WindowInterface
{
public:
virtual bool Fullscreen() = 0;
virtual bool RestoreFromFullscreen() = 0;
virtual bool Open() = 0;
virtual bool Close() = 0;
virtual bool ForceClose() = 0;
virtual bool CloseAndReopenInPlace() = 0;
virtual bool IsFocused() = 0;
};

class XWindowImplementation : public WindowInterface {};
class Win32WindowImplementation : public WindowInterface {};
class ConsoleWindowImplementation : public WindowInterface {};
class SDLWindowImplementation : public WindowInterface {};

class InputInterface
{
public:
}

class LinuxInputImplementation : public InputInterface {};
class Win32InputImplementation: public InputInterface {};
class AndroidInputImplementation: public InputInterface {};
class SDLInputImplementation: public InputInterface {};

class RendererInterface
{
public:
}

class GLXRenderer : public RendererInterface {};
class WGLRenderer: public RendererInterface {};
class VKRenderer: public RendererInterface {};
class SDLRenderer: public RendererInterface {};

// User-API Window class. It's turning into the full platform abstraction layer at this point?
class RWindow
{
public:
RWindow()
{
    #ifdef WIN32
        window = Win32Window();
        input = Win32Input();
        render = WGLRenderer();
    #elif LINUX
        window = XWindow();
        input = XInputImplementation();
        render = GLXRenderer();
#endif
}
virtual void OnOpen() {}
virtual void OnClosing() {}
virtual void OnClosed() {}

// Member functions we intend for user API

protected:
    WindowPlatformInterface* window;
    InputPlatformImpl* input;
    RenderPlatformImpl* render;
    
}
The main RWindow class will be refactored with the "Pimpl" idiom. User-input will be abstracted as much as possible, ideally into a separate ReInput project that interlinks with ReWindow. The ReWindow project files will be reorganized as well. RenderingAPI will be expanded to a Renderer class interface, with default implementations provided for OpenGL (GLX, WGL), Vulkan, and WebGL backends. ```cpp // Pure virtual class that defines the Interface for what RWindow's pImpl class WindowInterface { public: virtual bool Fullscreen() = 0; virtual bool RestoreFromFullscreen() = 0; virtual bool Open() = 0; virtual bool Close() = 0; virtual bool ForceClose() = 0; virtual bool CloseAndReopenInPlace() = 0; virtual bool IsFocused() = 0; }; class XWindowImplementation : public WindowInterface {}; class Win32WindowImplementation : public WindowInterface {}; class ConsoleWindowImplementation : public WindowInterface {}; class SDLWindowImplementation : public WindowInterface {}; class InputInterface { public: } class LinuxInputImplementation : public InputInterface {}; class Win32InputImplementation: public InputInterface {}; class AndroidInputImplementation: public InputInterface {}; class SDLInputImplementation: public InputInterface {}; class RendererInterface { public: } class GLXRenderer : public RendererInterface {}; class WGLRenderer: public RendererInterface {}; class VKRenderer: public RendererInterface {}; class SDLRenderer: public RendererInterface {}; // User-API Window class. It's turning into the full platform abstraction layer at this point? class RWindow { public: RWindow() { #ifdef WIN32 window = Win32Window(); input = Win32Input(); render = WGLRenderer(); #elif LINUX window = XWindow(); input = XInputImplementation(); render = GLXRenderer(); #endif } virtual void OnOpen() {} virtual void OnClosing() {} virtual void OnClosed() {} // Member functions we intend for user API protected: WindowPlatformInterface* window; InputPlatformImpl* input; RenderPlatformImpl* render; } ```
maxine self-assigned this 2024-12-10 19:58:08 -05:00
Collaborator

We're still going to need to have a windows CPP file for the Windows implementation. This is due to Windows being dumb and deciding this is a great idea.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

Right now this function uses global pointer variables to access RWindow data. Though I'm sure we can come to a solution.

My idea is for the Windows impl to have a public method wrapped by the WindowProc function. There should be a pointer to this method rather than the class itself. This is still dirty, but atleast the entire RWindow class isn't global.

WindowProcImpl(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
  /* Logic */
}
We're still going to need to have a windows CPP file for the Windows implementation. This is due to Windows being dumb and deciding this is a great idea. ``` LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { ``` Right now this function uses global pointer variables to access RWindow data. Though I'm sure we can come to a solution. My idea is for the Windows impl to have a public method wrapped by the WindowProc function. There should be a pointer to this method rather than the class itself. This is still dirty, but atleast the entire RWindow class isn't global. ``` WindowProcImpl(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { /* Logic */ } ```
Collaborator

Bill says to use our Event system, make it global, add methods to it and have WindowProc invoke it.

Bill says to use our Event system, make it global, add methods to it and have WindowProc invoke it.
Collaborator

*similar to how the event system works.

*similar to how the event system works.
Author
Owner

Side project that intertwines: Static Service Namespaces

WindowService
InputService
RenderService
AudioService
NetworkService

Side project that intertwines: Static Service Namespaces WindowService InputService RenderService AudioService NetworkService
Collaborator
RWindow(WindowPlatformInterface* window, InputPlatformImpl* input, RenderPlatformImpl* render) {}

class SOMETHING {
  X() = 0;
  Y() = 0;
  Z() = 0;
}

class THINGIMPL : SOMETHING {
  X() {};
  Y() {};
  Z() {};
}

RWindow R(wpi, ipi, rpi, THINGIMPL)
``` RWindow(WindowPlatformInterface* window, InputPlatformImpl* input, RenderPlatformImpl* render) {} class SOMETHING { X() = 0; Y() = 0; Z() = 0; } class THINGIMPL : SOMETHING { X() {}; Y() {}; Z() {}; } RWindow R(wpi, ipi, rpi, THINGIMPL) ```
Collaborator

Bill and I came up with this pImpl setup. We figured out that we could just make an Impl class derived by RWindow as it accomplishes the same thing in an easier to read and better way. We're also able to fill in the Impl class definition in platform specific .cpp files. These platform specific .cpp files can contain custom platform specific code used by the filled in public methods. It is also likely we can derive other classes structured the same way for RWindow.

// include/rwindow.hpp

// RWindow Impl
class Impl {
// Store XYZ private information
private:
    unsigned int width, height;
public:
    // Public implementation defined methods 
    void Open();
    void Close();
    void Example();
    // Constructor and destructor defined by implementer 
    Impl(unsigned int width, unsigned int height);
    ~Impl() { /* deleted window */ };
};

// RWindow Interface
// Derives Impl as Private to hide Impl details from users
class RWindow : private Impl {
public:
    // Public RWindow interface methods
    void Open() {Impl::Open();};
    void Close() {Impl::Close();}
    void Example() {Impl::Example();};
    // Construct Impl and RWindow
    // For destruction bottom up C++ destruction still applies
    // https://isocpp.org/wiki/faq/dtors
    RWindow(unsigned int width, unsigned int height) : Impl(width, height) {};
};
// src/platform/linux/impl.cpp
#include <test.hpp>

// Define our implementation for Linux
void Impl::Open() { std::cout << "Linux Impl::Open" << std::endl;}
void Impl::Close() { std::cout << "Linux Impl::Close" << std::endl;}
void Impl::Example() { std::cout << "Linux Impl::Example" << std::endl;}
Impl::Impl(unsigned int width, unsigned int height) {
    this->width = width;
    this->height = height;
}
// src/platform/windows/impl.cpp
#include "test.hpp"

// Windows specific functions for Windows implementation
void Win32Shit() { std::cout << "Some custom windows shit" << std::endl; }

// Define our implementation for Windows
void Impl::Open() { std::cout << "Win32 Impl::Open" << std::endl;}
void Impl::Close() { std::cout << "Win32 Impl::Close" << std::endl; Win32Shit();}
void Impl::Example() { std::cout << "Win32 Impl::Example" << std::endl; Win32Shit(); }
Impl::Impl(unsigned int width, unsigned int height) {
    this->width = width;
    this->height = height;
}
// main.cpp
#include <test.hpp>

int main() {
    // Finally Create RWindow and run public methods
    RWindow r(1,1);
    r.Open();
    r.Close();
    r.Example();
}
// CMakeLists.txt

// Use Linux RWindow Impl
if(UNIX AND NOT APPLE AND NOT BEOS)
    message("000000000000000000000 unix 000000000000000000000")
    file(GLOB_RECURSE SOURCES "src/platform/linux/*.cpp")
endif()

// Use Windows RWindow Impl
if(WIN32)
    message("000000000000000000000 win 000000000000000000000")
    file(GLOB_RECURSE SOURCES "src/platform/windows/*.cpp")
endif()
Bill and I came up with this pImpl setup. We figured out that we could just make an Impl class derived by RWindow as it accomplishes the same thing in an easier to read and better way. We're also able to fill in the Impl class definition in platform specific .cpp files. These platform specific .cpp files can contain custom platform specific code used by the filled in public methods. It is also likely we can derive other classes structured the same way for RWindow. ``` // include/rwindow.hpp // RWindow Impl class Impl { // Store XYZ private information private: unsigned int width, height; public: // Public implementation defined methods void Open(); void Close(); void Example(); // Constructor and destructor defined by implementer Impl(unsigned int width, unsigned int height); ~Impl() { /* deleted window */ }; }; // RWindow Interface // Derives Impl as Private to hide Impl details from users class RWindow : private Impl { public: // Public RWindow interface methods void Open() {Impl::Open();}; void Close() {Impl::Close();} void Example() {Impl::Example();}; // Construct Impl and RWindow // For destruction bottom up C++ destruction still applies // https://isocpp.org/wiki/faq/dtors RWindow(unsigned int width, unsigned int height) : Impl(width, height) {}; }; ``` ``` // src/platform/linux/impl.cpp #include <test.hpp> // Define our implementation for Linux void Impl::Open() { std::cout << "Linux Impl::Open" << std::endl;} void Impl::Close() { std::cout << "Linux Impl::Close" << std::endl;} void Impl::Example() { std::cout << "Linux Impl::Example" << std::endl;} Impl::Impl(unsigned int width, unsigned int height) { this->width = width; this->height = height; } ``` ``` // src/platform/windows/impl.cpp #include "test.hpp" // Windows specific functions for Windows implementation void Win32Shit() { std::cout << "Some custom windows shit" << std::endl; } // Define our implementation for Windows void Impl::Open() { std::cout << "Win32 Impl::Open" << std::endl;} void Impl::Close() { std::cout << "Win32 Impl::Close" << std::endl; Win32Shit();} void Impl::Example() { std::cout << "Win32 Impl::Example" << std::endl; Win32Shit(); } Impl::Impl(unsigned int width, unsigned int height) { this->width = width; this->height = height; } ``` ``` // main.cpp #include <test.hpp> int main() { // Finally Create RWindow and run public methods RWindow r(1,1); r.Open(); r.Close(); r.Example(); } ``` ``` // CMakeLists.txt // Use Linux RWindow Impl if(UNIX AND NOT APPLE AND NOT BEOS) message("000000000000000000000 unix 000000000000000000000") file(GLOB_RECURSE SOURCES "src/platform/linux/*.cpp") endif() // Use Windows RWindow Impl if(WIN32) message("000000000000000000000 win 000000000000000000000") file(GLOB_RECURSE SOURCES "src/platform/windows/*.cpp") endif() ```
Author
Owner

This is a good start, but consider the inheritance model for this setup, and how the user creates the object. Are we sticking with our design goal that the user has one base RWindow class that they create, and can derive, but platform-specific stuff is moved into WindowInterfaceImpl?

This is a good start, but consider the inheritance model for this setup, and how the user creates the object. Are we sticking with our design goal that the user has one base RWindow class that they create, and can derive, but platform-specific stuff is moved into WindowInterfaceImpl?
Collaborator

This is a good start, but consider the inheritance model for this setup, and how the user creates the object. Are we sticking with our design goal that the user has one base RWindow class that they create, and can derive, but platform-specific stuff is moved into WindowInterfaceImpl?

Yes. See the pImpl I started work on. Anything OS/Platform dependent will be handled by the Impl. The Impl also stores information as protected so shared public facing functions in RWindow can access them directly, though the Impl would not be able to use the shared public facing functions. I think it's fairly safe to have the shared functions be defined by RWindow and access the Impl variables. The Impl variables are a part of the interface of the Impl and should not store any platform specific data. Impls should be focused on defining how the data is handled in a standard way.

> This is a good start, but consider the inheritance model for this setup, and how the user creates the object. Are we sticking with our design goal that the user has one base RWindow class that they create, and can derive, but platform-specific stuff is moved into WindowInterfaceImpl? Yes. [See the pImpl I started work on](https://git.redacted.cc/Redacted/ReWindow/commit/5ff4fb2a7306c96ad0cb3085392416bc120ac19f#diff-18b3cf01a58fe914fe7ad5417844cc7f7c1d3730). Anything OS/Platform dependent will be handled by the Impl. The Impl also stores information as protected so shared public facing functions in RWindow can access them directly, though the Impl would not be able to use the shared public facing functions. I think it's fairly safe to have the shared functions be defined by RWindow and access the Impl variables. The Impl variables are a part of the interface of the Impl and should not store any platform specific data. Impls should be focused on defining how the data is handled in a standard way.
Collaborator

We could also define the shared functions like the getters in the Impl then have their functionality defined as well leaving. With that we can access the shared functions within the Impl and private the Impl variables.

We could also define the shared functions like the getters in the Impl then have their functionality defined as well leaving. With that we can access the shared functions within the Impl and private the Impl variables.
Collaborator

I have most of RWindow refactored to use the pImpl idiom now. I had to hack the shit out of it to test the progress so far and it works fairly well. See this working commit.

The RWindowImpl now has a Platform class which is the more interesting part of the refactor. The Platform class has purposefully been left undefined and is left up to implementers (mainly us) to utilize it.

To quote myself:

    // Class for platform specific "global" variables, information, functionality, etc
    // It is private as it should not be accessed outside the Impl. It has also
    // purposefully been left undefined, so implementors can define it as they see fit.
    // Make sure to do proper cleanup of the class and pointer if utilized. It should
    // be handled with care.

As defined in RWindowImpl:

    class Platform;
    Platform* pPlatform = nullptr;

Usage of the Platform class is completely optional and is fairly safe to use if the programmer is being smart about it.

So far I've only used it for the Linux and X11 stuff. Mainly I have used it to store those nasty globals per instance.

src/platform/linux/window.cpp

class RWindowImpl::Platform {
public:
    Platform() = default;
public:
    Window window;
    XEvent xev;
    Display* display = XOpenDisplay(nullptr);
    int defaultScreen = DefaultScreen(display);
    XVisualInfo* visual;
    XSetWindowAttributes xSetWindowAttributes;
    XWindowAttributes windowAttributes;
    Atom wmDeleteWindow;
    // Make it start as floating because fuck tiling WMs
    Atom windowTypeAtom;
    Atom windowTypeUtilityAtom;
    XSizeHints hints;
    GLXContext glContext;
    Cursor invisible_cursor = 0;

    Vector2 render_area_position = {0, 0};
    Vector2 position = {0, 0};
    bool should_poll_x_for_mouse_pos = true;
};

A simple example of it being used for the Linux Impl:

void RWindowImpl::Raise() const {
    Logger::Debug(std::format("Raising window '{}'", this->title));

    // Get the position of the renderable area relative to the rest of the window.
    XGetWindowAttributes(pPlatform->display, pPlatform->window, &pPlatform->windowAttributes);
    pPlatform->render_area_position =  { (float) pPlatform->windowAttributes.x, (float) pPlatform->windowAttributes.y };

    XRaiseWindow(pPlatform->display, pPlatform->window);
}

I haven't tried to create 2 RWindows at the same time yet, but I'll eventually get to testing that. For now my focus is on the refactor.

I have most of RWindow refactored to use the pImpl idiom now. I had to hack the shit out of it to test the progress so far and it works fairly well. [See this working commit](https://git.redacted.cc/Redacted/ReWindow/commit/20b6f1041f76d10e0b00347d4a7969649a873e4d). The RWindowImpl now has a Platform class which is the more interesting part of the refactor. The Platform class has purposefully been left undefined and is left up to implementers (mainly us) to utilize it. To quote myself: ``` // Class for platform specific "global" variables, information, functionality, etc // It is private as it should not be accessed outside the Impl. It has also // purposefully been left undefined, so implementors can define it as they see fit. // Make sure to do proper cleanup of the class and pointer if utilized. It should // be handled with care. ``` As defined in RWindowImpl: ``` class Platform; Platform* pPlatform = nullptr; ``` Usage of the Platform class is completely optional and is fairly safe to use if the programmer is being smart about it. So far I've only used it for the Linux and X11 stuff. Mainly I have used it to store those nasty globals per instance. src/platform/linux/window.cpp ``` class RWindowImpl::Platform { public: Platform() = default; public: Window window; XEvent xev; Display* display = XOpenDisplay(nullptr); int defaultScreen = DefaultScreen(display); XVisualInfo* visual; XSetWindowAttributes xSetWindowAttributes; XWindowAttributes windowAttributes; Atom wmDeleteWindow; // Make it start as floating because fuck tiling WMs Atom windowTypeAtom; Atom windowTypeUtilityAtom; XSizeHints hints; GLXContext glContext; Cursor invisible_cursor = 0; Vector2 render_area_position = {0, 0}; Vector2 position = {0, 0}; bool should_poll_x_for_mouse_pos = true; }; ``` A simple example of it being used for the Linux Impl: ``` void RWindowImpl::Raise() const { Logger::Debug(std::format("Raising window '{}'", this->title)); // Get the position of the renderable area relative to the rest of the window. XGetWindowAttributes(pPlatform->display, pPlatform->window, &pPlatform->windowAttributes); pPlatform->render_area_position = { (float) pPlatform->windowAttributes.x, (float) pPlatform->windowAttributes.y }; XRaiseWindow(pPlatform->display, pPlatform->window); } ``` I haven't tried to create 2 RWindows at the same time yet, but I'll eventually get to testing that. For now my focus is on the refactor.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Redacted/ReWindow#23
No description provided.