Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

GoudEngine

GoudEngine is a Rust game engine with multi-language SDK support. All game logic lives in Rust. SDKs for C#, Python, TypeScript, and Rust provide thin wrappers over the FFI boundary.

SDK Support

SDKPackageBackend
C#NuGetDllImport (P/Invoke)
PythonPyPIctypes
TypeScriptnpmnapi-rs (Node.js) + wasm-bindgen (Web)
Rustcrates.ioDirect linking (no FFI)

Status

GoudEngine is in alpha. APIs change frequently. Report issues on GitHub.

Getting Started — C# SDK

Alpha — GoudEngine is under active development. APIs change frequently. Report issues

Prerequisites

Installation

Create a new console project and add the NuGet package:

dotnet new console -n MyGame
cd MyGame
dotnet add package GoudEngine

Open MyGame.csproj and add <AllowUnsafeBlocks>true</AllowUnsafeBlocks>. The SDK uses unsafe interop internally.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="GoudEngine" Version="0.0.815" />
  </ItemGroup>
</Project>

First Project

Replace Program.cs with a minimal window that closes on Escape:

using GoudEngine;

using var game = new GoudGame(800, 600, "My Game");

while (!game.ShouldClose())
{
    game.BeginFrame(0.2f, 0.3f, 0.4f, 1.0f); // RGBA clear color

    float dt = game.DeltaTime;

    if (game.IsKeyPressed(Keys.Escape))
    {
        game.Close();
        continue;
    }

    game.EndFrame();
}

BeginFrame clears the screen to the given color and prepares the frame. EndFrame swaps buffers and polls events. DeltaTime gives seconds since the last frame — use it to keep movement frame-rate independent.

Run it:

dotnet run

For a 3D window, pass the renderer type:

using var game = new GoudGame(800, 600, "3D Game", RendererType.Renderer3D);

Drawing a Sprite

Load textures once before the loop. Drawing happens inside the loop between BeginFrame and EndFrame.

using GoudEngine;

using var game = new GoudGame(800, 600, "My Game");

ulong textureId = game.LoadTexture("assets/sprite.png");

float x = 100f, y = 100f;
float width = 64f, height = 64f;

while (!game.ShouldClose())
{
    game.BeginFrame(0.1f, 0.1f, 0.1f, 1.0f);

    if (game.IsKeyPressed(Keys.Escape)) { game.Close(); continue; }

    game.DrawSprite(textureId, x, y, width, height);

    game.EndFrame();
}

To draw a colored quad without a texture:

game.DrawQuad(x, y, width, height, new Color(1.0f, 0.0f, 0.0f, 1.0f));

Put your image files in an assets/ folder next to the project. The path is relative to the working directory when you run dotnet run.

Handling Input

IsKeyPressed returns true every frame the key is held. Use it for movement. For one-shot actions, track state yourself.

float speed = 200f;

while (!game.ShouldClose())
{
    game.BeginFrame(0.1f, 0.1f, 0.1f, 1.0f);

    float dt = game.DeltaTime;

    if (game.IsKeyPressed(Keys.Escape)) { game.Close(); continue; }

    if (game.IsKeyPressed(Keys.Left))  x -= speed * dt;
    if (game.IsKeyPressed(Keys.Right)) x += speed * dt;
    if (game.IsKeyPressed(Keys.Up))    y -= speed * dt;
    if (game.IsKeyPressed(Keys.Down))  y += speed * dt;
    if (game.IsKeyPressed(Keys.Space)) { /* fire, jump, etc. */ }

    game.DrawSprite(textureId, x, y, width, height);

    game.EndFrame();
}

Mouse input follows the same pattern:

if (game.IsMouseButtonPressed(MouseButton.Left)) { /* click action */ }

float mouseX = game.MouseX;
float mouseY = game.MouseY;

Running an Example Game

The repository includes several complete C# games. Clone and run them directly:

git clone https://github.com/aram-devdocs/GoudEngine.git
cd GoudEngine
./dev.sh --game flappy_goud     # Flappy Bird clone
./dev.sh --game goud_jumper     # Platformer
./dev.sh --game 3d_cube         # 3D rendering demo
./dev.sh --game isometric_rpg   # Isometric RPG
./dev.sh --game hello_ecs       # ECS basics

dev.sh builds the engine and runs the example in one step. Source for each example is in examples/csharp/.

To use a locally built version of the engine instead of the published NuGet package:

./build.sh
./package.sh --local
./dev.sh --game flappy_goud --local

Next Steps

Getting Started: Python SDK

Alpha — APIs change frequently. Report issues

This guide covers installing the Python SDK, opening a window, drawing a sprite, and handling input.

See also: C# guide · TypeScript guide · Rust guide

Prerequisites

  • Python 3.9 or later
  • A supported OS: Windows x64, macOS x64, macOS ARM64, or Linux x64

Installation

pip install goudengine

The package bundles the native Rust library (.so, .dylib, or .dll). No separate build step is needed when installing from PyPI.

First Project

Create main.py:

from goud_engine import GoudGame, Key

game = GoudGame(800, 600, "My Game")

while not game.should_close():
    game.begin_frame()

    if game.is_key_just_pressed(Key.ESCAPE):
        game.close()

    game.end_frame()

game.destroy()

Run it:

python main.py

A window opens at 800x600 and closes when you press Escape.

begin_frame() polls events and clears the screen. end_frame() presents the frame. Everything you draw goes between those two calls.

Drawing a Sprite

Load textures once before the game loop, then draw each frame.

from goud_engine import GoudGame, Key

game = GoudGame(800, 600, "My Game")

# Load returns an integer handle. Store it and reuse it every frame.
player_tex = game.load_texture("assets/player.png")

while not game.should_close():
    game.begin_frame()

    if game.is_key_just_pressed(Key.ESCAPE):
        game.close()

    # draw_sprite(texture_id, center_x, center_y, width, height)
    game.draw_sprite(player_tex, 400, 300, 64, 64)

    game.end_frame()

game.destroy()

draw_sprite takes the center position of the sprite, not the top-left corner.

An optional sixth argument sets rotation in radians:

import math
game.draw_sprite(player_tex, 400, 300, 64, 64, math.pi / 4)

Handling Input

Keyboard

Two modes are available: pressed this frame, or held continuously.

from goud_engine import GoudGame, Key

game = GoudGame(800, 600, "My Game")

x = 400.0

while not game.should_close():
    game.begin_frame()
    dt = game.delta_time

    # is_key_just_pressed: true only on the frame the key is first pressed
    if game.is_key_just_pressed(Key.ESCAPE):
        game.close()

    # is_key_pressed: true every frame the key is held down
    if game.is_key_pressed(Key.LEFT):
        x -= 200 * dt
    if game.is_key_pressed(Key.RIGHT):
        x += 200 * dt

    game.end_frame()

game.destroy()

delta_time is the elapsed seconds since the last frame. Use it to make movement frame-rate independent.

Common key constants: Key.ESCAPE, Key.SPACE, Key.ENTER, Key.W, Key.A, Key.S, Key.D, Key.LEFT, Key.RIGHT, Key.UP, Key.DOWN.

Mouse

from goud_engine import GoudGame, MouseButton

game = GoudGame(800, 600, "My Game")

while not game.should_close():
    game.begin_frame()

    # is_mouse_button_just_pressed: true on the frame the button is first clicked
    if game.is_mouse_button_just_pressed(MouseButton.LEFT):
        pos = game.get_mouse_position()
        print(f"Click at ({pos.x:.0f}, {pos.y:.0f})")

    game.end_frame()

game.destroy()

Mouse button constants: MouseButton.LEFT, MouseButton.RIGHT, MouseButton.MIDDLE.

Running an Example Game

The repository includes a complete Flappy Bird clone in Python. Clone the repo and run it with dev.sh:

git clone https://github.com/aram-devdocs/GoudEngine.git
cd GoudEngine
./dev.sh --sdk python --game python_demo    # Basic demo
./dev.sh --sdk python --game flappy_bird    # Flappy Bird clone

dev.sh builds the native library and launches the example. It requires a Rust toolchain (cargo) to be installed.

Running Examples from Source (Without dev.sh)

If you have the repository checked out and the native library built, add the SDK path manually:

import sys
from pathlib import Path

sdk_path = Path(__file__).parent.parent.parent / "sdks" / "python"
sys.path.insert(0, str(sdk_path))

from goud_engine import GoudGame, Key

Build the native library first:

cargo build --release

Available Types

ImportDescription
GoudGameWindow, game loop, rendering, input
KeyKeyboard key constants (GLFW values)
MouseButtonMouse button constants
Vec22D vector with arithmetic methods
ColorRGBA color (Color.red(), Color.from_hex(0xFF0000))
Transform2D2D position, rotation, scale
SpriteSprite rendering component
EntityECS entity handle

Next Steps

Getting Started — Rust SDK

The Rust SDK links directly against the engine with no FFI overhead. It re-exports goud_engine::sdk::* from a single crate, so all engine types are available through use goudengine::*;.

Other SDKs: C# · Python · TypeScript


Prerequisites

Rust toolchain

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup update stable

System dependencies

Linux:

sudo apt-get install libglfw3-dev libgl1-mesa-dev

macOS:

brew install glfw
# OpenGL is provided by the OS — no extra package needed.

Windows: Install GLFW via vcpkg or download the pre-built binaries from glfw.org.


Installation

Create a new project and add the dependency:

cargo new my-game
cd my-game
cargo add goud-engine

Or set the version directly in Cargo.toml:

[package]
name = "my-game"
version = "0.1.0"
edition = "2021"

[dependencies]
goud-engine = "0.0.825"

First Project

This opens a window, clears it to a blue-grey color each frame, and exits when the window is closed.

use goudengine::{GameConfig, GoudGame};

fn main() {
    let config = GameConfig::new("My Game", 800, 600);
    let mut game = GoudGame::with_platform(config).expect("Failed to create game");

    game.enable_blending();

    while !game.should_close() {
        let _dt = game.poll_events().unwrap_or(0.016);
        game.begin_render();
        game.clear(0.2, 0.3, 0.4, 1.0); // RGBA: dark blue-grey

        game.end_render();
        game.swap_buffers().expect("swap_buffers failed");
    }
}

GameConfig::new takes window title, width, and height. GoudGame::with_platform creates the window and OpenGL context. poll_events returns the elapsed time in seconds since the last frame, which you use to scale physics and animations.


Drawing a Sprite

Load a texture once before the loop, then call draw_sprite each frame.

use goudengine::{GameConfig, GoudGame};

fn main() {
    let config = GameConfig::new("Sprite Demo", 800, 600);
    let mut game = GoudGame::with_platform(config).expect("Failed to create game");

    game.enable_blending();

    // Load returns an opaque u64 handle. Keep it for the lifetime of the game.
    let texture = game.load("assets/sprite.png");

    while !game.should_close() {
        let _dt = game.poll_events().unwrap_or(0.016);
        game.begin_render();
        game.clear(0.2, 0.3, 0.4, 1.0);

        // draw_sprite(texture, center_x, center_y, width, height,
        //             rotation_radians, scale_x, scale_y, r, g, b, a)
        game.draw_sprite(
            texture,
            400.0, 300.0, // center position
            64.0,  64.0,  // size
            0.0,          // rotation (radians)
            1.0, 1.0,     // scale
            1.0, 1.0, 1.0, 1.0, // color tint (white = no tint)
        );

        game.end_render();
        game.swap_buffers().expect("swap_buffers failed");
    }
}

Positions are in pixels from the top-left corner. The center_x/center_y arguments are the sprite’s center, not its top-left corner.


Handling Input

Query key state inside the game loop with is_key_pressed.

use goudengine::{GameConfig, GoudGame};
use goudengine::input::Key;

fn main() {
    let config = GameConfig::new("Input Demo", 800, 600);
    let mut game = GoudGame::with_platform(config).expect("Failed to create game");

    game.enable_blending();

    let mut x = 400.0_f32;

    while !game.should_close() {
        let dt = game.poll_events().unwrap_or(0.016);
        game.begin_render();
        game.clear(0.2, 0.3, 0.4, 1.0);

        if game.is_key_pressed(Key::Escape) {
            break;
        }
        if game.is_key_pressed(Key::Left) {
            x -= 200.0 * dt;
        }
        if game.is_key_pressed(Key::Right) {
            x += 200.0 * dt;
        }

        game.end_render();
        game.swap_buffers().expect("swap_buffers failed");
    }
}

is_key_pressed returns true as long as the key is held down. Mouse buttons use is_mouse_button_pressed(MouseButton::Button1).


Running the Example Game

The repository includes a complete Flappy Bird clone in examples/rust/flappy_bird/. It demonstrates texture loading, sprite drawing, physics, collision detection, and input handling across multiple modules.

git clone https://github.com/aram-devdocs/GoudEngine.git
cd GoudEngine
cargo run -p flappy-bird

Controls: Space or left click to flap, R to restart, Escape to quit.

The example must be run from the repository root so asset paths resolve correctly. The game reuses the shared asset directory at examples/csharp/flappy_goud/assets/.


Next Steps

Getting Started — TypeScript SDK

Alpha — This SDK is under active development. APIs change frequently. Report issues

The TypeScript SDK ships a single npm package (goudengine) with two backends:

  • Node.js — native addon via napi-rs, uses GLFW + OpenGL, near-native performance
  • Web — WASM module via wasm-bindgen, runs in any browser with WebAssembly support

Both backends expose the same TypeScript API. Game logic written for one target works on the other.

Other getting-started guides: C# · Python · Rust


Prerequisites

  • Node.js 16 or later
  • npm 7 or later
  • For web: a browser with WebAssembly support (all modern browsers qualify)

Installation

npm install goudengine

The package uses conditional exports. Node.js projects get the napi-rs backend automatically. For the browser, import from the goudengine/web sub-path (see the web section below).


First Project: Desktop (Node.js)

Create game.ts:

import { GoudGame } from 'goudengine';

const game = new GoudGame({ width: 800, height: 600, title: 'My Game' });

while (!game.shouldClose()) {
    game.beginFrame(0.2, 0.3, 0.4, 1.0);  // RGBA clear color

    const dt = game.deltaTime;

    // Press Escape to exit
    if (game.isKeyPressed(256)) { break; }

    game.endFrame();
}

game.destroy();

Run it:

npx tsx game.ts

This opens an 800x600 GLFW window. The loop calls beginFrame to clear the screen, runs your logic, then calls endFrame to swap buffers. deltaTime returns seconds elapsed since the last frame.


First Project: Web (WASM)

Create index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My Game</title>
</head>
<body>
<canvas id="canvas" width="800" height="600"></canvas>

<script type="importmap">
{
  "imports": {
    "goudengine/web": "/node_modules/goudengine/dist/web/generated/web/index.g.js"
  }
}
</script>

<script type="module">
import { GoudGame } from 'goudengine/web';

const canvas = document.getElementById('canvas');

const game = await GoudGame.create({
    width: 800,
    height: 600,
    title: 'My Game',
    canvas,
    wasmUrl: '/node_modules/goudengine/wasm/goud_engine_bg.wasm'
});

game.setClearColor(0.2, 0.3, 0.4, 1.0);

game.run((dt) => {
    // Game logic here — dt is seconds since last frame
});
</script>
</body>
</html>

Serve the directory (the importmap requires a real HTTP server, not file://):

npx serve .

Key differences from the Node.js version:

Node.jsWeb
Constructornew GoudGame({...})await GoudGame.create({...})
Extra parameterscanvas, wasmUrl
Game loopwhile (!game.shouldClose())game.run((dt) => { ... })
Clear colorbeginFrame(r, g, b, a)setClearColor(r, g, b, a)

Drawing a Sprite

loadTexture is asynchronous on both targets — it returns a Promise<number>. The returned number is a texture handle you pass to drawSprite.

// Load before the game loop
const textureId = await game.loadTexture('assets/player.png');

// Inside the game loop
game.drawSprite(textureId, x, y, width, height);

// Optional: draw with rotation (radians)
game.drawSprite(textureId, x, y, width, height, Math.PI / 4);

The x and y coordinates are the center of the sprite. All numeric parameters are f64 at the JavaScript boundary; the engine converts to f32 internally.

Node.js example:

import { GoudGame } from 'goudengine';

const game = new GoudGame({ width: 800, height: 600, title: 'Sprite Demo' });

const playerTex = await game.loadTexture('assets/player.png');

while (!game.shouldClose()) {
    game.beginFrame(0.1, 0.1, 0.1, 1.0);
    game.drawSprite(playerTex, 400, 300, 64, 64);
    game.endFrame();
}

game.destroy();

Web example — same logic, different setup:

const game = await GoudGame.create({
    width: 800, height: 600, title: 'Sprite Demo',
    canvas, wasmUrl: '/node_modules/goudengine/wasm/goud_engine_bg.wasm'
});

const playerTex = await game.loadTexture('assets/player.png');

game.setClearColor(0.1, 0.1, 0.1, 1.0);
game.run((_dt) => {
    game.drawSprite(playerTex, 400, 300, 64, 64);
});

Handling Input

Use isKeyPressed with GLFW key codes. Common codes:

KeyCode
Escape256
Space32
R82
Arrow Left263
Arrow Right262
Arrow Up265
Arrow Down264

For mouse input, use isMouseButtonPressed. Button 0 is the left mouse button.

while (!game.shouldClose()) {
    game.beginFrame(0.1, 0.1, 0.1, 1.0);

    if (game.isKeyPressed(256)) { break; }          // Escape: quit
    if (game.isKeyPressed(32) ||
        game.isMouseButtonPressed(0)) {
        // Space or left-click: jump
    }
    if (game.isKeyPressed(263)) { /* move left */ }
    if (game.isKeyPressed(262)) { /* move right */ }

    game.endFrame();
}

On the web target, isKeyPressed and isMouseButtonPressed work the same way. The WASM backend handles browser keyboard and mouse events internally.


Running an Example Game

Clone the repository and run the Flappy Bird example:

git clone https://github.com/aram-devdocs/GoudEngine.git
cd GoudEngine

# Desktop (Node.js)
./dev.sh --sdk typescript --game flappy_bird

# Web (browser, serves on localhost)
./dev.sh --sdk typescript --game flappy_bird_web

dev.sh handles building the SDK and running the example in one step.

To run the example manually:

# Build the native addon first
cd sdks/typescript && npm run build:native && cd ../..

# Desktop
cd examples/typescript/flappy_bird
npm install
npm run desktop

# Web
npm run build:web   # Compile TS to dist/
npm run web         # Start HTTP server on port 8765
# Open http://localhost:8765/examples/typescript/flappy_bird/web/index.html

Controls: Space or left-click to flap, R to restart, Escape to quit (desktop only).


Next Steps

Dev Environment Setup

GoudEngine builds on Rust with SDK bindings for C#, Python, and TypeScript. The sections below cover system dependencies, toolchain installation, and verification.

Prerequisites

ToolVersionNotes
Ruststable (edition 2021)Installed via rustup
.NET SDK8.0For C# SDK and examples
Python3.9+ (3.11 recommended)For codegen scripts and Python SDK
Node.js16+ (20 recommended)For TypeScript SDK
cbindgen0.29cargo install cbindgen
wasm-packlatestOnly needed for TypeScript Web/WASM builds
cargo-denylatestcargo install cargo-deny

Python and TypeScript SDK support is optional. Only Rust and the C# SDK are required for core development.

System Dependencies

macOS

Xcode Command Line Tools provide everything needed:

xcode-select --install

GLFW and OpenGL headers ship with macOS. Homebrew is not required for core development.

Linux (Ubuntu/Debian)

sudo apt-get update
sudo apt-get install -y \
    build-essential cmake pkg-config \
    libgl1-mesa-dev libglu1-mesa-dev \
    libxrandr-dev libxinerama-dev libxcursor-dev \
    libxi-dev libxxf86vm-dev \
    libasound2-dev

Required to build GLFW and the audio subsystem.

Note: libglu1-mesa-dev and libxxf86vm-dev are required by CI but are not installed by install.sh. Include them when setting up a fresh machine.

Linux (Fedora)

sudo dnf install -y gcc gcc-c++ cmake pkgconfig \
    alsa-lib-devel libXrandr-devel libXinerama-devel \
    libXcursor-devel libXi-devel mesa-libGL-devel

Linux (Arch)

sudo pacman -S --needed \
    base-devel cmake pkgconf alsa-lib \
    libxrandr libxinerama libxcursor libxi mesa

Using install.sh

The repo includes install.sh to automate system dependency installation:

./install.sh

What it does:

  1. Detects the OS (Linux distro or macOS)
  2. Installs system libraries (OpenGL, X11, ALSA) via the native package manager
  3. Installs .NET SDK 8.0 on Ubuntu/Debian if not already present
  4. Installs Rust via rustup if cargo is not found
  5. Installs cbindgen via cargo install

The script takes no flags. It runs unconditionally based on OS detection.

Limitations:

  • Does not install Node.js, Python, wasm-pack, or cargo-deny
  • Does not install libglu1-mesa-dev or libxxf86vm-dev (needed by CI)
  • Does not cover .NET SDK installation on Fedora
  • On macOS, relies on Xcode Command Line Tools being installed (prompts if missing)

Run the manual system dependency commands above even after running install.sh to match CI parity exactly.

Step-by-Step Setup

  1. Clone the repo

    git clone https://github.com/aram-devdocs/GoudEngine.git
    cd GoudEngine
    
  2. Install system dependencies — run ./install.sh or use the manual commands in the System Dependencies section above.

  3. Install Rust (skip if install.sh already installed it — run cargo --version to check)

    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
    source "$HOME/.cargo/env"
    
  4. Install Rust tools

    cargo install cbindgen
    cargo install cargo-deny
    
  5. Verify the build

    cargo check
    cargo build
    cargo test
    
  6. Install .NET SDK 8.0 (required for C# SDK work) — download from dotnet.microsoft.com/download/dotnet/8.0.

  7. Install Python 3.11+ (for codegen and Python SDK) — verify with python3 --version.

  8. Install Node.js 20 (for TypeScript SDK) — use nvm or the official installer.

  9. (Optional) Install wasm-pack (for TypeScript Web/WASM builds)

    curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
    
  10. Run a smoke test

    cargo check
    cargo fmt --all -- --check
    cargo clippy -- -D warnings
    cargo test
    

Verify Your Setup

All five commands must pass before pushing code:

cargo check                        # Type checks
cargo fmt --all -- --check         # Format check
cargo clippy -- -D warnings        # Lint check
cargo test                         # Unit tests
cargo deny check                   # Dependency audit

Common Issues

glfw build fails on Linux

Missing X11 or OpenGL dev libraries. Install the full set from the Linux (Ubuntu/Debian) section above.

libasound2-dev not found (Ubuntu 24.04+)

Ubuntu 24.04 ships a transitional package. If libasound2-dev fails, try libasound-dev instead.

dotnet command not found after install.sh

The .NET SDK install may need a new shell session to take effect. Close and reopen your terminal, or run source ~/.bashrc.

cargo test fails with “failed to initialize any backend” or GL errors

Tests that need an OpenGL context fail without a display server. On headless Linux (CI, WSL without WSLg, SSH sessions), use Xvfb:

sudo apt-get install -y xvfb
xvfb-run -a cargo test

Permission denied on install.sh

chmod +x install.sh

cbindgen version mismatch

The project uses cbindgen 0.29. If you have an older version installed:

cargo install cbindgen --force

Node.js native addon build fails (TypeScript SDK)

node-gyp requires a C++ compiler and Python 3:

# Ubuntu/Debian
sudo apt-get install -y g++ python3
# macOS — Xcode Command Line Tools covers this

Optional Tools

ToolPurposeInstall
mdbookBuild and preview docs locallycargo install mdbook
cargo-tarpaulinCode coverage reportscargo install cargo-tarpaulin
cargo-auditSecurity vulnerability scanningcargo install cargo-audit
GraphVizModule dependency graph (./graph.sh)sudo apt install graphviz / brew install graphviz
cargo-modulesModule dependency graph (./graph.sh)cargo install cargo-modules

Next Steps

Building GoudEngine

Alpha — GoudEngine is under active development. APIs change frequently. Report issues · Contact

Core Build Commands

cargo build                 # Debug build
cargo build --release       # Release build
cargo test                  # Run all tests
cargo test -- --nocapture   # Tests with output

Release Build

build.sh compiles the engine in release mode and copies the native library into the SDK output directories:

./build.sh
./build.sh --release        # Explicit release flag

After running, the compiled .dylib / .so / .dll is placed in the appropriate SDK directory. The C# build also triggers csbindgen to regenerate NativeMethods.g.cs.

Packaging

package.sh creates a NuGet package from the built artifacts:

./package.sh                # Package to nuget_package_output/
./package.sh --local        # Package and push to local NuGet feed

The local NuGet feed is at $HOME/nuget-local. To consume it in an example project:

./dev.sh --game <game> --local
# or manually:
dotnet add package GoudEngine --version <version> --source $HOME/nuget-local

SDK Tests

# C# SDK tests
dotnet test sdks/csharp.tests/

# Python SDK tests
python3 sdks/python/test_bindings.py

# TypeScript SDK tests
cd sdks/typescript && npm test

Module Dependency Graph

Generate a visual graph of module dependencies:

./graph.sh

This creates docs/diagrams/module_graph.png and .pdf using cargo modules and GraphViz.

Development Guide

Alpha — GoudEngine is under active development. APIs change frequently. Report issues · Contact

Quick Start

Use dev.sh to build and run examples in one step:

# C# SDK (default)
./dev.sh --game flappy_goud         # 2D game example
./dev.sh --game 3d_cube             # 3D game example
./dev.sh --game goud_jumper         # Platform game example
./dev.sh --game flappy_goud --local # Use local NuGet feed

# Python SDK
./dev.sh --sdk python --game python_demo  # SDK demo
./dev.sh --sdk python --game flappy_bird  # Flappy Bird

# TypeScript SDK
./dev.sh --sdk typescript --game flappy_bird      # Desktop (Node.js)
./dev.sh --sdk typescript --game flappy_bird_web  # Web (WASM)

# Rust SDK
./dev.sh --sdk rust  # Run Rust SDK tests

Local Development Cycle

./build.sh                       # 1. Build engine and SDKs
./package.sh --local             # 2. Deploy to local NuGet feed
./dev.sh --game <game> --local   # 3. Test with example

Git Hooks

This project uses husky-rs for Git hook management.

Pre-commit (fast): format, clippy, basic tests, Python SDK checks.

Pre-push (thorough): full test suite, doctests, security audit.

After editing .husky/hooks/pre-commit or .husky/hooks/pre-push, run:

cargo clean && cargo test

This is required for husky-rs to reload the hooks via build.rs.

Version Management

Versioning is automated through release-please via conventional commits.

  1. Use conventional commit prefixes (feat:, fix:, chore:) in PR titles.
  2. On merge to main, release-please creates or updates a Release PR.
  3. When the Release PR merges, it creates a tag and GitHub release.
  4. The tag triggers the publish pipeline (npm, NuGet, PyPI, crates.io).

For local testing, ./increment_version.sh updates versions manually:

./increment_version.sh           # Patch version (0.0.X)
./increment_version.sh --minor   # Minor version (0.X.0)
./increment_version.sh --major   # Major version (X.0.0)

The script updates goud_engine/Cargo.toml (source of truth), sdks/csharp/GoudEngine.csproj, and all .csproj files under examples/.

Pre-commit Checks

Run these before pushing to confirm the build is clean:

cargo check
cargo fmt --all -- --check
cargo clippy -- -D warnings
cargo deny check
cargo test

AI Agent Setup

This repository includes configuration for AI coding assistants (Claude Code, Codex, Cursor, Gemini) with shared infrastructure across tools.

Directory Structure

.agents/              # Shared cross-tool configuration (source of truth)
├── rules/            # Coding/domain rules (dependency hierarchy, FFI, TDD, etc.)
└── skills/           # Cross-tool skills (shared between Claude, Codex, Cursor, Gemini)
    ├── subagent-driven-development/
    ├── review-changes/
    ├── code-review/
    ├── gh-issue/
    ├── hardening-checklist/
    ├── tdd-workflow/
    ├── sdk-parity-check/
    └── ...

.claude/              # Claude Code configuration
├── agents/           # Subagent definitions (implementer, debugger, reviewers, etc.)
├── rules/            # -> symlinks to .agents/rules/
├── hooks/            # Lifecycle hooks (quality checks, secret scanning, session state)
├── skills/           # -> symlinks to .agents/skills/
├── memory/           # Session state (gitignored)
├── specs/            # Feature specs for multi-session work
└── settings.local.json

.codex/               # OpenAI Codex configuration
└── config.toml       # Agent roles pointing to shared .agents/rules/

.cursor/              # Cursor IDE configuration
├── rules/            # Cursor-specific contextual rules (.mdc files)
└── skills/           # -> symlink to .agents/skills/

Key Files

FilePurpose
AGENTS.mdRoot agent instructions (commands, architecture, anti-patterns)
CLAUDE.mdSymlink to AGENTS.md (Claude Code compatibility)
GEMINI.mdSymlink to AGENTS.md (Gemini compatibility)
.cursorignoreExcludes build artifacts from Cursor indexing

Distributed AGENTS.md

Each subdirectory with non-trivial logic has its own AGENTS.md providing module-specific context to agents working in that area. A CLAUDE.md symlink exists alongside each for Claude Code compatibility. Key locations:

  • goud_engine/AGENTS.md – engine core patterns
  • goud_engine/src/ffi/AGENTS.md – FFI boundary rules
  • sdks/AGENTS.md – SDK development rules
  • codegen/AGENTS.md – codegen pipeline details
  • examples/AGENTS.md – example game conventions

Adding New Skills

Skills live at .agents/skills/<skill-name>/SKILL.md. They are available to both Claude Code and Cursor through symlinks at .claude/skills/ and .cursor/skills/.

To add a skill:

  1. Create .agents/skills/<skill-name>/SKILL.md
  2. The symlinks pick it up automatically – no further configuration needed.

SDK-First Architecture

GoudEngine is a Rust game engine with multi-language SDK support. All game logic lives in Rust. SDKs are thin wrappers: they marshal data and call FFI functions, never implementing logic of their own.

Layer Architecture

Dependencies flow DOWN only. A higher-numbered layer may import from lower layers; the reverse is a violation.

Layer 1 (Core)   : goud_engine/src/core/   — error types, math, handles, context registry
Layer 2 (Engine) : goud_engine/src/sdk/    — native Rust API, zero FFI overhead
Layer 3 (FFI)    : goud_engine/src/ffi/    — C-compatible exports, #[no_mangle] extern "C"
Layer 4 (SDKs)   : sdks/                   — C#, Python, TypeScript wrappers
Layer 5 (Apps)   : examples/               — game demos per SDK language

Each layer knows nothing about the layers above it. ffi/ may import from core/, sdk/, ecs/, and assets/. It must never import from sdks/.

Layer Enforcement

A lint-layers binary scans use crate:: imports across all source files and fails the build if any upward dependency is found. It runs in two places:

  • codegen.sh step 2 (cargo run -p lint-layers)
  • Pre-commit hook (via .husky/)

Key Files by Layer

Core (goud_engine/src/core/)

FilePurpose
types.rsShared FFI-compatible types (FfiVec2, GoudContextId, GoudResult)
context_registry.rsThread-safe registry mapping GoudContextId → engine instances
component_ops.rsGeneric component CRUD used by FFI component handlers
math.rsVec2, Vec3, Color, Rect, Mat3x3 with #[repr(C)] for FFI
error.rsGoudError, GoudErrorCode, GoudResult

FFI (goud_engine/src/ffi/)

Each domain has its own file. All public functions are #[no_mangle] pub extern "C".

FileDomain
context.rsEngine context create/destroy
entity.rsEntity spawn/despawn
component.rsGeneric component add/remove/query
component_transform2d.rsTransform2D component operations
component_sprite.rsSprite component operations
window.rsWindow management, frame lifecycle
renderer.rs2D rendering
renderer3d.rs3D rendering
input.rsInput state queries
collision.rsCollision detection
types.rsRe-exports core/types.rs types at the FFI boundary

SDK (sdks/)

DirectoryTarget
sdks/csharp/.NET 8, DllImport, NuGet package
sdks/python/Python 3, ctypes, local install
sdks/typescript/TypeScript, Node.js (napi-rs) + Web (WASM)

Codegen Pipeline

All SDK source files under sdks/*/generated/ and sdks/typescript/src/generated/ are auto-generated. Do not edit them by hand.

goud_sdk.schema.json   — universal type/method definitions
ffi_mapping.json       — maps schema methods → C ABI function names + signatures
ffi_manifest.json      — auto-extracted from Rust source by build.rs (each cargo build)
                                           │
                    ┌──────────────────────┼───────────────────┐
                    ▼                      ▼                   ▼
             gen_csharp.py        gen_python.py         gen_ts_node.py
                    │                      │             gen_ts_web.py
                    ▼                      ▼                   ▼
          sdks/csharp/generated/   sdks/python/generated/   sdks/typescript/src/generated/

Run the full pipeline:

./codegen.sh

The script runs 8 steps in order:

  1. cargo build — builds Rust, triggers csbindgen (C# NativeMethods.g.cs) and writes ffi_manifest.json
  2. lint-layers — validates no upward imports
  3. validate_coverage.py — checks every function in ffi_manifest.json is covered by ffi_mapping.json
  4. gen_csharp.py — generates C# SDK wrappers
  5. gen_python.py — generates Python SDK wrappers
  6. gen_ts_node.py — generates TypeScript Node.js SDK
  7. gen_ts_web.py — generates TypeScript Web (WASM) SDK
  8. validate.py — schema consistency check

Steps 3 and 8 are validation gates. If either fails, the script exits with an error.

Schema Files

codegen/goud_sdk.schema.json — the single source of truth. Defines:

  • types — value types (e.g., Color, Vec2, Transform2D) with fields and factory methods
  • enums — enumeration types (e.g., Key, MouseButton) with values and optional platform maps
  • tools — high-level objects like GoudGame with constructor, destructor, lifecycle hooks, and methods

codegen/ffi_mapping.json — implementation details. Defines:

  • ffi_types — how each schema type maps to a C struct name and its fields
  • ffi_handles — opaque handle types (GoudContextId, GoudTextureHandle)
  • ctypes_mappings — Python ctypes annotations for pointer types
  • tools — per-method mapping from schema method name to FFI function name and parameters

codegen/ffi_manifest.json — auto-generated by build.rs. Contains the full list of #[no_mangle] extern "C" functions extracted from goud_engine/src/ffi/. Used by validate_coverage.py to detect unmapped functions.

Shared Utilities (codegen/sdk_common.py)

All generators import sdk_common.py for:

  • load_schema() / load_ffi_mapping() — JSON loading
  • Name converters: to_pascal(), to_snake(), to_camel(), to_screaming_snake()
  • Type maps: CSHARP_TYPES, PYTHON_TYPES, TYPESCRIPT_TYPES, CTYPES_MAP, CSHARP_FFI_TYPES
  • write_generated(path, content) — writes output files, creating parent directories

Key Design Decisions

Rust-first. Logic is never duplicated in SDKs. If a SDK method does anything beyond marshaling and calling an FFI function, that logic belongs in Rust.

Single schema, four generators. Adding a type to goud_sdk.schema.json causes it to appear in every SDK on the next codegen run. Naming conventions (PascalCase in C#, snake_case in Python, camelCase in TypeScript) are applied by the generators.

C# bindings are doubly generated. NativeMethods.g.cs is produced by csbindgen (a Rust build dependency) on every cargo build. The higher-level C# wrapper classes in sdks/csharp/generated/ are produced by gen_csharp.py. The two files work together: csbindgen handles the raw [DllImport] declarations; the Python generator handles the ergonomic wrapper API.

Context handles, not pointers. All FFI calls take a GoudContextId (an opaque u64) rather than a raw pointer. The context registry resolves handles to engine instances under a mutex. This prevents use-after-free and type confusion across the FFI boundary.

Error propagation. FFI functions return GoudResult (an i32) — 0 for success, negative for error. Detailed error messages are stored in thread-local storage and retrieved via goud_get_last_error_message().

What Goes Where

You want to…Where to put it
Add a new game mechanicgoud_engine/src/ (core, ecs, or assets)
Expose a mechanic to SDKsAdd #[no_mangle] extern "C" function in goud_engine/src/ffi/
Add a new type to all SDKsAdd to codegen/goud_sdk.schema.json, run ./codegen.sh
Change method naming in one SDKEdit the relevant generator in codegen/gen_<lang>.py
Add a new SDK languageSee Adding a New Language

Adding a New Language Target

This guide walks through adding a new language binding (e.g., Lua, Go, Java) to GoudEngine. The existing generators are the best reference: codegen/gen_csharp.py for a complete, mature implementation and codegen/gen_ts_web.py for a simpler one.

Before starting, read SDK-First Architecture to understand the pipeline.


Step 1: Understand the Schema

Read the two source-of-truth files:

  • codegen/goud_sdk.schema.json — all types, enums, and tool methods that every SDK must expose
  • codegen/ffi_mapping.json — the C ABI function names and signatures behind each schema method

The schema’s types section defines value types (Color, Vec2, Transform2D) with fields and factory constructors. The enums section defines Key, MouseButton, and similar. The tools section defines GoudGame with a constructor, destructor, lifecycle methods (beginFrame, endFrame), and all game methods.

Your generator reads both files and emits code for each of these sections.


Step 2: Add Type Mappings

Open codegen/sdk_common.py and add a type mapping table for your language. The existing tables show the pattern:

# Example for a hypothetical language "Lua" (Lua has no static types,
# so this would map to annotation strings or documentation stubs)
LUA_TYPES = {
    "f32": "number", "f64": "number",
    "u8": "integer", "u16": "integer", "u32": "integer", "u64": "integer",
    "i8": "integer", "i16": "integer", "i32": "integer", "i64": "integer",
    "bool": "boolean", "string": "string", "void": "nil",
}

The schema uses type names like f32, bool, string, and composite names like Color or Transform2D. Your mapping table handles the primitives; the generator handles the composite types by looking them up in the schema.

Also add a ctypes-equivalent mapping if your language calls the native library through a C interop layer that requires explicit type annotations.


Step 3: Create the Generator

Create codegen/gen_<lang>.py. The generator must produce at minimum:

  1. FFI declarations — how to call the C functions from your language (e.g., DllImport in C#, ctypes.CDLL in Python, napi-rs bindings in TypeScript/Node)
  2. Value type wrappers — classes or structs for Color, Vec2, Vec3, Rect, Transform2D, Sprite
  3. Enum definitionsKey and MouseButton with their numeric values
  4. Tool wrappers — the GoudGame class with all methods from schema["tools"]["GoudGame"]

Generator structure

#!/usr/bin/env python3
"""Generates the <Lang> SDK from the universal schema."""

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent))
from sdk_common import (
    HEADER_COMMENT, SDKS_DIR, load_schema, load_ffi_mapping,
    to_pascal, to_snake, write_generated,
)

OUT = SDKS_DIR / "<lang>"
schema = load_schema()
mapping = load_ffi_mapping()


def gen_types():
    # Emit value types from schema["types"]
    ...


def gen_enums():
    # Emit enums from schema["enums"]
    ...


def gen_game():
    # Emit GoudGame wrapper from schema["tools"]["GoudGame"]
    # and mapping["tools"]["GoudGame"]
    ...


if __name__ == "__main__":
    print("Generating <Lang> SDK...")
    gen_types()
    gen_enums()
    gen_game()
    print("<Lang> SDK generation complete.")

All output files MUST start with the HEADER_COMMENT constant so readers know not to edit them:

lines = [f"// {HEADER_COMMENT}", ""]

Use write_generated(path, content) to write output files — it creates parent directories automatically and prints a status line.

Mapping methods to FFI calls

For each method in schema["tools"]["GoudGame"]["methods"], look up the corresponding entry in mapping["tools"]["GoudGame"]["methods"] to get the FFI function name:

for method_name, method_def in schema["tools"]["GoudGame"]["methods"].items():
    ffi_fn = mapping["tools"]["GoudGame"]["methods"][method_name]["ffi"]
    # ffi_fn is a string like "goud_renderer_draw_sprite"

The ffi_mapping.json lifecycle section covers beginFrame and endFrame, which call multiple FFI functions in sequence. Check mapping["tools"]["GoudGame"]["lifecycle"] for those.


Step 4: Add to codegen.sh

Add a step to codegen.sh between the existing generator steps and the final validation:

echo "║ [N/8] Generating <Lang> SDK..."
python3 codegen/gen_<lang>.py

Update the step count in the surrounding echo messages.


Step 5: Create the SDK Directory

Create sdks/<lang>/ with the package manifest for your language’s package manager:

sdks/<lang>/
├── <package manifest>   # e.g., go.mod, build.gradle, rockspec
├── README.md
└── generated/           # files emitted by gen_<lang>.py

The SDK directory must contain at minimum one working example of how a game calls GoudGame. See sdks/csharp/ and sdks/python/ for the expected structure.


Step 6: Add Tests

Add a test file that verifies the generated bindings load and the FFI calls round-trip correctly. At minimum, test:

  • Library loads without error
  • GoudGame constructor (or equivalent) initializes
  • A type factory (e.g., Color.red()) returns the expected values

For Python the equivalent is sdks/python/test_bindings.py. Run it with:

python3 sdks/python/test_bindings.py

Add a corresponding command to the project AGENTS.md Essential Commands section.


Step 7: Add an Example

Port an existing example to your new language. The simplest starting point is examples/csharp/hello_ecs/, which demonstrates ECS basics without physics or complex rendering.

For a fuller parity test, port examples/csharp/flappy_goud/ — the Python SDK already has a matching examples/python/flappy_bird.py, so you can compare those two implementations side by side.

Place the example under examples/<lang>/.


Checklist

Before merging a new language target:

  • codegen/sdk_common.py has a type map for the new language
  • codegen/gen_<lang>.py exists and runs without error
  • All generated files begin with HEADER_COMMENT
  • codegen.sh includes the generator step
  • sdks/<lang>/ has a package manifest
  • ./codegen.sh runs end-to-end cleanly
  • Test file exists and passes
  • At least one example game exists under examples/<lang>/
  • AGENTS.md Essential Commands lists how to run the new SDK

Reference: Existing Generators

FileTargetNotes
codegen/gen_csharp.py.NET 8Most complete; handles struct marshaling, builder pattern, DllImport
codegen/gen_python.pyPython 3Uses ctypes; reference for dynamic-type languages
codegen/gen_ts_node.pyTypeScript (Node)Uses napi-rs; generates both Rust glue and TypeScript wrapper
codegen/gen_ts_web.pyTypeScript (Web)Smallest generator; wraps a WASM module, no FFI declarations needed

gen_ts_web.py is the simplest because it targets WASM — there are no ctypes or DllImport declarations. It wraps a pre-built WASM module handle directly. If your target also has a managed runtime that handles memory, this may be the closest analogue.

gen_csharp.py is the most detailed example of FFI struct mapping, null checking, and builder construction. Read it before writing a generator for a statically typed, natively-compiled language.

RFCs

RFCs (Request for Comments) are the mechanism for proposing and deciding on significant changes to GoudEngine. Use an RFC when a change affects public API, architecture, cross-cutting concerns, or long-term direction.

Small bug fixes, refactors that do not change behavior, and documentation updates do not need RFCs.

Numbering

RFCs use zero-padded 4-digit numbers: RFC-0001, RFC-0002, etc. Assign the next available number when opening the PR.

File Format

Each RFC lives at docs/rfcs/RFC-NNNN-short-title.md and starts with YAML front matter:

---
rfc: "0001"
title: "Short descriptive title"
status: draft
created: YYYY-MM-DD
authors: ["github-username"]
---

Status Lifecycle

draft → proposed → accepted → implemented → superseded
StatusMeaning
draftWork in progress, not ready for review
proposedPR open, ready for review
acceptedPR merged with review approval
implementedCode is shipped; RFC is complete
supersededReplaced by a later RFC (link to successor)

Acceptance requires at least one PR review approval from a maintainer. The proposedaccepted transition is automated: a GitHub Action (rfc-approve.yml) updates the front matter status when the PR merges. The implemented and superseded transitions remain manual.

Writing an RFC

  1. Copy the template: docs/rfcs/RFC-0000-template.md
  2. Assign the next number and rename the file
  3. Fill in: motivation, detailed design, drawbacks, alternatives considered
  4. Open a PR; set status: proposed in the front matter
  5. Address review feedback on the PR
  6. On merge: rfc-approve.yml automatically sets status: accepted; update the index below

Index

RFCTitleStatus
RFC-0001Provider Trait Patternproposed
RFC-0002NetworkProvider Trait Designaccepted

RFC-0001: Provider Trait Pattern


rfc: “0001” title: Provider Trait Pattern status: accepted created: 2026-03-06 authors: [“aram-devdocs”] tracking-issue: “#217”

RFC-0001: Provider Trait Pattern

1. Summary

This RFC defines a universal provider abstraction for all GoudEngine subsystems. It replaces the hardcoded OpenGLBackend in GoudGame with configurable, swappable providers selected at engine initialization. The pattern applies uniformly to rendering, physics, audio, windowing, and input. SDK users select built-in providers via enums; Rust SDK users may supply custom implementations.


2. Motivation

GoudGame in goud_engine/src/sdk/game/instance.rs currently holds:

#![allow(unused)]
fn main() {
#[cfg(feature = "native")]
render_backend: Option<OpenGLBackend>,
#[cfg(feature = "native")]
sprite_batch: Option<SpriteBatch<OpenGLBackend>>,
}

This hardcoding creates several problems:

  • Adding a wgpu backend requires duplicating every code path that touches backend and sprite_batch.
  • Physics, audio, and windowing have the same problem — there is no swap point.
  • Cross-platform targets (consoles, mobile) need NDA-bound backends that cannot ship in the public repo. There is no way to inject them without forking engine internals.
  • Runtime renderer selection (e.g., falling back from Vulkan to OpenGL) is not possible.

PlatformBackend in goud_engine/src/libs/platform/mod.rs already solves this for the platform layer: GoudGame stores Option<Box<dyn PlatformBackend>>. This RFC extends that pattern to all subsystems.


3. Design

3.1 Provider Supertrait

All providers implement a common base trait:

#![allow(unused)]
fn main() {
pub trait Provider: Send + Sync + 'static {
    fn name(&self) -> &str;
    fn version(&self) -> &str;
    fn capabilities(&self) -> Box<dyn std::any::Any>;
}
}

Send + Sync + 'static is required because providers are stored in ProviderRegistry, which may be accessed from worker threads during asset streaming. The exception is WindowProvider (see §3.7). Subsystem traits extend Provider with domain-specific methods.


3.2 Subsystem Provider Traits

RenderProvider

#![allow(unused)]
fn main() {
pub trait RenderProvider: Provider {
    // Lifecycle — FrameContext token enforces begin/end pairing
    fn begin_frame(&mut self) -> GoudResult<FrameContext>;
    fn end_frame(&mut self, frame: FrameContext) -> GoudResult<()>;
    fn resize(&mut self, width: u32, height: u32) -> GoudResult<()>;
    // Resources
    fn create_texture(&mut self, desc: &TextureDesc) -> GoudResult<TextureHandle>;
    fn destroy_texture(&mut self, handle: TextureHandle);
    fn create_buffer(&mut self, desc: &BufferDesc) -> GoudResult<BufferHandle>;
    fn destroy_buffer(&mut self, handle: BufferHandle);
    fn create_shader(&mut self, desc: &ShaderDesc) -> GoudResult<ShaderHandle>;
    fn destroy_shader(&mut self, handle: ShaderHandle);
    fn create_pipeline(&mut self, desc: &PipelineDesc) -> GoudResult<PipelineHandle>;
    fn destroy_pipeline(&mut self, handle: PipelineHandle);
    fn create_render_target(&mut self, desc: &RenderTargetDesc) -> GoudResult<RenderTargetHandle>;
    fn destroy_render_target(&mut self, handle: RenderTargetHandle);
    // Drawing
    fn draw(&mut self, cmd: &DrawCommand) -> GoudResult<()>;
    fn draw_batch(&mut self, cmds: &[DrawCommand]) -> GoudResult<()>;
    fn draw_mesh(&mut self, cmd: &MeshDrawCommand) -> GoudResult<()>;
    fn draw_text(&mut self, cmd: &TextDrawCommand) -> GoudResult<()>;
    fn draw_particles(&mut self, cmd: &ParticleDrawCommand) -> GoudResult<()>;
    // State
    fn set_viewport(&mut self, x: i32, y: i32, width: u32, height: u32);
    fn set_camera(&mut self, camera: &CameraData);
    fn set_render_target(&mut self, handle: Option<RenderTargetHandle>);
    fn clear(&mut self, color: [f32; 4]);
}
}

FrameContext is an opaque token returned by begin_frame and consumed by end_frame, ensuring the caller cannot skip frame finalization. PipelineDesc abstracts render pipeline state (shader + vertex layout + blend mode) required by wgpu; OpenGL providers can map this to their internal state tracking.

Built-inFeature FlagNotes
OpenGLRenderProvidernativeWraps existing OpenGLBackend
WgpuRenderProviderwgpuF02-03; wraps existing wgpu_backend/ modules
NullRenderProvideralwaysNo-op, for headless tests

The existing RenderBackend trait (goud_engine/src/libs/graphics/backend/render_backend.rs) is NOT object-safe by design. It becomes an internal detail of OpenGLRenderProvider and WgpuRenderProvider and does not appear in the public provider API.

A partial wgpu backend already exists at goud_engine/src/libs/graphics/backend/wgpu_backend/ (frame, texture, shader, buffer, pipeline modules). F02-03 will wrap this existing code inside WgpuRenderProvider rather than rewriting it.

PhysicsProvider

#![allow(unused)]
fn main() {
pub trait PhysicsProvider: Provider {
    fn step(&mut self, delta: f32) -> GoudResult<()>;
    fn set_gravity(&mut self, gravity: Vec2);
    fn gravity(&self) -> Vec2;
    fn create_body(&mut self, desc: &BodyDesc) -> GoudResult<BodyHandle>;
    fn destroy_body(&mut self, handle: BodyHandle);
    fn body_position(&self, handle: BodyHandle) -> GoudResult<Vec2>;
    fn set_body_position(&mut self, handle: BodyHandle, pos: Vec2) -> GoudResult<()>;
    fn body_velocity(&self, handle: BodyHandle) -> GoudResult<Vec2>;
    fn set_body_velocity(&mut self, handle: BodyHandle, vel: Vec2) -> GoudResult<()>;
    fn apply_force(&mut self, handle: BodyHandle, force: Vec2) -> GoudResult<()>;
    fn apply_impulse(&mut self, handle: BodyHandle, impulse: Vec2) -> GoudResult<()>;
    fn create_collider(&mut self, body: BodyHandle, desc: &ColliderDesc) -> GoudResult<ColliderHandle>;
    fn destroy_collider(&mut self, handle: ColliderHandle);
    fn raycast(&self, origin: Vec2, dir: Vec2, max_dist: f32) -> Option<RaycastHit>;
    fn overlap_circle(&self, center: Vec2, radius: f32) -> Vec<BodyHandle>;
    fn drain_collision_events(&mut self) -> Vec<CollisionEvent>;
    fn contact_pairs(&self) -> Vec<ContactPair>;
    fn create_joint(&mut self, desc: &JointDesc) -> GoudResult<JointHandle>;
    fn destroy_joint(&mut self, handle: JointHandle);
    fn debug_shapes(&self) -> Vec<DebugShape>;
}
}

drain_collision_events returns owned Vec rather than a slice reference to avoid lifetime coupling between the event buffer and the provider borrow — callers process events after the physics step without holding a borrow on the provider.

Built-inFeature FlagNotes
Rapier2DPhysicsProviderrapier2d2D rigid-body physics
Rapier3DPhysicsProviderrapier3d3D rigid-body physics
SimplePhysicsProvideralwaysAABB collision + gravity only, no rapier dependency
NullPhysicsProvideralwaysNo-op passthrough

AudioProvider

#![allow(unused)]
fn main() {
pub trait AudioProvider: Provider {
    fn update(&mut self) -> GoudResult<()>;
    fn play(&mut self, handle: SoundHandle, config: &PlayConfig) -> GoudResult<PlaybackId>;
    fn stop(&mut self, id: PlaybackId) -> GoudResult<()>;
    fn pause(&mut self, id: PlaybackId) -> GoudResult<()>;
    fn resume(&mut self, id: PlaybackId) -> GoudResult<()>;
    fn is_playing(&self, id: PlaybackId) -> bool;
    fn set_volume(&mut self, id: PlaybackId, volume: f32) -> GoudResult<()>;
    fn set_master_volume(&mut self, volume: f32);
    fn set_channel_volume(&mut self, channel: AudioChannel, volume: f32);
    fn set_listener_position(&mut self, pos: Vec3);
    fn set_source_position(&mut self, id: PlaybackId, pos: Vec3) -> GoudResult<()>;
}
}
Built-inFeature FlagNotes
RodioAudioProvideraudioUses existing rodio integration
WebAudioProviderwebBrowser/WASM via web-sys
NullAudioProvideralwaysNo-op, for CI / headless

WindowProvider

WindowProvider extracts the surface-management part of PlatformBackend. It is NOT Send + Sync because GLFW requires all window calls on the main thread (see §3.7).

#![allow(unused)]
fn main() {
pub trait WindowProvider: 'static {  // NOT Send + Sync — see §3.7
    fn init(&mut self) -> GoudResult<()>;
    fn shutdown(&mut self);
    fn should_close(&self) -> bool;
    fn set_should_close(&mut self, value: bool);
    fn poll_events(&mut self);
    fn swap_buffers(&mut self);
    fn get_size(&self) -> (u32, u32);
    fn get_framebuffer_size(&self) -> (u32, u32);
}
}

WindowProvider does NOT extend Provider (which requires Send + Sync) but does include its own init()/shutdown() methods matching the ProviderLifecycle contract. It cannot implement ProviderLifecycle directly because that trait will likely require Provider as a supertrait.

Built-inFeature FlagNotes
GlfwWindowProvidernativeWraps existing GLFW platform layer
WinitWindowProviderwinitFuture — needed for mobile/web targets
NullWindowProvideralwaysNo-op for headless contexts

GLFW is the current platform layer (goud_engine/src/libs/platform/glfw_platform.rs). The roadmap targets winit for broader platform support (mobile, web); WinitWindowProvider will be added when that migration begins.

InputProvider

Extracted from PlatformBackend separately because input has a different update cadence and can be mocked without a real window (e.g., test harnesses injecting synthetic events). For the common GLFW case, a WindowInputBridge helper wires GLFW window events into the InputProvider interface.

#![allow(unused)]
fn main() {
pub trait InputProvider: Provider {
    fn key_pressed(&self, key: KeyCode) -> bool;
    fn key_just_pressed(&self, key: KeyCode) -> bool;
    fn key_just_released(&self, key: KeyCode) -> bool;
    fn mouse_position(&self) -> Vec2;
    fn mouse_delta(&self) -> Vec2;
    fn mouse_button_pressed(&self, button: MouseButton) -> bool;
    fn scroll_delta(&self) -> Vec2;
    fn gamepad_connected(&self, id: GamepadId) -> bool;
    fn gamepad_axis(&self, id: GamepadId, axis: GamepadAxis) -> f32;
    fn gamepad_button_pressed(&self, id: GamepadId, button: GamepadButton) -> bool;
}
}
Built-inFeature FlagNotes
GlfwInputProvidernativeReads from GLFW event queue
NullInputProvideralwaysAll buttons unpressed

3.3 Lifecycle Protocol

Every provider follows a five-phase lifecycle:

  1. Create — constructed with a config struct (RenderConfig, PhysicsConfig, etc.)
  2. Initinit() called during GoudGame::new(). Failure is fatal unless a fallback is configured (§3.9).
  3. Update — per-frame update(delta) for providers that need it (physics, audio).
  4. Shutdownshutdown() called during GoudGame::drop(). Must not fail.
  5. Drop — provider dropped after shutdown. All GPU/OS resources must be released before this point.
#![allow(unused)]
fn main() {
pub trait ProviderLifecycle {
    fn init(&mut self) -> GoudResult<()>;
    fn update(&mut self, delta: f32) -> GoudResult<()>;
    fn shutdown(&mut self);
}
}

All subsystem provider traits (RenderProvider, PhysicsProvider, etc.) extend both Provider and ProviderLifecycle. For example: pub trait RenderProvider: Provider + ProviderLifecycle { ... }.


3.4 Capability Query Pattern

Each subsystem defines a typed capability struct, building on the existing BackendCapabilities pattern in goud_engine/src/libs/graphics/backend/capabilities.rs:

#![allow(unused)]
fn main() {
pub struct RenderCapabilities {
    pub max_texture_units: u32,
    pub max_texture_size: u32,
    pub supports_instancing: bool,
    pub supports_compute: bool,
    pub supports_msaa: bool,
}
}

Each subsystem trait also provides a typed accessor that avoids the downcast:

#![allow(unused)]
fn main() {
pub trait RenderProvider: Provider + ProviderLifecycle {
    fn render_capabilities(&self) -> &RenderCapabilities;
    // ... other methods
}
}

The generic Provider::capabilities() returning Box<dyn Any> remains available for code that operates on providers generically (e.g., logging all provider capabilities at startup). For subsystem-specific code, prefer the typed accessor:

#![allow(unused)]
fn main() {
// Preferred — no downcast, no runtime failure path
let caps = render_provider.render_capabilities();
if caps.supports_instancing {
    renderer.use_instanced_path();
} else {
    renderer.use_fallback_path();
}
}

3.5 Registration and Selection

ProviderRegistry lives at Layer 2 (goud_engine/src/core/providers/registry.rs):

#![allow(unused)]
fn main() {
pub struct ProviderRegistry {
    pub render: Box<dyn RenderProvider>,
    pub physics: Box<dyn PhysicsProvider>,
    pub audio: Box<dyn AudioProvider>,
    pub input: Box<dyn InputProvider>,
    // WindowProvider is !Send+Sync, stored separately in GoudGame.
}
}

Rust SDK — builder pattern with Null*Provider defaults for unconfigured slots:

#![allow(unused)]
fn main() {
let game = GoudEngine::builder()
    .with_renderer(OpenGLRenderProvider::new(RenderConfig::default()))
    .with_physics(Rapier2DPhysicsProvider::new(PhysicsConfig {
        gravity: Vec2::new(0.0, -9.81), ..Default::default()
    }))
    .with_audio(RodioAudioProvider::new(AudioConfig::default()))
    .with_window(GlfwWindowProvider::new(WindowConfig {
        width: 1280, height: 720, title: "My Game".into(),
    }))
    .build()?;
}

FFI SDKs — enum-based selection (custom providers require the Rust SDK):

#![allow(unused)]
fn main() {
#[repr(C)]
pub enum GoudRendererType {
    WgpuAuto = 0,     // Auto-select best wgpu backend
    WgpuVulkan = 1,
    WgpuMetal = 2,
    WgpuDx12 = 3,
    WgpuWebGpu = 4,
    OpenGL = 10,
    Null = 99,
}

#[repr(C)]
pub enum GoudPhysicsType { Rapier2D = 0, Rapier3D = 1, Simple = 2, Null = 99 }

#[repr(C)]
pub enum GoudAudioType { Rodio = 0, WebAudio = 1, Null = 99 }
}

The renderer enum exposes wgpu backend sub-selection to allow SDK users to force a specific GPU API when needed (e.g., Vulkan for Linux, Metal for macOS).


3.6 Object Safety and Dispatch Strategy

All provider traits are object-safe: no associated types, no generic methods. Stored as Box<dyn RenderProvider> etc. Dynamic dispatch overhead is acceptable here because calls are coarse-grained (per-frame or per-batch), not per-vertex.

The existing RenderBackend trait (goud_engine/src/libs/graphics/backend/render_backend.rs) is intentionally NOT object-safe and remains an internal detail inside concrete providers. This mirrors AssetLoader/ErasedAssetLoader in goud_engine/src/assets/loader/traits.rs (same crate): typed generics for hot paths, erased trait objects for storage.

For future cross-provider resource sharing, providers MAY expose fn shared_resources(&self) -> Option<&dyn Any> as an extension point.

For performance-critical inner loops needing direct backend access, providers expose typed accessors:

#![allow(unused)]
fn main() {
impl OpenGLRenderProvider {
    pub(crate) fn backend(&mut self) -> &mut OpenGLBackend { ... }
}
}

3.7 Thread Safety

Default bound: Send + Sync + 'static on all provider traits.

Exception: WindowProvider. GLFW requires main-thread access, so WindowProvider is !Send + !Sync. The engine enforces this by storing it outside ProviderRegistry directly in GoudGame, making GoudGame itself !Send when a native window is present. This matches PlatformBackend in goud_engine/src/libs/platform/mod.rs. If an async executor is adopted in the future, WindowProvider calls must be scheduled on the main thread via a MainThreadScheduler that queues operations from async tasks.


3.8 Hot-Swap (Dev Mode)

Dev-mode only, gated behind #[cfg(debug_assertions)] or dev-tools feature. Protocol:

  1. shutdown() on active provider → drop it
  2. Create and init() replacement provider
  3. Invalidate all resource handles (textures, buffers, shaders) from old provider
  4. Trigger one-frame resource re-upload

Providers declare support with fn supports_hot_swap(&self) -> bool (default false). Implementation tracked in F02-08; this RFC defines constraints only.

Constraints: handle invalidation must produce errors (not silent UB), hot-swap must not be called from multiple threads, and replacement must pass init() before the old provider is dropped.

Expected invalidation mechanism: the engine already uses generational handles (see core/handle.rs). On hot-swap, the provider epoch increments; all existing handles carry the old epoch and fail validation on next use. This avoids scanning all live handles.


3.9 Error Handling

All fallible provider methods return GoudResult<T>. A new variant is added to GoudError (goud_engine/src/core/error/types.rs):

#![allow(unused)]
fn main() {
ProviderError { subsystem: &'static str, message: String }
}

This uses a struct variant (unlike the existing tuple variants like InitializationFailed(String)) because provider errors need the subsystem discriminator for FFI error code routing — error codes 600–609 for render, 610–619 for physics, etc. A single String would require parsing to extract the subsystem.

If init() fails and no fallback is configured, GoudGame::new() returns Err(GoudError::ProviderError { ... }). Fallback providers can be configured via .with_fallback_renderer(NullRenderProvider::new()).


3.10 Layer Placement

libs/ is a module within goud_engine/src/libs/, not a standalone workspace crate. Per CLAUDE.md, libs/ is Layer 1 (lowest) and must not import from Layer 2 (core/, assets/, sdk/) or higher.

ComponentLayerPath
Provider trait definitionsLayer 1goud_engine/src/libs/providers/ (new module)
Concrete implementationsLayer 1goud_engine/src/libs/providers/impls/
ProviderRegistryLayer 2goud_engine/src/core/providers/registry.rs
Builder (GoudEngine::builder())Layer 2goud_engine/src/core/providers/builder.rs
FFI enum selectionLayer 3goud_engine/src/ffi/providers.rs
SDK enum wrappersLayer 4generated via codegen from goud_sdk.schema.json

Provider traits in goud_engine/src/libs/providers/ may import from sibling Layer 1 modules (libs/graphics/, libs/ecs/) but must not import from core/, sdk/, or ffi/ — those are Layer 2+ and importing them would violate the downward-only rule.

Prerequisite: error type placement. Provider trait methods return GoudResult<T>, but GoudResult and GoudError currently live in goud_engine/src/core/error/ (Layer 2). Existing libs/ modules already import from core/error/ (e.g., libs/graphics/backend/ uses GoudResult), which is an existing Layer 1→2 violation. Before implementing this RFC, error types must be moved to a Layer 1 location (e.g., libs/error/) so that provider traits can reference them without upward imports. This is tracked as a prerequisite for F02-02.


3.11 FFI Boundary

SDK users never interact with provider traits. The FFI exposes enum parameters on init, capability query functions returning #[repr(C)] structs, and no provider handles. The high-level API (draw_sprite, play_sound) is unchanged.

#![allow(unused)]
fn main() {
#[no_mangle]
pub unsafe extern "C" fn goud_game_create(
    width: u32, height: u32, title: *const c_char,
    renderer_type: GoudRendererType,
    physics_type: GoudPhysicsType,
    audio_type: GoudAudioType,
) -> *mut GoudGame { ... }
}

4. Alternatives Considered

Feature-flag-only selection (compile-time)

Selecting the backend at compile time with #[cfg(feature = "opengl")] / #[cfg(feature = "wgpu")] avoids dynamic dispatch. It does not support runtime fallback, cannot support NDA backends that cannot be checked in, and requires separate binaries for each backend configuration. Rejected.

Dynamic plugin system (.so/.dll loading)

Loading provider implementations from shared libraries at runtime would allow third-party backends without engine recompilation. It introduces significant complexity: platform differences in library loading, symbol resolution, versioning, and safety. The marginal benefit does not justify the cost for an engine at this stage. Rejected; revisit post-1.0.

Enum dispatch instead of trait objects

Wrapping all built-in providers in an enum and dispatching with match avoids dynamic dispatch costs. It prevents custom providers entirely and grows the match arms with every new backend. It also does not solve the NDA backend problem. Rejected.

Merge Window+Input into a single PlatformProvider

PlatformBackend currently handles both windowing and input together. Keeping them merged is simpler. However, input and windowing have different threading and testing requirements: input can be mocked without a real window, but a window cannot exist without being on the main thread. Splitting them enables cleaner headless testing. The split is the recommended design (see §3.2); merging is noted as an open question (§6) if implementation complexity proves too high.


5. Impact

Breaking Changes

  • GoudGame struct changes: Option<OpenGLBackend> and SpriteBatch<OpenGLBackend> fields are replaced by ProviderRegistry and Option<Box<dyn WindowProvider>>.
  • SpriteBatch<B: RenderBackend> generic parameter changes: SpriteBatch will receive a &mut dyn RenderProvider or a concrete backend reference via downcast. The public API surface of SpriteBatch may change.
  • GoudGame::new(width, height, title, renderer_type) signature expands to accept physics and audio provider selections.

FFI Changes

  • goud_game_create gains GoudPhysicsType and GoudAudioType parameters.
  • New capability query functions added to the FFI surface.
  • C# bindings regenerated via csbindgen after cargo build.
  • Minimum migration for existing games: pass GoudRendererType::OpenGL, GoudPhysicsType::Null, GoudAudioType::Null to preserve current behavior with no functional change.

SDK Changes

  • All three SDK wrappers (C#, Python, TypeScript) updated via codegen from the schema.
  • Init functions gain physics and audio type parameters.
  • Existing game code that uses the default OpenGL + Null physics + Null audio continues to work with updated init calls.

Examples

  • All C# examples in examples/csharp/ updated to pass the new init parameters.
  • Python and TypeScript examples updated in parallel.

Migration Path

Implementation proceeds in phases F02-02 through F02-09 as defined in ALPHA_ROADMAP.md:

  • F02-02: Define libs/providers/ module with all trait definitions.
  • F02-03: Implement OpenGLRenderProvider wrapping the existing backend.
  • F02-04: Implement Rapier2DPhysicsProvider.
  • F02-05: Implement RodioAudioProvider.
  • F02-06: Split PlatformBackend into WindowProvider + GlfwInputProvider.
  • F02-07: Wire ProviderRegistry into GoudGame, remove hardcoded fields.
  • F02-08: Hot-swap mechanism (dev-tools feature only).
  • F02-09: FFI enum selection + SDK codegen updates.

6. Resolved Decisions

  1. Window+Input: Keep Separate. WindowProvider and InputProvider remain separate traits. glfw_platform.rs pumps events via poll_events(&mut self, input: &mut InputManager) — tight event dispatch but loose storage. InputManager is an independent resource, and GamepadState infrastructure in core/input_manager/types.rs exists but isn’t wired to GLFW, proving input sources can be window-independent. Headless testing needs input without a window; gamepad/network inputs don’t come from windows. WindowProvider is !Send + !Sync (GLFW main-thread) while InputProvider can be Send + Sync. A WindowInputBridge helper wires GLFW events → InputProvider for the common case.

  2. Resource Ownership: Provider-Owned. Providers own their resources. A shared ResourcePool is deferred to post-stabilization. RenderBackend already uses handle-based resource management (create_buffer(), create_texture(), destroy_*()), and GoudGame is the single root owner. No cross-subsystem resource sharing exists today. Extension point: providers MAY expose fn shared_resources(&self) -> Option<&dyn Any> for future cross-provider sharing.

  3. NullProvider: Explicit Structs. Use explicit Null*Provider structs, not default method implementations. The codebase uses Option<T> for conditional features and AssetState enum with explicit states (NotLoaded, Loading, Failed) rather than silent defaults. Explicit structs are visible, debuggable, and testable (can count draw calls, track state). FFI enum values (GoudRendererType::Null) map cleanly to concrete structs. Trait methods stay without defaults — forces implementors to be explicit.

  4. Async & Main-Thread: Document Constraint, Defer. The !Send constraint on GoudGame when a native WindowProvider is present is documented and correct. No async resolution is needed now — there is no async code in the codebase, and parallelism is Rayon scope-based (parallel.rs). GLFW requires main-thread access (glfw_platform.rs lines 7–10) and GoudContext is already !Send + !Sync. If an async executor is adopted later, WindowProvider calls must be scheduled on the main thread via a MainThreadScheduler that queues operations from async tasks.

API Reference

Auto-generated API documentation for each SDK. Updated on every push to main.

Rust

Full Rust API docs generated by cargo doc.

Rust API Reference

C#

C# API docs generated by DocFX from XML doc comments.

C# API Reference

Python

Python API docs generated by pdoc from docstrings.

Python API Reference

TypeScript

TypeScript API docs generated by TypeDoc from JSDoc comments.

TypeScript API Reference

husky-rs

CI Crates.io Documentation License

husky-rs is a Git hooks management tool for Rust projects, inspired by Husky.

Features

  • Easy setup and configuration
  • Automatic installation of Git hooks
  • Support for all Git hooks
  • Cross-platform compatibility (Unix-like systems and Windows)

Quick Start

  1. Adding husky-rs to your project:

    You have several options:

    # Option 1: Add as a Regular Dependency
    cargo add husky-rs
    
    # Option 2: Add as a Dev Dependency
    cargo add --dev husky-rs
    
    # Option 3: Use the Main Branch
    cargo add --git https://github.com/pplmx/husky-rs --branch main
    cargo add --dev --git https://github.com/pplmx/husky-rs --branch main
    
  2. Create hooks directory:

    mkdir -p .husky/hooks
    
  3. Add a hook (e.g., pre-commit):

    echo '#!/bin/sh\necho "Running pre-commit hook"' > .husky/hooks/pre-commit
    
  4. Install hooks:

    Note: Due to the execution mechanism of build.rs, running cargo clean is required when installing or updating hooks.

    cargo clean && cargo test
    

Tip: If you add this library to the [dependencies] section, both cargo build and cargo test will work. However, if it’s added under [dev-dependencies], only cargo test will function as expected.

Usage

Supported Git Hooks

husky-rs aims to support a wide range of Git hooks, including:

  • pre-commit
  • prepare-commit-msg
  • commit-msg
  • post-commit
  • pre-push

For a complete list of supported hooks, refer to the Git documentation.

If you encounter any unsupported hooks, please don’t hesitate to open an issue.

Configuration

To skip hook installation:

NO_HUSKY_HOOKS=1 cargo build

Best Practices

  • Keep hooks lightweight to avoid slowing down Git operations
  • Use hooks for tasks like running tests, linting code, and validating commit messages
  • Non-zero exit status in a hook script will abort the Git operation

Development

For information on setting up the development environment, running tests, and contributing to the project, please refer to our Development Guide.

Troubleshooting

If you encounter any issues while using husky-rs, please check our Troubleshooting Guide for common problems and their solutions. If you can’t find a solution to your problem, please open an issue on our GitHub repository.

Contributing

We welcome contributions! Please see our Contributing Guide for details on how to submit pull requests, report issues, or suggest improvements.

License

This project is licensed under either of:

  • Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  • MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

Changelog

For a detailed history of changes to this project, please refer to our CHANGELOG.md.

Acknowledgments

  • Inspired by cargo-husky
  • Thanks to the Rust community for their amazing tools and libraries

cbindgen User Guide

cbindgen creates C/C++11 headers for Rust libraries which expose a public C API.

While you could do this by hand, it’s not a particularly good use of your time. It’s also much more likely to be error-prone than machine-generated headers that are based on your actual Rust code. The cbindgen developers have also worked closely with the developers of Rust to ensure that the headers we generate reflect actual guarantees about Rust’s type layout and ABI.

C++ headers are nice because we can use operator overloads, constructors, enum classes, and templates to make the API more ergonomic and Rust-like. C headers are nice because you can be more confident that whoever you’re interoperating with can handle them. With cbindgen you don’t need to choose! You can just tell it to emit both from the same Rust library.

There are two ways to use cbindgen: as a standalone program, or as a library (presumably in your build.rs). There isn’t really much practical difference, because cbindgen is a simple rust library with no interesting dependencies. Using it as a program means people building your software will need it installed. Using it in your library means people may have to build cbindgen more frequently (e.g. every time they update their rust compiler).

It’s worth noting that the development of cbindgen has been largely adhoc, as features have been added to support the usecases of the maintainers. This means cbindgen may randomly fail to support some particular situation simply because no one has put in the effort to handle it yet. Please file an issue if you run into such a situation. Although since we all have other jobs, you might need to do the implementation work too :)

Quick Start

To install cbindgen, you just need to run

cargo install --force cbindgen

(–force just makes it update to the latest cbindgen if it’s already installed)

To use cbindgen you need two things:

  • A configuration (cbindgen.toml, which can be empty to start)
  • A Rust crate with a public C API

Then all you need to do is run it:

cbindgen --config cbindgen.toml --crate my_rust_library --output my_header.h

This produces a header file for C++. For C, add the --lang c switch.
cbindgen also supports generation of Cython bindings, use --lang cython for that.

See cbindgen --help for more options.

Get a template cbindgen.toml here.

build.rs

If you don’t want to use cbindgen as an application, here’s an example build.rs script:

extern crate cbindgen;

use std::env;

fn main() {
    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    cbindgen::Builder::new()
      .with_crate(crate_dir)
      .generate()
      .expect("Unable to generate bindings")
      .write_to_file("bindings.h");
}

You can add configuration options using the Builder interface.

When actively working on code, you likely don’t want cbindgen to fail the entire build. Instead of expect-ing the result of the header generation, you could ignore parse errors and let rustc or your code analysis bring up:

#![allow(unused)]
fn main() {
    // ...
    .generate()
    .map_or_else(
        |error| match error {
            cbindgen::Error::ParseSyntaxError { .. } => {}
            e => panic!("{:?}", e),
        },
        |bindings| {
            bindings.write_to_file("target/include/bindings.h");
        },
    );
}
}

Be sure to add the following section to your Cargo.toml:

[build-dependencies]
cbindgen = "0.24.0"

If you’d like to use a build.rs script with a cbindgen.toml, consider using cbindgen::generate() instead.

Writing Your C API

cbindgen has a simple but effective strategy. It walks through your crate looking for:

  • #[no_mangle] pub extern fn (“functions”)
  • #[no_mangle] pub static (“globals”)
  • pub const (“constants”)

and generates a header declaring those items. But to declare those items, it needs to also be able to describe the layout and ABI of the types that appear in their signatures. So it will also spider through your crate (and optionally its dependencies) to try to find the definitions of every type used in your public API.

🚨 NOTE: A major limitation of cbindgen is that it does not understand Rust’s module system or namespacing. This means that if cbindgen sees that it needs the definition for MyType and there exists two things in your project with the type name MyType, it won’t know what to do. Currently, cbindgen’s behaviour is unspecified if this happens. However this may be ok if they have different cfgs.

If a type is determined to have a guaranteed layout, a full definition will be emitted in the header. If the type doesn’t have a guaranteed layout, only a forward declaration will be emitted. This may be fine if the type is intended to be passed around opaquely and by reference.

Examples

🚧 🏗

It would be really nice to have some curated and clean examples, but we don’t have those yet.

The README has some useful links though.

Supported Types

Most things in Rust don’t have a guaranteed layout by default. In most cases this is nice because it enables layout to be optimized in the majority of cases where type layout isn’t that interesting. However this is problematic for our purposes. Thankfully Rust lets us opt into guaranteed layouts with the repr attribute.

You can learn about all of the different repr attributes by reading Rust’s reference, but here’s a quick summary:

  • #[repr(C)]: give this struct/union/enum the same layout and ABI C would
  • #[repr(u8, u16, ... etc)]: give this enum the same layout and ABI as the given integer type
  • #[repr(transparent)]: give this single-field struct the same ABI as its field (useful for newtyping integers but keeping the integer ABI)

cbindgen supports the #[repr(align(N))] and #[repr(packed)] attributes, but currently does not support #[repr(packed(N))].

cbindgen also supports using repr(C)/repr(u8) on non-C-like enums (enums with fields). This gives a C-compatible tagged union layout, as defined by this RFC 2195. repr(C) will give a simpler layout that is perhaps more intuitive, while repr(u8) will produce a more compact layout.

If you ensure everything has a guaranteed repr, then cbindgen will generate definitions for:

  • struct (named-style or tuple-style)
  • enum (fieldless or with fields)
  • union
  • type
  • [T; n] (arrays always have a guaranteed C-compatible layout)
  • &T, &mut T, *const T, *mut T, Option<&T>, Option<&mut T> (all have the same pointer ABI)
  • fn() (as an actual function pointer)
  • bitflags! { ... } (if macro_expansion.bitflags is enabled)

structs, enums, unions, and type aliases may be generic, although certain generic substitutions may fail to resolve under certain configurations. In C mode generics are resolved through monomorphization and mangling, while in C++ mode generics are resolved with templates. cbindgen cannot support generic functions, as they do not actually have a single defined symbol.

cbindgen sadly cannot ever support anonymous tuples (A, B, ...), as there is no way to guarantee their layout. You must use a tuple struct.

cbindgen also cannot support wide pointers like &dyn Trait or &[T], as their layout and ABI is not guaranteed. In the case of slices you can at least decompose them into a pointer and length, and reconstruct them with slice::from_raw_parts.

If cbindgen determines that a type is zero-sized, it will erase all references to that type (so fields of that type simply won’t be emitted). This won’t work if that type appears as a function argument because C, C++, and Rust all have different definitions of what it means for a type to be empty.

Don’t use the [u64; 0] trick to over-align a struct, we don’t support this.

cbindgen contains the following hardcoded mappings (again completely ignoring namespacing, literally just looking at the name of the type):

std types

  • bool => bool
  • char => uint32_t
  • u8 => uint8_t
  • u16 => uint16_t
  • u32 => uint32_t
  • u64 => uint64_t
  • usize => uintptr_t
  • i8 => int8_t
  • i16 => int16_t
  • i32 => int32_t
  • i64 => int64_t
  • isize => intptr_t
  • f32 => float
  • f64 => double
  • VaList => va_list
  • RawFd => int
  • PhantomData => evaporates, can only appear as the field of a type
  • PhantomPinned => evaporates, can only appear as the field of a type
  • () => evaporates, can only appear as the field of a type
  • MaybeUninit, ManuallyDrop, and Pin => T

libc types

  • c_void => void
  • c_char => char
  • c_schar => signed char
  • c_uchar => unsigned char
  • c_float => float
  • c_double => double
  • c_short => short
  • c_int => int
  • c_long => long
  • c_longlong => long long
  • c_ushort => unsigned short
  • c_uint => unsigned int
  • c_ulong => unsigned long
  • c_ulonglong => unsigned long long

stdint types

  • uint8_t => uint8_t
  • uint16_t => uint16_t
  • uint32_t => uint32_t
  • uint64_t => uint64_t
  • uintptr_t => uintptr_t
  • size_t => size_t
  • int8_t => int8_t
  • int16_t => int16_t
  • int32_t => int32_t
  • int64_t => int64_t
  • intptr_t => intptr_t
  • ssize_t => ssize_t
  • ptrdiff_t => ptrdiff_t

Configuring Your Header

cbindgen supports several different options for configuring the output of your header, including target language, styling, mangling, prefixing, includes, and defines.

Defines and Cfgs

As cbindgen spiders through your crate, it will make note of all the cfgs it found on the path to every item. If it finds multiple declarations that share a single name but have different cfgs, it will then try to emit every version it found wrapped in defines that correspond to those cfgs. In this way platform-specific APIs or representations can be properly supported.

However cbindgen has no way of knowing how you want to map those cfgs to defines. You will need to use the [defines] section in your cbindgen.toml to specify all the different mappings. It natively understands concepts like any() and all(), so you only need to tell it how you want to translate base concepts like target_os = "freebsd" or feature = "serde".

Note that because cbindgen just parses the source of your crate, you mostly don’t need to worry about what crate features or what platform you’re targetting. Every possible configuration should be visible to the parser. Our primitive mappings should also be completely platform agnostic (i32 is int32_t regardless of your target).

While modules within a crate form a tree with uniquely defined paths to each item, and therefore uniquely defined cfgs for those items, dependencies do not. If you depend on a crate in multiple ways, and those ways produce different cfgs, one of them will be arbitrarily chosen for any types found in that crate.

Annotations

While output configuration is primarily done through the cbindgen.toml, in some cases you need to manually override your global settings. In those cases you can add inline annotations to your types, which are doc comments that start with cbindgen:. Here’s an example of using annotations to rename a struct’s fields and opt into overloading operator==:

#![allow(unused)]
fn main() {
/// cbindgen:field-names=[x, y]
/// cbindgen:derive-eq
#[repr(C)]
pub struct Point(pub f32, pub f32);
}

An annotation may be a bool, string (no quotes), or list of strings. If just the annotation’s name is provided, =true is assumed. The annotation parser is currently fairly naive and lacks any capacity for escaping, so don’t try to make any strings with =, ,, [ or ].

Most annotations are just local overrides for identical settings in the cbindgen.toml, but a few are unique because they don’t make sense in a global context. The set of supported annotation are as follows:

Ignore annotation

cbindgen will automatically ignore any #[test] or #[cfg(test)] item it finds. You can manually ignore other stuff with the ignore annotation attribute:

#![allow(unused)]
fn main() {
pub mod my_interesting_mod;

/// cbindgen:ignore
pub mod my_uninteresting_mod; // This won't be scanned by cbindgen.
}

No export annotation

cbindgen will usually emit all items it finds, as instructed by the parse and export config sections. This annotation will make cbindgen skip this item from the output, while still being aware of it. This is useful for a) suppressing “Can’t find” errors and b) emitting struct my_struct for types in a different header (rather than a bare my_struct).

There is no equivalent config for this annotation - by comparison, the export exclude config will cause cbindgen to not be aware of the item at all.

Note that cbindgen will still traverse no-export structs that are repr(C) to emit types present in the fields. You will need to manually exclude those types in your config if desired.

/// cbindgen:no-export
#[repr(C)]
pub struct Foo { .. }; // This won't be emitted by cbindgen in the header

#[repr(C)]
fn bar() -> Foo { .. } // Will be emitted as `struct foo bar();`

Struct Annotations

  • field-names=[field1, field2, …] – sets the names of all the fields in the output struct. These names will be output verbatim, and are not eligible for renaming.

The rest are just local overrides for the same options found in the cbindgen.toml:

  • rename-all=RenameRule
  • derive-constructor
  • derive-eq
  • derive-neq
  • derive-lt
  • derive-lte
  • derive-gt
  • derive-gte
  • {eq,neq,lt,lte,gt,gte}-attributes: Takes a single identifier which will be emitted before the signature of the auto-generated operator== / operator!= / etc(if any). The idea is for this to be used to annotate the operator with attributes, for example:
#![allow(unused)]
fn main() {
/// cbindgen:eq-attributes=MY_ATTRIBUTES
#[repr(C)]
pub struct Foo { .. }
}

Will generate something like:

  MY_ATTRIBUTES bool operator==(const Foo& other) const {
    ...
  }

Combined with something like:

#define MY_ATTRIBUTES [[nodiscard]]

for example.

Enum Annotations

  • enum-trailing-values=[variant1, variant2, …] – add the following fieldless enum variants to the end of the enum’s definition. These variant names will have the enum’s renaming rules applied.

WARNING: if any of these values are ever passed into Rust, behaviour will be Undefined. Rust does not know about them, and will assume they cannot happen.

The rest are just local overrides for the same options found in the cbindgen.toml:

  • rename-all=RenameRule
  • add-sentinel
  • derive-helper-methods
  • derive-const-casts
  • derive-mut-casts
  • derive-tagged-enum-destructor
  • derive-tagged-enum-copy-constructor
  • enum-class
  • prefix-with-name
  • private-default-tagged-enum-constructor
  • {destructor,copy-constructor,copy-assignment}-attributes: See the description of the struct attributes, these do the same for the respective generated code.

Enum variant annotations

These apply to both tagged and untagged enum variants.

  • variant-{constructor,const-cast,mut-cast,is}-attributes: See the description of the struct attributes. These do the same for the respective functions.

TODO: We should allow to override the derive-{const,mut}-casts, helper methods et al. with per-variant annotations, probably.

Union Annotations

  • field-names=[field1, field2, …] – sets the names of all the fields in the output union. These names will be output verbatim, and are not eligible for renaming.

The rest are just local overrides for the same options found in the cbindgen.toml:

  • rename-all=RenameRule

Function Annotations

All function attributes are just local overrides for the same options found in the cbindgen.toml:

  • rename-all=RenameRule
  • prefix
  • postfix
  • ptrs-as-arrays=[[ptr_name1; array_length1], [ptr_name2; array_length2], …] – represents the pointer arguments of a function as arrays. Below how the mappings are performed:
arg: *const T --> const T arg[array_length]
arg: *mut T ---> T arg[array_length]

If array_length is not specified:

arg: *const T --> const T arg[]
arg: *mut T --> T arg[]

Generating Swift Bindings

In addition to parsing function names in C/C++ header files, the Swift compiler can make use of the swift_name attribute on functions to generate more idiomatic names for imported functions and methods.

This attribute is commonly used in Objective-C/C/C++ via the NS_SWIFT_NAME and CF_SWIFT_NAME macros.

Given configuration in the cbindgen.toml, cbindgen can generate these attributes for you by guessing an appropriate method signature based on the existing function name (and type, if it is a method in an impl block).

This is controlled by the swift_name_macro option in the cbindgen.toml.

cbindgen.toml

Most configuration happens through your cbindgen.toml file. Every value has a default (that is usually reasonable), so you can start with an empty cbindgen.toml and tweak it until you like the output you’re getting.

Note that many options defined here only apply for one of C or C++. Usually it’s an option specifying whether we should try to make use of a feature in C++’s type system or generate a helper method.

# The language to output bindings in
#
# possible values: "C", "C++", "Cython"
#
# default: "C++"
language = "C"




# Options for wrapping the contents of the header:

# An optional string of text to output at the beginning of the generated file
# default: doesn't emit anything
header = "/* Text to put at the beginning of the generated file. Probably a license. */"

# An optional string of text to output at the end of the generated file
# default: doesn't emit anything
trailer = "/* Text to put at the end of the generated file */"

# An optional name to use as an include guard
# default: doesn't emit an include guard
include_guard = "mozilla_wr_bindings_h"

# Whether to add a `#pragma once` guard
# default: doesn't emit a `#pragma once`
pragma_once = true

# An optional string of text to output between major sections of the generated
# file as a warning against manual editing
#
# default: doesn't emit anything
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"

# Whether to include a comment with the version of cbindgen used to generate the file
# default: false
include_version = true

# An optional namespace to output around the generated bindings
# default: doesn't emit a namespace
namespace = "ffi"

# An optional list of namespaces to output around the generated bindings
# default: []
namespaces = ["mozilla", "wr"]

# An optional list of namespaces to declare as using with "using namespace"
# default: []
using_namespaces = ["mozilla", "wr"]

# A list of sys headers to #include (with angle brackets)
# default: []
sys_includes = ["stdio", "string"]

# A list of headers to #include (with quotes)
# default: []
includes = ["my_great_lib.h"]

# Whether cbindgen's default C/C++ standard imports should be suppressed. These
# imports are included by default because our generated headers tend to require
# them (e.g. for uint32_t). Currently, the generated imports are:
#
# * for C: <stdarg.h>, <stdbool.h>, <stdint.h>, <stdlib.h>, <uchar.h>
#
# * for C++: <cstdarg>, <cstdint>, <cstdlib>, <new>, <cassert> (depending on config)
#
# default: false
no_includes = false

# Whether to make a C header C++ compatible.
# These will wrap generated functions into a `extern "C"` block, e.g.
#
# #ifdef __cplusplus
# extern "C" {
# #endif // __cplusplus
#
# // Generated functions.
#
# #ifdef __cplusplus
# } // extern "C"
# #endif // __cplusplus
#
# If the language is not C this option won't have any effect.
#
# default: false
cpp_compat = false

# A list of lines to add verbatim after the includes block
after_includes = "#define VERSION 1"



# Code Style Options

# The style to use for curly braces
#
# possible values: "SameLine", "NextLine"
#
# default: "SameLine"
braces = "SameLine"

# The desired length of a line to use when formatting lines
# default: 100
line_length = 80

# The amount of spaces to indent by
# default: 2
tab_width = 3

# Include doc comments from Rust as documentation
documentation = true

# How the generated documentation should be commented.
#
# possible values:
# * "c": /* like this */
# * "c99": // like this
# * "c++": /// like this
# * "doxy": like C, but with leading *'s on each line
# * "auto": "c++" if that's the language, "doxy" otherwise
#
# default: "auto"
documentation_style = "doxy"

# How much of the documentation for each item is output.
#
# possible values:
# * "short": Only the first line.
# * "full": The full documentation.
#
# default: "full"
documentation_length = "short"




# Codegen Options

# When generating a C header, the kind of declaration style to use for structs
# or enums.
#
# possible values:
# * "type": typedef struct { ... } MyType;
# * "tag": struct MyType { ... };
# * "both": typedef struct MyType { ... } MyType;
#
# default: "both"
style = "both"

# If this option is true `usize` and `isize` will be converted into `size_t` and `ptrdiff_t`
# instead of `uintptr_t` and `intptr_t` respectively.
usize_is_size_t = true

# A list of substitutions for converting cfg's to ifdefs. cfgs which aren't
# defined here will just be discarded.
#
# e.g.
# `#[cfg(target = "freebsd")] ...`
# becomes
# `#if defined(DEFINE_FREEBSD) ... #endif`
[defines]
"target_os = freebsd" = "DEFINE_FREEBSD"
"feature = serde" = "DEFINE_SERDE"





[export]
# A list of additional items to always include in the generated bindings if they're
# found but otherwise don't appear to be used by the public API.
#
# default: []
include = ["MyOrphanStruct", "MyGreatTypeRename"]

# A list of items to not include in the generated bindings
# default: []
exclude = ["Bad"]

# A prefix to add before the name of every item
# default: no prefix is added
prefix = "CAPI_"

# Types of items that we'll generate. If empty, then all types of item are emitted.
#
# possible items: (TODO: explain these in detail)
# * "constants":
# * "globals":
# * "enums":
# * "structs":
# * "unions":
# * "typedefs":
# * "opaque":
# * "functions":
#
# default: []
item_types = ["enums", "structs", "opaque", "functions"]

# Whether applying rules in export.rename prevents export.prefix from applying.
#
# e.g. given this toml:
#
# [export]
# prefix = "capi_"
# [export.rename]
# "MyType" = "my_cool_type"
#
# You get the following results:
#
# renaming_overrides_prefixing = true:
# "MyType" => "my_cool_type"
#
# renaming_overrides_prefixing = false:
# "MyType => capi_my_cool_type"
#
# default: false
renaming_overrides_prefixing = true

# Table of name conversions to apply to item names (lhs becomes rhs)
[export.rename]
"MyType" = "my_cool_type"
"my_function" = "BetterFunctionName"

# Table of things to prepend to the body of any struct, union, or enum that has the
# given name. This can be used to add things like methods which don't change ABI,
# mark fields private, etc
[export.pre_body]
"MyType" = """
  MyType() = delete;
private:
"""

# Table of things to append to the body of any struct, union, or enum that has the
# given name. This can be used to add things like methods which don't change ABI.
[export.body]
"MyType" = """
  void cppMethod() const;
"""

# Configuration for name mangling
[export.mangle]
# Whether the types should be renamed during mangling, for example
# c_char -> CChar, etc.
rename_types = "PascalCase"
# Whether the underscores from the mangled name should be omitted.
remove_underscores = false

[layout]
# A string that should come before the name of any type which has been marked
# as `#[repr(packed)]`. For instance, "__attribute__((packed))" would be a
# reasonable value if targeting gcc/clang. A more portable solution would
# involve emitting the name of a macro which you define in a platform-specific
# way. e.g. "PACKED"
#
# default: `#[repr(packed)]` types will be treated as opaque, since it would
# be unsafe for C callers to use a incorrectly laid-out union.
packed = "PACKED"

# A string that should come before the name of any type which has been marked
# as `#[repr(align(n))]`. This string must be a function-like macro which takes
# a single argument (the requested alignment, `n`). For instance, a macro
# `#define`d as `ALIGNED(n)` in `header` which translates to
# `__attribute__((aligned(n)))` would be a reasonable value if targeting
# gcc/clang.
#
# default: `#[repr(align(n))]` types will be treated as opaque, since it
# could be unsafe for C callers to use a incorrectly-aligned union.
aligned_n = "ALIGNED"


[fn]
# An optional prefix to put before every function declaration
# default: no prefix added
prefix = "WR_START_FUNC"

# An optional postfix to put after any function declaration
# default: no postix added
postfix = "WR_END_FUNC"

# How to format function arguments
#
# possible values:
# * "horizontal": place all arguments on the same line
# * "vertical": place each argument on its own line
# * "auto": only use vertical if horizontal would exceed line_length
#
# default: "auto"
args = "horizontal"

# An optional string that should prefix function declarations which have been
# marked as `#[must_use]`. For instance, "__attribute__((warn_unused_result))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "MUST_USE_FUNC"
# default: nothing is emitted for must_use functions
must_use = "MUST_USE_FUNC"

# An optional string that should prefix function declarations which have been
# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "DEPRECATED_FUNC"
# default: nothing is emitted for deprecated functions
deprecated = "DEPRECATED_FUNC"

# An optional string that should prefix function declarations which have been
# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the
# double-quoted string. For instance, "__attribute__((deprecated({})))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "DEPRECATED_FUNC_WITH_NOTE(note)"
# default: nothing is emitted for deprecated functions
deprecated_with_notes = "DEPRECATED_FUNC_WITH_NOTE"

# An optional string that will be used in the attribute position for functions
# that don't return (that return `!` in Rust).
#
# For instance, `__attribute__((noreturn))` would be a reasonable value if
# targeting gcc/clang.
no_return = "NO_RETURN"

# An optional string that, if present, will be used to generate Swift function
# and method signatures for generated functions, for example "CF_SWIFT_NAME".
# If no such macro is available in your toolchain, you can define one using the
# `header` option in cbindgen.toml
# default: no swift_name function attributes are generated
swift_name_macro = "CF_SWIFT_NAME"

# A rule to use to rename function argument names. The renaming assumes the input
# is the Rust standard snake_case, however it accepts all the different rename_args
# inputs. This means many options here are no-ops or redundant.
#
# possible values (that actually do something):
# * "CamelCase": my_arg => myArg
# * "PascalCase": my_arg => MyArg
# * "GeckoCase": my_arg => aMyArg
# * "ScreamingSnakeCase": my_arg => MY_ARG
# * "None": apply no renaming
#
# technically possible values (that shouldn't have a purpose here):
# * "SnakeCase": apply no renaming
# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?)
# * "UpperCase": same as ScreamingSnakeCase in this context
# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context
#
# default: "None"
rename_args = "PascalCase"

# This rule specifies the order in which functions will be sorted.
#
# "Name": sort by the name of the function
# "None": keep order in which the functions have been parsed
#
# default: "None"
sort_by = "Name"

[struct]
# A rule to use to rename struct field names. The renaming assumes the input is
# the Rust standard snake_case, however it acccepts all the different rename_args
# inputs. This means many options here are no-ops or redundant.
#
# possible values (that actually do something):
# * "CamelCase": my_arg => myArg
# * "PascalCase": my_arg => MyArg
# * "GeckoCase": my_arg => mMyArg
# * "ScreamingSnakeCase": my_arg => MY_ARG
# * "None": apply no renaming
#
# technically possible values (that shouldn't have a purpose here):
# * "SnakeCase": apply no renaming
# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?)
# * "UpperCase": same as ScreamingSnakeCase in this context
# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context
#
# default: "None"
rename_fields = "PascalCase"

# An optional string that should come before the name of any struct which has been
# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "MUST_USE_STRUCT"
#
# default: nothing is emitted for must_use structs
must_use = "MUST_USE_STRUCT"

# An optional string that should come before the name of any struct which has been
# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "DEPRECATED_STRUCT"
# default: nothing is emitted for deprecated structs
deprecated = "DEPRECATED_STRUCT"

# An optional string that should come before the name of any struct which has been
# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the
# double-quoted string. For instance, "__attribute__((deprecated({})))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "DEPRECATED_STRUCT_WITH_NOTE(note)"
# default: nothing is emitted for deprecated structs
deprecated_with_notes = "DEPRECATED_STRUCT_WITH_NOTE"

# Whether a Rust type with associated consts should emit those consts inside the
# type's body. Otherwise they will be emitted trailing and with the type's name
# prefixed. This does nothing if the target is C, or if
# [const]allow_static_const = false
#
# default: false
# associated_constants_in_body: false

# Whether to derive a simple constructor that takes a value for every field.
# default: false
derive_constructor = true

# Whether to derive an operator== for all structs
# default: false
derive_eq = false

# Whether to derive an operator!= for all structs
# default: false
derive_neq = false

# Whether to derive an operator< for all structs
# default: false
derive_lt = false

# Whether to derive an operator<= for all structs
# default: false
derive_lte = false

# Whether to derive an operator> for all structs
# default: false
derive_gt = false

# Whether to derive an operator>= for all structs
# default: false
derive_gte = false





[enum]
# A rule to use to rename enum variants, and the names of any fields those
# variants have. This should probably be split up into two separate options, but
# for now, they're the same! See the documentation for `[struct]rename_fields`
# for how this applies to fields. Renaming of the variant assumes that the input
# is the Rust standard PascalCase. In the case of QualifiedScreamingSnakeCase,
# it also assumed that the enum's name is PascalCase.
#
# possible values (that actually do something):
# * "CamelCase": MyVariant => myVariant
# * "SnakeCase": MyVariant => my_variant
# * "ScreamingSnakeCase": MyVariant => MY_VARIANT
# * "QualifiedScreamingSnakeCase": MyVariant => ENUM_NAME_MY_VARIANT
# * "LowerCase": MyVariant => myvariant
# * "UpperCase": MyVariant => MYVARIANT
# * "None": apply no renaming
#
# technically possible values (that shouldn't have a purpose for the variants):
# * "PascalCase": apply no renaming
# * "GeckoCase": apply no renaming
#
# default: "None"
rename_variants = "None"

# Whether an extra "sentinel" enum variant should be added to all generated enums.
# Firefox uses this for their IPC serialization library.
#
# WARNING: if the sentinel is ever passed into Rust, behaviour will be Undefined.
# Rust does not know about this value, and will assume it cannot happen.
#
# default: false
add_sentinel = false

# Whether enum variant names should be prefixed with the name of the enum.
# default: false
prefix_with_name = false

# Whether to emit enums using "enum class" when targeting C++.
# default: true
enum_class = true

# Whether to generate static `::MyVariant(..)` constructors and `bool IsMyVariant()`
# methods for enums with fields.
#
# default: false
derive_helper_methods = false

# Whether to generate `const MyVariant& AsMyVariant() const` methods for enums with fields.
# default: false
derive_const_casts = false

# Whether to generate `MyVariant& AsMyVariant()` methods for enums with fields
# default: false
derive_mut_casts = false

# The name of the macro/function to use for asserting `IsMyVariant()` in the body of
# derived `AsMyVariant()` cast methods.
#
# default: "assert" (but also causes `<cassert>` to be included by default)
cast_assert_name = "MOZ_RELEASE_ASSERT"

# An optional string that should come before the name of any enum which has been
# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "MUST_USE_ENUM"
#
# Note that this refers to the *output* type. That means this will not apply to an enum
# with fields, as it will be emitted as a struct. `[struct]must_use` will apply there.
#
# default: nothing is emitted for must_use enums
must_use = "MUST_USE_ENUM"

# An optional string that should come before the name of any enum which has been
# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "DEPRECATED_ENUM"
# default: nothing is emitted for deprecated enums
deprecated = "DEPRECATED_ENUM"

# An optional string that should come before the name of any enum which has been
# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the
# double-quoted string. For instance, "__attribute__((deprecated({})))"
# would be a reasonable value if targeting gcc/clang. A more portable solution
# would involve emitting the name of a macro which you define in a
# platform-specific way. e.g. "DEPRECATED_ENUM_WITH_NOTE(note)"
# default: nothing is emitted for deprecated enums
deprecated_with_notes = "DEPRECATED_ENUM_WITH_NOTE"

# An optional string that should come after the name of any enum variant which has been
# marked as `#[deprecated]` without note. For instance, "__attribute__((deprecated))"
# would be a reasonable value if targeting gcc/clang. A more portable solution would
# involve emitting the name of a macro which you define in a platform-specific
# way. e.g. "DEPRECATED_ENUM_VARIANT"
# default: nothing is emitted for deprecated enum variants
deprecated_variant = "DEPRECATED_ENUM_VARIANT"

# An optional string that should come after the name of any enum variant which has been
# marked as `#[deprecated(note = "reason")]`. `{}` will be replaced with the
# double-quoted string. For instance, "__attribute__((deprecated({})))" would be a
# reasonable value if targeting gcc/clang. A more portable solution would involve
# emitting the name of a macro which you define in a platform-specific
# way. e.g. "DEPRECATED_ENUM_WITH_NOTE(note)"
# default: nothing is emitted for deprecated enum variants
deprecated_variant_with_notes = "DEPRECATED_ENUM_VARIANT_WITH_NOTE({})"

# Whether enums with fields should generate destructors. This exists so that generic
# enums can be properly instantiated with payloads that are C++ types with
# destructors. This isn't necessary for structs because C++ has rules to
# automatically derive the correct constructors and destructors for those types.
#
# Care should be taken with this option, as Rust and C++ cannot
# properly interoperate with eachother's notions of destructors. Also, this may
# change the ABI for the type. Either your destructor-full enums must live
# exclusively within C++, or they must only be passed by-reference between
# C++ and Rust.
#
# default: false
derive_tagged_enum_destructor = false

# Whether enums with fields should generate copy-constructor. See the discussion on
# derive_tagged_enum_destructor for why this is both useful and very dangerous.
#
# default: false
derive_tagged_enum_copy_constructor = false
# Whether enums with fields should generate copy-assignment operators.
#
# This depends on also deriving copy-constructors, and it is highly encouraged
# for this to be set to true.
#
# default: false
derive_tagged_enum_copy_assignment = false

# Whether enums with fields should generate an empty, private destructor.
# This allows the auto-generated constructor functions to compile, if there are
# non-trivially constructible members. This falls in the same family of
# dangerousness as `derive_tagged_enum_copy_constructor` and co.
#
# default: false
private_default_tagged_enum_constructor = false





[const]
# Whether a generated constant can be a static const in C++ mode. I have no
# idea why you would turn this off.
#
# default: true
allow_static_const = true

# Whether a generated constant can be constexpr in C++ mode.
#
# default: true
allow_constexpr = false

# This rule specifies the order in which constants will be sorted.
#
# "Name": sort by the name of the constant
# "None": keep order in which the constants have been parsed
#
# default: "None"
sort_by = "Name"




[macro_expansion]
# Whether bindings should be generated for instances of the bitflags! macro.
# default: false
bitflags = true






# Options for how your Rust library should be parsed

[parse]
# Whether to parse dependent crates and include their types in the output
# default: false
parse_deps = true

# A white list of crate names that are allowed to be parsed. If this is defined,
# only crates found in this list will ever be parsed.
#
# default: there is no whitelist (NOTE: this is the opposite of [])
include = ["webrender", "webrender_traits"]

# A black list of crate names that are not allowed to be parsed.
# default: []
exclude = ["libc"]

# Whether to use a new temporary target directory when running `rustc -Zunpretty=expanded`.
# This may be required for some build processes.
#
# default: false
clean = false

# Which crates other than the top-level binding crate we should generate
# bindings for.
#
# default: []
extra_bindings = ["my_awesome_dep"]

[parse.expand]
# A list of crate names that should be run through `cargo expand` before
# parsing to expand any macros. Note that if a crate is named here, it
# will always be parsed, even if the blacklist/whitelist says it shouldn't be.
#
# default: []
crates = ["euclid"]

# If enabled,  use the `--all-features` option when expanding. Ignored when
# `features` is set. For backwards-compatibility, this is forced on if
# `expand = ["euclid"]` shorthand is used.
#
# default: false
all_features = false

# When `all_features` is disabled and this is also disabled, use the
# `--no-default-features` option when expanding.
#
# default: true
default_features = true

# A list of feature names that should be used when running `cargo expand`. This
# combines with `default_features` like in your `Cargo.toml`. Note that the features
# listed here are features for the current crate being built, *not* the crates
# being expanded. The crate's `Cargo.toml` must take care of enabling the
# appropriate features in its dependencies
#
# default: []
features = ["cbindgen"]

[ptr]
# An optional string to decorate all pointers that are
# required to be non null. Nullability is inferred from the Rust type: `&T`,
# `&mut T` and `NonNull<T>` all require a valid pointer value.
non_null_attribute = "_Nonnull"

# Options specific to Cython bindings.

[cython]

# Header specified in the top level `cdef extern from header:` declaration.
#
# default: *
header = '"my_header.h"'

# `from module cimport name1, name2` declarations added in the same place
# where you'd get includes in C.
[cython.cimports]
module = ["name1", "name2"]