something

This commit is contained in:
2026-06-12 22:12:26 +02:00
parent e4fea66df6
commit d8057a47d2
3 changed files with 324 additions and 27 deletions
Generated
+57
View File
@@ -30,6 +30,8 @@ version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5351dcebb14b579ccab05f288596b2ae097005be7ee50a7c3d4ca9d0d5a66f6a"
dependencies = [
"enumn",
"serde",
"uuid",
]
@@ -225,6 +227,7 @@ dependencies = [
"cfg-if",
"getrandom 0.3.4",
"once_cell",
"serde",
"version_check",
"zerocopy",
]
@@ -1449,6 +1452,7 @@ checksum = "55f6cc0cb3b84a21232c468db972ebcddd34decbf1ff02cdebffd807c13bbd81"
dependencies = [
"bytemuck",
"emath",
"serde",
]
[[package]]
@@ -1466,6 +1470,7 @@ dependencies = [
"egui_glow",
"glutin",
"glutin-winit",
"home",
"image",
"js-sys",
"log",
@@ -1477,6 +1482,8 @@ dependencies = [
"pollster",
"profiling",
"raw-window-handle",
"ron",
"serde",
"static_assertions",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -1501,6 +1508,8 @@ dependencies = [
"log",
"nohash-hasher",
"profiling",
"ron",
"serde",
"smallvec",
"unicode-segmentation",
]
@@ -1541,6 +1550,7 @@ dependencies = [
"objc2-ui-kit 0.3.2",
"profiling",
"raw-window-handle",
"serde",
"smithay-clipboard",
"web-time",
"webbrowser",
@@ -1597,6 +1607,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a74fbbf7501c430b89df62d102b6bfa02162faaf3e155512c677c9d20f5708d1"
dependencies = [
"bytemuck",
"serde",
]
[[package]]
@@ -1635,6 +1646,17 @@ dependencies = [
"syn",
]
[[package]]
name = "enumn"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "epaint"
version = "0.34.2"
@@ -1652,6 +1674,7 @@ dependencies = [
"parking_lot",
"profiling",
"self_cell",
"serde",
"skrifa",
"smallvec",
"vello_cpu",
@@ -1868,6 +1891,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b38ad915f6dadd993ced50848a8291a543bd41ca62bc10740d5e64e2ab4cfd7"
dependencies = [
"bytemuck",
"serde",
]
[[package]]
@@ -2360,6 +2384,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "home"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "htmlparser"
version = "0.2.1"
@@ -4819,6 +4852,20 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "ron"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4147b952f3f819eca0e99527022f7d6a8d05f111aeb0a62960c74eb283bec8fc"
dependencies = [
"bitflags 2.11.1",
"once_cell",
"serde",
"serde_derive",
"typeid",
"unicode-ident",
]
[[package]]
name = "rowan"
version = "0.16.1"
@@ -5216,6 +5263,9 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
dependencies = [
"serde",
]
[[package]]
name = "smithay-client-toolkit"
@@ -5461,6 +5511,7 @@ dependencies = [
"egui_plot",
"fuzzy-matcher",
"libsvld",
"rfd",
]
[[package]]
@@ -5839,6 +5890,12 @@ dependencies = [
"serde",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.20.0"
+5 -1
View File
@@ -12,8 +12,12 @@ bench = false
libsvld = { path = "../libsvld" }
can-dbc = "9.1"
eframe = "0.34"
eframe = {
version = "0.34",
features = ["persistence"]
}
egui = "0.34"
egui_plot = "0.35"
egui_ltreeview = "0.7"
fuzzy-matcher = "0.3"
rfd = "0.17"
+262 -26
View File
@@ -3,7 +3,13 @@ mod plot_list;
mod signal;
mod tree;
use std::{fs, path::Path};
use std::{
collections::HashMap,
fs,
path::PathBuf,
sync::{mpsc::{channel, Receiver}, Arc},
thread,
};
use can_dbc::Dbc;
use eframe::egui;
@@ -20,50 +26,171 @@ use crate::tree::{build_tree, decode_signal_node_id, show_dbc_tree, MsgNode};
fn main() {
let native_options = eframe::NativeOptions::default();
eframe::run_native(
"My egui App",
"svldplotter",
native_options,
Box::new(|cc| Ok(Box::new(MyEguiApp::new(cc)))),
Box::new(|cc| {
let mut app = MyEguiApp::default();
// Optional CLI quick-load: `svldplotter [DBC [ASC]]`. Both args
// are optional and intended for dev iteration.
let args: Vec<String> = std::env::args().collect();
if let Some(dbc) = args.get(1) {
app.load_dbc(PathBuf::from(dbc));
}
if let Some(asc) = args.get(2) {
app.asc_paths.push(PathBuf::from(asc));
app.set_active_asc(app.asc_paths.len() - 1, Some(&cc.egui_ctx));
}
Ok(Box::new(app))
}),
)
.unwrap();
}
#[derive(Default, PartialEq, Eq)]
enum LeftTab {
#[default]
Signals,
Files,
}
struct DecodeJob {
asc_index: usize,
asc_path: PathBuf,
receiver: Receiver<Arc<DecodeResult>>,
}
#[derive(Default)]
struct MyEguiApp {
data: DecodeResult,
dbc: Option<Arc<Dbc>>,
dbc_path: Option<PathBuf>,
asc_paths: Vec<PathBuf>,
active_asc: Option<usize>,
data: Option<Arc<DecodeResult>>,
decode_cache: HashMap<PathBuf, Arc<DecodeResult>>,
pending: Option<DecodeJob>,
tree: Vec<MsgNode>,
filter: String,
plots: Vec<PlotGroup>,
next_plot_id: u64,
cursor_x: Option<f64>,
left_tab: LeftTab,
}
impl MyEguiApp {
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
let args: Vec<_> = std::env::args().collect();
let dbc_text =
fs::read_to_string(Path::new(&args[1])).expect("Unable to read input file");
let dbc = Dbc::try_from(dbc_text.as_str()).expect("Failed to parse dbc file");
let asc_path = Path::new(&args[2]);
let data = libsvld::decode(&dbc, asc_path);
let tree = build_tree(&dbc, &data);
Self {
data,
tree,
filter: String::new(),
plots: Vec::new(),
next_plot_id: 1,
cursor_x: None,
fn load_dbc(&mut self, path: PathBuf) {
let Ok(text) = fs::read_to_string(&path) else {
return;
};
let Ok(dbc) = Dbc::try_from(text.as_str()) else {
return;
};
self.dbc = Some(Arc::new(dbc));
self.dbc_path = Some(path);
// DBC change invalidates the decode cache.
self.decode_cache.clear();
self.pending = None;
self.data = None;
self.tree.clear();
if let Some(idx) = self.active_asc {
self.start_or_use_cached(idx, None);
}
}
fn add_asc(&mut self, path: PathBuf) {
self.asc_paths.push(path);
if self.active_asc.is_none() {
self.set_active_asc(self.asc_paths.len() - 1, None);
}
}
/// Switch to a given ASC. Use the cached DecodeResult if present;
/// otherwise spawn a background decode and show progress via the
/// `pending` job.
fn set_active_asc(&mut self, idx: usize, ctx: Option<&egui::Context>) {
if idx >= self.asc_paths.len() {
return;
}
self.active_asc = Some(idx);
self.start_or_use_cached(idx, ctx);
}
fn start_or_use_cached(&mut self, idx: usize, ctx: Option<&egui::Context>) {
let Some(dbc) = self.dbc.clone() else {
return;
};
let path = self.asc_paths[idx].clone();
if self.decode_cache.contains_key(&path) {
self.activate_decoded(idx);
return;
}
let (tx, rx) = channel();
let dbc_for_thread = dbc;
let path_for_thread = path.clone();
let ctx_for_thread = ctx.cloned();
thread::spawn(move || {
let result = libsvld::decode(&dbc_for_thread, &path_for_thread);
let _ = tx.send(Arc::new(result));
if let Some(ctx) = ctx_for_thread {
ctx.request_repaint();
}
});
self.pending = Some(DecodeJob {
asc_index: idx,
asc_path: path,
receiver: rx,
});
}
/// Pull a finished decode out of `pending` and install it.
fn poll_pending(&mut self) {
let Some(job) = self.pending.as_ref() else {
return;
};
let Ok(result) = job.receiver.try_recv() else {
return;
};
let asc_index = job.asc_index;
let asc_path = job.asc_path.clone();
self.pending = None;
self.decode_cache.insert(asc_path, result);
if self.active_asc == Some(asc_index) {
self.activate_decoded(asc_index);
}
}
/// Point `self.data`/`self.tree` at the cached result for `idx` and
/// re-resolve every plotted signal against the new data.
fn activate_decoded(&mut self, idx: usize) {
let Some(dbc) = self.dbc.as_ref() else {
return;
};
let path = &self.asc_paths[idx];
let Some(data) = self.decode_cache.get(path).cloned() else {
return;
};
self.tree = build_tree(dbc, &data);
// Re-resolve plotted signals so existing plots survive an ASC switch.
for plot in &mut self.plots {
for sig in &mut plot.signals {
if let Some((msg_id, sig_idx)) = decode_signal_node_id(sig.id) {
if let Some(new_sig) = compute_signal(&data, msg_id, sig_idx, sig.id) {
*sig = new_sig;
}
}
}
}
self.data = Some(data);
}
fn handle_dbc_toggles(&mut self, toggles: Vec<u64>) {
let Some(data) = self.data.clone() else {
return;
};
for id in toggles {
if contains_signal(&self.plots, id) {
remove_signal(&mut self.plots, id);
} else if let Some((msg_id, sig_idx)) = decode_signal_node_id(id) {
if let Some(sig) = compute_signal(&self.data, msg_id, sig_idx, id) {
if let Some(sig) = compute_signal(&data, msg_id, sig_idx, id) {
add_signal_auto(&mut self.plots, &mut self.next_plot_id, sig);
}
}
@@ -82,8 +209,64 @@ impl MyEguiApp {
}
}
enum FilesAction {
LoadDbc(PathBuf),
AddAsc(PathBuf),
SetActive(usize),
}
fn show_files_tab(
ui: &mut egui::Ui,
dbc_path: Option<&PathBuf>,
asc_paths: &[PathBuf],
active: Option<usize>,
) -> Vec<FilesAction> {
let mut actions = Vec::new();
ui.horizontal(|ui| {
if ui.button("Load DBC").clicked() {
if let Some(p) = rfd::FileDialog::new()
.add_filter("DBC files", &["dbc"])
.pick_file()
{
actions.push(FilesAction::LoadDbc(p));
}
}
let label = dbc_path
.map(|p| p.file_name().map(|s| s.to_string_lossy().into_owned()).unwrap_or_default())
.unwrap_or_else(|| "(none)".to_string());
ui.label(label);
});
ui.separator();
ui.horizontal(|ui| {
if ui.button("Add ASC").clicked() {
if let Some(p) = rfd::FileDialog::new()
.add_filter("ASC files", &["asc"])
.pick_file()
{
actions.push(FilesAction::AddAsc(p));
}
}
ui.label(format!("{} file(s)", asc_paths.len()));
});
egui::ScrollArea::vertical().show(ui, |ui| {
for (i, path) in asc_paths.iter().enumerate() {
let name = path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| path.display().to_string());
if ui.selectable_label(active == Some(i), name).clicked() {
actions.push(FilesAction::SetActive(i));
}
}
});
actions
}
impl eframe::App for MyEguiApp {
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
self.poll_pending();
let ctx = ui.ctx().clone();
egui::Panel::left(egui::Id::new("LeftPanel"))
.resizable(true)
.show_inside(ui, |ui| {
@@ -92,13 +275,66 @@ impl eframe::App for MyEguiApp {
.default_size(220.0)
.show_inside(ui, |ui| show_plot_list(ui, &self.plots))
.inner;
let toggles = egui::CentralPanel::default()
.show_inside(ui, |ui| show_dbc_tree(ui, &mut self.filter, &self.tree))
let (toggles, files_actions) = egui::CentralPanel::default()
.show_inside(ui, |ui| {
ui.horizontal(|ui| {
ui.selectable_value(
&mut self.left_tab,
LeftTab::Signals,
"Signals",
);
ui.selectable_value(
&mut self.left_tab,
LeftTab::Files,
"Files",
);
});
ui.separator();
match self.left_tab {
LeftTab::Signals => (
show_dbc_tree(ui, &mut self.filter, &self.tree),
Vec::new(),
),
LeftTab::Files => (
Vec::new(),
show_files_tab(
ui,
self.dbc_path.as_ref(),
&self.asc_paths,
self.active_asc,
),
),
}
})
.inner;
self.handle_dbc_toggles(toggles);
self.handle_plot_actions(plot_actions);
for action in files_actions {
match action {
FilesAction::LoadDbc(p) => self.load_dbc(p),
FilesAction::AddAsc(p) => self.add_asc(p),
FilesAction::SetActive(i) => self.set_active_asc(i, Some(&ctx)),
}
}
});
show_plot_panel(ui, &self.plots, &mut self.cursor_x);
if let Some(job) = self.pending.as_ref() {
let name = job
.asc_path
.file_name()
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_else(|| job.asc_path.display().to_string());
egui::Modal::new(egui::Id::new("DecodingModal")).show(&ctx, |ui| {
ui.set_min_width(280.0);
ui.vertical_centered(|ui| {
ui.heading("Decoding...");
ui.add_space(6.0);
ui.label(name);
ui.add_space(6.0);
ui.spinner();
});
});
}
}
}