LibGodotConnector (Elixir)
This is a proof-of-concept showing how to interface Elixir with LibGodot using NIFs.
Architecture
- Elixir to Godot: The
LibGodotmodule provides functions likestart/0anditeration/0which call into the C++ NIF. It also providessend_message/2, which enqueues messages for Godot to consume via theElixirBussingleton. - Godot to Elixir: Godot can call
ElixirBus.send_event(kind, payload)to push events back to the subscribed Elixir process. You can "subscribe" a process usingLibGodot.subscribe(self()).
How to build
-
Ensure you have built
libgodotin the root directory. - Build the NIF via Mix (recommended):
cd samples/elixir_sample
mix deps.get
mix compile
This uses elixir_make and the local Makefile to drive a CMake build.
Optional: use precompiled NIFs from GitHub Releases
If you publish precompiled artefacts (tarballs) to GitHub Releases for the current version,
mix compile can download them instead of building locally.
cd samples/elixir_sample
mix compile
To override the default URL template, set LIBGODOT_PRECOMPILED_URL to a template containing
@{artefact_filename}.
To force a local build (skip download attempts):
export LIBGODOT_FORCE_BUILD=1
mix compileUsing this as a dependency (local + Hex)
This project is set up to work like a typical Hex package that ships precompiled NIFs.
Key idea: end-users installing from Hex should not need C++ tooling. During mix compile, elixir_make will:
-
Determine the current target triple (see
LibGodotConnector.Precompiler.current_target/0). -
Compute the expected artefact filename (
@{artefact_filename}in the URL template). -
Download and unpack the tarball into the dependency's
priv/directory. -
Optionally verify checksums using
checksum-lib_godot_connector.exsif it is present in the Hex package.
If the precompiled artefact is missing/unavailable, elixir_make falls back to building locally (unless LIBGODOT_FORCE_BUILD=1 is set).
Test "install elsewhere" locally (without publishing)
- Create a new scratch Elixir project somewhere else:
mix new /tmp/test_libgodot_dep
cd /tmp/test_libgodot_dep-
Add a path dependency to this sample in
/tmp/test_libgodot_dep/mix.exs:
defp deps do
[
{:lib_godot_connector, path: "/Users/dragosdaian/Documents/appsinacup/libgodot/samples/elixir_sample"}
]
end- Run:
mix deps.get
mix compileThis uses the exact same compilation/precompile logic that Hex would.
Test the precompiled download flow locally
You can simulate GitHub Releases by hosting the precompiled tarballs over HTTP locally.
-
In
samples/elixir_sample/, build a precompiled archive for your current machine:
cd samples/elixir_sample
mix deps.get
mix elixir_make.precompileThe task prints the path of the generated archive (under your elixir_make cache dir).
- Serve the directory containing that archive:
cd <the-folder-with-the-generated-.tar.gz>
python3 -m http.server 8080- In your scratch project, point the URL template to that server and compile:
export LIBGODOT_PRECOMPILED_URL='http://localhost:8080/@{artefact_filename}'
mix deps.clean --all
mix deps.get
mix compile
If everything matches, mix compile will download instead of building.
Publishing precompiled artefacts (Hex + GitHub Releases)
To publish this to Hex in a way that "just works" for users:
-
Ensure
mix.exsversion (and release tag) match (e.g.v4.5.1). -
Build per-target/per-OTP (NIF version) tarballs on CI using
mix elixir_make.precompile. -
Upload the
.tar.gzartefacts and their checksum sidecar files (e.g..sha256) to GitHub Releases. -
Generate and commit
checksum-lib_godot_connector.exsso Hex installs can verify downloads:
mix elixir_make.checksum --all- Make sure the checksum file is included in the Hex package (this project already includes it when present).
Note: artefacts are keyed by target triple and Erlang NIF version. If you want to support multiple OTP versions, you need to publish artefacts for each of those NIF versions.
- Alternatively, build the NIF with CMake directly:
cd samples/elixir_sample
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build buildFor a debug build:
cd samples/elixir_sample
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build- Run the Elixir code:
mix compile
iex -S mix
To run it as a normal OTP application (starts LibGodot.Driver under supervision):
mix run --no-haltExample Usage
# In IEx
LibGodot.subscribe(self())
{:ok, godot} =
LibGodot.create([
"godot",
"--headless",
"--quit"
])
# If you want to be explicit about which libgodot to load:
# {:ok, godot} = LibGodot.create("../../build/libgodot.dylib", ["godot", "--headless"]) # macOS
# {:ok, godot} = LibGodot.create("../../build/libgodot.so", ["godot", "--headless"]) # Linux
LibGodot.start(godot)
# Run a few frames (or drive this from a GenServer timer)
LibGodot.iteration(godot)
LibGodot.iteration(godot)
LibGodot.shutdown(godot)
# Wait for message
flush()
# Should see: {:godot_status, :started}
Driving iteration/1 from Elixir
For convenience, this sample includes a tiny GenServer that owns the Godot instance
and calls iteration/1 on a timer:
iex -S mix
{:ok, _pid} =
LibGodot.Driver.start_link(
interval_ms: 16,
notify_pid: self()
)
# You should see messages like:
# {:godot_status, :started}
flush()
# Send a message into Godot (available via Engine.get_singleton("ElixirBus") in scripts)
:ok = LibGodot.Driver.send_message("hello from Elixir")
# Request/reply: sends a request into Godot and blocks waiting for a response.
# Godot must call ElixirBus.respond(request_id, response) for this to complete.
{:ok, resp} = LibGodot.Driver.request("ping", 1_000)
IO.inspect(resp)
# Receive events sent from Godot via ElixirBus.send_event/2
LibGodot.subscribe(self())
flush()
On macOS, embedding Godot inside the BEAM is only supported in headless mode.
The driver defaults to --headless and the native layer disables AppKit initialization when it detects --headless.
You can override by passing args: [...], but windowed mode may crash due to AppKit main-thread requirements.
Note: the sample project lives at samples/project/, so from samples/elixir_sample/ the path is ../project/.