document the source chooser
Both files, per this project's rule: the probe entries and the two invariants in CLAUDE.md, the feature and why the list is filtered in README.
This commit is contained in:
1 parent
54e840e590
commit
b8d0ce8625
2 files changed
+63
No files matched your search
@@ -17,6 +17,9 @@ Options: `--server`, `--port`, `--multicast`, `--interface`, `--buffer-mb` (per
|
|||||||
buffer, default 200), `--max-window`, `--holdoff`, `--recv-buffer`, `--plots` (the row
|
buffer, default 200), `--max-window`, `--holdoff`, `--recv-buffer`, `--plots` (the row
|
||||||
count to *start* with, 1..`RowSet::kMaxRows`; the layout editor owns it afterwards).
|
count to *start* with, 1..`RowSet::kMaxRows`; the layout editor owns it afterwards).
|
||||||
|
|
||||||
|
Run with no `--server` and udpscope asks which source to open: the sessions it has stored, plus a
|
||||||
|
form for a new host. Every probe below passes `--server`, so none of them sees it.
|
||||||
|
|
||||||
`deps/plotter` is built from source with only the GTK4 adapter enabled
|
`deps/plotter` is built from source with only the GTK4 adapter enabled
|
||||||
(`PLOT_BUILD_GTK4`); its tests and examples are forced off. `deps/udpsclient` is a
|
(`PLOT_BUILD_GTK4`); its tests and examples are forced off. `deps/udpsclient` is a
|
||||||
vendored C99 source pair compiled into a static lib.
|
vendored C99 source pair compiled into a static lib.
|
||||||
@@ -445,6 +448,22 @@ Two lines of the self-check output are assertions in disguise:
|
|||||||
substitutes a missing-image placeholder, the button still works, and every other line of the
|
substitutes a missing-image placeholder, the button still works, and every other line of the
|
||||||
dump still passes. Changing an icon name is a one-line change in `build_toolbar()` and this
|
dump still passes. Changing an icon name is a one-line change in `build_toolbar()` and this
|
||||||
is the only check that it names something that exists.
|
is the only check that it names something that exists.
|
||||||
|
- `[sources] N known: HOST:PORT plots=P signals=S [multicast=G] [interface=I] | ...` (stderr,
|
||||||
|
`UDPSCOPE_PROBE_SOURCES=1`) — the startup chooser's discovery, printed and exited **before
|
||||||
|
`gtk_init()`**, so this one needs no display and no `xvfb-run`. **Only sources with a named
|
||||||
|
signal may appear**: a development machine accumulates one session file per probe run on a
|
||||||
|
throwaway port (140 of them when this was written), and the filter in `known_sources()` is
|
||||||
|
what keeps the list the sources a user configured. Order is newest first by file mtime.
|
||||||
|
- `[sources] picked HOST:PORT multicast=G interface=I` (stderr, `UDPSCOPE_PROBE_PICK_MS`) —
|
||||||
|
shows the chooser, then activates row 0 the way a click does and continues into the real
|
||||||
|
application, so a full run with **no `--server`** must reach the scope and emit every other
|
||||||
|
dump line unchanged. `multicast=`/`interface=` coming back non-empty from a stored row is the
|
||||||
|
assertion that a multicast session reconnects to its group, and it needs **no multicast
|
||||||
|
networking**: write the session file by hand and read the line. With
|
||||||
|
`UDPSCOPE_PROBE_NEWHOST=host:port` it drives the form instead — `[sources] form open,
|
||||||
|
connect_sensitive=1` then a `picked` line, with no SIGSEGV, which is as much a crash test of a
|
||||||
|
dialog built and destroyed as it is a state check. An empty list must print `picked nothing`
|
||||||
|
rather than dereferencing a null row.
|
||||||
- **The session layout round trip** (`~/.config/udpscope/<host>_<port>.toml`) — run once
|
- **The session layout round trip** (`~/.config/udpscope/<host>_<port>.toml`) — run once
|
||||||
with a drop and a window change, then again with no interaction: the second run's dump
|
with a drop and a window change, then again with no interaction: the second run's dump
|
||||||
must read `layout loaded from ...` and `layout: rows=R window_s=W row0=[...]` with the
|
must read `layout loaded from ...` and `layout: rows=R window_s=W row0=[...]` with the
|
||||||
@@ -1234,4 +1253,24 @@ far the largest file.
|
|||||||
`gtk_icon_theme_has_icon()` is checked for every toolbar button and reported by the dump (see
|
`gtk_icon_theme_has_icon()` is checked for every toolbar button and reported by the dump (see
|
||||||
`toolbar icons:` under Verification): a resource path that was never added does not raise an
|
`toolbar icons:` under Verification): a resource path that was never added does not raise an
|
||||||
error, it draws `image-missing`.
|
error, it draws `image-missing`.
|
||||||
|
- **The chooser runs before `ScopeModel` exists, and `--server` is what decides.** `Config` carries
|
||||||
|
`server_specified`, set only by the `--server` branch, and `main()` asks only when it is false —
|
||||||
|
so declining costs no buffer and no socket, and `ScopeModel`, `Acquisition`, `RowSet` and
|
||||||
|
`run_ui()` need no notion of "no source yet". It is `--server` alone and not "no arguments":
|
||||||
|
`--multicast` still needs a server to fetch CONFIG from. **Every documented probe passes
|
||||||
|
`--server`**, which is what makes the feature invisible to the regression net — a probe added
|
||||||
|
without it would reach the chooser and hang. `gtk_init()` plus a nested `GMainLoop`, and then
|
||||||
|
`run_ui()`'s `GtkApplication` afterwards in the same process, is a verified combination
|
||||||
|
(`gtk_init()` is idempotent; running your own loop is the documented GTK4 replacement for the
|
||||||
|
removed `gtk_dialog_run()`).
|
||||||
|
- **A session file's name cannot say how its source was reached, so the file says it.**
|
||||||
|
`<host>_<port>.toml` carries neither the multicast group nor the interface, so a chooser row
|
||||||
|
could not reconnect to a multicast source at all. `LayoutState::multicast`/`interface_addr` are
|
||||||
|
written in the **top block, before the first `[[table]]`** — a bare key after a table header is
|
||||||
|
that table's key in TOML, which is exactly how `enabled` was parsed into the last
|
||||||
|
`[[calibration]]` record and lost without a word. A file from an older build has neither key and
|
||||||
|
loads as unicast on the default route. The chooser's list filters to sessions that **name at
|
||||||
|
least one signal**, because a probe run leaves a file per throwaway port and all of them hold
|
||||||
|
`signals = ["", "", "", ""]`; `known_sources()` splits the filename at the **last** `_`, since
|
||||||
|
`sanitize()` turns anything unsafe in a host into one.
|
||||||
- Every source file carries an SPDX MIT header; match it in new files.
|
- Every source file carries an SPDX MIT header; match it in new files.
|
||||||
@@ -458,6 +458,30 @@ a math channel's name and that name only exists as a signal once its graph is in
|
|||||||
reopened on the same source comes back as the one that was closed — including a signal that was
|
reopened on the same source comes back as the one that was closed — including a signal that was
|
||||||
enabled only to feed a math channel.
|
enabled only to feed a math channel.
|
||||||
|
|
||||||
|
**Launched with no `--server`, udpscope asks which source to open.** It used to connect to
|
||||||
|
`127.0.0.1:8000` because that is the default in `Config` — fine for one streamer, useless for
|
||||||
|
several, and there was no way to see which sources the app already knew about. The chooser lists
|
||||||
|
one row per stored session: the server and port it was named after, how many plots and signals it
|
||||||
|
held, and its multicast group when it had one, newest first. `New host…` is a four-field form —
|
||||||
|
server, port, multicast group, interface — and nothing more: `--buffer-mb` and the rest are tuning
|
||||||
|
rather than "which source", and a form that asks for them is a form you have to read before using
|
||||||
|
the app. Cancel exits before anything is allocated, because declining to choose a source is not a
|
||||||
|
failure.
|
||||||
|
|
||||||
|
It runs **before the model exists**, which is the whole reason it is cheap: no buffer, no socket
|
||||||
|
and no window until a source is known, and `ScopeModel`, `Acquisition` and `run_ui()` are
|
||||||
|
untouched by the feature. Every headless probe passes `--server`, so none of them reaches it.
|
||||||
|
|
||||||
|
Two things the list had to get right. A session file is named `<host>_<port>.toml` and that name
|
||||||
|
cannot say whether the source was multicast or which interface it joined on, so the file now
|
||||||
|
records both — in the top block, before the first table, because a bare key written after a
|
||||||
|
`[[table]]` header belongs to that table in TOML, which is how the `enabled` key was silently lost
|
||||||
|
once already. And the list is **filtered to sessions that name a signal**: a development machine
|
||||||
|
accumulates one file per probe run on a throwaway port — 140 of them here, every one with
|
||||||
|
`signals = ["", "", "", ""]` — so an unfiltered chooser opens as a wall of ports nobody chose. A
|
||||||
|
row's `×` forgets a source by deleting its session file, with no confirmation: the gesture is the
|
||||||
|
confirmation, and what it destroys is one layout the app rewrites on the next clean exit.
|
||||||
|
|
||||||
**Math channels.** A math channel is derived from the others by a graph of operations and is then
|
**Math channels.** A math channel is derived from the others by a graph of operations and is then
|
||||||
an *ordinary channel*: the model synthesises a signal entry for it, so an enabled one is acquired,
|
an *ordinary channel*: the model synthesises a signal entry for it, so an enabled one is acquired,
|
||||||
plotted, measured, exported and listed in the signal panel exactly like a streamed signal — and
|
plotted, measured, exported and listed in the signal panel exactly like a streamed signal — and
|
||||||
|
|||||||
Reference in new issue
Block a user