MuxClientLoom

class MuxClientLoom(base: Loom, baseRendezvous: Rendezvous, scope: CoroutineScope, nameOf: (Rendezvous) -> String) : Loom

A client Loom that weaves one base fabric and serves many logical sessions as named channels over a single NamedMux — the fix for "every join opens a new socket."

In plain terms: a client that joins a lobby and a table normally opens two connections. MuxClientLoom opens one connection and splits it into independent named channels, so join("lobby") and join("table-7") share a single underlying link.

How it works

The first weave lazily weaves base once (using baseRendezvous) and wraps the resulting Seam in a NamedMux. Every weave thereafter returns namedMux.channel(nameOf(rendezvous)) — so host and join for the same logical tag (mapped through nameOf) land on the same channel name. Concurrent first-weaves are serialised by an internal Mutex; the base weaves exactly once.

Each returned channel is a stable, resumable handle: weaving the same name twice returns the same Seam, and closing it (Seam.close) tears down only that channel — the base stays live for the others (per-channel close).

Resume — heal every channel over one re-established base

If the base fabric tears (the socket drops), the next weave re-weaves the base once and re-keys every previously-woven channel name onto the new base. Each prior handle heals transparently — callers keep the same Seam instances and the same stable Seam.selfId, so a server can re-associate each per-channel membership by PeerId. One re-established base heals all channels; no fan-out of N reconnections.

A handle's observable surfaces heal asymmetrically, by design: Seam.selfId is frozen across resumes, Seam.state and Seam.peers follow the handle onto the fresh generation (a flow captured before the resume keeps up), but Seam.incoming is per-generation — it completes at its generation's SeamState.Torn and must be re-collected after a resume. Making incoming follow would break its "completes on Torn" termination contract, which downstream onCompletion cleanup relies on.

Parameters

base

the underlying transport Loom (any fabric — WebSocket, TCP, in-memory).

baseRendezvous

how to weave the single base fabric (host a new session or join one).

scope

a CoroutineScope owning the per-generation NamedMux collectors. Required — no real-dispatcher default, so tests drive the mux under virtual time.

nameOf

maps a Rendezvous to the channel name a weave resolves to. host and join for one logical session must map to the same name.

Samples

runTest(UnconfinedTestDispatcher()) {
    val client = MuxClientLoom(
        base = InMemoryLoom(),
        baseRendezvous = Rendezvous.New(Pattern("base")),
        scope = backgroundScope,
        nameOf = { rendezvous ->
            when (rendezvous) {
                is Rendezvous.New -> rendezvous.pattern.sessionName
                is Rendezvous.Existing -> rendezvous.tag.sessionName
            }
        },
    )

    val lobby: Seam = client.join(InMemoryTag("lobby"))
    val table: Seam = client.join(InMemoryTag("table-7"))

    // Both channels ride one base socket; the same name is idempotent.
    check(lobby !== table)
    check(client.join(InMemoryTag("lobby")) === lobby)
}

Constructors

Link copied to clipboard
constructor(base: Loom, baseRendezvous: Rendezvous, scope: CoroutineScope, nameOf: (Rendezvous) -> String)

Functions

Link copied to clipboard

Whether this fabric can be attempted now — the availability half of capability. Derived; do not override.

Link copied to clipboard
open override fun capability(): TransportCapability

The base fabric's verdict, verbatim. Multiplexing changes how many logical sessions share a link — not which medium carries it, nor whether that medium is usable on this runtime — so the base's capability is this loom's capability, in both halves.

Link copied to clipboard
suspend fun closeBase(reason: CloseReason = CloseReason.Normal)

Closes the current base fabric, tearing down the single shared socket. The next weave re-weaves the base and heals every channel handle onto it.

Link copied to clipboard
open suspend fun host(pattern: Pattern): Seam

Host / start a new session.

Link copied to clipboard
open suspend fun join(tag: Tag): Seam

Join an existing session. The advertisement carries enough info to reach the existing peer set.

Link copied to clipboard
open suspend override fun weave(rendezvous: Rendezvous): Seam

Establish a Seam according to rendezvous — either host a new session or join an existing one.