Plugins
Everything above the core is a plugin: physics, rendering, audio, networking.
One Plugin trait covers them all, and one source builds either way.
| Static | Dynamic | |
|---|---|---|
| Ships as | Part of the engine binary, behind a cargo feature | A shared library in the project's extensions/ directory |
| Runs on | Every platform, the web included | macOS, Linux and Windows |
| Written in | Rust | Rust, or C, Zig, Odin, C++ |
| Needs | An engine built from source | A desktop release, which has the extensions feature |
| Starter | crates/balaur_physics | examples/extension_greeter (Rust), examples/extension_c_counter (C) |
A dynamic plugin in Rust
balaur_plugin is the one dependency.
Cargo.toml
[package]
name = "greeter"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
balaur_plugin = { git = "https://github.com/balaurengine/balaur", tag = "v0.2.0" }
src/lib.rs
use balaur_plugin::prelude::*;
pub struct Greeter {
manifest: Manifest,
}
impl Default for Greeter {
fn default() -> Self {
Self {
manifest: Manifest::new("greeter", env!("CARGO_PKG_VERSION")),
}
}
}
impl Plugin for Greeter {
fn manifest(&self) -> &Manifest {
&self.manifest
}
fn declare(&mut self, reg: &mut Registry<'_>) -> Result<()> {
let mut m = reg.script_module("greeter")?;
m.function("greet", |_: &Engine, name: String| Ok(format!("hello, {name}")));
m.constant("VERSION", Value::Str(env!("CARGO_PKG_VERSION").into()));
Ok(())
}
}
balaur_plugin::export_plugin!(Greeter);
cargo build --release
cp target/release/libgreeter.dylib my-game/extensions/ # .so on Linux, greeter.dll on Windows
Scripts call greeter::greet("world") like any built-in module.
- Match the engine's compiler. Copy the engine's
rust-toolchain.tomlinto the plugin and pintagto the versionbalaur --versionprints. The host refuses any other build and names the difference. Registryalso takes resources, systems, components, presets, asset types and replay sources.balaur_pluginre-exportsbalaur_core,balaur_script,anyhowandtoml.- A Rust plugin reads and writes its own resources, not the engine's. The two builds compute different type ids for the same type.
A dynamic plugin in C
Any compiler with a C ABI works. No toolchain match is needed.
- The header is
include/balaur_extension.hin every desktop download, andcrates/balaur_plugin/includein the repository. - Export
balaur_extension_abi,balaur_extension_name,balaur_extension_versionandbalaur_extension_declare. declarereceives a table of host functions, opens a script module and adds functions and constants to it.- Values are borrowed for one call, and the host copies what is returned. Static storage keeps a returned string alive.
int32_t balaur_extension_declare(const BalaurApi *api, BalaurRegistry *registry) {
BalaurModule *m = api->module_open(registry, balaur_str("counter"));
api->module_function(m, balaur_str("add"), add, NULL);
api->module_close(m);
return 0;
}
cc -shared -fPIC -I path/to/include -o libcounter.so counter.c # .dylib on macOS
A C plugin covers script functions and constants. Components, systems and calls back into scripts are on the roadmap.
A static plugin
A static plugin is part of the engine build and runs on the web and mobile.
- Add the crate to the
modules!table incrates/balaur/src/lib.rs, behind a cargo feature. - Build the editor and the runtime templates with that feature
(A custom engine). The editor needs the
feature too:
balaur exportcompiles scripts against the modules its own binary has.
Turning plugins on and off
[plugins] in project.toml decides what a project loads.
audio = falseturns a plugin off. Turning off one every build has, such asphysics, is refused.- A table turns a plugin on and hands it the table, read with
reg.config(). - Asking for a plugin nothing registered is an error naming the feature to build with.
Manifest::new(..).requiring(&["physics"])declares a dependency. Plugins load by name and requirement, in the same order on every machine.balaur exportships the libraries for its target with a desktop game:extensions/beside a flat executable,Contents/PlugInsinside a.app, signed with the game's identity. Keep a.so, a.dyliband a.dllside by side to export for all three.- A web, iOS or Android export warns and ships without them. A plugin those platforms need is a static one.
The design is in ARCHITECTURE.md.