LavaUI docs

class InputChannel

Swift

final class InputChannel

The app's end of SubscribeInput: an async stream on one side, a frame loop that is not async on the other.

Same shape as LoopQueue in the other direction and for the same reason — events arrive on an NPRPC thread, and the only thread allowed to touch the arena is the one writing frames into it.

Sources/LavaClient/ControlPlane.swift:301

Constructors

init(stream: NPRPCBidiStream<InputAck, WireInputEvent>)

Not documented.

Sources/LavaClient/ControlPlane.swift:307

Properties

var consumedSerial: UInt32 { get }

The newest serial this client has taken out of the queue.

Rides on every Present (see LavaClient.open), which is what lets the compositor tell a frame drawn with an event from one drawn just before it — the opening Resize being the one that matters, since a window is held off screen until a frame comes back laid out at the size it was given. The ack cannot answer that: it is sent from here, on the way in, and the frame is laid out afterwards.

Read from the frame loop, written on the same thread inside drain, but under the lock like everything else here — enqueue runs on an NPRPC thread and this object has one lock, not one per field.

Sources/LavaClient/ControlPlane.swift:419

var isClosed: Bool { get }

True once the renderer has gone away — the app's cue to stop drawing frames nobody will read.

Sources/LavaClient/ControlPlane.swift:388

var onArrival: (@Sendable () -> Void)? { get set }

Called on the NPRPC thread the moment something arrives, for a client whose frame loop parks rather than polls.

A LavaUI client blocks in pumpEvents until something happens, and an input that only becomes visible on the next tick would be an input that arrives when the next tick happens to be — which for an idle app is never.

Arming it is itself an arrival, for whatever landed while it was nil. The reader task starts in init, and the compositor answers SubscribeInput with the opening Resize immediately — well before a caller finishing its bring-up has got as far as installing this. Without the catch-up call that size waits in the queue for some other event to wake the loop, which for a window nobody has touched yet never comes: the window sits at its requested size inside a frame the compositor has already placed and sized, with the desktop showing between the two.

Sources/LavaClient/ControlPlane.swift:363

Methods

func close()

Ends the lease cleanly from this side.

Finishes the ack stream so the writer closes, which is what the compositor treats as "the surface is gone" when DestroySurface is not used first. Cheap if already closed.

Sources/LavaClient/ControlPlane.swift:342

func drain() -> [WireInputEvent]

Everything that arrived since the last call. Acknowledges the batch, which is the honest moment to do it: the serial now means "consumed", not "received".

Sources/LavaClient/ControlPlane.swift:397