[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 : LoomBridgeBase {
// members go here
} Required:
- The class must inherit
LoomBridgeBase. The source generator errors if it does not. - The class must be
partial. The generator adds plumbing methods to it.
Note: You do not instantiate the bridge or call RegisterWithLoom() yourself. Loom invokes the generated RegisterWithLoom() automatically when
the LoomUI component initializes the runtime on Play.
Member attributes
Three attributes mark members for inclusion in the bridge surface.
[BridgeState]
[BridgeState] public int Score { get; set; } State members are plain { get; set; } properties (not fields). Assigning the
property pushes the new value to the UI; the setter is required (Loom rewrites
it to sync). Set an initial value in the property initializer
(public int Score { get; set; } = 10;).
The type can be any serialisable type: primitives, string, enums, DTOs (see DTO conventions), and collections.
For collections you have two options:
- Snapshot.
List<T>,T[],Dictionary<K,V>. Reassign the property to sync (Items = newList); mutating in place won’t push. - Reactive.
ReactiveList<T>/ReactiveMap<K,V>(declared get-only,{ get; } = new();). Mutate in place; each insert/remove/element-change pushes granularly instead of resending the whole collection.
[BridgeAction]
[BridgeAction] public void Pause() { /* ... */ }
[BridgeAction] public void TakeDamage(float amount) { /* ... */ }
[BridgeAction] public async Task<bool> SubmitScore(ScoreEntry entry) { /* ... */ } Actions are public methods. They take at most one parameter (pass a DTO when
you need several fields) and may return void, a value T, Task, or Task<T>. The generated TS surface exposes them as bridge.actions.pause() and bridge.actions.takeDamage(amount), and always returns a Promise; a Task<T> / T action resolves it with the value so the UI can await a
result. You can also surface results by mutating state, which the UI sees
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
static Instanceproperty onGameBridgefor access from gameplay code.A generated
RegisterWithLoom()override onGameBridgethat wires the members intoLoomRuntime. Loom invokes it automatically; you don’t call it yourself.A TS file at
<UI>/generated/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 : LoomBridgeBase {
[BridgeState]
public int Score { get; set; }
[BridgeAction]
public void AddPoint() { Score += 1; }
[BridgeEvent]
public Event<ScoredEvent> Scored { get; } = new();
}// (generated)
bridge.state.score
bridge.actions.addPoint()
onEvent('scored', (e: ScoredEvent) => {...})