Uderzo
Uderzo is mostly an idea at the moment, a work in progress. Maybe some code will follow. Needless to say, named after the Asterix creator, the half from the genius duo that made the images (if I ever write a Elixir-based wordprocessor, it'll be called Goscinny. Promise).
The idea is to have an Elixir GUI in the following way:
- An executable that listens on an Erlang port and includes NanoVG
- A library in Elixir that roughly replicates NanoGUI
The reason to separate the vector graphics and the GUI are multiple:
- With the OpenGL stuff in a separate executable, it can crash without taking down BEAM. Restart strategies are possible;
- The interesting bits are kept in Elixir and thus will be modifiable, etcetera. There's a ton of fun stuff you can do when your widget library is truly native and you have a code environment that's dynamic. See Morphic, Lively Kernel, etcetera.
- I'd like to abuse the opportunity to see whether the widgets can be generated by a DSL, pulling all the stops on macros and maybe some more advanced parsing as well.
Steps:
- Build a basic wrapper executable
- Open a window, show the NanoVG Demo
- Receive a mouseclick
- Lots and lots and lots of hard work to tie everything together.
Setup
-
Have a bunch of stuff installed. Use
make -f setup.mk (linux|mac)to ensure that the outide things are in place. Thelinuxtarget is for Debian, so it might need tweaking on other distributions. Themactarget assumes homebrew is available. mix deps.getmix compilemix testshould now briefly open a window with the first frame. If that works, you can go to the next level:iex -S mixand runUderzo.Demo.runfrom there.
Note: I'm doing nothing yet to make this work on non-linux envs, but also nothing to make that hard. Nanovg is portable, and the wrapper executable should be minimal. It already works on Linux OpenGL under Xorg and Broadcom's VideoCore.
Protocol
To get started, we KISS and just use erlang term format. Later on we can have some hand-optimized RPC going on.
Messages are fully async, so if you want a response, you need to send a pid to receive the
response along. By convention, we send that pid as the last argument on a call and
return responses as {pid, response} - the graphics genserver then simply dispatches
that with send(pid, response) and we're in Elixir-land from there.
We use the direct names of the GLFW/OpenGL/NanoVG libraries, uncamelcased. Pointers to hold return values are converted to return tuples. E.g.
double mx, my;
GLFWwindow *window;
glfwGetCursorPos(window, &mx, &my);becomes
glfw_get_cursor_pos(window, self())
receive do
{mx, my} -> ....
endThere is ample room for crashing the graphics server - pointers to windows, etcetera, are directly returned as 64 bit numbers. As long as you just store and send them back as opaque handles, everything is fine. If you don't, you will have spectacular crashes.
Note that crashing the graphics server should be fine: processes can monitor the GenServer that will crash along, re-open the window, and simply resume the rendering loop. With a bit of luck, it should be just a flicker.
TBD: Keyboard, mouse callbacks. These will be sent to pre-registered processes that want them. On the other hand, for embedded setups, there's no thing like GLFW to do this and a separate process to process input would be just fine.