Skip to content

PreviewDriver

Source: PreviewDriver.h

PreviewDriver

class PreviewDriver
src/light/drivers/PreviewDriver.h:70

Inherits: DriverBase, ClientMessageSink

Streams a true-shape 3D preview to the web UI over the binary WebSocket.

The preview is a POINT LIST, not a dense grid: only the real lights are sent, at their real (x,y,z) positions. This is the proven MoonLight model (virtual grid → physical sparse lights; positions sent once at mapping time, channels per frame). Two message types — PreviewDriver owns both wire formats; the HTTP server is a domain-neutral BinaryBroadcaster that just writes the bytes: 0x03 coordinate table (sent ONLY in answer to a client's [0x52] request; the client caches tables per (epoch, stride), so a stride change to a known rung asks nothing): [0x03][count:u32][bx:u8][by:u8][bz:u8][stride:u16][epoch:u8][(x,y,z):u8x3 x count] bx/by/bz = bounding-box extent (for client centring); positions are 1 byte/axis (scaled when an axis exceeds 255). count is u32 so a >65535-light panel isn't capped by the wire format; epoch bumps on every geometry rebuild and keys the client's cache.

0x02 per-frame channels: [0x02][count:u32][stride:u16][epoch:u8][drops:u8][(r,g,b) x count] RGB of every kept light, in the coord table's order. drops = frames discarded at the source since the last delivered one, the congestion signal the client's controller adapts on.

0x04 per-frame AIM, sent only for a rig whose fixtures carry pan/tilt (a moving-head rig); a plain LED wall never emits it and costs nothing for it: [0x04][count:u32][stride:u16][epoch:u8][reserved:u8][(pan,tilt):u8x2 x count] Same order and same (epoch, stride) key as 0x02, so aim[k] belongs to the light the color frame's k-th entry colors. The BROWSER decides how to draw it (today a beam line from the fixture): the wire carries where a head points, never a rendered look, so a richer visual later is a shader change and not a protocol change.

Client requests (masked WS frames, unmasked by core, interpreted only here): [0x51][stride][fps] standing frame request (most conservative across viewers wins; the targetFps control is the ceiling) [0x52][stride] one-shot: send me the coordinate table count is the number of points actually kept after lattice downsampling (the lights whose position satisfies pos ≡ 0 mod stride) — a client sizes its buffer from this count, not from the light total. stride rises above 1 when a client requests it, or when the memory cap forces a floor; with no cap in play every light is sent (stride 1), so a sparse layout streams in full. Its own channel (/wsp), and why. Preview frames are lossy and large; control-plane state is small and latency-sensitive. Sharing one WebSocket made the small messages queue behind the big ones, head-of-line blocking, which users saw as a flickering connection indicator and a UI that stopped responding while a large layout streamed. Separate TCP connections is the standard remedy for that mixed-criticality pairing.

Resolution is client-driven. The browser reads the drops counter each frame carries (the device's own congestion signal) and posts the [0x51][stride][fps] standing request it wants; the device serves the most conservative request across viewers. The memory cap (maxPreviewPoints()) is the only floor a request cannot go finer than.

No request, no work.[tick()] returns immediately when no standing request exists, so a dismissed preview pane (or a hidden tab) costs the device nothing, not merely nothing on the wire.

PreviewDriver card

Public Attributes

uint8_t targetFps = 24 : The frame rate the preview aims for (Hz), independent of render FPS.

Public Methods

virtual inline bool userEditable() const override : The 3D preview the web UI renders streams from this driver.

inline void setBroadcaster(BinaryBroadcaster * b) : Set the sink each message is pushed to (HttpServerModule, as a BinaryBroadcaster).

inline void onClientMessage(int slot, const uint8_t * payload, int len) override : The pull protocol, this producer's whole request vocabulary: [0x51][stride][fps] standing frame request: serve stride s at rate f (fps 0/absent = the targetFps control's value).

inline void onClientGone(int slot) override

inline nrOfLightsType downscaleForTest() const : The currently served downsample factor (1 = full resolution).

virtual inline bool hasCorrectionControls() const override : Preview shows the raw logical buffer, no correction.

virtual inline void defineDriverControls() override : Bind the controls: targetFps (1-25), the frame rate the preview aims for.

virtual inline void setSourceBuffer(Buffer * buf) override : Point the driver at the sparse driver buffer the LED/ArtNet drivers also read (the MappingLUT fills it with exactly the real lights).

virtual inline void prepare() override : A rebuild (layout add/replace/remove, resize, modifier change) ran — the light set / positions may have changed, so rebuild + broadcast the coordinate table (the MoonLight "positions once at mapping time").

virtual inline void release() override : Clear every shared status string on release — fail buffer, config error, and config warning — so a stopped driver leaves nothing behind (frees the owned failBuf_; retracts the warning the same "clear only MY status" way as the error), then chain to MoonModule::release() so any ScratchBuffer the driver holds frees on disable and children recurse.

virtual inline bool affectsPrepare(const char *) const override : No control changes the transport structure: targetFps is a plain value edit, so nothing here re-runs prepare.

virtual inline void tick() override : Per-tick: (re)stream the coordinate table when the geometry or client set changed, then stream one color frame if the previous one finished draining.

inline void buildCoordTable() : Build (or rebuild) the cached coordinate table from the layout's real lights and broadcast it (the 0x03 message).

inline bool sendCoordTable() : Answer a [0x52] table request: one 0x03 message for the SERVED stride, through the same resumable slot as everything else.

inline bool sendAim() : Stream one per-frame 0x02 RGB message straight from the producer buffer — no intermediate copy.

inline bool sendFrame()