udpscope: an oscilloscope view for a UDPStreamer source
GTK4 and libadwaita. It receives one UDP stream through the vendored udpsclient, buffers it in a large ring, and draws it with the vendored plotter: a trigger with a pre/post event window, a pause that freezes the window, cursors, measurements, math channels in a node editor, an event cache, named configurations, preferences, and an export of the visible window to .npy/.csv. Waveform files can be opened and analysed in place. The interface is two threads -- a producer that owns the socket and the GTK main thread that owns every widget and the GL context -- sharing one model under one mutex, with samples crossing through lock-free rings. deps/plotter is a submodule pinned to the commit this tree builds against; deps/udpsclient is vendored in place. docs/claude/ documents the rules that cost a defect to learn, and SPECS.md is the requirement document.
This commit is contained in:
1 parent
8f54841c2c
commit
0e256764d5
41 files changed
+2081
-154
No files matched your search
@@ -526,6 +526,13 @@ double ScopeModel::channel_dt(std::size_t channel) const {
|
||||
BufferStore& ScopeModel::live() { return *store_; }
|
||||
|
||||
void ScopeModel::live_window_locked(double lo_s, double hi_s, u64& first, u64& last) {
|
||||
// There may be **no store at all**: `rebuild_locked()` resets it for a stream with no channels, which
|
||||
// is what the model is in until a CONFIG arrives. `live()` dereferences the pointer, so every path
|
||||
// that can be reached without a stream has to ask first -- pausing a scope that is not connected did
|
||||
// not, and walked into this function through `publish_pause_view()`, a null dereference.
|
||||
first = 0;
|
||||
last = 0;
|
||||
if (store_ == nullptr) return;
|
||||
BufferStore& src = live();
|
||||
plot::data::RingBuffer<f64>& tsrc = src.time();
|
||||
|
||||
@@ -578,6 +585,10 @@ void ScopeModel::live_window_locked(double lo_s, double hi_s, u64& first, u64& l
|
||||
}
|
||||
|
||||
void ScopeModel::publish_pause_view(double lo_s, double hi_s, double x_offset) {
|
||||
// Nothing has arrived yet, so there is nothing to freeze: pausing a scope that is not connected is a
|
||||
// legitimate gesture (the button works), and it must not take a window from a store that does not
|
||||
// exist.
|
||||
if (store_ == nullptr) return;
|
||||
// Timed from before the lock: the wait for it is part of what freezing a view costs the thread
|
||||
// doing it, and this one is the UI's.
|
||||
const std::uint64_t t0 = now_ns();
|
||||
|
||||
Reference in new issue
Block a user