How it works
Loom is three pieces that talk to each other through a defined protocol.
flowchart LR
subgraph Game ["Unity Editor / Player"]
CS["C# code<br/>[Bridge] class"]
Native["Native plugin<br/>(Rust + GPU)"]
CS <--"FFI"--> Native
end
subgraph UI ["UI process"]
JS["JS components<br/>(Solid.js)"]
Boot["@loomgui/bridge<br/>runtime"]
JS <--"reactive"--> Boot
end
Native <--"WebSocket<br/>MessagePack"--> Boot
classDef cs fill:#1b1f3a,stroke:#7c87ff,color:#f4f4f8
classDef js fill:#11332b,stroke:#5cdbb5,color:#f4f4f8
class CS,Native cs
class JS,Boot js
The native plugin
The native plugin is a Rust-based GPU engine bundled inside the Unity package. It:
- Renders your UI to a surface Unity composites over the game scene.
- Forwards mouse, keyboard, and touch events into C# input handling.
- Carries state, actions, and events between C# and the UI runtime.
You don’t write code against the plugin directly — your C# code uses the bridge.
The bridge
You write a C# class and tag it with [Bridge]:
[Bridge]public partial class GameBridge { [BridgeState] public Observable<int> Score { get; } = new(0); [BridgeAction] public void Pause() { /* ... */ } [BridgeEvent] public Event<DamageEvent> Damaged { get; } = new();}A Roslyn source generator emits two things from this class:
- A runtime helper that serialises state and dispatches actions over the WebSocket.
- A TypeScript
.d.tsfile that types the same surface for your UI code.
Your UI code uses useBridge() from @loomgui/bridge and gets a fully typed
bridge.state.score, bridge.actions.pause(), bridge.events.damaged.on(...).
See The bridge for the full data model.
The UI runtime
The UI is a normal Vite-built Solid app. @loomgui/vite-plugin injects the
boot wiring at build time so you don’t manage the WebSocket connection
yourself — useBridge() just returns the live, reactive bridge instance.
The same UI runs in two modes:
- Connected. The bridge talks to a live Unity Editor or player. State pushes from the engine; actions call back.
- Mock mode. No engine. Your
bootBridge({ ... })call provides mock actions and scenarios. The UI runs entirely in the browser. Used for fast iteration on visuals; see Mock mode.
The packaging story
You install one Unity package. It bundles the two npm tarballs inside its
UI~/ folder; an Editor menu copies them into your project’s UI/.loom/,
updates package.json, and runs npm install. See
Sync UI Dependencies for the
detailed flow.