Chicken-Scheme FFI Examples

I'm currently working on refactoring the FFI implementation for the Rebel Game Engine. It was previously written using the Bind chicken egg but I wanted to have more control over the implementation by using the low level foreign functions.

To help me better understand I made some examples that has the basic FFI implementations that I'll be needing for my project.


foreign-lambda example

Let's say we have a structure Vec3 and a function Vec3Create that we want to access from chicken-scheme.

typedef struct Vec3 {
    float x;
    float y;
    float z;
} Vec3;

Vec3* Vec3Create(float x, float y, float z)
{
    Vec3* v = (Vec3*)malloc(sizeof(Vec3));
    v->x = x;
    v->y = y;
    v->z = z;
    return v;
}

We could use foreign-lambda to bind to the function:

(define vec3_create
  (foreign-lambda
    (c-pointer (struct "Vec3"))   ; Return type, a pointer to a struct object of Vec3
    "Vec3Create"                  ; Name fo the function
    float float float))           ; The three parameters (x,y,z) to pass to the function

This would allow us to call the vec3_create function like so:

(vec3_create 1.1 2.2 3.3)

foreign-lambda* example

Let's bind another C function Vec3Print.

void Vec3Print(Vec3 v)
{
    printf("Vec3 to print: (%f, %f, %f)\n", v.x, v.y, v.z);
}

We could also use foreign-lambda* (Notice the asterisk). This is similar to foreign-lambda but accepts C code as a string.

(define vec3_print
  (foreign-lambda*
    void                          ; The return type
    (((c-pointer (struct "Vec3")) a0)) ; The parameter to pass, a pointer to a Vec3 object
    "Vec3Print(*a0);"))           ; The C code in string from
                                  ; Vec3Print accepts a non pointer, we dereference it

(vec3_print (vec3_create 1.1 2.2 3.3))   ; Creates a vec3 and prints it

Inline C code in foreign-lambda*

Here's another example using foreign-lambda*. This time there is no predefined C function, but instead we define the code inside the lisp function's body.

(define vec3_zero
  (foreign-lambda*
    (c-pointer (struct "Vec3"))
    ()                            ; Empty variables
    "Vec3* v = (Vec3*)Vec3Create(0.0f, 0.0f, 0.0f); 
    C_return(v);"))               ; Instead of "return", we use "C_return".

(vec3_print (vec3_zero))          ; Calls vec3_zero and prints the returned Vec3

Note that due to obscure technical reasons C_return must be used instead of return when returning a value. More info about this here.


Free-ing Vec3 pointers

Since Vec3Create allocates memory for a Vec3 struct using malloc, it's a good idea to free this when we are done using it. To do this we could bind a function to free.

(define free (foreign-lambda void "free" c-pointer))

(let ((v (vec3_zero)))
  (vec3_print v)
  (free v))

Setting up getters and setters

If we want to have access to variables of a struct object. We could do something like this:

(define vec3_x
  (foreign-lambda*
    float
    (((c-pointer (struct "Vec3")) a0))
    "C_return(a0->x);"))

(display (vec3_x (vec3_create 8.8 8.8 8.8)))

Now this is fine but it'll be a pain to specify an accessor for every variable. A better solution is to use the foreigners egg which allows the use of macros that will make our lives easier.

(import (chicken foreign))
(import foreigners)

;; Set up the accessors for Vec3 struct
(define-foreign-record-type (vec3 Vec3)
  (float x vec3_x vec3_x!)   ; vec3_x is a getter, vec3_x! is a setter
  (float y vec3_y vec3_y!)
  (float z vec3_z vec3_z!))

(let ((v (vec3_create 4.4 5.5 6.6)))
  (display (vec3_x v)) ; Display value of x
  (newline)

  (vec3_x! v 7.7)      ; Set x to 7.7
  (display (vec3_x v)) ; Display value of x
  (newline)

  (free v))

Binding enums

The foreigners egg also allows for the binding of enums using define-foreign-enum-type. Say we have an enum declaration Keys.

enum Keys {
  UP = 0,
  RIGHT = 1,
  DOWN = 2,
  LEFT = 3
};

:::scheme
(define-foreign-enum-type (keys int)
  (keys->int int->keys)
  ((up keys/up) UP)
  ((right keys/right) RIGHT)
  ((down keys/down) DOWN)
  ((left keys/left) LEFT))

(display keys/right)
(display keys/down)

These are the basic FFI implementations that I have explored. It should be enough for most uses. The example project with working code can be found on Github.

Also, check out the chicken-bind egg, it already has all of the code above conveniently in one package. If you don't need full control and just want a simple FFI solution then this is what you need.

#5 - Switching from C/C++ to C

After the recent changes to the lisp side of my engine, I took some time to review the C/C++ side. You'll notice that I have written C/C++ and that's because my codebase uses both of them.

When I started my project, I initially intended for it to use just pure C, as this is the one I'm more familiar with. But over time some C++ features crept in. Features like namespacess, bools, and function overloading proved to be useful so I kept using them. Now my code uses C concepts with new nifty C++ features.

Now, I could have just continued with this approach. It works, after all. But I wondered if I should just stick to C and drop C++ altogether. My thinking is that sticking with just one language would make the code simpler as I only have to use it's subset of features. I know it's not a solid reason but I figured it's better to act now while it is still early.

switching-from-c-c---to-c-01

For the most part, dropping C++ was easy. Most of the difficulty I encountered was making sure the changes worked on all three supported platforms. There was a situation when I thought I was done only to find it doesn't work on Mac and Windows. I had to slowly re-apply the changes just to see where exactly things went wrong.

What's funny is that I learned that I was using a lot more C++ features than I thought. Namespaces and default arguments are some that really surprised me. I always assumed they were supported on both languages. This just proves to me that I still have a lot to learn with these languages.

I also took the chance during the transition to switch from GLM, an OpenGL Mathematics lirary using C++, to CGLM a similar library that uses C. It is claimed that the latter is more optimized and, with it being in C, is easier to integrate with my codebase.

While these changes did not do much in terms of progress, I am happy that my codebase now feels tighter and more coherent. I'm hoping to work on something engine-related next.

If you are interested to check out my still-under-construction game engine, you can do so here.

#4 - Following Lispy conventions

following-lispy-conventions-01

I was adding new Lisp functions to my game engine when I noticed that I had functions that had a naming scheme that were inconsistent with others. For example, I had ones that create objects like sprite_create and shader_create but this one function I named make_vec3. I proceeded to rename make_vec3 to vec3_create. Not only is it consistent with other names but it made me realize that having a pattern of object_verb makes it easy to parse the function and what it does.

This made me wonder if there are other ways I could improve which led me to this page about variable naming conventions for Scheme. I learned that the language employs a rather effective yet simple naming convention for functions and variables. I've noticed them before but never really thought about their usefulness.

For example, adding a ? prefix easily indicates that the function, when called, will always return a boolean value. I looked at my code and I had the function is_key_down. Changing it to key_down? looked weird at first but I liked how it made the function name shorter and the ? prefix made it easy to spot and parse.

Okay, cool! What's next?

Adding a ! indicates a function that mutates data. Most commonly used for setting a variable. I saw I had variables like set_vec3_x, to which I changed to vec3_x!.

This went on as I continue to find improvements. Here's a list of all the naming convention changes that I've made:

The change From To
: infix for namespaces. vec3_create vec3:create
? postfix for boolean functions key_down? key:down?
! postfix for destructive functions set_camera_position camera:position!
% postfix for low level functions free free%
% prefix and postfix for low level variables shader-pointer %shader-pointer%
* prefix and postfix for global variables cube-shader *cube-shader*

Again, these new changes felt weird at first but I quickly became accustomed the more of these functions I changed. The code became easier to scan as there are now key characters for my eyes to easily latch onto. Something to appreciate especially with multi-level nested expressions.

Here's how the code now looks like with the new functions:

(define MOVEMENT_SPEED 0.001)

(define *cube*)
(define *cube-shader*)
(define *cube-positions*)

(define (init)
  (set! *cube*
    (cube:create "assets/textures" "awesomeface.png"))
  (set! *cube-shader*
    (shader:create "shaders/simple-3d.vs" "shaders/simple.fs"))
  (set! *cube-positions*
    (list (vec3:create 0 0 0)
          (vec3:create 1.25 0 0)
          (vec3:create -1.25 0 0))))

(define (update)
  (window:clear)

  (let* ((main-camera (camera:main))
     (current-projection (camera:projection main-camera))
     (camera-pos (camera:position main-camera)))

    (when (key:up? KEY_C)
      (if (= current-projection PERSPECTIVE)
      (camera:projection! main-camera ORTHOGRAPHIC)
      (camera:projection! main-camera PERSPECTIVE)))

    (when (key:down? KEY_A)
      (vec3:x! camera-pos
          (+ (vec3:x camera-pos) MOVEMENT_SPEED)))
    (when (key:down? KEY_E)
      (vec3:x! camera-pos
          (- (vec3:x camera-pos) MOVEMENT_SPEED)))
    (when (key:down? KEY_COMMA)
      (vec3:z! camera-pos
          (+ (vec3:z camera-pos) MOVEMENT_SPEED)))
    (when (key:down? KEY_O)
      (vec3:z! camera-pos
          (- (vec3:z camera-pos) MOVEMENT_SPEED))))

  (for-each
   (lambda (position)
     (let ((%tint% (vec3:create% 1 0 1)))
       (cube:draw *cube* position 1 1 %tint% *cube-shader*)
       (free% %tint%)))
   *cube-positions*)

  (window:swap))

I also changed my C functions to reflect a NamespaceVerb convention. I could have used namespace::Verb but the FFI that I use to communicate with C cannot parse C namespaces. So instead of Shader::Create, I am left with ShaderCreate. This is unfortunate, but I'm fine with it since the lisp scripting side of the engine will be the most prominently used (Plus, Raylib also uses this convention for their functions).

I am happy that I was able to do these changes early. Because of this, readability of my code has increased, something I worried about when I first started implementing scripting.