Raylib 6.0 bindings

As I already commented in another post, I am working on some bindings for raylib. I open this one in order to have a dedicated thread for it.

It should be noted that my experience in Ada is limited, and believe that I have made some unwise decisions. Hopefully someone can bring some of them up :slight_smile: .

For now, I have binded:

  • rcore (windowing & input & timing & etc)
  • rcamera
  • rshapes
  • rtext (partially)

Design notes

I have chosen to split the library into packages as follows:

Types and conversions → Raylib.Types ( conversions are To_C and To_Ada )
Function bindings → Raylib.{Window,Draw,Time,Camera,Shapes,Text}

Many conversions are mostly just direct and provided for uniformity. The remaining ones are just for converting between types.

Where a const char * was received, I changed them to take String and the use Interfaces.C.Strings.New_String and Interfaces.C.Strings.Free to convert to a C string.

One that I am concerned about is on functions that take a pointer to the first of several elements. e.g Raylib.Shapes.Check_Collision_Point_Poly.

I have modeled it this way:

---- Raylib.Types
--
-- Polygon[3D] - Set of points representing a polygon
--
   type Polygon is array (Natural range <>) of aliased Point
      with Convention => C;
   type Polygon3D is array (Natural range <>) of aliased Point3D
      with Convention => C;

---- Raylib.Shapes
   function Check_Collision_Point_Poly
            (Point : Vector2;
             Poly : Polygon) return Boolean is
      function CheckCollisionPointPoly
        (point : Vector2;
         points : access constant Vector2;
         pointCount : Int) return Bool;
      pragma Import (C, CheckCollisionPointPoly, "CheckCollisionPointPoly");
   begin
      return To_Ada (CheckCollisionPointPoly (Point,
                                              Poly(Poly'First)'Access,
                                              Int (Poly'Length)));
   end Check_Collision_Point_Poly;


Roadmap

The bindings should be close to ready for use in basic 2D games. If anyone would like to give some feedback or contribute, it would be much appreciated.

This are some steps in the roadmap:

  • Add a Math module equivalent to raymath
  • Add support for more modules
  • Package for alire (for this one a more experience hand would really help)

Repository

As a final note, I have used some generative AI for generating some bindings, but its use has been limited to basically copy-pasting and trivial changes

You might want to talk to @Lucretia about the best way to proceed, as he’s been doing the SDL binding, IIRC.

Though, personally, I prefer very thick bindings (i.e. all Ada from the POV of usage, with as little leak from abstractions as possible).

The best way forward for binding types is to remodel them and then come back later and restrict them when you find out their range, or do that at the same time if you can be bothered. Most C libs don’t really define ranges as they can’t really be bothered.

But essentially, instead of just having generic unsigned_32, define new types of convention C, but that also means you need a well designed C API which is consistent where these types are passed, not all are.

Then obviously there are strings. The biggest issue here is the fact that conversions are done character by character which isn’t exactly fast and there’s no DMA way to do it, you’d literally need a DMA controller just to handle it if there was.

So, where strings are involved, you can wrap them with thicker bindings, i.e. passing in String and converting to chars_ptr, char_array and vice versa and remake all your strings so you don’t keep doing conversions especially inside any tight loops.

I still haven’t really worked out a good way around strings yet. Because inside any loops, you might want the C representation rather than Ada. Ada’s string conversions are not fast.

I’m not saying Ada is an outlier here, it’s not, all languages are slow when dealing with text and files and I/O to screen is a file. When I worked in games, I was logging to a file on every frame and it was slow af, disabled the logging and the fps went right back up, that was C++.

But you also need to understand that Ada does something else, it copies a lot, especially strings. If you return a string, that return value gets COPIED on return, it might even get copied multiple times.

Also, GNAT will convert a string literal to whatever it’s being passed to, so if it’s being passed to a C function, it’ll convert it IIRC.

Converting strings is perfectly OK when it is a string. But when a C library uses char * for a buffer instead of void * then bindings should treat it as an in-place buffer of Strorage_Element and never copy it. E.g. if you are responsible for the buffer, you simply pass the address of the array element down to C and return back Last to indicate the updated chunk as the standard library does, e.g. Ada.Text_IO.Get_Line.

I have updated my low-level bindings to Raylib 6.0: Update for Raylib 6.0 Ā· Fabien-Chouteau/raylib-ada@5751245 Ā· GitHub available in Alire: Raylib 2.0.0 (#1909) Ā· alire-project/alire-index@ccc1c91 Ā· GitHub

Hi,

I have taken a look at sdlada and it is really cool :slight_smile: . Probably will make use of it in the future. For the bindings, I have copied your approach of dividing into the Coordinate and Dimension types.

Looks like a good idea to me, I am also taking that approach. At least to avoid programmer error and help to define intent it should be useful.

For the strings, since there are few and small in use and in probably non-critical places for the meantime that should be not an issue for me.

Note that you can do that in a safe way:

type Count is new Interfaces.C.unsigned; -- Still a C type

Better is:

type Count is new Interfaces.Unsigned_32; -- Or whatever size it needs.

That depends solely on how C declares it. If C says unsigned then it is Interfaces.C.unsigned, because, for whatever reason, they wanted to make it machine-dependent. If it says uint32_t or something alike then it is Interfaces.Unsigned_32.

On this, how do you generally approach when the type is of the same name that the variables that you are naming? I find it difficult to find good names, specially in cases like this.

I have no good answer. I tend to add some qualifier: File_Length, Parameters_Count.

That would be better if possible. Bad thing that many programs just assume that unsigned int refers to 32-bit without stating it :frowning: . I think I will stick with Interfaces.C.unsigned for unsigned int as cited by @dmitry-kazakov.

Another quick question. Is it possible to expose the ā€œ+ā€, ā€œ-ā€, etc functions for all types in a given package without polluting the namespace with other elements?

e.g. make the following work, where Value is added 1.

package A is
   type A is new Integer;
end A;

-- Other File
with A;

package B is
   procedure P (Value : A) is
   begin
      return Value + 1;
   end P;
end B;

There is use type clause.

You must also consider yourself in either of two camps: use-lovers and use-haters. So make your design use-clause-friendly or else ask the government to reduce VAT on extra wide displays. :grinning:

Be careful with renaming. Renaming is broken in Ada:

  • Renaming introduces name conflict even if conflicting names resolve to the same entity. You will see that on example of exceptions when Data_Error gets suddenly invisible if you use-ed standard library packages renaming it.
  • Renaming ignores constraints (subtypes). When you rename to a constrained subtype or to different array bounds (for index sliding) you are asking for trouble:
   S : String (4..8) := "4567";
   subtype Sliding is String (1..4);
   T : Sliding renames S;

T (1) is Constraint_Error. Subtype indication in a renaming is basically a lie.

Note that renaming creates an object. This is why renaming a function call does its result. It is not a lazy expression re-evaluated on each reference as in FORTRAN. In Ada 95 times renaming a result was used in hope that the compiler will not copy the result and deploy a managed reference instead:

   S1 : constant String := Get_Line; -- Read line, copy it
   S2 : String renames Get_Line;     -- Read line, return a managed reference

In these days it would be same, I guess.

BUT. Always rename[*] formal types of a generic. Generics are complicated. GNAT and/or the language always has bugs related to visibility in generics. If GNAT refuses to recognize a formal type, use its renaming.

[*] Types are renamed using subtype S is T.

Try to use names that ā€˜make sense’; there are several ways to overcome this sort of name-clash.
One is to have a set of packages where the type is Instance and the package full-name describes the what — e.g. in package GUI.Common.Button having a type, Instance; in these sorts of situations, I rather like naming parameters Object, so you might have Procedure Init( Object : out Instance; Parent : in out GUI.Base.Instance'Class; Text : String; X, Y, H, W : Natural );. (I’m not 100% in the type Instance camp, but it is acceptable in well-segmented & consistent libraries.)

This is what Use type Whatever; does.
But you can also consider using use in the best scope that uses it; I’m not in the ā€˜haters’ group, but not the ā€˜lovers’ either, let how much you use/depend on the namespace to guide your use of use: if, for example, you’re writing a library that does string manipulations, then Use Ada.Strings.Fixed is likely going to be appropriate, if you’re using some geometry package in some GUI, then perhaps in a declare where the operations are needed, if there’s a lot of the use of that, than the body of the subprogram, if many subprograms than the translation-unit itself.

hmmm. :thinking: I take note.

I finally put the whole bindings into a single file and package Raylib.

I guess that the preferred approach will be to use use type Coordinate, etc whenever possible. Else you could just use the whole Raylib, which would make for very close behavior to C. :person_shrugging:

Interesting.
I’m working on a library myself, and it’s got quite a lot of parts, so I’m sometimes using a lot of other dependencies, consider in an package body:

Function Get_Bytes(Name : String) return String is
  Use System.Storage_Elements;

  Package IO is new Ada.Direct_IO( Storage_Element );
  Use IO;

  Function Get_File return File_Type is
  Begin
     Return Result : File_Type do
       Open( File => Result, Mode => In_File, Name => File_Name );
     End return;
  End Get_File;

  File : File_Type:= Get_File;
Begin
  Return Result : String(1..Natural(Size(File))) do
    For X in Result'Range loop
      Declare
        C : Character renames Result(X);
        E : Storage_Element with Import, Address => C'Address;
      Begin
        Read( Item => E, File => File );
      End;
    End loop;
    Close( File );
  End return;
End Get_Bytes;

As you can see, I’ve omitted prefixing the internals with System.Storage_Elements and IO via use, ā€˜factoring’ them to the outermost scopes of the function, yet having these namespaces exposed in the implementation does not ā€˜bleed’ into the rest of the functions in the package. (Yes, I could have used Character directly on Direct_IO’s instantiation, or just used Text_IO, but the point was giving some sort of example.)