Class: MaquinaStream::Manifest
- Inherits:
-
Object
- Object
- MaquinaStream::Manifest
- Defined in:
- lib/maquina_stream/manifest.rb
Overview
What the browser is told a message currently is, in a bounded number of bytes.
{ seq: 412, cutoff: 38, rollup: "7c1f…",
blocks: [["ms-m8f21-b38", "a91c…"], ["ms-m8f21-b39", "4fe2…"]] }
Not HTML. The client diffs this against its own DOM, asks for the blocks whose digests differ, and morphs only those — so repair costs what has drifted rather than what the message weighs.
Why it is windowed
docs/design.md said the manifest is "a few hundred bytes regardless of message size". Measured, listing every sealed block gave 1.8KB for a 2KB message and 88KB for a 100KB one — it tracked length almost exactly, because block count does. A keyframe every four seconds carrying 88KB is the bandwidth problem the manifest was introduced to prevent.
So the manifest carries the last window sealed blocks in full, plus one
rollup digest covering everything older. A client whose rollup matches knows
its history is intact and only has to consider the window; a client whose
rollup differs asks for the whole thing, which is rare and is what a cold
page load does anyway.
Payload is then bounded by the window, not by the message.
Wire format
| Key | Meaning |
|---|---|
seq |
the sequence number this manifest describes |
cutoff |
how many sealed blocks fall behind the window |
rollup |
one digest covering every block behind the cutoff |
blocks |
[[id, digest], …] for the blocks inside the window |
Instance Attribute Summary collapse
-
#blocks ⇒ Object
readonly
Every sealed Block, in order — including the ones behind the window, which #rollup covers and #windowed_entries omits.
-
#seq ⇒ Object
readonly
The sequence number this manifest describes.
-
#window ⇒ Object
readonly
How many sealed blocks are listed in full.
Class Method Summary collapse
-
.for(record, config: MaquinaStream.config, window: nil, full: false) ⇒ Object
Builds the manifest for a record, over its current buffer.
Instance Method Summary collapse
-
#cutoff ⇒ Object
How many entries fall behind the window and are covered by #rollup instead of being listed.
-
#diff(client_digests) ⇒ Object
Which of the client's blocks disagree with ours, within the window.
-
#entries ⇒ Object
Every sealed block, id and digest.
-
#initialize(seq:, blocks:, window: 50) ⇒ Manifest
constructor
Builds a manifest over a list of sealed blocks.
-
#rollup ⇒ Object
One digest covering every block older than the window.
-
#stale_history?(client_rollup) ⇒ Boolean
True when the client's view of the history behind the window disagrees with ours, and the windowed diff is therefore not enough.
-
#to_h ⇒ Object
The wire format, as a Hash.
-
#to_json(*args) ⇒ Object
The wire format, as JSON.
-
#windowed_entries ⇒ Object
The entries actually sent: everything from #cutoff onwards.
Constructor Details
#initialize(seq:, blocks:, window: 50) ⇒ Manifest
Builds a manifest over a list of sealed blocks. .for is the usual
entry point.
77 78 79 80 81 |
# File 'lib/maquina_stream/manifest.rb', line 77 def initialize(seq:, blocks:, window: 50) @seq = seq @blocks = blocks @window = window end |
Instance Attribute Details
#blocks ⇒ Object (readonly)
Every sealed Block, in order — including the ones behind the window, which #rollup covers and #windowed_entries omits.
48 49 50 |
# File 'lib/maquina_stream/manifest.rb', line 48 def blocks @blocks end |
#seq ⇒ Object (readonly)
The sequence number this manifest describes.
44 45 46 |
# File 'lib/maquina_stream/manifest.rb', line 44 def seq @seq end |
#window ⇒ Object (readonly)
How many sealed blocks are listed in full. Everything older is covered by
#rollup. Float::INFINITY when the manifest was built with full: true.
52 53 54 |
# File 'lib/maquina_stream/manifest.rb', line 52 def window @window end |
Class Method Details
.for(record, config: MaquinaStream.config, window: nil, full: false) ⇒ Object
Builds the manifest for a record, over its current buffer.
full: true drops the window and lists every sealed block — what a
client asks for when its #rollup disagrees with ours and the windowed
diff is therefore not enough. Rare, and no more expensive than the cold
page load it resembles.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/maquina_stream/manifest.rb', line 60 def self.for(record, config: MaquinaStream.config, window: nil, full: false) document = Document.new( record.maquina_stream_buffer, config: config, sid: record.maquina_stream_id, mode: record.maquina_stream_open? ? :streaming : :static ) new( seq: record.maquina_stream_sequence, blocks: document.sealed_blocks, window: full ? Float::INFINITY : (window || config.manifest_window) ) end |
Instance Method Details
#cutoff ⇒ Object
How many entries fall behind the window and are covered by #rollup instead of being listed. Zero for a full manifest.
90 91 92 93 94 |
# File 'lib/maquina_stream/manifest.rb', line 90 def cutoff return 0 if window.infinite? [entries.length - window, 0].max end |
#diff(client_digests) ⇒ Object
Which of the client's blocks disagree with ours, within the window.
Blocks the client has and we do not are not reported: the server is the authority on what exists, and a stale block is removed by the morph rather than by a separate instruction.
123 124 125 126 127 128 129 |
# File 'lib/maquina_stream/manifest.rb', line 123 def diff(client_digests) client = client_digests.to_h windowed_entries.filter_map do |id, digest| id unless client[id] == digest end end |
#entries ⇒ Object
Every sealed block, id and digest. The wire format sends a slice of this.
84 85 86 |
# File 'lib/maquina_stream/manifest.rb', line 84 def entries blocks.map(&:to_manifest_entry) end |
#rollup ⇒ Object
One digest covering every block older than the window. Order matters: two clients holding the same blocks in a different order are not in the same state.
104 105 106 |
# File 'lib/maquina_stream/manifest.rb', line 104 def rollup Digest::SHA256.hexdigest(entries.take(cutoff).flatten.join(" "))[0, 16] end |
#stale_history?(client_rollup) ⇒ Boolean
True when the client's view of the history behind the window disagrees with ours, and the windowed diff is therefore not enough.
133 134 135 |
# File 'lib/maquina_stream/manifest.rb', line 133 def stale_history?(client_rollup) cutoff.positive? && client_rollup != rollup end |
#to_h ⇒ Object
The wire format, as a Hash. What the manifest endpoint renders.
109 110 111 |
# File 'lib/maquina_stream/manifest.rb', line 109 def to_h {seq: seq, cutoff: cutoff, rollup: rollup, blocks: windowed_entries} end |
#to_json(*args) ⇒ Object
The wire format, as JSON.
114 115 116 |
# File 'lib/maquina_stream/manifest.rb', line 114 def to_json(*args) to_h.to_json(*args) end |
#windowed_entries ⇒ Object
The entries actually sent: everything from #cutoff onwards.
97 98 99 |
# File 'lib/maquina_stream/manifest.rb', line 97 def windowed_entries entries.drop(cutoff) end |