chrisdill / raylib-cs

C# bindings for raylib, a simple and easy-to-use library to learn videogames programming

Home Page:http://www.raylib.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[All] Consider wrapping raylib structs and methods into managed classes

Shadowblitz16 opened this issue · comments

Before submitting a new issue, please verify and check:

  • The issue is specific to Raylib-cs and not raylib
  • I checked there is no similar issue already reported
  • My code has no errors or misuse of Raylib-cs

Issue description

I came across raylib-cpp recently and was wondering if maybe a similar approach would be good for raylib-cs
https://github.com/RobLoach/raylib-cpp

Environment

NA

Issue screenshot

NA

Code example

    int screenWidth = 800;
    int screenHeight = 450;

    Window window = new Window(screenWidth, screenHeight, "raylib-cs - basic window");
    Texture logo = new Texture("raylib_logo.png");

    window.SetTargetFPS(60);

    while (!window.ShouldClose())
    {
        window.BeginDrawing();

        window.ClearBackground(RAYWHITE);

        var text = "Congrats! You created your first window!" 
        text.Draw(190, 200, 20, LIGHTGRAY);

        // Object methods.
        logo.Draw(
            screenWidth / 2 - logo.GetWidth() / 2,
            screenHeight / 2 - logo.GetHeight() / 2);

        window.EndDrawing();
    }

    // UnloadTexture() and CloseWindow() are called automatically using finalizer and IDisposable.

This would make the bindings rather opinionated and force users to write code in a specific way, potentially kneecapping non-standard/common architectures. I would suggest leaving this to downstream frameworks and libraries.

I believe we want Raylib-cs to be a direct bindings to raylib; choosing a design pattern should be left up to users.

The current approach with structs closely matches how raylib uses them. I have considered wrapping the structs into classes though having multiple types for the same thing makes it more confusing to learn/maintain.

That said, I want to continue improving the utils where possible and wrapper classes may be useful here in some places.

I think getting rid of structs is not possible, because we directly use them to interop with the native raylib library. Raylib c uses structs as well.

Heres an example:

        [DllImport(nativeLibName, CallingConvention = CallingConvention.Cdecl)]
        public static extern void ClearBackground(Color color);

This is the color struct:
https://github.com/ChrisDill/Raylib-cs/blob/master/Raylib-cs/types/Color.cs

Raylib doesn't support multiple windows as well:
https://github.com/raysan5/raylib/wiki/Use-multiple-windows

Writing a Window class in C# would require you to use a singleton.
Using finalizers doesn't work either since dotnet core. U will have to implement IDisposable and/or use the ProcessExit event in AppDomain:
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/finalizers

So here is an example of how this could look like:

using Raylib_cs;

public class Window : IDisposable
{
    //singleton
    private static Window? s_instance;
    private int _width, _height;
    private string _title;
    private static bool s_disposed;

    private Window(int width, int height, string title)
    {
        Raylib.InitWindow(width, height, title);
        (_width, _height, _title) = (width, height, title);
        s_instance = this;
        //in case someone forgets to dispose
        AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
    }

    public int Width { get => _width; set { _width = value; Raylib.SetWindowSize(_width, _height); } }

    public int Height { get => _height; set { _height = value; Raylib.SetWindowSize(_width, _height); } }

    public string Title { get => _title; set { _title = value; Raylib.SetWindowTitle(_title); } }

    public bool ShouldClose => Raylib.WindowShouldClose();

    public static Window Instance(int width, int height, string title)
    {
        if (s_instance is null)
        {
            return new(width, height, title);
        }

        (s_instance.Width, s_instance.Height, s_instance.Title) = (width, height, title);
        return s_instance;
    }

    public void BeginDrawing() => Raylib.BeginDrawing();

    public void EndDrawing() => Raylib.EndDrawing();

    public void ClearBackground(Color color) => Raylib.ClearBackground(color);

    public void Dispose()
    {
        //make sure the window can only be closed once
        if (s_disposed)
        {
            return;
        }

        Raylib.CloseWindow();
        s_disposed = true;
    }
}

This is the main method:

using Raylib_cs;

using Window window = Window.Instance(1280, 720, "My Game");

while (!window.ShouldClose)
{
    window.BeginDrawing();
    window.ClearBackground(Color.BEIGE);
    window.EndDrawing();
}

I don't really care about structs too much, the only thing I dislike is pointers and manual memory management.

I would love to see the whole library get rid of anything unsafe.
If someone needs a lower level c# raylib library auto bindings could be made,
they would be needed anyways for the safe version to wrap around.

I have thought about it some more and decided to try experimenting with the idea. I am cautious about making the project too opinionated but I can see the benefit in examples like what @JupiterRider has provided.

@chrisdill Yeah, I think Raylib-cs should be a binding only. So Creating a extra project for additional stuff could be useful or at least fun.

Yeah, changing the approach to the library (e.g. combining functions into classes and such) is not a good idea for this library, as it's better to keep it as close to the original as possible, so the same documentation could be used.

BUT changing the way it works to get rid of any unsafe code is absolute requirement. The fact I have to fiddle with the unsafe code was sadly a deal breaker for me...

Agree a more direct mapping/binding makes sense (as then it's easier to translate existing C++ examples/docs/etc...) and leave more abstraction to building on top of it.

However, as long as the structs size and positioning is the same that's how interop directly with raylib is done right? In that case, it could be nice to at least PascalCase the public struct field names to make them to align to C# style guidelines. See https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions#pascal-case

Yeah, changing the approach to the library (e.g. combining functions into classes and such) is not a good idea for this library, as it's better to keep it as close to the original as possible, so the same documentation could be used.

BUT changing the way it works to get rid of any unsafe code is absolute requirement. The fact I have to fiddle with the unsafe code was sadly a deal breaker for me...

@chrisdill Yeah, I think Raylib-cs should be a binding only. So Creating a extra project for additional stuff could be useful or at least fun.

Guys you know raylibcs-lo is a binding only library right?
It literally just generates bindings for C#

We don't have to use classes we can use structs but I would just use classes because they can be inherited and extended.

I think also requiring a window to keep track of resources like textures would be good too, since you can't really create gl assets without a window anyways..

using Raylib_cs;
Raylib.InitWindow(0,0, "Example");

var texture = Raylib.LoadTexture("myImage.png");
while(!Raylib.ShouldWindowClose())
{

    Raylib.BeginDrawing()
    Raylib.DrawTexture(texture,0,0);
    Raylib.EndDrawing()
}

Raylib.UnloadTexture(texture);
Raylib.CloseWindow();

would be this...

using Raylib_cs;
using (var window = new Window(0,0, "Example"))
{

      var texture = new Texture(window, "myImage.png"); // Texture is kept track of by window.
      while(!window.ShouldClose())
      {
            window.BeginDrawing()
            texture.Draw(0,0);
            window.EndDrawing()
      }


}   // Window disposed with IDisposable and Texture unloaded
// If Window is not disposed here window is disposed with AppDomain.ProcessExit Event and ICLRPolicyManager::SetTimeout

This makes raylib-cs even more safe

That would amazing!

commented

i like Raylib.DrawTexture(texture,0,0); more but i think automatically unloading texture is a good idea because i sometimes forget to unload Textures

i like Raylib.DrawTexture(texture,0,0); more but i think automatically unloading texture is a good idea because i sometimes forget to unload Textures

Not a bad idea, I think just changing it to this:

/// <summary>
/// Texture2D type<br/>
/// NOTE: Data stored in GPU memory
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public partial struct Texture2D : IDisposable
{
    /// <summary>
    /// OpenGL texture id
    /// </summary>
    public uint id;

    /// <summary>
    /// Texture base width
    /// </summary>
    public int width;

    /// <summary>
    /// Texture base height
    /// </summary>
    public int height;

    /// <summary>
    /// Mipmap levels, 1 by default
    /// </summary>
    public int mipmaps;

    /// <summary>
    /// Data format (PixelFormat type)
    /// </summary>
    public PixelFormat format;

    /// <summary>Unload texture from GPU memory (VRAM)</summary>
    public readonly void Dispose() => Raylib.UnloadTexture(this);
}

should work, however I wouldn't know if this is a disired feature.

commented

if you want the binding to be as close to Raylib as possible then this is unnecessary, but then again this is C# and this is good reminder to the user that this can cause memory leaks if not being unload correctly

i like Raylib.DrawTexture(texture,0,0); more but i think automatically unloading texture is a good idea because i sometimes forget to unload Textures

I don't like static Raylib methods because it means we can accidentally call functions without initializing raylib.
I think at least they should be tried to a Window class that we create by using the constructor or a static singleton Instance() method.

The only reason to use the constructor way is if raylib-cs supports multiple windows

see this for an example on why static methods are not good... #166

The idea of using the window to make sure raylib is initialized seems interesting. Though not sure what the full api for that approach would look like and I would only want it to apply to methods that need raylib initialized if I would add something like it.

I don't want to spread all the methods that require a window across all the resource types but moving a large chunk of graphics methods into a window class is not great either. Maybe additional module classes that can be accessed from the window? Not quite sure what that should look like but worth considering.

To clarify the goal of Raylib-cs. I don't mind adding utilities and maybe wrapper classes but there should be a good balance of being both close to raylib while adding just enough to be nice to use in C# without limiting you to a specific approach.

I want to create a pull request for it :)

Pls give me some feedback #188

@MrScautHD Update on this issue. Over time I have looked at various options for this but haven't found one that proved suitable to be directly part of this project. Instead I have decided it is better to leave this to downstream frameworks and libraries as was originally suggested.

I still think the idea is useful and I am interested to see any projects try to implement these ideas. However I am closing the issue since it is now out of scope for this project.