Skip to content

[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() on GameBridge that wires the members into LoomRuntime.

  • A TS file at <UI>/src/bridge.d.ts containing:

    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

C#
[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();
}
TS
// (generated)
bridge.state.score
bridge.actions.addPoint()
onEvent('scored', (e: ScoredEvent) => {...})