loomgui.com ↗

v0.1.x → v0.2.x

Loom v0.2 is a breaking release with two source changes you make by hand:

  1. Bridge setup. Your [Bridge] class inherits a base class, and a single Loom UI component does the wiring and runtime setup you used to write yourself.
  2. Bridge state API. Observable<T> and ObservableList<T> are gone. State is now plain { get; set; } properties; lists and maps use ReactiveList<T> / ReactiveMap<K,V> (or snapshot collections).

Updating the package itself is a single step; everything ships together. Your Solid UI, your member attributes ([BridgeState], [BridgeAction], [BridgeEvent]), and your event-payload DTOs are unchanged. Most projects finish in a few minutes; it’s mechanical, mostly find-and-replace.

What changed at a glance

v0.1.xv0.2.x
Bridge classclass GameBridgeclass GameBridge : LoomBridgeBase
Constructionnew GameBridge() in your bootstrapLoom constructs it
Registrationyou call RegisterWithLoom()automatic
Runtimeyou call LoomRuntime.Initialize() / Shutdown()automatic
Scene setupa bootstrap MonoBehaviour + Loom Viewone Loom UI component
Bridge accessMyBootstrap.Bridge.XGameBridge.Instance.X
Scalar stateObservable<int> X { get; } = new(0)int X { get; set; }
Read / write stateX.ValueX
List stateObservableList<T>ReactiveList<T> (or snapshot List<T>)
long / ulong in TSnumberbigint
  1. Update the Unity package.

    Update com.loomgui to 0.2.0, then restart the Unity Editor so the new native plugin loads. Everything Loom needs ships in that one package, so there are no separate pieces to version or update by hand.

    Loom -> Sync UI Dependencies installs the matching @loomgui/bridge and @loomgui/vite-plugin into your UI app from the package you just updated.

  2. Inherit LoomBridgeBase.

    Your [Bridge] class must now inherit LoomBridgeBase. The source generator errors (LOOM005) on a [Bridge] class that doesn’t.

     [Bridge]
    -public partial class GameBridge {
    +public partial class GameBridge : LoomBridgeBase {
       // members
     }
  3. Move state from Observable<T> to plain properties.

    A [BridgeState] member is now a plain { get; set; } property. Assigning it pushes to the UI. Drop the Observable<T> wrapper and the .Value accessor everywhere (reads and writes use the property directly), and move the initial value into the property initializer.

     [Bridge]
     public partial class GameBridge : LoomBridgeBase {
    -  [BridgeState] public Observable<int> Score { get; } = new(0);
    +  [BridgeState] public int Score { get; set; } = 0;
    
    -  [BridgeAction] public void AddPoint() { Score.Value += 1; }
    +  [BridgeAction] public void AddPoint() { Score += 1; }
     }

    If you used Observable<T>.Subscribe(...) for engine-side reactivity, move that logic to where you set the value (a plain property has no Subscribe).

  4. Move lists to ReactiveList<T> (or a snapshot collection).

    ObservableList<T> is replaced by two options:

    • ReactiveList<T> / ReactiveMap<K,V>: declare get-only and mutate in place (Add, Remove, Clear, indexer). Each change pushes granularly.
    • Snapshot List<T> / T[] / Dictionary<K,V>: a { get; set; } property you reassign to sync (mutating in place won’t push).
    -[BridgeState] public ObservableList<string> Messages { get; } = new();
    +[BridgeState] public ReactiveList<string> Messages { get; } = new();

    Add / Remove / Clear carry over unchanged. For a wholesale replace, either clear-then-add, or use a snapshot List<T> and assign the new list. See State, collections & events.

  5. Delete your bootstrap and add the Loom UI component.

    Remove the MonoBehaviour that constructed the bridge and started the runtime. Everything it did is now handled for you:

    // DELETE this whole file (e.g. LoomBootstrap.cs).
    [DefaultExecutionOrder(-1000)]
    public class LoomBootstrap : MonoBehaviour {
      public static GameBridge Bridge { get; private set; }
    
      private void Awake() {
        Bridge = new GameBridge();
        Bridge.RegisterWithLoom();
        LoomRuntime.Initialize(/**/);
      }
    
      private void OnDestroy() => LoomRuntime.Shutdown();
    }

    In your startup scene, run Loom -> Setup UI in Current Scene. This adds a single Loom UI GameObject that builds the canvas, input capture, and ticker and initializes the runtime on Play. Delete the old “Loom Bootstrap” GameObject if you had one.

    See Loom UI for the full component reference.

  6. Update how you reach the bridge.

    The generator now emits a static Instance accessor on your bridge class. Replace references to your old bootstrap singleton, and drop .Value:

    -LoomBootstrap.Bridge.Score.Value = 10;
    +GameBridge.Instance.Score = 10;
  7. Check long / ulong reads in your UI.

    64-bit integers now cross to TypeScript as bigint (they were number). If your UI reads a long / ulong state field or event payload, use bigint literals (1n), and String(v) to display, since JSON.stringify throws on a bigint. If you only need a large id as text, use a string field instead.

  8. Recompile and regenerate types.

    Let Unity recompile, then run Loom -> Regenerate Types. The contract now includes the new built-in loom.* state (below) and your converted members, so the generated bridge.d.ts updates to match.

New in v0.2: built-in loom.* state

Every bridge now exposes a read-only loom block (active scene name, viewport size, device pixel ratio, connection status) with no declaration on your part. The UI reads it as bridge.state.loom.*. It’s additive, so adopting it is optional. See Built-in state.

What didn’t change

  • Your Solid UI, components, and CSS.
  • The member attributes themselves ([BridgeState], [BridgeAction], [BridgeEvent]) and the generated TypeScript names (Scorescore).
  • Event<T> and event-payload DTO conventions.
  • Player builds: the UI is still bundled into StreamingAssets/Loom.