# LibGodotConnector (Elixir)
This is a proof-of-concept showing how to interface Elixir with LibGodot using NIFs.
## Architecture
1. **Elixir to Godot:** The `LibGodot` module provides functions like `start/0` and `iteration/0` which call into the C++ NIF.
It also provides `send_message/2`, which enqueues messages for Godot to consume via the `ElixirBus` singleton.
2. **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 using `LibGodot.subscribe(self())`.
## How to build
1. Ensure you have built `libgodot` in the root directory.
2. Build the NIF via Mix (recommended):
```bash
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.
```bash
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):
```bash
export LIBGODOT_FORCE_BUILD=1
mix compile
```
## Using 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:
1. Determine the current target triple (see `LibGodotConnector.Precompiler.current_target/0`).
2. Compute the expected artefact filename (`@{artefact_filename}` in the URL template).
3. Download and unpack the tarball into the dependency's `priv/` directory.
4. Optionally verify checksums using `checksum-lib_godot_connector.exs` if 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)
1. Create a new scratch Elixir project somewhere else:
```bash
mix new /tmp/test_libgodot_dep
cd /tmp/test_libgodot_dep
```
2. Add a path dependency to this sample in `/tmp/test_libgodot_dep/mix.exs`:
```elixir
defp deps do
[
{:lib_godot_connector, path: "/Users/dragosdaian/Documents/appsinacup/libgodot/samples/elixir_sample"}
]
end
```
3. Run:
```bash
mix deps.get
mix compile
```
This 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.
1. In `samples/elixir_sample/`, build a precompiled archive for your current machine:
```bash
cd samples/elixir_sample
mix deps.get
mix elixir_make.precompile
```
The task prints the path of the generated archive (under your elixir_make cache dir).
2. Serve the directory containing that archive:
```bash
cd <the-folder-with-the-generated-.tar.gz>
python3 -m http.server 8080
```
3. In your scratch project, point the URL template to that server and compile:
```bash
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:
1. Ensure `mix.exs` version (and release tag) match (e.g. `v4.5.1`).
2. Build per-target/per-OTP (NIF version) tarballs on CI using `mix elixir_make.precompile`.
3. Upload the `.tar.gz` artefacts and their checksum sidecar files (e.g. `.sha256`) to GitHub Releases.
4. Generate and commit `checksum-lib_godot_connector.exs` so Hex installs can verify downloads:
```bash
mix elixir_make.checksum --all
```
5. 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.
3. Alternatively, build the NIF with CMake directly:
```bash
cd samples/elixir_sample
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
```
For a debug build:
```bash
cd samples/elixir_sample
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
```
4. Run the Elixir code:
```bash
mix compile
iex -S mix
```
To run it as a normal OTP application (starts `LibGodot.Driver` under supervision):
```bash
mix run --no-halt
```
## Example Usage
```elixir
# 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:
```elixir
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/`.