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 or SeamState.Woven with no other peers: defined no-op, never silent.
sendTo when the addressed peer is absent from peers: throws PeerNotConnected.
Either call when SeamState.Torn: throws IllegalStateException.
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()
}