The rate the media dtypes could carry was one frame every second or two: each
was a file on the data volume, an event on the socket, and a request back for
the bytes. This closes both halves of that, and they are one feature.
`save_artifact(..., volatile=True)` writes to a `VolatileStore` — the same
content-addressed store, in `/dev/shm`, bounded by size with the oldest falling
out (`ARTIFACT_VOLATILE_BYTES`, 48 MB under the container's raised `shm_size`).
Nothing sweeps it: a frame nobody kept is not worth walking the store to find.
`ArtifactStore.path` falls through to it, which is what lets a volatile frame be
an ordinary reference everywhere else — the dtype check, a panel's digest scope,
`load_artifact` in a node, and the widget's own fetch all work on one unchanged.
`adopt` copies one into the store when a run records it, so "returned media is
kept, emitted media is not" stays true.
The bytes then go down the flows websocket as a length-prefixed binary frame,
sent just ahead of the `message_value` naming them, so a tile has the frame when
it hears the value moved. Nothing is pushed unasked: a client names the messages
it is drawing (`{"type":"media","names":[…]}`), a panel's list is intersected
with the scope it already had, and only the newest frame per name in a batch is
sent — a client that fell behind is not handed frames it would draw over. The
tunnel relays text only, so a screen reached through a portal falls back to
fetching, which is why the rate table now has two rows.
Around the edges: the remote worker's fetch cache is bounded at last
(`FLUKSIO_ARTIFACT_CACHE_BYTES`), since content addressing means nothing in it
ever expires and a media stream fills it with chunks nothing asks for twice; a
port carrying an image draws the frame in the node panel rather than only
saying `image/png · frame.png · 1.79kB`; and an edge chip says that much instead
of a line of hash. The media screenshot stops waiting for `networkidle` — a
camera is a socket that never goes quiet, which is the point of it.
64 lines
2.0 KiB
TypeScript
64 lines
2.0 KiB
TypeScript
/**
|
|
* The framing a pushed frame arrives in, and what a malformed one must do.
|
|
*
|
|
* Run: `bun src/lib/media.check.ts` (there is no unit runner; the suite in
|
|
* `tests/` drives a running stack).
|
|
*/
|
|
import assert from "node:assert/strict"
|
|
|
|
import { describeArtifact, isRef, parseMediaFrame } from "./media"
|
|
|
|
function frame(header: object, payload: Uint8Array): ArrayBuffer {
|
|
const encoded = new TextEncoder().encode(JSON.stringify(header))
|
|
const out = new Uint8Array(4 + encoded.length + payload.length)
|
|
new DataView(out.buffer).setUint32(0, encoded.length)
|
|
out.set(encoded, 4)
|
|
out.set(payload, 4 + encoded.length)
|
|
return out.buffer
|
|
}
|
|
|
|
const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47])
|
|
const parsed = parseMediaFrame(
|
|
frame(
|
|
{
|
|
type: "media",
|
|
name: "cam.frame",
|
|
digest: `sha256:${"a".repeat(64)}`,
|
|
media_type: "image/png",
|
|
ts: 1,
|
|
},
|
|
bytes,
|
|
),
|
|
)
|
|
assert.ok(parsed)
|
|
assert.equal(parsed.header.name, "cam.frame")
|
|
assert.equal(parsed.header.media_type, "image/png")
|
|
assert.equal(parsed.bytes.size, 4)
|
|
assert.equal(parsed.bytes.type, "image/png")
|
|
|
|
// A frame the client cannot read is dropped, never thrown on: this runs on
|
|
// every socket message.
|
|
assert.equal(parseMediaFrame(new ArrayBuffer(0)), null)
|
|
assert.equal(parseMediaFrame(new ArrayBuffer(2)), null)
|
|
assert.equal(parseMediaFrame(frame({ name: "n" }, bytes)), null)
|
|
// A length longer than the frame is truncation, not a payload.
|
|
const short = new Uint8Array(8)
|
|
new DataView(short.buffer).setUint32(0, 999)
|
|
assert.equal(parseMediaFrame(short.buffer), null)
|
|
|
|
assert.ok(isRef({ digest: `sha256:${"b".repeat(64)}` }))
|
|
assert.equal(isRef({ digest: "nope" }), false)
|
|
assert.equal(isRef(null), false)
|
|
|
|
assert.equal(
|
|
describeArtifact({ media_type: "image/png", name: "f.png", size: 48000 }),
|
|
"image/png · f.png · 48kB",
|
|
)
|
|
// No media type is still worth summarising as something.
|
|
assert.equal(
|
|
describeArtifact({ media_type: "application/octet-stream", size: 12 }),
|
|
"artifact · 12B",
|
|
)
|
|
|
|
console.log("media.check.ts ok")
|