[Bridge] attribute
The [Bridge] attribute marks a C# class as the root of a bridge surface.
A Roslyn source generator picks it up at build time and emits the runtime
plumbing + matching TypeScript types.
Class declaration
using Loom;
[Bridge]public partial class GameBridge { // members go here}Required:
- The class must be
partial. The generator adds methods to it. - The class must derive (transitively) from
object. No constraints beyond that.
Optional:
- The class can have a constructor that takes parameters; you instantiate it
yourself and pass the instance to
RegisterWithLoom().
Member attributes
Three attributes mark members for inclusion in the bridge surface.
[BridgeState]
[BridgeState] public Observable<int> Score { get; } = new(0);State members must be properties (not fields) of type Observable<T> or
ObservableList<T>. The property must have a get accessor; the setter is
optional. The initial value is set in the property initializer.
T can be any type that serialises through MessagePack — primitives, strings,
DTOs with [Serializable]-style fields, lists of those.
[BridgeAction]
[BridgeAction] public void Pause() { /* ... */ }[BridgeAction] public void TakeDamage(float amount) { /* ... */ }Actions are methods. They must be public, return void (no async, no
return value), and take zero or more serialisable parameters. The generated
TS surface exposes them as bridge.actions.pause(), bridge.actions.takeDamage(amount).
If your action needs to surface a result, mutate state — the UI sees it reactively.
[BridgeEvent]
[BridgeEvent] public Event<DamageEvent> Damaged { get; } = new();Events are properties of type Event<TPayload>. C# fires them with
Damaged.Fire(new DamageEvent { ... }); the UI listens with
onEvent('damaged', (e) => { ... }).
The payload type must follow DTO conventions.
Generated output
For a class GameBridge with Score, Pause, and Damaged, the generator
emits:
-
A partial method
RegisterWithLoom()onGameBridgethat wires the members intoLoomRuntime. -
A TS file at
<UI>/src/bridge.d.tscontaining:export interface BridgeState {score: number;}export interface BridgeActions {pause(): void;}export interface BridgeEvents {damaged: DamageEvent;}
The TS file is regenerated whenever the C# source changes. To regenerate
manually (e.g. after a fresh clone), run Loom → Regenerate Types from the
Unity menu.
A complete minimal example
[Bridge]public partial class GameBridge { [BridgeState] public Observable<int> Score { get; } = new(0);
[BridgeAction] public void AddPoint() { Score.Value += 1; }
[BridgeEvent] public Event<ScoredEvent> Scored { get; } = new();}// (generated)bridge.state.scorebridge.actions.addPoint()onEvent('scored', (e: ScoredEvent) => {...})