This is a messy document about some of the issues and thoughts I had while working on breakout in Elixir.
I initially tried to move the resource manager to a separate application in the supervision tree, but the message passing slowdown was too significant. I think this was largely because I was trying to get the resource information (textures/shaders) in the render loop, which needs to be pretty fast, as it’s executed every few milliseconds. This is a pretty obviously bad place to try to separate applications, but you live in learn.
I still think there are ways to structure things as separate
applications to good effect, but I’m not sure exactly how to do that
yet. The main issue is having a single rendering context, I think. This
can actually be worked around with wxErlang, via set_env/1
and get_env/0
,
I think, but I’m not sure how useful it would be due to the message
passing latency. I did, however, have some potential success with
Task
s.
My only test here was with matrix multiplication, which is a pretty easy thing to split up into tasks. I’ve previously done some tests with this kind of task-spread-out in my ((also) very bad) ray tracer in Elixir. This kind of math is easily parallelizable. My profiling showed that it definitely sped up matrix multiplication calls, but the new biggest bottleneck was message passing. It didn’t seem to slow things down, but also didn’t really speed things up. I think it’s a reasonable place to spread things out, though.
This is maybe the biggest issue I had, although that’s mostly self-inflicted. I wanted to start with a mostly straight-forward port from the C++ source, with the intention of “Elixir-izing” it after. Hopefully I’ll make a post about that in the near future.
Updating deeply nested state, especially when it’s in deeply nested branching logic, is not fun. I’m curious to see how managing state with ETS, for example, would improve things - both in terms of code quality and potentially performance.
@icefoxen on a graphmath issue pointed out that guards on function definitions seem to help with performance. They noticed Wings3D does this, and the results are pretty significant. In my testing with a 4x4 matrix multiplication function, where the matrix is, implemented as a four-element tuple each containing four-element tuples, I found the following:
The speed improvement is nearly 1ns per call (roughly 1.2us vs 0.3us). When doing ~60k calls per second to the matrix multiplication function, that’s the difference between that function taking ~15% of processing time vs ~4%. Pretty impressive!
I should guard more. It feels slightly awkward to write
@spec
s and simple “is_type” guards, but it seems worth
it.
One of the main issues I ran into was figuring out silent failures. This is pretty vague and general, but there were a lot of situations where I felt like I should’ve had some kind of louder error, but the application would just crash.
Huh, wait, I really should’ve gotten some messages..
While writing this I realized I must be doing something wrong. Turns out, I had this:
@impl :wx_object
def terminate(_reason, state) do
Supervisor.stop(Breakout.Supervisor)
{:shutdown, state}
end
Which, in retrospect, obviously just eats any errors that come up. I just wrote it once at some point and didn’t think about it. It definitely made figuring out most errors much more difficult.
Ain’t that just the way.
This isn’t super relevant, but implementing a (partial) PNG parser was very pleasant. In another project, I’ve written a 3D model loader, which was also nice to write. These types of things are definitely a joy to write in Elixir (or Erlang), and I wish I had more of a reason to publish packages for blob parsing in general. If you have any requests, let me know!
One of the goals of this project is to see what Elixir can do in the video game space without the use of NIFs. I’m not really sure why - everyone has said it’s a bad idea. And really, it probably is a bad idea. I’m restricted to wxWidgets and OpenGL. It makes a lot of things more difficult or impossible. I have to recreate existing things. The list goes on.
However, this has been pretty positive, I think. Elixir isn’t the best at number crunching by itself, but it’s better than I thought. I’m excited to keep trying things out, and seeing what can be done with Elixir. At some point I’ll need to use NIFs and other packages, but this has been a good experience overall.
Some things I plan on working with next:
:gl
from
Erlang/OTP, but it would be nice to be able to use Metal, Vulkan, and
DirectX for rendering.