Clixir
Disclaimer: for now, this is design documentation, actual implementation may have differences. See
the code, lib/uderzo/clixir.ex
and the associated test for actuals
We write the C code in Elixir and use macros to generate both the Elixir bindings and the C code. This idea was blatantly stolen from Squeak Smalltalk. As we aim for a very low level interface, all the functions will have the same structure in C: unmarshall arguments, make call, marshall return values; so “Clixir” can start out simple.
defgfx glfw_get_cursor_pos(window, pid) do
cdecl "GLFWwindow *": window
cdecl erlang_pid: pid
cdecl double: [mx, my]
glfwGetCursorPos(window, &mx, &my)
{pid, {mx, my}}
end
will result in this Elixir code (both a regular and a blocking synchronous version are generated, although I think the sync version shouldn’t be used ;-)):
@spec glfw_get_cursor_pos(integer, pid) :: none
def glfw_get_cursor_pos(window, pid) do
GraphicsServer.send_command(GraphicsServer, {:glfw_get_cursor_pos, window, pid})
end
@spec glfw_get_cursor_pos_s(integer) :: {float, float}
def glfw_get_cursor_pos(window) do
glfw_get_cursor_pos(window, self)
receive do
{mx, my} when is_float(mx) and is_float(my) -> {mx, my}
end
end
and the following C code:
static void _dispatch_glfw_get_cursor_pos(const char *buf, unsigned short len, int *index) {
GLFWwindow *window;
erlang_pid pid;
double mx, my;
assert(ei_decode_longlong(buf, index, (long long *) &window) == 0);
assert(ei_decode_pid(buf, index, &pid) == 0);
glfwGetCursorPos(window, &mx, My);
ei_encode_version(response, &response_index);
ei_encode_tuple_header(response, &response_index, 2);
ei_encode_pid(response, &response_index, &pid);
ei_encode_tuple_header(response, &response_index, 2);
ei_encode_double(response, &response_index, mx);
ei_encode_double(response, &response_index, my);
write_response_bytes(response, response_index);
}
The idea is to write a Clixir wrapper for every NanoVG/GLFW/OpenGL function under the sun.
Performance
Performance of the protocol should be very good, for the following reasons:
- the
ei_decode_..
family seems to do very little copying and is efficient by keeping a pointer in a buffer that moves up; ideally, this means that a message is scanned in a single loop the length of the message; - dispatching is done using a
gperf
generated hashtable. These perfect hashtables are very fast, usually requiring just two memory lookups to find the function pointer to dispatch to. - most of the tight rendering code is (and should be kept) async - you’re just sending messages over a local file descriptor pipe and keeping the pipe filled when drawing a frame should be very easy.