Push a frame instead of storing and fetching it
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.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YC4u66vjzW54fnHu5Juhh9
This commit is contained in:
+12
-2
@@ -137,7 +137,8 @@ def process(speech): # an `audio` port
|
||||
def process(camera_url):
|
||||
for index, jpeg in enumerate(grab(camera_url)): # a generator
|
||||
frame = fluksio.save_artifact(
|
||||
jpeg, f"frame-{index:05d}.jpg", media_type="image/jpeg"
|
||||
jpeg, f"frame-{index:05d}.jpg",
|
||||
media_type="image/jpeg", volatile=True,
|
||||
)
|
||||
frame["meta"] = {"seq": index}
|
||||
yield {"frame": frame} # an `image` stream port
|
||||
@@ -148,12 +149,21 @@ has to match, so a node declaring `audio` never receives a video by accident.
|
||||
See [Payload types](../reference/payload-types.md#image-audio-video) for what
|
||||
each carries and what rates are realistic.
|
||||
|
||||
`volatile=True` is for a frame rather than a result. The bytes go to a ring in
|
||||
memory instead of the data volume, and the engine pushes them down the
|
||||
websocket to whichever screens are drawing that message — so a camera runs at
|
||||
ten frames a second without writing anything to disk. They last as long as it
|
||||
takes newer frames to need the room. Leave it off for a clip somebody asked to
|
||||
keep.
|
||||
|
||||
!!! warning "Emitted media is not kept; returned media is"
|
||||
|
||||
Only what a node *returns* is recorded against its run. Frames yielded
|
||||
along the way are replaced in state by the next one, and the artifact sweep
|
||||
removes bytes nothing refers to any more, which is what stops a camera
|
||||
filling the disk. If a particular frame matters, return it.
|
||||
filling the disk. If a particular frame matters, return it: a volatile one
|
||||
is copied out of the ring when the run records it, so returning it is also
|
||||
what makes it outlive the next few seconds.
|
||||
|
||||
## Printing
|
||||
|
||||
|
||||
@@ -141,6 +141,14 @@ filesystem writes to it directly; one that does not fetches and uploads over
|
||||
HTTP, using the artifact endpoint beside the socket it already has. Either way
|
||||
your node code is the same two calls.
|
||||
|
||||
A fetch is cached on the worker by digest, since content addressing means an
|
||||
entry is never stale. Nothing expires on its own, so the cache is bounded by
|
||||
size and the oldest fall out: `FLUKSIO_ARTIFACT_CACHE` says where it lives
|
||||
(default a directory in the temporary directory) and
|
||||
`FLUKSIO_ARTIFACT_CACHE_BYTES` how much it holds (default 1 GiB). Worth raising
|
||||
where a worker reads the same large inputs repeatedly, and worth leaving alone
|
||||
where it reads a media stream — those are chunks nothing asks for twice.
|
||||
|
||||
## Seeing what is attached
|
||||
|
||||
```sh
|
||||
|
||||
Reference in New Issue
Block a user