Seam
One peer's view of a multi-peer session.
Symmetry: every peer in the session holds an identical Seam — there is no client/server distinction at this layer. A two-peer topology (e.g. the existing WebSocket transport) is just the degenerate case with peers.value.size == 2.
Fabric lifecycle: state tracks whether the fabric can carry frames. Wait for SeamState.Woven before sending on fabrics that may take time to establish their link (radio/mesh transports). Relay transports reach SeamState.Woven essentially immediately.
Send semantics:
broadcast while SeamState.Weaving (the fabric is forming, or a recoverable multipath rollup has no live ply right now — e.g. a composite whose every ply is currently torn), or SeamState.Woven with no other peers: best-effort — never throws; delivery is simply not guaranteed until SeamState.Woven. A fully-degraded but recoverable composite broadcast is therefore a best-effort zero-target no-op, not an error. (A tiered union whose both tiers are torn is instead terminal SeamState.Torn — its one-shot merged
incomingcannot recover — so a send there throws, per theTornrule below.)sendTo when the addressed peer is absent from peers: throws PeerNotConnected.
sendTo of a payload over maxPayloadBytes: throws PayloadTooLarge; broadcast instead drops it (best-effort, as above). An obligation on an implementation that publishes a budget — a seam reporting
nullowes nothing here — and one every in-tree seam that publishes one now meets (#2069). See maxPayloadBytes. Note it is the one refusal that does not depend on the seam's state — aWovenseam raises it — so a caller catching only PeerNotConnected does not cover addressed sends on a seam that publishes a budget.Either call when SeamState.Torn: throws IllegalStateException.
Tornis the only state-driven refusal (it is unconditionally terminal — see SeamState); the payload-size refusal above is orthogonal to state.
Collecting incoming frames
Collect incoming exactly once per Seam. For multiple consumers, wrap with shareIn in a coroutine scope you control.
Samples
runTest {
val loom = InMemoryLoom()
val host = loom.host(Pattern("Alice"))
val joiner = loom.join(InMemoryTag("Bob"))
// Collect three frames then stop; proves the flow is cold and single-shot.
val collected = launch { host.incoming.take(3).toList() }
repeat(3) { joiner.broadcast("frame-$it".encodeToByteArray()) }
collected.join()
host.close()
joiner.close()
}Inheritors
Properties
Functions
Disconnect from the session. Idempotent.
Run body but abort it with a SeamCollapsedException the instant this Seam collapses mid-operation — either the fabric latches SeamState.Torn (transport tear) OR the live peer set satisfies abortWhen (membership drain). Whichever of body, the tear, or the drain resolves first wins; the losers are cancelled.