raysan5 / raylib

A simple and easy-to-use library to enjoy videogames programming
http://www.raylib.com
zlib License
23.05k stars 2.3k forks source link

[core] Splitting `rcore` by platform into submodules #3313

Closed michaelfiber closed 1 year ago

michaelfiber commented 1 year ago

Issue description

As part of my experimenting in pr #3311 to divide rcore into submodules, one of the things I found was that the CoreData structure adds a lot of complexity to that endeavor and MAY benefit from being split up as well. I believe it may benefit because the current CoreData structure can be fairly difficult to understand with the large number of preprocessor directives within the struct itself. This is largely done to accommodate the many different target platforms. But if rcore is divided into different submodules based on platforms there is an opportunity to increase clarity.

Here are a couple of initial ideas for approaches:

  1. Maintain a single instance of CoreData but have CoreData only contain data that all submodules use so that it is a simpler struct with no preprocessor directives. A single CORE instance would be defined extern in rcore.h, defined in rcore.c, and accessed from rcore.c and all the submodules. In addition to this each submodule would have a small CoreData like struct of its own, i.e. DesktopData, WebData, etc. The data that is currently in CoreData that is specific to an submodule would be moved to the data struct in that submodule. Each struct definition would become much more legible because it wouldn't be full of preprocessor directives but there would be more structs overall and they'd be more spread out instead of centralized in rcore.

  2. Another option could be to have the CoreData struct defined entirely within a submodule specific header file so that each submodule has a complete CoreData struct and rcore.c can include a specific one based on the PLATFORM variable. There'd be more duplication but fewer structs and data would not be divided across multiple structs. Any changes to the data in CoreData that is used by all submodules would require applying the change to each submodule.

Personally I'm leaning towards the first option but am interested in what others think.

Environment

Experiments are being coded in Ubuntu 23.04 and leverage github actions to attempt to run build steps for all platforms.

Code Example

3311

ghost commented 1 year ago

Under approach 1, what would happen to a member that is used, lets say, by PLATFORM_DESKTOP and PLATFORM_WEB but not PLATFORM_ANDROID nor PLATFORM_DRM.

Would it be under rcore?

Or not in rcore but duplicated on rcore_desktop and on rcore_web?

raysan5 commented 1 year ago

I think both approaches have pros and cons.

I think the best option could be 1, despite the cons. concerns. I think the number of custom variables is not that big and it should be easier to manage if well organized and documented in the code.

In an ideal scenario, the "extra" required variables could be, maybe, removed or integrated in some way into the main CoreData struct... Actually, that's another possibility, just keep everything in CoreData independently of the platform, beeing used or not. Probably the overload won't be that dramatic...

raysan5 commented 1 year ago

EDIT: Copying the info from wishlist for reference:

This is not an easy task, still deciding the proper approach for it. There are 45 functions depending on ~100 GLFW functions, those 45 functions should be reproduced separately for the different platforms/technologies and exposed through the same common API.

Also related to this submodules project, some time ago I created a list of functions dependant on GLFW (PLATFORM_DESKTOP), that's probably the list of functions that should be duplicated between platform:

void CloseWindow(void)
    glfwDestroyWindow();
    glfwTerminate();
bool WindowShouldClose(void)
    glfwWaitEvents();
    glfwWindowShouldClose();
void ToggleFullscreen(void)
    glfwGetWindowPos()
    glfwGetMonitors();
    glfwSetWindowMonitor()
    glfwSwapInterval();
void MaximizeWindow(void)
    glfwGetWindowAttrib()
    glfwMaximizeWindow()
void MinimizeWindow(void)
    glfwIconifyWindow()
void RestoreWindow(void)
    glfwRestoreWindow()
void SetWindowState(unsigned int flags)
    glfwSwapInterval();
    glfwSetWindowAttrib()
    glfwHideWindow()
    glfwShowWindow()
void SetWindowIcon(Image image)
    glfwSetWindowIcon()
void SetWindowIcons(Image *images, int count)
    glfwSetWindowIcon()
void SetWindowTitle(const char *title)
    glfwSetWindowTitle()
void SetWindowPosition(int x, int y)
    glfwSetWindowPos()
void SetWindowMonitor(int monitor)
    glfwGetMonitors()
    glfwGetMonitorName()
    glfwGetVideoMode()
    glfwSetWindowMonitor()
void SetWindowMinSize(int width, int height)
    glfwGetVideoMode()
    glfwSetWindowSizeLimits()
void SetWindowSize(int width, int height)
    glfwSetWindowSize()
void SetWindowOpacity(float opacity)
    glfwSetWindowOpacity()
void *GetWindowHandle(void)
    glfwGetWin32Window()
    glfwGetX11Window()
    glfwGetCocoaWindow()
int GetMonitorCount(void)
    glfwGetMonitors();
int GetCurrentMonitor(void)
    glfwGetMonitors();
    glfwGetWindowMonitor()
    glfwGetWindowPos()
    glfwGetMonitorWorkarea()
Vector2 GetMonitorPosition(int monitor)
    glfwGetMonitors()
    glfwGetMonitorPos()
int GetMonitorWidth(int monitor)
    glfwGetMonitors()
    glfwGetVideoMode()
int GetMonitorHeight(int monitor)
    glfwGetMonitors()
    glfwGetVideoMode()
int GetMonitorPhysicalWidth(int monitor)
    glfwGetMonitors()
    glfwGetMonitorPhysicalSize()
int GetMonitorPhysicalHeight(int monitor)
    glfwGetMonitors()
    glfwGetMonitorPhysicalSize()
int GetMonitorRefreshRate(int monitor)
    glfwGetMonitors()
    glfwGetVideoMode()
Vector2 GetWindowPosition(void)
    glfwGetWindowPos()
Vector2 GetWindowScaleDPI(void)
    glfwGetMonitors()
    glfwGetMonitorContentScale()
    glfwGetMonitorWorkarea()
const char *GetMonitorName(int monitor)
    glfwGetMonitors()
    glfwGetMonitorName()
void SetClipboardText(const char *text)
    glfwSetClipboardString()
const char *GetClipboardText(void)
    glfwGetClipboardString()
void ShowCursor(void)
    glfwSetInputMode()
void HideCursor(void)
    glfwSetInputMode()
void EnableCursor(void)
    glfwSetInputMode()
void DisableCursor(void)
    glfwSetInputMode()
double GetTime(void)
    glfwGetTime()
const char *GetGamepadName(int gamepad)
    glfwGetJoystickName();
void SetMousePosition(int x, int y)
    glfwSetCursorPos()
void SetMouseCursor(int cursor)
    glfwSetCursor()
    glfwCreateStandardCursor()
static bool InitGraphicsDevice(int width, int height)
    glfwSetErrorCallback();
    glfwInitHint()
    glfwInit()
    glfwDefaultWindowHints();
    glfwWindowHint()
    glfwSetJoystickCallback();
    glfwGetPrimaryMonitor();
    glfwGetVideoMode(monitor);
    glfwGetVideoModes()
    glfwCreateWindow()
    glfwSetWindowMonitor()
    glfwTerminate();
    glfwSetWindowSizeCallback()
    glfwSetWindowMaximizeCallback()
    glfwSetWindowIconifyCallback()
    glfwSetWindowFocusCallback()
    glfwSetDropCallback()
    glfwSetKeyCallback()
    glfwSetCharCallback()
    glfwSetMouseButtonCallback()
    glfwSetCursorPosCallback()
    glfwSetScrollCallback()
    glfwSetCursorEnterCallback()
    glfwMakeContextCurrent();
    glfwSetInputMode()
    glfwSwapInterval()
    glfwGetFramebufferSize()
    glfwGetProcAddress()
static void SetupViewport(int width, int height)
    glfwGetWindowContentScale()
void SwapScreenBuffer(void)
    glfwSwapBuffers();
void PollInputEvents(void)
    glfwJoystickPresent()
    glfwGetGamepadState()
    glfwWaitEvents()
    glfwPollEvents()
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
    glfwSetWindowShouldClose()

Here the functions list clean:

void CloseWindow(void)
bool WindowShouldClose(void)
void ToggleFullscreen(void)
void MaximizeWindow(void
void MinimizeWindow(void)
void RestoreWindow(void)
void SetWindowState(unsigned int flags)
void SetWindowIcon(Image image)
void SetWindowIcons(Image *images, int count)
void SetWindowTitle(const char *title)
void SetWindowPosition(int x, int y)
void SetWindowMonitor(int monitor)
void SetWindowMinSize(int width, int height)
void SetWindowSize(int width, int height)
void SetWindowOpacity(float opacity)
void *GetWindowHandle(void)
int GetMonitorCount(void)
int GetCurrentMonitor(void)
Vector2 GetMonitorPosition(int monitor)
int GetMonitorWidth(int monitor)
int GetMonitorHeight(int monitor)
int GetMonitorPhysicalWidth(int monitor)
int GetMonitorPhysicalHeight(int monitor)
int GetMonitorRefreshRate(int monitor)
Vector2 GetWindowPosition(void)
Vector2 GetWindowScaleDPI(void)
const char *GetMonitorName(int monitor)
void SetClipboardText(const char *text)
const char *GetClipboardText(void)
void ShowCursor(void)
void HideCursor(void)
void EnableCursor(void)
void DisableCursor(void)
double GetTime(void)
const char *GetGamepadName(int gamepad)
void SetMousePosition(int x, int y)
void SetMouseCursor(int cursor)
static bool InitGraphicsDevice(int width, int height)
static void SetupViewport(int width, int height)
void SwapScreenBuffer(void)
void PollInputEvents(void)

Most of those functions could be not required on target platform and just fallback to some default value.

ghost commented 1 year ago

Actually, that's another possibility, just keep everything in CoreData independently of the platform, beeing used or not. Probably the overload won't be that dramatic...

This would be excellent. Would make a lot simpler for anyone that want/need to extend it. I'd vote for this one.

michaelfiber commented 1 year ago

Actually, that's another possibility, just keep everything in CoreData independently of the platform, beeing used or not. Probably the overload won't be that dramatic...

I like that. It would be super clear. And a shared data structure would probably guide platform specific development to re-use rather than reinvent since every platform would have access to all the data structure anyway.

I hadn't considered multiple windows when I was thinking through it so the approach I was leaning towards before would probably need a lot of work to handle that sanely. And it would lose a lot of clarity.

raysan5 commented 1 year ago

@michaelfiber @ubkp I've been thinking about the best approach for code organization and I got to the conclusion that avoiding breaking all raylib current build systems (and potentially many bindings or user codebases) is the best option, my idea is to just keep the rcore.c as the base module, containing all common functions BUT also the inclusion of the required submodules:

// rcore.c

// Required data types and variables

#if defined(PLATFORM_DESKTOP)
    #include "rcore_desktop.c"
#elif defined(PLATFORM_WEB)
    #include "rcore_web.c"
#elif defined(PLATFOM_DRM)
    #include "rcore_drm.c"
#elif defined(PLATFOM_ANDROID)
    #include "rcore_android.c"
#else
    // Software rendering backend, user needs to provide buffer ;)
#endif

// Common functions to all platforms

From an academic point of view it could be ugly but in practice it works very well.

raysan5 commented 1 year ago

@michaelfiber @ubkp I'd also like to accelerate the development of this improvement, considering the latest events on the gamedev industry I think it could be beneficial for raylib in multiple ways, for example to allow other contributors to hook additional platforms in an easy way; at this moment I know is quite scary to look at rcore.

Please, let me know if you could help me with this big redesign!

ghost commented 1 year ago

my idea is to just keep the rcore.c as the base module, containing all common functions BUT also the inclusion of the required submodules

  1. In that scenario, for example, IsWindowMinimized()(L1154-L1160), would it be under rcore.c with its #ifdefs? Or not in rcore.c but duplicated on rcore_desktop.c and rcore_web.c?

    I ask this because, if it's under rcore.c, then I don't think much would change. rcore.c would still have a considerable amount of #ifdefs, thus harder to track. I guess most functions would fall under that case.

if defined(PLATFORM_DESKTOP)

include "rcore_desktop.c"

elif

  1. Please correct me if I'm wrong (and I'm probably wrong), but I was under the impression that this way of including would be a bad idea (refs: 1, 2, 3).

for example to allow other contributors to hook additional platforms in an easy way;

  1. I'd say the only (actual) missing platform would be iOS. IMHO, I don't think we'd be seeing aditional (e.g.: consoles) platforms even if the conditions were right because of the SDKs and NDAs. Just as a reference, quoting the Godot documentation on a similar subject:

The reason other consoles are not officially supported are:

  • To develop for consoles, one must be licensed as a company. As an open source project, Godot has no legal structure to provide console ports.

  • Console SDKs are secret and covered by non-disclosure agreements. Even if we could get access to them, we could not publish the platform-specific code under an open source license.

  1. I admit I'm having a hard time visualizing it all (separation, building, etc). Would it be a good idea to test it first in a sort of very minimal skeleton/prototype with just one or two structs/functions inside just to see what works or not?

Edit:

I'd also like to accelerate the development of this improvement

  1. When the new structure is set in stone, to speed up its implementation, I'd suggest setting a temporary freeze in new PRs (like Linux distros do) to avoid having to play catch-up given the structural nature of the change.
michaelfiber commented 1 year ago

@michaelfiber @ubkp I've been thinking about the best approach for code organization and I got to the conclusion that avoiding breaking all raylib current build systems (and potentially many bindings or user codebases) is the best option, my idea is to just keep the rcore.c as the base module, containing all common functions BUT also the inclusion of the required submodules:

// rcore.c

// Required data types and variables

#if defined(PLATFORM_DESKTOP)
    #include "rcore_desktop.c"
#elif defined(PLATFORM_WEB)
    #include "rcore_web.c"
#elif defined(PLATFOM_DRM)
    #include "rcore_drm.c"
#elif defined(PLATFOM_ANDROID)
    #include "rcore_android.c"
#else
    // Software rendering backend, user needs to provide buffer ;)
#endif

// Common functions to all platforms

From an academic point of view it could be ugly but in practice it works very well.

Doing it this way feels wrong but I completely understand not wanting to break builds. Updating the Makefile was easy but I don't know about updating the others. Right now for testing I'm going to leave the Makefile change and then as I progress through this maybe I'll spot another way that doesn't feel so wrong? With the position of raylib as a great tool for education I'd hate to include something that would have a comment like // You shouldn't do this

Also for the CoreData I realized that if all the data is always there without preprocessor directives then it either needs all dependencies included no matter that target platform OR a lot of void pointers that will have to be cast each time in the platform specific rcore files.

Right now I am leaving the #if defined(PLATFORM_*) directives in the struct as I split out the rest of the functions. The CoreData structure doesn't get any more difficult to read than it did before but it maintains its very difficult to read directives. Another less disruptive approach may be a simple refactor to move platform specific stuff so there's just one directive per platform in CoreData. CORE->handle would become CORE->desktop->handle for instance. It does mean some repeated data structures between platforms but I think the readability would increase dramatically.

TL;DR:

raysan5 commented 1 year ago

@ubkp here the answers for your questions:

  1. In those cases the function would be duplicated for all platforms requiring it.

  2. It's not recommended and probably bad practice from an academic point of view, still, it can be used and it's really handy. Actually raylib uses that approach for all its external libraries, #define XXXX_IMPLEMENTATION does exactly that.

  3. Yes, most noticeable missing platform is PLATFORM_IOS, maybe PLATFORM_SOFTWARE could also be an option... BUT this structure opens the door to the addition of console platforms (by licensed users) in an easier way with minimal raylib modifications. Console homebrew platforms could also follow this same pattern.

  4. Yes, it's a very big change in structure. I don't fully visualize it at the moment but I think it could work.

  5. Completely agree. Still considering if moving the platform-specific parts from CoreData structure is a better approach, actually CoreData was created quite recently for organization (as a struct of structs), not long ago it was just a bunch of global variables.

    Doing it this way feels wrong but I completely understand not wanting to break builds.

@michaelfiber I know, it feels wrong... but it actually works like a charm for this kind of situations.

michaelfiber commented 1 year ago

@raysan5 Ok, I can try that out.

michaelfiber commented 1 year ago

EDIT: Copying the info from wishlist for reference:

* [ ]  **REDESIGN: Module  subdivision**. This module has grown a lot with every new supported platform (~7000 loc), at some point it could be divided into separate modules. Two possible approaches:

  * Divide by platform:

    * `rcore_desktop.c`  - `PLATFORM_DESKTOP` (Windows, Linux, macOS)
    * `rcore_android.c` - `PLATFORM_ANDROID`
    * `rcore_native.c` - `PLATFORM_DRM`/`PLATFORM_RPI`
    * `rcore_web.c` - `PLATFORM_WEB`
  * Divide by technology

    * `rcore_glfw.c` - GLFW library based
    * `rcore_sdl.c` -  SDL library based
    * `rcore_native.c` - Native implementation

This is not an easy task, still deciding the proper approach for it. There are 45 functions depending on ~100 GLFW functions, those 45 functions should be reproduced separately for the different platforms/technologies and exposed through the same common API.

Also related to this submodules project, some time ago I created a list of functions dependant on GLFW (PLATFORM_DESKTOP), that's probably the list of functions that should be duplicated between platform:

void CloseWindow(void)
    glfwDestroyWindow();
    glfwTerminate();
bool WindowShouldClose(void)
    glfwWaitEvents();
    glfwWindowShouldClose();
void ToggleFullscreen(void)
    glfwGetWindowPos()
    glfwGetMonitors();
    glfwSetWindowMonitor()
    glfwSwapInterval();
void MaximizeWindow(void)
    glfwGetWindowAttrib()
    glfwMaximizeWindow()
void MinimizeWindow(void)
    glfwIconifyWindow()
void RestoreWindow(void)
    glfwRestoreWindow()
void SetWindowState(unsigned int flags)
    glfwSwapInterval();
    glfwSetWindowAttrib()
    glfwHideWindow()
    glfwShowWindow()
void SetWindowIcon(Image image)
    glfwSetWindowIcon()
void SetWindowIcons(Image *images, int count)
    glfwSetWindowIcon()
void SetWindowTitle(const char *title)
    glfwSetWindowTitle()
void SetWindowPosition(int x, int y)
    glfwSetWindowPos()
void SetWindowMonitor(int monitor)
    glfwGetMonitors()
    glfwGetMonitorName()
    glfwGetVideoMode()
    glfwSetWindowMonitor()
void SetWindowMinSize(int width, int height)
    glfwGetVideoMode()
    glfwSetWindowSizeLimits()
void SetWindowSize(int width, int height)
    glfwSetWindowSize()
void SetWindowOpacity(float opacity)
    glfwSetWindowOpacity()
void *GetWindowHandle(void)
    glfwGetWin32Window()
    glfwGetX11Window()
    glfwGetCocoaWindow()
int GetMonitorCount(void)
    glfwGetMonitors();
int GetCurrentMonitor(void)
    glfwGetMonitors();
    glfwGetWindowMonitor()
    glfwGetWindowPos()
    glfwGetMonitorWorkarea()
Vector2 GetMonitorPosition(int monitor)
    glfwGetMonitors()
    glfwGetMonitorPos()
int GetMonitorWidth(int monitor)
    glfwGetMonitors()
    glfwGetVideoMode()
int GetMonitorHeight(int monitor)
    glfwGetMonitors()
    glfwGetVideoMode()
int GetMonitorPhysicalWidth(int monitor)
    glfwGetMonitors()
    glfwGetMonitorPhysicalSize()
int GetMonitorPhysicalHeight(int monitor)
    glfwGetMonitors()
    glfwGetMonitorPhysicalSize()
int GetMonitorRefreshRate(int monitor)
    glfwGetMonitors()
    glfwGetVideoMode()
Vector2 GetWindowPosition(void)
    glfwGetWindowPos()
Vector2 GetWindowScaleDPI(void)
    glfwGetMonitors()
    glfwGetMonitorContentScale()
    glfwGetMonitorWorkarea()
const char *GetMonitorName(int monitor)
    glfwGetMonitors()
    glfwGetMonitorName()
void SetClipboardText(const char *text)
    glfwSetClipboardString()
const char *GetClipboardText(void)
    glfwGetClipboardString()
void ShowCursor(void)
    glfwSetInputMode()
void HideCursor(void)
    glfwSetInputMode()
void EnableCursor(void)
    glfwSetInputMode()
void DisableCursor(void)
    glfwSetInputMode()
double GetTime(void)
    glfwGetTime()
const char *GetGamepadName(int gamepad)
    glfwGetJoystickName();
void SetMousePosition(int x, int y)
    glfwSetCursorPos()
void SetMouseCursor(int cursor)
    glfwSetCursor()
    glfwCreateStandardCursor()
static bool InitGraphicsDevice(int width, int height)
    glfwSetErrorCallback();
    glfwInitHint()
    glfwInit()
    glfwDefaultWindowHints();
    glfwWindowHint()
    glfwSetJoystickCallback();
    glfwGetPrimaryMonitor();
    glfwGetVideoMode(monitor);
    glfwGetVideoModes()
    glfwCreateWindow()
    glfwSetWindowMonitor()
    glfwTerminate();
    glfwSetWindowSizeCallback()
    glfwSetWindowMaximizeCallback()
    glfwSetWindowIconifyCallback()
    glfwSetWindowFocusCallback()
    glfwSetDropCallback()
    glfwSetKeyCallback()
    glfwSetCharCallback()
    glfwSetMouseButtonCallback()
    glfwSetCursorPosCallback()
    glfwSetScrollCallback()
    glfwSetCursorEnterCallback()
    glfwMakeContextCurrent();
    glfwSetInputMode()
    glfwSwapInterval()
    glfwGetFramebufferSize()
    glfwGetProcAddress()
static void SetupViewport(int width, int height)
    glfwGetWindowContentScale()
void SwapScreenBuffer(void)
    glfwSwapBuffers();
void PollInputEvents(void)
    glfwJoystickPresent()
    glfwGetGamepadState()
    glfwWaitEvents()
    glfwPollEvents()
static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
    glfwSetWindowShouldClose()

Here the functions list clean:

void CloseWindow(void)
bool WindowShouldClose(void)
void ToggleFullscreen(void)
void MaximizeWindow(void
void MinimizeWindow(void)
void RestoreWindow(void)
void SetWindowState(unsigned int flags)
void SetWindowIcon(Image image)
void SetWindowIcons(Image *images, int count)
void SetWindowTitle(const char *title)
void SetWindowPosition(int x, int y)
void SetWindowMonitor(int monitor)
void SetWindowMinSize(int width, int height)
void SetWindowSize(int width, int height)
void SetWindowOpacity(float opacity)
void *GetWindowHandle(void)
int GetMonitorCount(void)
int GetCurrentMonitor(void)
Vector2 GetMonitorPosition(int monitor)
int GetMonitorWidth(int monitor)
int GetMonitorHeight(int monitor)
int GetMonitorPhysicalWidth(int monitor)
int GetMonitorPhysicalHeight(int monitor)
int GetMonitorRefreshRate(int monitor)
Vector2 GetWindowPosition(void)
Vector2 GetWindowScaleDPI(void)
const char *GetMonitorName(int monitor)
void SetClipboardText(const char *text)
const char *GetClipboardText(void)
void ShowCursor(void)
void HideCursor(void)
void EnableCursor(void)
void DisableCursor(void)
double GetTime(void)
const char *GetGamepadName(int gamepad)
void SetMousePosition(int x, int y)
void SetMouseCursor(int cursor)
static bool InitGraphicsDevice(int width, int height)
static void SetupViewport(int width, int height)
void SwapScreenBuffer(void)
void PollInputEvents(void)

Most of those functions could be not required on target platform and just fallback to some default value.

I'll probably just go function by function through rcore and move the functions that have platform_specific logic. That will probably align with the list of functions that reference glfw but I'm not sure. Also If you are keeping a list then SetWindowFocused should be on it too.

michaelfiber commented 1 year ago

Immediately as I make this transition I see the opportunity to add some simple javascript functions to the default HTML templates for emscripten that would help with things like reading the clipboard and disabling the cursor without having the ID of the canvas element hard coded into raylib. That makes me think this submodule approach might be a good one. It's much easier to check at a glance how complete each implementation is.

ghost commented 1 year ago

@michaelfiber If you need help with anything, please let me know.

raysan5 commented 1 year ago

@michaelfiber thank you very much for this big effort! Undoubtely it opens the door to many per-platform improvements.

This is a lot of work, please, let me and @ubkp help with it.

I created a separate branch to start merging the changes: https://github.com/raysan5/raylib/tree/rcore_platform_split

We can divide the functions to port in chunks between us.

Also, I'm locking any change in rcore module for now, at least until we get a functional version and we can validate if it's a good line to follow for raylib (I'm pretty convinced of it!).

michaelfiber commented 1 year ago

I'm doing a basic split on the platform specific preprocessor directives so it's actually very easy. But once its done the real work of making sure each submodule is correct and builds will be more time consuming I think and that'll be very easy to divvy up.

I should be done splitting rcore today and maybe after that we can share going through the submodules?

raysan5 commented 1 year ago

I should be done splitting rcore today and maybe after that we can share going through the submodules?

Perfect, it sounds like a plan. Feel free to send the redesign to rcore_platform_split branch and we can continue from there.

I'm not touching rcore module for now.

michaelfiber commented 1 year ago

@raysan5 I changed PR #3311 to target rcore_platform_split and finished moving the listed functions. There were a couple of functions not on the list that I moved because their logic was highly platform dependent. I have a feeling more such functions are there in rcore but I just haven't had the chance to go through every function yet. When a function like that is found it needs to be moved from rcore to all submodules at the same time. So that might create logistical issues as we work on this.

I did all my work on Linux and it shows as the linux builds are the only ones working at this time. The windows build has been succeeding every time I push even though it was definitely actually failing so that needs more than just the github action to verify if it is working at all.

I think this is a good place to start divvying up the work since different people can work on different submodules simuiltaneously and the only real friction will be when changes need to be made to rcore.c or rcore.h.

I'm going to be away from my computer for a few days now but when I get back I can continue to hack away at some of this. I would probably start working specifically on PLATFORM_DRM since I have some experience with it and a variety of devices already running raylib projects to test with.

raysan5 commented 1 year ago

@michaelfiber Thank you very much for the big amount work put into this improvement!

I'm merging it right now and reviewing Windows build in the following days! Thanks!

ghost commented 1 year ago

@raysan5 @michaelfiber I can review PLATFORM_WEB (it's the one I got more experience with).

orcmid commented 1 year ago

@raysan5 @michaelfiber I have cloned the branch and my Windows x64 build using VC/C++ fails with a fatal error in compiling rcore.c.

 rcore_desktop.c(3) fatal error C1083: Cannot open inlude file: 'rcore.h': No such file or directory

Seems to be the case.

2023-09-21T00:02Z @ubkp @raysan5 @michaelfiber That problem is now fixed and a simple conformation test of raylib component compiles works fine.

ghost commented 1 year ago

@orcmid The previous version had rcore.h (https://github.com/raysan5/raylib/commit/f27454c57ae252808e03b5a0da074838c305357b) but @raysan5 moved its content to rcore.c (https://github.com/raysan5/raylib/commit/71a12171f768eb25053ef908732b4ce8fdf802f7). Likely still cleaning up after the change and missed it on rcore_desktop.c (L3).

raysan5 commented 1 year ago

@orcmid @ubkp Sorry, I broke it just trying some things, reviewed it now, it should work but still some issues...

I'm afraid this redesign could be more complex than I anticipated... let's see how evolves...

ghost commented 1 year ago

@raysan5 Would it be a bad idea to move rcore.h's contents to raylib.h?

raysan5 commented 1 year ago

@ubkp I prefer to avoid that, it will add too much uneeded content in raylib.h and probably a lot of confusion that use raylib.h as their reference cheatsheet. rcore.h is ok.

michaelfiber commented 1 year ago

Yeah I think its more likely rcore.h will go away than be integrated with raylib.h. With the #includes being there there isn't a ton of need for it except that I think the IDE experience might break down a bit without it. I could be wrong though.

michaelfiber commented 1 year ago

@raysan5 I moved raymath.h out of rcore.h because it was clobbering the #define RAYMATH_IMPLEMENTATION directive. Submitted as pr #3331

That fixed my build issues across platform related to not being able to find raymath functions.

EDIT: I closed that down because I was still committing to the branch. I am trying to rush things a bit too much.

I also moved the block of code that includes the submodules to the end of rcore.c because I realized I put it ahead of function definitions originally and that was creating issues compiling examples.

@ubkp did you encounter this as well in your work?

raysan5 commented 1 year ago

Please note that I pushed a commit to rcore master branch (https://github.com/raysan5/raylib/commit/a2b3b1ebe43cdf394b49f901cbaedb2c87959168). I'm afraid it was an issue that could cause a stack overflow in several platforms when using CompressData() function, it was actually breaking some of my tools in PLATFORM_WEB because emscripten sets a maximum size for stack of 65KB.

ghost commented 1 year ago

@ubkp did you encounter this as well in your work?

@michaelfiber Not yet. Testing PLATFORM_WEB is going really smoothly here. After fixing the initial compiling errors I jumped to test the examples and (aside from a few that are actually not working even on current master branch) just found two examples that needed review.

I'm still going through all functions but, so far, everything is working really well. You did an incredible job on the split. :+1:

ghost commented 1 year ago

@raysan5 @michaelfiber Sorry to ask this again, but, just to be absolutely sure, for example, in this case (rcore.c#L2124-L2132):

// Get touch position X for touch point 0 (relative to screen size)
int GetTouchX(void)
{
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB)
    return (int)CORE.Input.Touch.position[0].x;
#else   // PLATFORM_DESKTOP, PLATFORM_DRM
    return GetMouseX();
#endif
}

Should this be removed from rcore.c and moved to rcore_android.c, rcore_web.c, rcore_desktop.c and rcore_drm.c?

If yes, would you like me to do it for rcore_desktop.c and rcore_android.c as well while I'm at it? And also for the others functions in similar situation?

michaelfiber commented 1 year ago

@raysan5 @michaelfiber Sorry to ask this again, but, just to be absolutely sure, for example, in this case (rcore.c#L2124-L2132):

// Get touch position X for touch point 0 (relative to screen size)
int GetTouchX(void)
{
#if defined(PLATFORM_ANDROID) || defined(PLATFORM_WEB)
    return (int)CORE.Input.Touch.position[0].x;
#else   // PLATFORM_DESKTOP, PLATFORM_DRM
    return GetMouseX();
#endif
}

Should this be removed from rcore.c and moved to rcore_android.c, rcore_web.c, rcore_desktop.c and rcore_drm.c?

If yes, would you like me to do it for rcore_desktop.c and rcore_android.c as well while I'm at it? And also for the others functions in similar situation?

I think it should be split out. But I guess it depends on the end goal. Is the goal to eliminate all the preprocessor stuff related to platform from rcore.c other than the part where the submodule gets included?

ghost commented 1 year ago

I agree. Otherwise we end up with a 50/50 mix of both, which kind of defeats the purpose of the split.

raysan5 commented 1 year ago

Should this be removed from rcore.c and moved to rcore_android.c, rcore_web.c, rcore_desktop.c and rcore_drm.c?

Yes.

I think it should be split out. But I guess it depends on the end goal. Is the goal to eliminate all the preprocessor stuff related to platform from rcore.c other than the part where the submodule gets included?

Yes, we want to avoid mixing both.

ghost commented 1 year ago

@michaelfiber @raysan5 I believe I've finished reviewing PLATFORM_WEB and PLATFORM_DESKTOP. While going through the functions I also reviewed them for PLATFORM_ANDROID, but couldn't test it. I've updated the modules and functions tables on #3339 in case it's needed. Also finished the PR #3345 (batch 5), which is ready for merging. :+1:

raysan5 commented 1 year ago

@ubkp Thank you very much for all your work on this new project! I can continue from this point and double check everything.

ghost commented 1 year ago

@raysan5 Note: the functions I moved on the batches from rcore.c to the submodules I just left commented instead of deleting them: L1940-L1947, L2109-L2122, L2124-L2132, L2134-L2142, L2144-L2162, L2449-L2721. They can safely be removed after your review.

raysan5 commented 1 year ago

@ubkp Ok, thanks for letting me know!

michaelfiber commented 1 year ago

That leaves PLATFORM_ANDROID. In theory I can get that set up and test out building the examples and such. But probably not until the weekend at the earliest. Whichever one of us can get to it first should just make another issue for tracking status and informing the others that testing is underway. Hopefully getting the android dev side of it set up isn't too painful.

ghost commented 1 year ago

@michaelfiber @raysan5

While I went through #3339 I also checked the functions for rcore_android.c. Unless I missed something, I think everything is there.

Following these wiki instructions and after the #3360 small fix, I was able to successfully compile raylib for PLATFORM_ANDROID for ARM and x86 for both 32bit and 64bit.

Unfortunately, I could not compile the examples automatically using make (I don't think the Makefile supports that). But I was able to successfully compile a core_basic_window.c example manually by using the build script from the wiki.

However, the compiled apk requires Android 6+, and my device is stuck on 5.1.1. So I was not able to run it to confirm function. This was as far as I could get. But I'd say there's a pretty good chance it's working.

Edit: @raysan5 Feel free to merge #3360, I don't have other commits to add there.

michaelfiber commented 1 year ago

I have hit a nonstop stream of issues trying to build them for Android. I think because of problem with my distro that is going to require a reinstall. A few other things are broken too. So I won't be able to test the android stuff any time soon. @raysan5 @ubkp not sure what the best way to test will be. @ubkp If you can send me the APK I can test it I just can't seem to get building to work right right now.

ghost commented 1 year ago

@michaelfiber I didn't keep the vm where I compiled it. But I'll rebuild it and try to rollback the Android version to one that matches 5.1.1 so I can test it here. Don't worry about it. :+1:

ghost commented 1 year ago

Update: I managed to setup the environment again and successfully compiled raylib for PLATFORM_ANDROID without issues.

However, the apk file generated was actually malformed. This does not appear to be related to raylib but to the compiling setup of the program/game (confirmed this case because even a simple int main(void) { return 0; } is having linking issues).

I'm investigating further.

Edit 1: issue was #3364. Continuing testing now. Edit 2: managed to generate a well formed apk and was even able to install it. The game crashes on launch tho, but I'm still using Android-29, so I'll try to rollback versions now.

ghost commented 1 year ago

@michaelfiber @raysan5

Update: I'm going through all examples and things are looking pretty good. Although Waydroid is not perfect, it appears to be enough for confirming function. I should have all tested in about one to two days.

ghost commented 1 year ago

@michaelfiber @raysan5

  1. I've finished testing PLATFORM_ANDROID. The full test logs and results were posted on:

    3371.

  2. Basically everything is working. The only issues I encountered appear to be related to Waydroid. In summary:

    • :x: Mouse buttons are not working on raylib on Waydroid (they appear to be stuck on pressed).
    • :x: Gestures are not working on Waydroid.
  3. Thus, the mouse presses are the only thing I couldn't really test. So I compiled the examples/textures/textures_mouse_painting.c for Android 6+ bellow:

textures_mouse_painting.zip

  1. Could any of you please test it? If it works, then I believe PLATFORM_ANDROID is ready.
michaelfiber commented 1 year ago

@ubkp Sorry it took so long! I finally got to test this out.

It works on Android 12. Very nice seeing the Raylib logo appear on the install dialog and it runs fine EXCEPT it cannot save images. The save appears to succeed but the file doesn't end up anywhere. I assume there are a ton of permission things that have to be set up to allow stuff like that so my guess is this is mostly indicative of how annoying it is to develop for Android

But the actual picking of colors and drawing works fine.

ghost commented 1 year ago

@michaelfiber That's excellent, thanks for testing! Yeah, Android is a nightmare.

Well guys, I guess this is it then, everything looks ready. :+1:

raysan5 commented 1 year ago

@ubkp @michaelfiber do you think it's about time to merge this redesign into master? It's a bit scary for me but there are MANY rcore issues waiting and probably we shouldn't wait way longer...

I've been using rcore_platform_split branch for my tools development on desktop and web for a couple of weeks and everything seems to work ok.

ghost commented 1 year ago

@raysan5 I've tested/reviewed everything I could multiple times. The only systems I couldn't test was Windows and MacOS because I have neither. But if something slip through, shouldn't be anything major, otherwise we probably would have seen it by now happening on the examples.

I think it's ready for merge.

Fail-safe: Just in case, before merging, could do a point release with current master branch. Or just add tag on the repository in case it needs reverting.

raysan5 commented 1 year ago

@michaelfiber @ubkp Just reviewed all submodules, mostly formatting and code-gardening. I'm creating a tag, merging the branch and we can continue from there.

ghost commented 1 year ago

@raysan5 I believe you'll have to reapply this commit 7351240218849b1579db962f30e42fb982400c7e change on rcore.c.

Basically changing LINE 1137 on rcore.c from:

float aspect = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height;

To (as per 7351240218849b1579db962f30e42fb982400c7e):

double aspect = ((double)width/(double)height);