plot host: rows hold 1..4 cells; split probe scaffold
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
79e69c28fa
commit
ae1d262be4
5 files changed
+433
-18
No files matched your search
@@ -3796,6 +3796,219 @@ gboolean layout_probe_step(gpointer user) {
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
// --- the horizontal plot split (`UDPSCOPE_PROBE_SPLIT_MS`) -------------------------------------
|
||||
//
|
||||
// Each step changes the arrangement of cells, re-points the area at the new figure, and lets the
|
||||
// next step (250 ms on) read what a frame was actually laid out with.
|
||||
|
||||
void split_probe_report(App& a, const char* tag) {
|
||||
RowSet& rs = *a.rows;
|
||||
const double scale = (a.area != nullptr && a.area->device_scale() > 0.0f)
|
||||
? static_cast<double>(a.area->device_scale()) : 1.0;
|
||||
const plot::FigureLayout& fl = rs.figure().layout();
|
||||
const int frows = rs.figure_row_count();
|
||||
// tile: every row's cells fill its rect edge to edge, share its y and height, and are one
|
||||
// `col_gap` apart.
|
||||
int tile = fl.cells.size() == static_cast<std::size_t>(rs.row_count()) &&
|
||||
fl.rows.size() == static_cast<std::size_t>(frows)
|
||||
? 1 : 0;
|
||||
std::string cells_s;
|
||||
std::string widths_s;
|
||||
std::string weights_s;
|
||||
for (int fr = 0; fr < frows && tile; ++fr) {
|
||||
const int first = rs.first_plot_of_row(fr);
|
||||
const int n = rs.cells_in_row(fr);
|
||||
const plot::Rect& row = fl.rows[static_cast<std::size_t>(fr)];
|
||||
const plot::Rect& c0 = fl.cells[static_cast<std::size_t>(first)];
|
||||
const plot::Rect& cn = fl.cells[static_cast<std::size_t>(first + n - 1)];
|
||||
if (std::fabs(c0.x - row.x) > 0.5f) tile = 0;
|
||||
if (std::fabs(cn.right() - row.right()) > 0.5f) tile = 0;
|
||||
for (int k = 0; k < n; ++k) {
|
||||
const plot::Rect& c = fl.cells[static_cast<std::size_t>(first + k)];
|
||||
if (std::fabs(c.y - row.y) > 0.5f || std::fabs(c.h - row.h) > 0.5f) tile = 0;
|
||||
if (k > 0) {
|
||||
const plot::Rect& prev = fl.cells[static_cast<std::size_t>(first + k - 1)];
|
||||
if (std::fabs((c.x - prev.right()) - fl.col_gap) > 0.5f) tile = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int fr = 0; fr < frows; ++fr) {
|
||||
const int first = rs.first_plot_of_row(fr);
|
||||
const int n = rs.cells_in_row(fr);
|
||||
cells_s += (fr == 0 ? "" : ",") + std::to_string(n);
|
||||
widths_s += fr == 0 ? "" : "|";
|
||||
weights_s += fr == 0 ? "" : "|";
|
||||
for (int k = 0; k < n; ++k) {
|
||||
char buf[32];
|
||||
const std::size_t idx = static_cast<std::size_t>(first + k);
|
||||
std::snprintf(buf, sizeof(buf), "%s%.0f", k == 0 ? "" : ",",
|
||||
idx < fl.cells.size() ? static_cast<double>(fl.cells[idx].w) / scale : -1.0);
|
||||
widths_s += buf;
|
||||
}
|
||||
const std::vector<float>& w = rs.cell_weights(fr);
|
||||
if (w.empty()) {
|
||||
weights_s += "-";
|
||||
} else {
|
||||
for (std::size_t k = 0; k < w.size(); ++k) {
|
||||
char buf[32];
|
||||
std::snprintf(buf, sizeof(buf), "%s%g", k == 0 ? "" : ",", static_cast<double>(w[k]));
|
||||
weights_s += buf;
|
||||
}
|
||||
}
|
||||
}
|
||||
// x_equal: every plot's x range is plot 0's. cursors_aligned: every plot's two vertical cursors
|
||||
// sit where plot 0's do.
|
||||
int x_equal = 1;
|
||||
int aligned = 1;
|
||||
const RowSet::Row& r0 = rs.row(0);
|
||||
if (r0.plot != nullptr) {
|
||||
const plot::Range ref = r0.plot->x_range(r0.axis_x);
|
||||
const double ref_a = r0.plot->cursor(r0.cursor_a).x;
|
||||
const double ref_b = r0.plot->cursor(r0.cursor_b).x;
|
||||
const double span = std::max(1e-12, std::fabs(ref.max - ref.min));
|
||||
for (int p = 1; p < rs.row_count(); ++p) {
|
||||
const RowSet::Row& r = rs.row(p);
|
||||
if (r.plot == nullptr) { x_equal = 0; aligned = 0; continue; }
|
||||
const plot::Range x = r.plot->x_range(r.axis_x);
|
||||
if (std::fabs(x.min - ref.min) > 1e-9 * span || std::fabs(x.max - ref.max) > 1e-9 * span) {
|
||||
x_equal = 0;
|
||||
}
|
||||
if (std::fabs(r.plot->cursor(r.cursor_a).x - ref_a) > 1e-9 * span) aligned = 0;
|
||||
if (std::fabs(r.plot->cursor(r.cursor_b).x - ref_b) > 1e-9 * span) aligned = 0;
|
||||
}
|
||||
}
|
||||
std::string traces;
|
||||
for (int p = 0; p < rs.row_count(); ++p) {
|
||||
traces += p == 0 ? "" : "|";
|
||||
traces += rs.plot_label(p) + ":";
|
||||
bool any = false;
|
||||
const RowSet::Row& row = rs.row(p);
|
||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) {
|
||||
const std::string& n = row.signal_name[static_cast<std::size_t>(s)];
|
||||
if (n.empty()) continue;
|
||||
traces += (any ? "," : "") + n;
|
||||
any = true;
|
||||
}
|
||||
if (!any) traces += "-";
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"[split-probe] %-14s plots=%d frows=%d cells=[%s] widths=[%s] tile=%d x_equal=%d "
|
||||
"cursors_aligned=%d weights=[%s] traces=[%s]\n",
|
||||
tag, rs.row_count(), frows, cells_s.c_str(), widths_s.c_str(), tile, x_equal,
|
||||
aligned, weights_s.c_str(), traces.c_str());
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// After any structural change: the new figure is the area's, and the frame tick re-lays it out.
|
||||
void split_probe_restructured(App& a) {
|
||||
if (a.area != nullptr) a.area->set_figure(&a.rows->figure());
|
||||
a.relayout_pending = true;
|
||||
}
|
||||
|
||||
/// Channel `c` onto plot `p` (the first free slot), by the same name/colour route a drop takes.
|
||||
void split_probe_assign(App& a, int p, std::size_t c) {
|
||||
if (c >= a.model->channel_count() || p < 0 || p >= a.rows->row_count()) return;
|
||||
const ChannelInfo& ch = a.model->channels()[c];
|
||||
a.rows->assign(p, 0, ch.name, ch.name, signal_color_index(a.model->signals(), ch.signal));
|
||||
}
|
||||
|
||||
void split_probe_clear_all(App& a) {
|
||||
for (int r = 0; r < RowSet::kMaxRows; ++r) {
|
||||
for (int s = 0; s < RowSet::kSlotsPerRow; ++s) a.rows->assign(r, s, "", "", 0);
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void split_probe_next(App& a) {
|
||||
++a.dbg_split_step;
|
||||
g_timeout_add(250, split_probe_step, &a);
|
||||
}
|
||||
|
||||
gboolean split_probe_step(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr || a->rows == nullptr) return G_SOURCE_REMOVE;
|
||||
RowSet& rs = *a->rows;
|
||||
switch (a->dbg_split_step) {
|
||||
case 0: {
|
||||
rs.set_cells_per_row({1});
|
||||
split_probe_clear_all(*a);
|
||||
// The `enable_all_probe` assignment, in one call and onto plot 0's slots.
|
||||
std::vector<std::size_t> all(a->model->signals().size());
|
||||
for (std::size_t i = 0; i < all.size(); ++i) all[i] = i;
|
||||
a->model->set_signals_enabled(all, true);
|
||||
int slot = 0;
|
||||
for (std::size_t c = 0; c < a->model->channel_count() && slot < RowSet::kSlotsPerRow; ++c) {
|
||||
const ChannelInfo& ch = a->model->channels()[c];
|
||||
rs.assign(0, slot++, ch.name, ch.name, signal_color_index(a->model->signals(), ch.signal));
|
||||
}
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
}
|
||||
case 1:
|
||||
split_probe_report(*a, "start");
|
||||
rs.insert_cell(0, 1);
|
||||
split_probe_clear_all(*a);
|
||||
split_probe_assign(*a, 0, 0);
|
||||
split_probe_assign(*a, 1, 1);
|
||||
rs.set_cursors_visible(true);
|
||||
rs.reset_cursors();
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
case 2:
|
||||
split_probe_report(*a, "split");
|
||||
rs.set_cell_weights(0, {3.0f, 1.0f});
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
case 3:
|
||||
split_probe_report(*a, "weights 3:1");
|
||||
rs.insert_row(1);
|
||||
rs.insert_cell(1, 1);
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
case 4: {
|
||||
split_probe_report(*a, "row+cell");
|
||||
// Fill row 0 to its cap, then keep adding rows to the plot cap; the first refusal of each kind
|
||||
// is what is being read.
|
||||
std::string cell_calls;
|
||||
int refused_cell = 0;
|
||||
for (int i = 0; i < RowSet::kMaxCellsPerRow + 1; ++i) {
|
||||
const bool ok = rs.insert_cell(0, rs.cells_in_row(0));
|
||||
cell_calls += (i == 0 ? "" : ",") + std::to_string(ok ? 1 : 0);
|
||||
if (!ok) { ++refused_cell; break; }
|
||||
}
|
||||
std::string row_calls;
|
||||
int refused_row = 0;
|
||||
for (int i = 0; i < RowSet::kMaxRows; ++i) {
|
||||
const bool ok = rs.insert_row(rs.figure_row_count());
|
||||
row_calls += (i == 0 ? "" : ",") + std::to_string(ok ? 1 : 0);
|
||||
if (!ok) { ++refused_row; break; }
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"[split-probe] caps-calls insert_cell=[%s] refused_cell=%d insert_row=[%s] "
|
||||
"refused_row=%d plots=%d\n",
|
||||
cell_calls.c_str(), refused_cell, row_calls.c_str(), refused_row, rs.row_count());
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
split_probe_report(*a, "caps");
|
||||
rs.remove_cell(rs.first_plot_of_row(0) + rs.cells_in_row(0) - 1);
|
||||
split_probe_restructured(*a);
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
default:
|
||||
split_probe_report(*a, "remove");
|
||||
break;
|
||||
}
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean enable_all_probe(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
// In one call, the way a restored session does it: enabling them one at a time re-plans
|
||||
|
||||
Reference in new issue
Block a user