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

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. The canonical layer definition lives in tools/lint_layers.rs.

Layer 1 (Foundation) : goud_engine/src/core/   — error types, math, handles, provider traits
Layer 2 (Libs)       : goud_engine/src/libs/   — graphics backend, platform, native providers
Layer 3 (Services)   : goud_engine/src/ecs/, assets/  — ECS, asset loading
Layer 4 (Engine)     : goud_engine/src/sdk/, rendering/, context_registry/  — game API, render orchestration
Layer 5 (FFI)        : goud_engine/src/ffi/, wasm/    — C-ABI exports consumed by external SDKs

Beyond the engine crate, two additional layers complete the picture:

  • SDKs (sdks/) — language-specific wrappers over FFI for all SDK languages
  • Apps (examples/) — example games that use SDK APIs

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/)

All SDK languages live under sdks/. Each has a generated wrapper surface and a thin hand-written API layer. See the sdks/ directory for the full list of supported languages.

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)
goud_engine.h          — auto-generated C header at codegen/generated/ (each native cargo build)
                                           |
                                           v
                              codegen/gen_*.py generators
                                           |
                                           v
                              sdks/*/generated/ output directories

Run the full pipeline:

./codegen.sh

The script runs scaffolding, build, validation, and generation steps in order. See codegen.sh for the current step count and sequence. Validation gates (header checks, lint-layers, coverage, schema consistency) abort the pipeline on failure.

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, multiple generators. Adding a type to goud_sdk.schema.json causes it to appear in every SDK on the next codegen run. Each generator (codegen/gen_*.py) applies language-appropriate naming conventions. Run ./codegen.sh to regenerate all SDKs.

C# bindings are doubly generated. NativeMethods.g.cs is produced by csbindgen 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, and the Python generator handles the public 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