Plushie.Bridge (Plushie v0.7.1)

Copy Markdown View Source

Bridge to the plushie renderer process.

Manages the connection to the renderer, buffers partial JSONL lines (JSON mode) or receives length-prefixed frames (MessagePack mode), and forwards decoded events to the runtime process.

Transport modes

Transport I/O is delegated to modules implementing Plushie.Transport. Controlled by the :transport option:

  • :spawn (default) - spawns the renderer binary as a child process using an Erlang Port (Plushie.Transport.Port).

  • :stdio - reads/writes the BEAM's own stdin/stdout. Used when the renderer spawns the Elixir process (Plushie.Transport.Port).

  • {:iostream, pid} - sends and receives protocol messages via an external process (Plushie.Transport.IOStream). See that module for the adapter protocol.

Wire formats

Controlled by the :format option:

  • :json - JSONL over stdio. Opt-in for debugging and observability.
  • :msgpack (default) - MessagePack with 4-byte length-prefixed framing.

On unexpected exit the bridge applies exponential back-off and attempts to restart the renderer up to max_restarts times. If the limit is exhausted the GenServer stops with {:max_restarts_reached, reason}.

During restart the runtime rebuilds renderer-owned state by re-sending settings, a full snapshot, subscriptions, and windows. Transient commands that cannot be rebuilt from that state are held until the runtime finishes resync, then sent in order.

Summary

Functions

Returns a specification to start this module under a supervisor.

Restarts the renderer process intentionally (e.g. after a Rust rebuild).

Captures a renderer screenshot and returns the raw response map.

Sends an advance_frame message to the renderer (headless/test mode).

Sends a widget-targeted command to the renderer.

Sends a batch of widget-targeted commands to the renderer.

Sends an effect request to the renderer.

Sends an image operation (create/update/delete) to the renderer.

Sends an interact request to the renderer.

Sends a font-load message to the renderer.

Sends a patch (list of diff ops) to the renderer.

Registers an effect stub with the renderer.

Sends application-level settings to the renderer.

Sends an encoded snapshot of tree to the renderer.

Subscribes to a renderer-side event source.

Sends a system-wide operation to the renderer.

Sends a system-wide query to the renderer.

Removes a previously registered effect stub.

Unsubscribes from a renderer-side event source.

Sends a widget operation to the renderer.

Sends a window lifecycle operation to the renderer.

Starts the bridge linked to the calling process.

Stops the bridge GenServer.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

restart_renderer(bridge)

@spec restart_renderer(bridge :: GenServer.server()) :: :ok

Restarts the renderer process intentionally (e.g. after a Rust rebuild).

Unlike crash recovery, this does not count against the restart limit and does not use exponential backoff. The existing renderer is closed cleanly before opening the new one. The runtime receives :renderer_restarted and re-syncs as usual.

screenshot(bridge, name, opts \\ [], timeout \\ 30000)

@spec screenshot(
  bridge :: GenServer.server(),
  name :: String.t(),
  opts :: keyword(),
  timeout :: timeout()
) :: map()

Captures a renderer screenshot and returns the raw response map.

Width and height are optional positive integers. The call blocks until the renderer replies with screenshot_response.

send_advance_frame(bridge, timestamp)

@spec send_advance_frame(bridge :: GenServer.server(), timestamp :: non_neg_integer()) ::
  :ok

Sends an advance_frame message to the renderer (headless/test mode).

send_command(bridge, id, family, value)

@spec send_command(
  bridge :: GenServer.server(),
  id :: String.t(),
  family :: String.t(),
  value :: term()
) :: :ok

Sends a widget-targeted command to the renderer.

send_commands(bridge, commands)

@spec send_commands(
  bridge :: GenServer.server(),
  commands :: [{String.t(), String.t(), term()}]
) :: :ok

Sends a batch of widget-targeted commands to the renderer.

send_effect(bridge, id, kind, payload)

@spec send_effect(
  bridge :: GenServer.server(),
  id :: String.t(),
  kind :: String.t(),
  payload :: map()
) :: :ok

Sends an effect request to the renderer.

send_image_op(bridge, op, payload)

@spec send_image_op(bridge :: GenServer.server(), op :: String.t(), payload :: map()) ::
  :ok

Sends an image operation (create/update/delete) to the renderer.

send_interact(bridge, id, action, selector, payload \\ %{})

@spec send_interact(
  bridge :: GenServer.server(),
  id :: String.t(),
  action :: String.t(),
  selector :: map(),
  payload :: map()
) :: :ok

Sends an interact request to the renderer.

The renderer will process the interaction against its widget tree and respond with interact_step / interact_response messages. These are forwarded to the runtime as {:interact_step, id, events} and {:interact_response, id, events}.

Parameters

  • id - unique request identifier, used to correlate responses.
  • action - the interaction verb. One of: "click", "toggle", "select", "type_text", "submit", "press", "release", "type_key", "slide", "paste", "scroll", "move_to", "sort", "canvas_press", "canvas_release", "canvas_move", "pane_focus_cycle".
  • selector - a map identifying the target widget. Keys are optional and include "by" (e.g. "id", "text", "role", "label", "focused") and "value" (the lookup value). An empty map targets the focused widget or the window root.
  • payload - action-specific data. Examples:
    • %{text: "hello"} for "type_text" / "paste"
    • %{value: "option"} for "select"
    • %{value: 0.5} for "slide"
    • %{key: "Enter", modifiers: %{}} for "press" / "release" / "type_key"
    • %{x: 10, y: 20, button: "left"} for "canvas_press" / "canvas_release"
    • %{x: 10, y: 20} for "canvas_move" / "move_to"
    • %{delta_x: 0, delta_y: -3} for "scroll"
    • %{column: "name", direction: "asc"} for "sort"
    • %{} for "click", "toggle", "submit", "pane_focus_cycle"

send_load_font(bridge, family, data)

@spec send_load_font(
  bridge :: GenServer.server(),
  family :: String.t(),
  data :: binary()
) :: :ok

Sends a font-load message to the renderer.

send_patch(bridge, ops)

@spec send_patch(bridge :: GenServer.server(), ops :: [map()]) :: :ok

Sends a patch (list of diff ops) to the renderer.

send_register_effect_stub(bridge, kind, response)

@spec send_register_effect_stub(
  bridge :: GenServer.server(),
  kind :: String.t(),
  response :: term()
) :: :ok

Registers an effect stub with the renderer.

send_settings(bridge, settings)

@spec send_settings(bridge :: GenServer.server(), settings :: map()) :: :ok

Sends application-level settings to the renderer.

send_snapshot(bridge, tree)

@spec send_snapshot(bridge :: GenServer.server(), tree :: map()) :: :ok

Sends an encoded snapshot of tree to the renderer.

send_subscribe(bridge, kind, tag, max_rate \\ nil, window_id \\ nil)

@spec send_subscribe(
  bridge :: GenServer.server(),
  kind :: String.t(),
  tag :: String.t(),
  max_rate :: non_neg_integer() | nil,
  window_id :: String.t() | nil
) :: :ok

Subscribes to a renderer-side event source.

send_system_op(bridge, op, settings \\ %{})

@spec send_system_op(
  bridge :: GenServer.server(),
  op :: String.t(),
  settings :: map()
) :: :ok

Sends a system-wide operation to the renderer.

send_system_query(bridge, op, settings \\ %{})

@spec send_system_query(
  bridge :: GenServer.server(),
  op :: String.t(),
  settings :: map()
) :: :ok

Sends a system-wide query to the renderer.

send_unregister_effect_stub(bridge, kind)

@spec send_unregister_effect_stub(bridge :: GenServer.server(), kind :: String.t()) ::
  :ok

Removes a previously registered effect stub.

send_unsubscribe(bridge, kind, tag \\ nil)

@spec send_unsubscribe(
  bridge :: GenServer.server(),
  kind :: String.t(),
  tag :: String.t() | nil
) :: :ok

Unsubscribes from a renderer-side event source.

send_widget_op(bridge, op, payload)

@spec send_widget_op(bridge :: GenServer.server(), op :: String.t(), payload :: map()) ::
  :ok

Sends a widget operation to the renderer.

send_window_op(bridge, op, window_id, settings \\ %{})

@spec send_window_op(
  bridge :: GenServer.server(),
  op :: String.t(),
  window_id :: String.t(),
  settings :: map()
) :: :ok

Sends a window lifecycle operation to the renderer.

start_link(opts)

@spec start_link(opts :: keyword()) :: GenServer.on_start()

Starts the bridge linked to the calling process.

Required opts:

  • :runtime - pid to receive {:renderer_event, event} messages

Required for :spawn transport (default):

  • :renderer_path - filesystem path to the plushie binary

Optional opts:

  • :name - registration name passed to GenServer.start_link/3
  • :transport - :spawn (default, spawns renderer as child process),
                   `:stdio` (reads/writes the BEAM's own stdin/stdout),
                   or `{:iostream, pid}` (custom transport via iostream adapter)
  • :format - wire format, :msgpack (default) or :json
  • :log_level - renderer log level (:off, :error, :warning, :info, :debug).
                   Default: `:error`. Ignored when `RUST_LOG` is set in the environment.
  • :renderer_args - extra CLI args prepended to the renderer command (e.g. ["--headless"])
  • :max_restarts - max restart attempts before giving up (default: 5)
  • :restart_delay - base delay in ms for exponential back-off (default: 100)
  • :heartbeat_interval - maximum time (ms) between renderer messages before
                   the bridge considers the renderer unresponsive and triggers
                   a restart. `nil` disables the watchdog. Default: `30_000`.

stop(bridge)

@spec stop(bridge :: GenServer.server()) :: :ok

Stops the bridge GenServer.