Fast Lane¶
The fast lane is MOSAIC’s zero-serialisation rendering path. It streams
live RGB frames from a training worker directly into shared memory, bypassing
the Slow Lane gRPC/SQLite pipeline entirely. The GUI-side
FastLaneConsumer polls the buffer every 16 ms (~60 Hz) and hands the
latest frame to a Render Tabs FastLaneTab for Qt Quick display.
%%{init: {"flowchart": {"curve": "linear"}} }%%
graph LR
W["Worker Process"] -->|"publish(frame)"| FLW["FastLaneWriter"]
FLW -->|"shared memory"| SHM[("SPSC Ring Buffer<br/>magic FLAN · seqlock")]
SHM -->|"latest_frame()"| FLR["FastLaneReader"]
FLR --> FLC["FastLaneConsumer<br/>QTimer 16 ms"]
FLC -->|"frame_ready signal"| FLT["FastLaneTab<br/>QQuickWidget · QML"]
style SHM fill:#e8f5e9,stroke:#2e8b57,color:#333
SPSC Ring Buffer¶
The core is a Single-Producer, Single-Consumer ring buffer in shared
memory, inspired by the LMAX Disruptor pattern. Implementation lives in
gym_gui/fastlane/buffer.py.
Data Classes¶
Class |
Fields |
|---|---|
|
|
|
|
|
|
All three are frozen dataclasses.
Seqlock Mechanism¶
The writer and reader coordinate without locks using a seqlock protocol:
Write path:
FastLaneWriter.publish(frame, *, metrics, metadata) → int:Computes
slot = head % capacity.Writes
seq = head * 2into the slot header (odd = in-flight).Copies RGB payload bytes.
Writes
seq = head * 2 + 2(even = committed).Atomically advances
headin the shared header.
Read path:
FastLaneReader.latest_frame() → FastLaneFrame | None:Reads
seq1from the slot header.If
seq1 % 2 == 1→ write in progress, skip.Copies the payload bytes.
Reads
seq2and verifiesseq1 == seq2→ data is consistent.On mismatch → retry or return
None.
Metrics path:
FastLaneReader.metrics() → FastLaneMetrics: readslast_reward,rolling_return,step_rate_hzdirectly from the header doubles.
Factory Methods¶
# Worker side
writer = FastLaneWriter.create(
run_id,
FastLaneConfig(width=84, height=84, channels=3, capacity=128),
)
seq = writer.publish(frame_bytes, metrics=FastLaneMetrics(...))
# GUI side
reader = FastLaneReader.attach(run_id)
frame = reader.latest_frame()
Design Rules¶
SPSC only: one writer, one reader, no mutexes.
Lossy: the consumer always jumps to the latest sequence; old frames are silently overwritten.
Batch-friendly: no frame debt; the reader skips ahead.
Simple payload: tight-packed RGB(A) bytes; HUD scalars in the header.
Invalidation:
FLAG_INVALIDATEDtells the reader that the writer has exited and the buffer should be re-attached.
Frame Tiling¶
When a worker uses vectorized environments,
tile_frames(frames: Sequence[np.ndarray]) → np.ndarray composites N
sub-environment frames into a near-square grid (rows = ceil(sqrt(N)),
cols = ceil(N / rows)). This mirrors Stable-Baselines3 VecEnv tiling
and allows streaming multiple environments in a single fast-lane slot.
Worker Integration Helpers¶
apply_fastlane_environment() injects canonical environment variables into
a worker’s subprocess launch dict:
Environment Variable |
Description |
|---|---|
|
|
|
Which vectorized-env index feeds the writer |
|
|
|
Max environments composited in grid mode (default 4) |
def apply_fastlane_environment(
env: Dict[str, Any],
*,
fastlane_only: bool,
fastlane_slot: int,
video_mode: str = "SINGLE",
grid_limit: int = 4,
) -> Dict[str, Any]: ...
FastLaneConsumer¶
FastLaneConsumer (gym_gui/ui/fastlane_consumer.py) is a QObject
that bridges shared memory to Qt signals.
Polling loop: a QTimer fires every 16 ms:
If not connected → attempt
FastLaneReader.attach(run_id).Check
FLAG_INVALIDATED→ trigger reconnection.Validate header (
capacity > 0,slot_size > 0).Read
reader.latest_frame()→ convert bytes toQImage(Format_RGB888orFormat_RGBA8888).Emit
frame_ready(FastLaneFrameEvent)with theQImageand a HUD string:"reward: {:.2f}\nreturn: {:.2f}\nstep/sec: {:.1f}".
Signals:
frame_ready(FastLaneFrameEvent): image + HUD text + optional metadata.status_changed(str):"connected"|"reconnecting"|"fastlane-unavailable".
FastLaneTab¶
FastLaneTab (gym_gui/ui/widgets/fastlane_tab.py) hosts a
QQuickWidget loading FastLaneView.qml for GPU-accelerated rendering.
See Render Tabs for how it plugs into the central tab widget.
FastLaneTab(
run_id: str,
agent_id: str,
*,
mode_label: str | None = None, # default "Fast lane"
run_mode: str | None = None, # "train" | "policy_eval"
parent: QWidget | None = None,
)
Modes:
"train"(default): live frames + reward / step-rate HUD."policy_eval": adds an evaluation summary overlay that reloadseval_summary.jsonevery 1 s (batch count, episodes, avg/min/max return).
Directory Layout¶
gym_gui/
fastlane/
__init__.py # Public API re-exports
buffer.py # SPSC shared-memory ring buffer
tiling.py # tile_frames() for multi-env compositing
worker_helpers.py # apply_fastlane_environment()
ui/
fastlane_consumer.py # FastLaneConsumer (QTimer → QImage)
widgets/
fastlane_tab.py # FastLaneTab (QQuickWidget host)
See Also¶
Slow Lane: the durable gRPC/SQLite telemetry path that complements the fast lane.
Render Tabs:
FastLaneTabis dynamically added toRenderTabsby worker presenters.Workers: the worker subprocess layer that produces fast-lane frames.
CleanRL Worker: CleanRL’s
FastLaneTelemetryWrapperintegration.Application Constants:
RenderDefaultsandBufferDefaultsfor queue-size tuning.