Posted November 27, 2020 by Clay Murray
Odin is a great programming language. It lives up to at least this part of its mission statement.
joy of programming
I hadn’t done any low level programming for a while before I started using Odin. Now I can’t get enough.
Odin, while great, does have its rough edges. You can expect that from a newer language with a smaller community. And because the creator Ginger Bill develops on a Windows machine there’s a few more rough edges in MacOS vs Windows. I am going to talk about one of the issues I ran into on Mac and how I solved it.
(Look I develop on MacOS. I come from a web background and like the OS and it’s ecosystem. Sue me.)
I am not sure of the root problem, but Odin isn’t passing certain structs properly. I think it has to do with the MacOS ABI wanting them passed as a 128 bit vector, but again I’m not sure.
This appears to be a major issue and it is, however we can work around it.
In my code and tool I use Raylib. Odin’s foreign system is awesome, making it super easy to include anything compiled to C code. I link to the raylib .a
file and done… In a perfect world. As mentioned, the calling convention is broken for a few structs. But as mentioned, there is a solution. It’s a simple solution. We can pass in our structs normally and modify our calls to pass in a pointer parameter for output instead of directly returning.
RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Returns the screen space position for a 2d camera world space position
Becomes
RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera, Vector2 *out); // Returns the screen space position for a 2d camera world space position
Before I was doing this a not smart way. I was modifying the entire raylib source to make these changes to the calls. This worked fine. I didn’t think much of it as I was building out Cute Exporter. Then it came time to start making sure it worked for windows. Windows doesn’t have these calling convention issues. Do I now need to maintain two different versions of raylib for MacOS and Windows solely to work around Odin? That could work but doesn’t seem optimal. Then I realized, Odin has a great foreign system we can do this better.
Instead of modifying raylib directly I made a small wrapper C library around functions that needed to change. I don’t even need to do it for every raylib function, only the broken ones. And in Windows we can import the raylib library code and be done.
In the C wrapper
#include "raylib.h"
Vector2 raylibext_GetScreenToWorld2D(Vector2 vec, Camera2D cam, Vector2 *input, Vector2 *result) {
Vector2 res = GetScreenToWorld2D(*input, cam);
*result = res;
return res;
}
I can then import my wrapper functions and work with them to have the same Odin calling convention in Windows or MacOS. (Odin also has a great system for OS specific code.)
//import_darwin.odin
//The _darwin part means this file only gets included and compiled when on a Darwin (MacOS) system.
when ODIN_OS == "darwin"{
foreign import raylib_host {
"libraylib.a",
"raylib_extended.o",
}
};
foreign raylib_host {
@(link_name="raylibext_GetScreenToWorld2D")
_GetScreenToWorld2D :: proc(raylib._Vector2, raylib.Camera2D, ^raylib._Vector2, ^raylib._Vector2) -> raylib._Vector2 ---;
}
GetScreenToWorld2D :: proc "c" (coord: raylib._Vector2, camera: raylib.Camera2D) -> raylib._Vector2 {
result : raylib._Vector2;
input := raylib._Vector2{coord.x, coord.y};
_GetScreenToWorld2D(input, camera, &input, &result);
return raylib._Vector2{result.x, result.y};
}
The advantage of this method being, I don’t need to maintain a different branch of my changes to raylib. If a newer version or bug fix come out I don’t need to worry about conflicts.
In the end it’s nothing too complex or crazy and that is fully owed to how flexible and awesome Odin is.