infinity-wasm-bridge
A three-layer bridge that lets an MSFS WASM gauge send and receive data to an external host application over WebSocket. WASM gauges run in a sandbox and cannot open TCP sockets directly — this library routes messages through the adjacent Coherent HTML gauge engine via the CommBus IPC mechanism.
Performance
This bridge is not the bottleneck.
- ~20–40 ms latency floor (hard limit of MSFS WASM tick rate)
- Fully saturates WASM throughput
- ~25,000 events/sec (burst)
- ~12–13 MB/s sustained
- Zero drops under load
- Linear scaling with concurrency
- Flat latency across large payloads (100s KB+) with binary transport
Reality
The only thing limiting performance is MSFS itself.
- ~20 ms ≈ 1 WASM tick
- ~40 ms ≈ round trip
- Same limitation applies to SimConnect in WASM
Binary Transport
- No JSON on the hot path
- No reserialization overhead
- Stable latency regardless of payload size
- Large payloads without degradation
Bottom Line
infinity-wasm-bridge fully saturates MSFS WASM performance while delivering larger payloads with a modern, seamless API when compared to SimConnect.
You are not limited by transport. You are limited by the sim.
┌─────────────────────────────── MSFS Process ──────────────────────────────────┐
│ │
│ ┌─────────────────────┐ CommBus JSON ┌──────────────────────────────┐ │
│ │ infinity-bridge-wasm │ ◄────────────────► │ infinity-bridge-relay (JS/TS) │ │
│ │ (Rust WASM gauge) │ │ (Coherent HTML gauge) │ │
│ └─────────────────────┘ └──────────┬───────────────────┘ │
│ │ WebSocket │
└────────────────────────────────────────────────────────┼───────────────────────┘
│
┌───────────▼───────────────────┐
│ infinity-bridge-host (Rust/Tokio) │
│ WebSocket server (axum) │
└───────────────────────────────┘
Crates
| Crate | Description |
|---|---|
infinity-bridge-wire |
Shared wire format types (no_std compatible) |
infinity-bridge-wasm |
WASM gauge side — CommBus abstraction and command router |
infinity-bridge-host |
Host application side — async WebSocket server |
infinity-bridge-relay |
TypeScript relay running inside the Coherent HTML gauge |
Quick Start
1. Host Application
Add the dependency:
[]
= "0.1.0"
= { = "1", = ["full"] }
Start the server and interact with the gauge:
use ;
use json;
use Duration;
async
2. TypeScript Relay (Coherent HTML Gauge)
Install the relay package into your HTML gauge project, then initialize it in your BaseInstrument:
import { BridgeRelay } from 'infinity-bridge-relay';
export class MyGauge extends BaseInstrument {
private relay!: BridgeRelay;
connectedCallback(): void {
super.connectedCallback();
this.relay = new BridgeRelay({
wsUrl: "ws://127.0.0.1:9876/bridge",
callEvent: "myaddon/bridge_call",
responseEvent: "myaddon/bridge_resp",
hello: { client: "msfs-gauge", aircraft: "DC-10-30" },
});
this.relay.init();
}
Update(): void {
super.Update();
this.relay.update();
}
}
3. WASM Gauge (Rust)
The WASM crate has no dependency on the MSFS SDK — you provide a thin CommBusBackend impl:
use ;
// Wire up your msfs crate version
;
// Set up the router — event names must match the relay config
let config = new;
let router = new
.command
.command
.event;
let bridge = new?;
// Later, emit a fire-and-forget event to the host
bridge.emit?;
Architecture
Wire Protocol
All messages are JSON with a "t" discriminant field. Defined in infinity-bridge-wire and mirrored in the TypeScript relay.
| Message | Direction | Description |
|---|---|---|
hello |
Gauge → Host | First message after WebSocket connect — identifies the client |
ping |
Host → Gauge | Keepalive probe |
pong |
Gauge → Host | Keepalive response |
cmd |
Host → Gauge | RPC request with a correlation UUID |
ack |
Gauge → Host | RPC response (success or error) |
event |
Either direction | Fire-and-forget named event |
Data Flow: Host → WASM (command/RPC)
BridgeServer::command("equip_query", payload, timeout)
→ UUID assigned, oneshot registered in pending map
→ WireMsg::Cmd serialized → WebSocket → JS relay
→ dedup check (duplicate IDs replied to immediately)
→ CommBus.call(callEvent, {requestId, payload})
→ Bridge<B>::dispatch (sync WASM callback)
→ Router matches handler, returns Result<Value>
→ B::call(responseEvent, {requestId, ok, response})
→ JS receives CommBus response
→ WireMsg::Ack sent over WebSocket
→ Hub resolves oneshot → caller receives Ok(Value) or Err
Data Flow: WASM → Host (fire-and-forget event)
bridge.emit("telemetry", json!({...}))
→ WireMsg::Event serialized
→ B::call(responseEvent, json) // direct CommBus, no requestId
→ JS relay: t === "event" → forwards raw over WebSocket
→ Hub.dispatch_event
→ event_tx.send(EventPayload)
→ all broadcast::Receiver<EventPayload> subscribers notified
Deduplication
The TypeScript relay maintains a bounded LRU ring (DedupRing, default capacity 128) keyed on command IDs. If a command is retransmitted before an ack is sent (e.g. during reconnect), the relay responds immediately with {ok: true, duplicate: true} without forwarding to the WASM side.
Reconnection
The relay reconnects to the WebSocket server automatically using exponential backoff with jitter: min(maxReconnectMs, baseReconnectMs × 2^attempt) + rand(0..250ms). Defaults: base 250 ms, max 30 s.
Configuration Reference
ServerConfig (host)
new
.ping_interval
.ping_timeout
.event_channel_capacity
BridgeRelayConfig (TypeScript)
{
wsUrl: string; // WebSocket URL of the host server
callEvent: string; // CommBus event name used to call into WASM
responseEvent: string; // CommBus event name WASM responds on
hello?: { // Optional handshake metadata
client?: string;
aircraft?: string;
tail?: string;
session?: string;
meta?: unknown;
};
dedupCapacity?: number; // Default 128
baseReconnectMs?: number; // Default 250
maxReconnectMs?: number; // Default 30_000
protocolVersion?: number; // Default 1
}
BridgeConfig (WASM)
new
The CommBus event names must be consistent across all three layers.
Integrating with Tauri
BridgeServer can be mounted inside an existing axum router, making it straightforward to embed inside a Tauri app:
let = router;
let app = new
.merge
.route;
// Forward gauge events to the Tauri webview
let mut events = bridge.subscribe_events;
let handle = app_handle.clone;
spawn;
Examples
| File | Description |
|---|---|
| examples/host-app/src/main.rs | Full host-side demonstration — commands, events, and error handling |
| examples/host-app/src/fake_gauge.rs | Simulates the relay + WASM pipeline as a plain WebSocket client for testing |
| examples/wasm_gauge_example.rs | Complete WASM integration reference (requires MSFS SDK at build time) |
| examples/cohierent.ts | TypeScript relay usage inside a BaseInstrument |
Run the bundled host example (starts server + fake gauge in the same process):