Class: MaquinaStream::Broadcaster
- Inherits:
-
Object
- Object
- MaquinaStream::Broadcaster
- Defined in:
- lib/maquina_stream/broadcaster.rb
Overview
Turns a growing buffer into a stream of small patches.
broadcaster = MaquinaStream::Broadcaster.new()
model.stream { |token| broadcaster.append(token) }
broadcaster.seal! # final frame, always
It remembers what the browser already has — block id to digest — and sends only what moved. A block that has not changed is never re-sent, which is the difference between bandwidth tracking drift and bandwidth tracking message length.
Transport is a seam. It defaults to Turbo Streams over whatever cable the host configured, and a test can hand it a recorder instead; that recorder is how the bandwidth budget is asserted rather than estimated.
One broadcaster per stream, held for the life of that stream: what the browser already has lives in the instance, so a fresh broadcaster mid-stream re-sends every block. It is not thread-safe; drive one stream from one place.
Deltas do not converge on their own, and are not meant to. Only the open tail is patched; a block that changes after it stops being the tail is fixed by the repair path. Correctness lives there.
Defined Under Namespace
Classes: TurboTransport
Instance Attribute Summary collapse
-
#config ⇒ Object
readonly
The Configuration this broadcaster reads —
frame_budget_msandseal_lagin particular. -
#record ⇒ Object
readonly
The host record being streamed.
-
#transport ⇒ Object
readonly
The object frames are emitted through.
Instance Method Summary collapse
-
#append(text, now: monotonic_ms) ⇒ Object
Appends
textto the record's buffer and broadcasts if the frame budget has elapsed. -
#broadcast(now: monotonic_ms) ⇒ Object
Broadcasts a frame if the budget has elapsed, without appending anything.
-
#document ⇒ Object
A Document over the record's current buffer, rendered in the mode its status implies.
-
#initialize(record, config: MaquinaStream.config, transport: nil) ⇒ Broadcaster
constructor
Builds a broadcaster over one record.
-
#seal!(status: :complete) ⇒ Object
Seals the record and emits the final frame.
Constructor Details
#initialize(record, config: MaquinaStream.config, transport: nil) ⇒ Broadcaster
Builds a broadcaster over one record.
transport: defaults to TurboTransport. It is a seam: anything answering
call(record:, frame:, config:) will do, which is how the bandwidth
budget is asserted against a recorder rather than estimated.
48 49 50 51 52 53 54 55 |
# File 'lib/maquina_stream/broadcaster.rb', line 48 def initialize(record, config: MaquinaStream.config, transport: nil) @record = record @config = config @transport = transport || TurboTransport.new @known = {} @tail_id = nil @last_flush = nil end |
Instance Attribute Details
#config ⇒ Object (readonly)
The Configuration this broadcaster reads — frame_budget_ms and
seal_lag in particular.
37 38 39 |
# File 'lib/maquina_stream/broadcaster.rb', line 37 def config @config end |
#record ⇒ Object (readonly)
The host record being streamed. Must satisfy MaquinaStream::Streamable.
33 34 35 |
# File 'lib/maquina_stream/broadcaster.rb', line 33 def record @record end |
#transport ⇒ Object (readonly)
The object frames are emitted through. Responds to
call(record:, frame:, config:).
41 42 43 |
# File 'lib/maquina_stream/broadcaster.rb', line 41 def transport @transport end |
Instance Method Details
#append(text, now: monotonic_ms) ⇒ Object
Appends text to the record's buffer and broadcasts if the frame budget
has elapsed. Returns the Frame that went out, or nil when this append was
coalesced into the next one.
This is the method a streaming loop calls, once per token or per chunk.
Host owns persistence: it appends to its own column, and only then is there anything to broadcast.
65 66 67 68 |
# File 'lib/maquina_stream/broadcaster.rb', line 65 def append(text, now: monotonic_ms) record.maquina_stream_append(text) broadcast(now: now) end |
#broadcast(now: monotonic_ms) ⇒ Object
Broadcasts a frame if the budget has elapsed, without appending anything. Returns the Frame or nil.
Useful when the buffer moved by some other route — a host that writes to the column itself and wants the browser told about it.
Coalescing happens BEFORE the render, not after it. Building a frame means rendering the whole buffer, so doing that per token and then throwing the result away is how a stream becomes quadratic in message length.
Skipping a frame costs nothing: the next one is computed against what the browser actually has, so it carries the accumulated difference.
95 96 97 98 99 |
# File 'lib/maquina_stream/broadcaster.rb', line 95 def broadcast(now: monotonic_ms) return nil unless due?(now) emit(now: now) end |
#document ⇒ Object
A Document over the record's current buffer, rendered in the mode its status implies. Rebuilt on every call, because the buffer moves.
A sealed message renders in static mode, which is what takes the caret off the last block.
106 107 108 109 110 111 112 113 |
# File 'lib/maquina_stream/broadcaster.rb', line 106 def document Document.new( record.maquina_stream_buffer, config: config, sid: record.maquina_stream_id, mode: record.maquina_stream_open? ? :streaming : :static ) end |
#seal!(status: :complete) ⇒ Object
Seals the record and emits the final frame. Returns that Frame.
status: is one of Streamable::SEAL_STATUSES. Call this exactly once,
including when a stream failed: an errored message still has text worth
keeping, and the client has no other way to learn the stream is over.
The final frame is never coalesced and never skipped. Every intra-stream drift becomes cosmetic and self-correcting because of this one.
78 79 80 81 |
# File 'lib/maquina_stream/broadcaster.rb', line 78 def seal!(status: :complete) record.maquina_stream_seal!(status: status) emit(now: monotonic_ms, final: true) end |