# infinity-rs
Safe, idiomatic Rust bindings for the **Microsoft Flight Simulator 2024 (MSFS 2024) WASM SDK**.
Write MSFS gauges and systems in Rust with safe, idiomatic wrappers over the entire WASM SDK surface — SimVars, Comm Bus, async HTTP, file I/O, NanoVG rendering, simulation events, the flow lifecycle, map view, airport charts, VFX, and the planned route.
📖 **Full docs:** <https://infinity-simulations.com/docs/developer/getting-started>
---
## Workspace Layout
This repo publishes four crates to crates.io. As a user you only ever depend on the first one and install the last one — the other two are pulled in automatically.
| `infinity-rs` | [`infinity-rs`](https://crates.io/crates/infinity-rs) | Main bindings crate — what you put in your `Cargo.toml` |
| `infinity_rs_derive` | [`infinity_rs_derive`](https://crates.io/crates/infinity_rs_derive) | Proc-macros (`VarStruct`, …) — re-exported from `infinity_rs` under the `derive` feature |
| `infinity_rs_sdk` | [`infinity_rs_sdk`](https://crates.io/crates/infinity_rs_sdk) | Build-script helper that locates the MSFS 2024 SDK on disk |
| `infinity-msfs` | [`infinity-msfs`](https://crates.io/crates/infinity-msfs) | CLI: SDK installer + WASM build/packaging — `cargo install infinity-msfs` |
> The lib crate is named **`infinity-rs`** (Rust path: `infinity_rs`). It used to be `msfs`, which collided with the FlyByWire crate of the same name; the rename in `0.2.0` is intentionally breaking. Update imports from `use msfs::...` to `use infinity_rs::...`, and `msfs::export_gauge!` / `msfs::export_system!` to `infinity_rs::export_gauge!` / `infinity_rs::export_system!`.
---
## Prerequisites
| Rust 1.85+ with `wasm32-wasip1` target | `rustup target add wasm32-wasip1` |
| `clang` + `llvm-ar` | Required for WASM compilation; install via [LLVM](https://releases.llvm.org/) or your system package manager |
| `wasm-opt` | Part of [Binaryen](https://github.com/WebAssembly/binaryen); only required when `wasm_opt.enabled = true` in your config |
The MSFS 2024 SDK itself is fetched on demand by the `infinity-msfs` CLI (see below) — works on Windows, macOS, and Linux. You don't need to install the SDK manually unless you want to point at an existing install via `MSFS2024_SDK`.
---
## Getting Started
> The walkthrough below is the short version. For tutorials, configuration reference, and end-to-end project setup, see the full developer docs at **<https://infinity-simulations.com/docs/developer/getting-started>**.
### 1. Install the build CLI
```bash
cargo install infinity-msfs
```
This gives you `infinity-msfs build`, `infinity-msfs sdk install`, `infinity-msfs doctor`, and the project commands.
### 2. Add the bindings crate
```toml
[dependencies]
infinity-rs = "0.2"
```
That pulls everything you need by default — the WASM-runtime APIs and the `#[derive(VarStruct)]` macro are re-exported under `infinity_rs::*`.
### 3. Build
```bash
infinity-msfs build
```
On the first build, if no SDK is installed, the CLI prompts to download it from `sdk.flightsimulator.com`. Re-run `infinity-msfs sdk install --force` later to upgrade. To use an existing SDK install instead, set `MSFS2024_SDK` to its root path.
### Feature flags
| `wasm` | yes | WASM-host runtime APIs (NVG, comm bus, IO, network, vars, gauge/system entry points). Disable for native-only builds. |
| `derive` | yes | Re-exports the `infinity_rs_derive` proc-macros (`VarStruct`, etc.). |
| `simconnect` | no | SimConnect bindings. Works on both WASM and native Windows. |
Common combinations:
```toml
# WASM gauge or system (the default)
infinity-rs = "0.2"
# Native SimConnect tool, no WASM target
infinity-rs = { version = "0.2", default-features = false, features = ["simconnect"] }
# WASM module that also uses SimConnect
infinity-rs = { version = "0.2", features = ["simconnect"] }
```
In code:
```rust
use infinity_rs::prelude::*;
```
---
## Core Concepts
### Build Tools
The build tools supplied with the project compile Rust WASM targets, optionally run `wasm-opt`, and can list the configured projects before building. Copy [`infinity-msfs.toml.example`](infinity-msfs.toml.example) to `infinity-msfs.toml` in your project root and adjust the package names and copy rules for your own workspace.
```toml
[build]
target = "wasm32-wasip1"
out_dir = "build/msfs"
copy = [
{ from = "manifest.json", to = "build/msfs/manifest.json" },
{ from = "layout.json", to = "build/msfs/layout.json" }
]
[[packages]]
package = "demo-gauge"
bin = "demo_gauge"
out_name = "demo-gauge.wasm"
[[packages]]
package = "demo-system"
bin = "demo_system"
out_name = "demo-system.wasm"
out_dir = "build/msfs/systems"
[wasm_opt]
enabled = true
args = [
"-O1",
"--signext-lowering",
"--enable-bulk-memory",
"--enable-nontrapping-float-to-int",
]
[scripts]
pre_build = [
"cargo fmt --check"
]
post_build = [
"python ./tools/fix_layout.py"
]
```
some example build commands:
```bash
infinity-msfs projects
infinity-msfs list-projects
infinity-msfs build
infinity-msfs build --release
infinity-msfs build --release --no-wasm-opt
infinity-msfs build --only demo-gauge --release
infinity-msfs build --verbose
```
### Systems and Gauges
Everything in MSFS runs as either a **System** or a **Gauge**. Implement the corresponding trait and export it with the matching macro.
**System** — logic-only, no rendering:
```rust
use infinity_rs::prelude::*;
pub struct MySystem { /* ... */ }
impl System for MySystem {
fn init(&mut self, ctx: &Context, install: &SystemInstall) -> bool { true }
fn update(&mut self, ctx: &Context, dt: f32) -> bool { true }
fn kill(&mut self, ctx: &Context) -> bool { true }
}
infinity_rs::export_system!(
name = my_system,
state = MySystem,
ctor = MySystem::new(),
);
```
**Gauge** — rendered panel element with optional mouse input:
```rust
use infinity_rs::prelude::*;
pub struct MyGauge { /* ... */ }
impl Gauge for MyGauge {
fn init(&mut self, ctx: &Context, install: &mut GaugeInstall) -> bool { true }
fn update(&mut self, ctx: &Context, dt: f32) -> bool { true }
fn draw(&mut self, ctx: &Context, draw: &mut GaugeDraw) -> bool { true }
fn kill(&mut self, ctx: &Context) -> bool { true }
fn mouse(&mut self, _ctx: &Context, x: f32, y: f32, flags: i32) { /* optional */ }
}
infinity_rs::export_gauge!(
name = my_gauge,
state = MyGauge,
ctor = MyGauge::new(),
);
```
The macros emit the correctly named `extern "C"` entry points expected by the simulator.
---
## Features
### SimVars — `infinity_rs::vars`
Read and write simulation variables via `AVar` (A-vars) and `LVar` (L-vars).
```rust
use infinity_rs::vars::{AVar, LVar};
// A-var: read-only aircraft variable
let airspeed = AVar::new("AIRSPEED INDICATED", "Knots")?;
let kts: f64 = airspeed.get()?;
// L-var: readable and writable local variable
let mut flag = LVar::new("L:MY_GAUGE_ACTIVE", "Bool")?;
flag.set(1.0)?;
let val = flag.get()?;
```
**Indexed A-vars** (e.g. per-engine data):
```rust
use infinity_rs::vars::{AVar, VarParamArray1};
let eng_rpm = AVar::new("GENERAL ENG RPM", "RPM")?;
let rpm = eng_rpm.get_with(VarParamArray1::new(1), Default::default())?; // engine 1
```
#### `#[derive(VarStruct)]`
Bundle multiple vars into a single struct and snapshot them all at once:
```rust
use infinity_rs::VarStruct;
#[derive(Debug, Clone, Copy, VarStruct)]
struct FlightData {
#[var(name = "A:PLANE ALTITUDE", unit = "Feet", kind = "A")]
altitude_ft: f64,
#[var(name = "A:PLANE HEADING DEGREES TRUE", unit = "Degrees", kind = "A")]
heading_deg: f64,
#[var(name = "A:GENERAL ENG RPM", unit = "RPM", kind = "A", index = 1)]
eng1_rpm: f64,
#[var(name = "L:MY_CUSTOM_VALUE", unit = "Number", kind = "L")]
custom: f64,
}
let snapshot = FlightData::read()?;
println!("Alt: {} ft Hdg: {}° ENG1: {} RPM", snapshot.altitude_ft, snapshot.heading_deg, snapshot.eng1_rpm);
```
---
### Comm Bus — `infinity_rs::comm_bus`
Send and receive binary messages between WASM modules, JavaScript, and the sim.
```rust
use infinity_rs::prelude::*;
// Subscribe to a named event
let _sub = Subscription::subscribe("my.module/event", |bytes| {
println!("Received {} bytes", bytes.len());
})?;
// Broadcast a message
let payload = 42u32.to_le_bytes();
commbus_call("my.module/event", &payload, BroadcastFlags::JS | BroadcastFlags::WASM);
```
**Broadcast flags:**
| `BroadcastFlags::JS` | JavaScript gauges |
| `BroadcastFlags::WASM` | Other WASM modules |
| `BroadcastFlags::WASM_SELF` | This WASM module itself |
| `BroadcastFlags::ALL` | Everyone |
| `BroadcastFlags::DEFAULT` | SDK default |
Subscriptions automatically unsubscribe when dropped.
---
### HTTP Networking — `infinity_rs::network`
Make asynchronous HTTP GET and POST requests from within a WASM module.
```rust
use infinity_rs::prelude::*;
let params = HttpParams {
headers: vec!["Accept: application/json".to_string()],
post_field: None,
body: vec![],
};
http_request(Method::Get, "https://example.com/data.json", params, |resp| {
if resp.error_code == 0 {
let body = String::from_utf8_lossy(&resp.data);
println!("Got: {body}");
}
})?;
```
Callbacks are invoked on the next simulator update tick after the response arrives.
---
### File I/O — `infinity_rs::io`
#### High-level API (`infinity_rs::io::fs`)
```rust
use infinity_rs::io::fs;
// Async read — callback fires when data is ready
})?;
// Async write
let req = fs::write("\\work/output.bin", &my_bytes)?;
// Poll completion in your update loop
if req.is_done() { /* ... */ }
if req.has_error() { eprintln!("{:?}", req.last_error()); }
```
#### Low-level API (`infinity_rs::io`)
Full control via `OpenFile`, `IoRequest`, and `OpenFlags` for advanced use cases.
**Supported open flags:** `RDONLY`, `WRONLY`, `RDWR`, `CREAT`, `TRUNC`, `HIDDEN`
---
### NanoVG Rendering — `infinity_rs::nvg`
Vector graphics rendering inside a `Gauge` using the NanoVG API.
```rust
use infinity_rs::nvg::*;
use infinity_rs::prelude::*;
// In Gauge::init
let nvg = NvgContext::new(ctx).expect("NVG init failed");
let font = nvg.create_font("sans", "./data/Roboto-Regular.ttf");
// In Gauge::draw
nvg.frame(win_w, win_h, px_ratio, |nvg| {
// Shapes
Shape::rect(10.0, 10.0, 200.0, 100.0)
.fill(Color::rgb(0, 120, 255))
.draw(nvg);
Shape::circle(150.0, 150.0, 40.0)
.stroke(Color::rgba(255, 255, 255, 200), 2.0)
.draw(nvg);
// Text
nvg.text(font_id, 16.0, 50.0, 50.0, "Hello MSFS!");
// Transforms
nvg.scoped(|nvg| {
nvg.translate(cx, cy);
nvg.rotate(angle_rad);
// ... draw rotated content ...
});
});
```
`NvgContext` is automatically cleaned up via `Drop`.
---
### Flow API — `infinity_rs::flow`
Subscribe to simulation lifecycle events (flight load, teleport start/done, replay boundaries, plane crash, …). Useful for resetting state on flight reload or pausing logic across slew/back-on-track windows. Subscriptions auto-unregister on `Drop`.
### Event API — `infinity_rs::events`
Subscribe to and trigger named simulation events (`KEY_TOGGLE_MASTER_BATTERY`, etc.) with typed parameter arguments via `FsParamArg`.
### Map Views — `infinity_rs::map_view`
`MapView` wraps `fsMapView*`: spawn a host-side map texture, configure aerial / altitude shading, follow mode, isolines, and an integrated weather radar (top / horizontal / vertical with custom rain-rate color ramps and cone angle). `MapView::image_pattern(...)` hands the texture straight to NanoVG so you can paint it through any gauge.
### Charts — `infinity_rs::charts`
Async access to the `fsCharts*` API. `get_index → get_pages → get_page_image` returns a `ChartImage` whose host id can be sampled by NanoVG via `nvg_pattern(...)`. `ChartIndex` and `ChartPages` are owned wrappers that release the host allocations on `Drop`; ref-views (`ChartCategoryRef`, `ChartMetadataRef`, `ChartPageRef`) decode strings as `&str` borrowed from the owner.
### VFX — `infinity_rs::vfx`
Spawn particle effects in the world (`spawn_in_world`) or attached to a sim object node (`spawn_on_sim_object`) with optional `VfxParam { name, rpn }` graph bindings. `VfxInstance` owns the host id and destroys it on `Drop`; `leak()` detaches if the host should keep emitting after the handle goes away.
### Planned Route — `infinity_rs::planned_route`
EFB / route-broadcast integration. `current_efb_route()` borrows the route the EFB currently holds; `subscribe_broadcast(...)` listens for pushes; `subscribe_requests(...)` receives `RouteRequest` handles when other modules ask for a route, and you reply via `request.respond(route)`. Both subscriptions auto-unregister on `Drop`.
## Examples
All examples are in [`msfs/examples/`](msfs/examples/).
| [`io_system_simple.rs`](msfs/examples/io_system_simple.rs) | Read and copy a file using the high-level `io::fs` API |
| [`io_system.rs`](msfs/examples/io_system.rs) | Full low-level file I/O API |
| [`vars_full_api.rs`](msfs/examples/vars_full_api.rs) | A-vars, L-vars, indexed vars, `VarStruct` snapshot |
| [`comm_bus_gauge.rs`](msfs/examples/comm_bus_gauge.rs) | Gauge that publishes Comm Bus events on mouse click |
| [`comm_bus_sytem.rs`](msfs/examples/comm_bus_sytem.rs) | System that receives Comm Bus commands and broadcasts state |
| [`network_fetch_system.rs`](msfs/examples/network_fetch_system.rs) | Fetch JSON over HTTP on a Comm Bus trigger |
| [`network_post_system.rs`](msfs/examples/network_post_system.rs) | HTTP POST with a request body |
| [`nvg_render.rs`](msfs/examples/nvg_render.rs) | Attitude indicator rendered with NanoVG |
| [`flow_system.rs`](msfs/examples/flow_system.rs) | Adjust logic based on sim events using the Flow API |
| [`event_api.rs`](msfs/examples/event_api.rs) | Subscribe to and trigger simulation events |
| [`map_view_weather_radar.rs`](msfs/examples/map_view_weather_radar.rs) | Texture-backed weather radar gauge with rain-rate color ramp + range overlay |
| [`charts_gauge.rs`](msfs/examples/charts_gauge.rs) | Async charts pipeline (index → pages → image) painted through NanoVG |
| [`vfx_system.rs`](msfs/examples/vfx_system.rs) | Sim-object-attached particle effect driven by N1 via an RPN-bound graph param |
| [`planned_route_system.rs`](msfs/examples/planned_route_system.rs) | Subscribe to EFB route broadcasts and respond to route requests |
---
## Crate Structure
```
msfs/src/
├── lib.rs — top-level re-exports
├── prelude.rs — convenient glob import
├── modules.rs — System / Gauge traits
├── exports.rs — export_system! / export_gauge! macros
├── context.rs — FsContext wrapper
├── types.rs — GaugeDraw, GaugeInstall, SystemInstall
├── sys.rs — raw bindgen bindings
├── abi.rs — ABI shims for host entry points
├── vars/ — AVar, LVar, VarKind, VarStruct
├── comm_bus/ — Subscription, BroadcastFlags, commbus_call
├── network/ — http_request, HttpParams, Method, HttpResponse
├── io/ — File I/O (low-level + fs high-level)
├── nvg/ — NanoVG: NvgContext, Shape, Color, Transform, …
├── events/ — Sim event helpers (subscribe / trigger / FsParamArg)
├── flow/ — Flow lifecycle subscription
├── map_view.rs — MapView (fsMapView*) — weather radar / terrain / 3D
├── charts.rs — Async charts API (fsCharts*) with owned wrappers
├── vfx.rs — VfxInstance (fsVfx*) with RAII destroy
├── planned_route.rs — EFB planned-route broadcast + request channels
├── host/ — Native testing shims (non-wasm targets)
├── utils/ — Internal utilities
└── bindgen_support/ — Headers consumed by the build script
```
---
## Projects Using infinity-rs
| **T-38 Talon** | Aero Dynamics |
| **DC-10** | Aero Dynamics |
---
## SDK Coverage
Every public header in `MSFS 2024 SDK/WASM/include/MSFS/` is wrapped:
| `MSFS.h`, `MSFS_Core.h`, `MSFS_Utils.h` | `infinity_rs::sys`, internal helpers |
| `MSFS_Vars.h` | `infinity_rs::vars` |
| `MSFS_CommBus.h` | `infinity_rs::comm_bus` |
| `MSFS_Network.h` | `infinity_rs::network` |
| `MSFS_IO.h` | `infinity_rs::io` |
| `MSFS_Render.h`, `Render/nanovg.h` | `infinity_rs::nvg` |
| `MSFS_GaugeContext.h`, `MSFS_SystemContext.h` | `infinity_rs::context`, `infinity_rs::modules` |
| `MSFS_Events.h`, `Types/MSFS_EventsEnum.h` | `infinity_rs::events` |
| `MSFS_Flow.h` | `infinity_rs::flow` |
| `MSFS_MapView.h` | `infinity_rs::map_view` |
| `MSFS_Charts.h` | `infinity_rs::charts` |
| `MSFS_Vfx.h` | `infinity_rs::vfx` |
| `MSFS_PlannedRoute.h`, `MSFS_FlightPlan.h` | `infinity_rs::planned_route` (FlightPlan types re-exposed via `sys`) |
| `MSFS_Weather.h` | re-exported via `infinity_rs::sys` (typed wrapper TBD) |
| `Legacy/gauges.h` | `infinity_rs::sys` (legacy gauge ABI) |
| `SimConnect.h` | `infinity_rs::sys` under the `simconnect` feature (native only) |