plushie/bridge
Bridge actor: manages the Erlang Port to the Rust binary.
The bridge is a stable entity that outlives individual renderer processes. When the renderer crashes, the bridge stays alive, queues transient messages during the restart window, and reopens the port after an exponential backoff delay.
Message classification during restart
Messages are classified as rebuildable or transient:
- Rebuildable (
Send): settings, snapshots, patches, subscriptions, window ops. Dropped when the port is down because the runtime rebuilds them during resync. - Transient (
SendTransient): effects, widget ops, image ops, widget commands, interact, advance_frame, stub registration. Queued when the port is down or awaiting resync, then flushed after the runtime signals resync is complete.
Transport modes
Spawn(default): spawns the renderer binary as a child process using an Erlang Port.Stdio: reads/writes the BEAM’s own stdin/stdout. Used when the renderer spawns the Gleam process (e.g.plushie-renderer --exec).Iostream: sends and receives protocol messages via an external process (the iostream adapter). Used for custom transports like SSH channels, TCP sockets, or WebSockets.
Wire framing
- MessagePack: 4-byte big-endian length prefix (Erlang {packet, 4})
- JSONL: newline-delimited (Erlang {line, 65536})
Types
Messages the bridge actor handles.
pub type BridgeMessage {
Send(data: BitArray)
SendTransient(data: BitArray)
PortData(data: dynamic.Dynamic)
PortLineData(line_data: renderer_port.LineData)
PortExit(status: dynamic.Dynamic)
IoStreamData(data: BitArray)
IoStreamClosed
HeartbeatTimeout
RegisterRuntime(process.Subject(RuntimeNotification))
ResyncComplete
RestartPort
Shutdown
}
Constructors
-
Send(data: BitArray)Send pre-encoded rebuildable wire bytes (settings, snapshot, patch, subscribe, window_op). Dropped when the port is down since the runtime rebuilds these during resync.
-
SendTransient(data: BitArray)Send pre-encoded transient wire bytes (effect, widget_op, image_op, widget command, interact, advance_frame, stub registration). Queued when the port is down or awaiting resync, flushed after ResyncComplete.
-
PortData(data: dynamic.Dynamic)Port data received from the Rust binary (via selector).
-
PortLineData(line_data: renderer_port.LineData)Line data received from the Rust binary in JSON mode (via selector).
-
PortExit(status: dynamic.Dynamic)Port closed/exited (via selector).
-
IoStreamData(data: BitArray)Data received from an iostream adapter.
-
IoStreamClosedThe iostream adapter closed the transport.
-
HeartbeatTimeoutHeartbeat watchdog timer fired (no renderer messages within interval).
-
RegisterRuntime(process.Subject(RuntimeNotification))Register the runtime’s notification subject. Sent by the runtime after it starts under the supervisor. Any events received before this message are buffered and flushed on receipt.
-
ResyncCompleteRuntime has finished resync (settings, snapshot, subscriptions, windows). The bridge flushes any queued transient messages.
-
RestartPortInternal: bridge’s own restart timer fired.
-
ShutdownGraceful shutdown request.
Internal bridge state.
pub opaque type BridgeState
Messages sent to an iostream adapter process. The adapter must handle these messages to integrate with a custom transport (TCP, WebSocket, SSH, etc.).
pub type IoStreamMessage {
IoStreamBridge(bridge: process.Subject(BridgeMessage))
IoStreamSend(data: BitArray)
}
Constructors
-
IoStreamBridge(bridge: process.Subject(BridgeMessage))The bridge is registering itself. The adapter should send IoStreamData messages to the bridge subject.
-
IoStreamSend(data: BitArray)The bridge wants to send data over the transport.
Notifications sent from bridge to runtime.
pub type RuntimeNotification {
InboundEvent(decode.InboundMessage)
RendererExited(exit: renderer_exit.RendererExit)
RendererRestarted
}
Constructors
-
InboundEvent(decode.InboundMessage)A decoded inbound message from the Rust binary.
-
RendererExited(exit: renderer_exit.RendererExit)The Rust binary process exited.
-
RendererRestartedThe bridge successfully reopened the port after a crash. The runtime should resync state and then send ResyncComplete.
Transport mode for the bridge.
pub type Transport {
TransportSpawn
TransportStdio
TransportIoStream(adapter: process.Subject(IoStreamMessage))
}
Constructors
-
TransportSpawnSpawn the renderer binary as a child process.
-
TransportStdioUse the BEAM’s own stdin/stdout.
-
TransportIoStream(adapter: process.Subject(IoStreamMessage))Use a custom iostream adapter process.
Values
pub fn start(
binary_path: String,
format: protocol.Format,
runtime: process.Subject(RuntimeNotification),
session: String,
renderer_args: List(String),
) -> Result(process.Subject(BridgeMessage), actor.StartError)
Start the bridge actor with spawn transport (default).
Opens a port to the plushie binary and begins forwarding messages. Inbound wire data is decoded and sent as RuntimeNotification values to the provided runtime subject.
pub fn start_supervised(
name: process.Name(BridgeMessage),
binary_path: String,
format: protocol.Format,
session: String,
renderer_args: List(String),
transport: Transport,
) -> Result(
actor.Started(process.Subject(BridgeMessage)),
actor.StartError,
)
Start the bridge under a supervisor with a registered name.
The runtime subject is not available yet; the runtime will send
a RegisterRuntime message after it starts. Events received before
registration are buffered and flushed automatically.
pub fn start_with_transport(
binary_path: String,
format: protocol.Format,
runtime: process.Subject(RuntimeNotification),
session: String,
renderer_args: List(String),
transport: Transport,
) -> Result(process.Subject(BridgeMessage), actor.StartError)
Start the bridge actor with an explicit transport mode.