From 8fc142d3167166e72e56807ee7879722f5e34794 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 28 Sep 2024 10:55:22 -0600 Subject: [PATCH] Debounce loaded object modification check Before, this was running 2 fs::metadata calls every frame. We don't need to do it nearly that often, so now it only checks once every 500ms. This required refactoring AppConfig into a separate AppState that holds transient runtime state along with the loaded AppConfig. --- objdiff-gui/src/app.rs | 231 +++++++++++++------------ objdiff-gui/src/config.rs | 34 ++-- objdiff-gui/src/views/config.rs | 245 ++++++++++++++------------- objdiff-gui/src/views/symbol_diff.rs | 20 +-- 4 files changed, 273 insertions(+), 257 deletions(-) diff --git a/objdiff-gui/src/app.rs b/objdiff-gui/src/app.rs index 9382cd1..16928f4 100644 --- a/objdiff-gui/src/app.rs +++ b/objdiff-gui/src/app.rs @@ -7,6 +7,7 @@ use std::{ atomic::{AtomicBool, Ordering}, Arc, Mutex, RwLock, }, + time::Instant, }; use filetime::FileTime; @@ -82,6 +83,36 @@ fn default_watch_patterns() -> Vec { DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect() } +pub struct AppState { + pub config: AppConfig, + pub objects: Vec, + pub object_nodes: Vec, + pub watcher_change: bool, + pub config_change: bool, + pub obj_change: bool, + pub queue_build: bool, + pub queue_reload: bool, + pub project_config_info: Option, + pub last_mod_check: Instant, +} + +impl Default for AppState { + fn default() -> Self { + Self { + config: Default::default(), + objects: vec![], + object_nodes: vec![], + watcher_change: false, + config_change: false, + obj_change: false, + queue_build: false, + queue_reload: false, + project_config_info: None, + last_mod_check: Instant::now(), + } + } +} + #[derive(Clone, serde::Deserialize, serde::Serialize)] pub struct AppConfig { // TODO: https://github.com/ron-rs/ron/pull/455 @@ -116,23 +147,6 @@ pub struct AppConfig { pub recent_projects: Vec, #[serde(default)] pub diff_obj_config: DiffObjConfig, - - #[serde(skip)] - pub objects: Vec, - #[serde(skip)] - pub object_nodes: Vec, - #[serde(skip)] - pub watcher_change: bool, - #[serde(skip)] - pub config_change: bool, - #[serde(skip)] - pub obj_change: bool, - #[serde(skip)] - pub queue_build: bool, - #[serde(skip)] - pub queue_reload: bool, - #[serde(skip)] - pub project_config_info: Option, } impl Default for AppConfig { @@ -153,30 +167,22 @@ impl Default for AppConfig { watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(), recent_projects: vec![], diff_obj_config: Default::default(), - objects: vec![], - object_nodes: vec![], - watcher_change: false, - config_change: false, - obj_change: false, - queue_build: false, - queue_reload: false, - project_config_info: None, } } } -impl AppConfig { +impl AppState { pub fn set_project_dir(&mut self, path: PathBuf) { - self.recent_projects.retain(|p| p != &path); - if self.recent_projects.len() > 9 { - self.recent_projects.truncate(9); + self.config.recent_projects.retain(|p| p != &path); + if self.config.recent_projects.len() > 9 { + self.config.recent_projects.truncate(9); } - self.recent_projects.insert(0, path.clone()); - self.project_dir = Some(path); - self.target_obj_dir = None; - self.base_obj_dir = None; - self.selected_obj = None; - self.build_target = false; + self.config.recent_projects.insert(0, path.clone()); + self.config.project_dir = Some(path); + self.config.target_obj_dir = None; + self.config.base_obj_dir = None; + self.config.selected_obj = None; + self.config.build_target = false; self.objects.clear(); self.object_nodes.clear(); self.watcher_change = true; @@ -187,33 +193,33 @@ impl AppConfig { } pub fn set_target_obj_dir(&mut self, path: PathBuf) { - self.target_obj_dir = Some(path); - self.selected_obj = None; + self.config.target_obj_dir = Some(path); + self.config.selected_obj = None; self.obj_change = true; self.queue_build = false; } pub fn set_base_obj_dir(&mut self, path: PathBuf) { - self.base_obj_dir = Some(path); - self.selected_obj = None; + self.config.base_obj_dir = Some(path); + self.config.selected_obj = None; self.obj_change = true; self.queue_build = false; } pub fn set_selected_obj(&mut self, object: ObjectConfig) { - self.selected_obj = Some(object); + self.config.selected_obj = Some(object); self.obj_change = true; self.queue_build = false; } } -pub type AppConfigRef = Arc>; +pub type AppStateRef = Arc>; #[derive(Default)] pub struct App { appearance: Appearance, view_state: ViewState, - config: AppConfigRef, + state: AppStateRef, modified: Arc, watcher: Option, app_path: Option, @@ -241,16 +247,17 @@ impl App { if let Some(appearance) = eframe::get_value::(storage, APPEARANCE_KEY) { app.appearance = appearance; } - if let Some(mut config) = deserialize_config(storage) { - if config.project_dir.is_some() { - config.config_change = true; - config.watcher_change = true; + if let Some(config) = deserialize_config(storage) { + let mut state = AppState { config, ..Default::default() }; + if state.config.project_dir.is_some() { + state.config_change = true; + state.watcher_change = true; } - if config.selected_obj.is_some() { - config.queue_build = true; + if state.config.selected_obj.is_some() { + state.queue_build = true; } - app.view_state.config_state.queue_check_update = config.auto_update_check; - app.config = Arc::new(RwLock::new(config)); + app.view_state.config_state.queue_check_update = state.config.auto_update_check; + app.state = Arc::new(RwLock::new(state)); } } app.appearance.init_fonts(&cc.egui_ctx); @@ -336,8 +343,8 @@ impl App { jobs.results.append(&mut results); jobs.clear_finished(); - diff_state.pre_update(jobs, &self.config); - config_state.pre_update(jobs, &self.config); + diff_state.pre_update(jobs, &self.state); + config_state.pre_update(jobs, &self.state); debug_assert!(jobs.results.is_empty()); } @@ -345,23 +352,23 @@ impl App { self.appearance.post_update(ctx); let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state; - config_state.post_update(ctx, jobs, &self.config); - diff_state.post_update(ctx, jobs, &self.config); + config_state.post_update(ctx, jobs, &self.state); + diff_state.post_update(ctx, jobs, &self.state); - let Ok(mut config) = self.config.write() else { + let Ok(mut state) = self.state.write() else { return; }; - let config = &mut *config; + let state = &mut *state; - if let Some(info) = &config.project_config_info { + if let Some(info) = &state.project_config_info { if file_modified(&info.path, info.timestamp) { - config.config_change = true; + state.config_change = true; } } - if config.config_change { - config.config_change = false; - match load_project_config(config) { + if state.config_change { + state.config_change = false; + match load_project_config(state) { Ok(()) => config_state.load_error = None, Err(e) => { log::error!("Failed to load project config: {e}"); @@ -370,47 +377,50 @@ impl App { } } - if config.watcher_change { + if state.watcher_change { drop(self.watcher.take()); - if let Some(project_dir) = &config.project_dir { - match build_globset(&config.watch_patterns).map_err(anyhow::Error::new).and_then( - |globset| { + if let Some(project_dir) = &state.config.project_dir { + match build_globset(&state.config.watch_patterns) + .map_err(anyhow::Error::new) + .and_then(|globset| { create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset) .map_err(anyhow::Error::new) - }, - ) { + }) { Ok(watcher) => self.watcher = Some(watcher), Err(e) => log::error!("Failed to create watcher: {e}"), } - config.watcher_change = false; + state.watcher_change = false; } } - if config.obj_change { + if state.obj_change { *diff_state = Default::default(); - if config.selected_obj.is_some() { - config.queue_build = true; + if state.config.selected_obj.is_some() { + state.queue_build = true; } - config.obj_change = false; + state.obj_change = false; } - if self.modified.swap(false, Ordering::Relaxed) && config.rebuild_on_changes { - config.queue_build = true; + if self.modified.swap(false, Ordering::Relaxed) && state.config.rebuild_on_changes { + state.queue_build = true; } if let Some(result) = &diff_state.build { - if let Some((obj, _)) = &result.first_obj { - if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { - if file_modified(path, timestamp) { - config.queue_reload = true; + if state.last_mod_check.elapsed().as_millis() >= 500 { + state.last_mod_check = Instant::now(); + if let Some((obj, _)) = &result.first_obj { + if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { + if file_modified(path, timestamp) { + state.queue_reload = true; + } } } - } - if let Some((obj, _)) = &result.second_obj { - if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { - if file_modified(path, timestamp) { - config.queue_reload = true; + if let Some((obj, _)) = &result.second_obj { + if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { + if file_modified(path, timestamp) { + state.queue_reload = true; + } } } } @@ -418,17 +428,20 @@ impl App { // Don't clear `queue_build` if a build is running. A file may have been modified during // the build, so we'll start another build after the current one finishes. - if config.queue_build && config.selected_obj.is_some() && !jobs.is_running(Job::ObjDiff) { - jobs.push(start_build(ctx, ObjDiffConfig::from_config(config))); - config.queue_build = false; - config.queue_reload = false; - } else if config.queue_reload && !jobs.is_running(Job::ObjDiff) { - let mut diff_config = ObjDiffConfig::from_config(config); + if state.queue_build + && state.config.selected_obj.is_some() + && !jobs.is_running(Job::ObjDiff) + { + jobs.push(start_build(ctx, ObjDiffConfig::from_config(&state.config))); + state.queue_build = false; + state.queue_reload = false; + } else if state.queue_reload && !jobs.is_running(Job::ObjDiff) { + let mut diff_config = ObjDiffConfig::from_config(&state.config); // Don't build, just reload the current files diff_config.build_base = false; diff_config.build_target = false; jobs.push(start_build(ctx, diff_config)); - config.queue_reload = false; + state.queue_reload = false; } if graphics_state.should_relaunch { @@ -453,7 +466,7 @@ impl eframe::App for App { self.pre_update(ctx); - let Self { config, appearance, view_state, .. } = self; + let Self { state, appearance, view_state, .. } = self; let ViewState { jobs, config_state, @@ -485,8 +498,8 @@ impl eframe::App for App { *show_project_config = !*show_project_config; ui.close_menu(); } - let recent_projects = if let Ok(guard) = config.read() { - guard.recent_projects.clone() + let recent_projects = if let Ok(guard) = state.read() { + guard.config.recent_projects.clone() } else { vec![] }; @@ -495,12 +508,12 @@ impl eframe::App for App { } else { ui.menu_button("Recent Projects…", |ui| { if ui.button("Clear").clicked() { - config.write().unwrap().recent_projects.clear(); + state.write().unwrap().config.recent_projects.clear(); }; ui.separator(); for path in recent_projects { if ui.button(format!("{}", path.display())).clicked() { - config.write().unwrap().set_project_dir(path); + state.write().unwrap().set_project_dir(path); ui.close_menu(); } } @@ -533,12 +546,12 @@ impl eframe::App for App { *show_arch_config = !*show_arch_config; ui.close_menu(); } - let mut config = config.write().unwrap(); + let mut state = state.write().unwrap(); let response = ui - .checkbox(&mut config.rebuild_on_changes, "Rebuild on changes") + .checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes") .on_hover_text("Automatically re-run the build & diff when files change."); if response.changed() { - config.watcher_change = true; + state.watcher_change = true; }; ui.add_enabled( !diff_state.symbol_state.disable_reverse_fn_order, @@ -554,7 +567,7 @@ impl eframe::App for App { ); if ui .checkbox( - &mut config.diff_obj_config.relax_reloc_diffs, + &mut state.config.diff_obj_config.relax_reloc_diffs, "Relax relocation diffs", ) .on_hover_text( @@ -562,26 +575,26 @@ impl eframe::App for App { ) .changed() { - config.queue_reload = true; + state.queue_reload = true; } if ui .checkbox( - &mut config.diff_obj_config.space_between_args, + &mut state.config.diff_obj_config.space_between_args, "Space between args", ) .changed() { - config.queue_reload = true; + state.queue_reload = true; } if ui .checkbox( - &mut config.diff_obj_config.combine_data_sections, + &mut state.config.diff_obj_config.combine_data_sections, "Combine data sections", ) .on_hover_text("Combines data sections with equal names.") .changed() { - config.queue_reload = true; + state.queue_reload = true; } }); }); @@ -603,7 +616,7 @@ impl eframe::App for App { } else { egui::SidePanel::left("side_panel").show(ctx, |ui| { egui::ScrollArea::both().show(ui, |ui| { - config_ui(ui, config, show_project_config, config_state, appearance); + config_ui(ui, state, show_project_config, config_state, appearance); jobs_ui(ui, jobs, appearance); }); }); @@ -613,11 +626,11 @@ impl eframe::App for App { }); } - project_window(ctx, config, show_project_config, config_state, appearance); + project_window(ctx, state, show_project_config, config_state, appearance); appearance_window(ctx, show_appearance_config, appearance); demangle_window(ctx, show_demangle, demangle_state, appearance); rlwinm_decode_window(ctx, show_rlwinm_decode, rlwinm_decode_state, appearance); - arch_config_window(ctx, config, show_arch_config, appearance); + arch_config_window(ctx, state, show_arch_config, appearance); debug_window(ctx, show_debug, frame_history, appearance); graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance); @@ -626,8 +639,8 @@ impl eframe::App for App { /// Called by the frame work to save state before shutdown. fn save(&mut self, storage: &mut dyn eframe::Storage) { - if let Ok(config) = self.config.read() { - eframe::set_value(storage, CONFIG_KEY, &*config); + if let Ok(state) = self.state.read() { + eframe::set_value(storage, CONFIG_KEY, &state.config); } eframe::set_value(storage, APPEARANCE_KEY, &self.appearance); } diff --git a/objdiff-gui/src/config.rs b/objdiff-gui/src/config.rs index 6f866b3..6721cf7 100644 --- a/objdiff-gui/src/config.rs +++ b/objdiff-gui/src/config.rs @@ -4,7 +4,7 @@ use anyhow::Result; use globset::Glob; use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS}; -use crate::app::AppConfig; +use crate::app::AppState; #[derive(Clone)] pub enum ProjectObjectNode { @@ -64,30 +64,30 @@ fn build_nodes( nodes } -pub fn load_project_config(config: &mut AppConfig) -> Result<()> { - let Some(project_dir) = &config.project_dir else { +pub fn load_project_config(state: &mut AppState) -> Result<()> { + let Some(project_dir) = &state.config.project_dir else { return Ok(()); }; if let Some((result, info)) = try_project_config(project_dir) { let project_config = result?; - config.custom_make = project_config.custom_make; - config.custom_args = project_config.custom_args; - config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p)); - config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p)); - config.build_base = project_config.build_base; - config.build_target = project_config.build_target; - config.watch_patterns = project_config.watch_patterns.unwrap_or_else(|| { + state.config.custom_make = project_config.custom_make; + state.config.custom_args = project_config.custom_args; + state.config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p)); + state.config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p)); + state.config.build_base = project_config.build_base; + state.config.build_target = project_config.build_target; + state.config.watch_patterns = project_config.watch_patterns.unwrap_or_else(|| { DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect() }); - config.watcher_change = true; - config.objects = project_config.objects; - config.object_nodes = build_nodes( - &config.objects, + state.watcher_change = true; + state.objects = project_config.objects; + state.object_nodes = build_nodes( + &state.objects, project_dir, - config.target_obj_dir.as_deref(), - config.base_obj_dir.as_deref(), + state.config.target_obj_dir.as_deref(), + state.config.base_obj_dir.as_deref(), ); - config.project_config_info = Some(info); + state.project_config_info = Some(info); } Ok(()) } diff --git a/objdiff-gui/src/views/config.rs b/objdiff-gui/src/views/config.rs index 43e6e7e..cfdeaf6 100644 --- a/objdiff-gui/src/views/config.rs +++ b/objdiff-gui/src/views/config.rs @@ -19,7 +19,7 @@ use objdiff_core::{ use strum::{EnumMessage, VariantArray}; use crate::{ - app::{AppConfig, AppConfigRef, ObjectConfig}, + app::{AppConfig, AppState, AppStateRef, ObjectConfig}, config::ProjectObjectNode, jobs::{ check_update::{start_check_update, CheckUpdateResult}, @@ -54,7 +54,7 @@ pub struct ConfigViewState { } impl ConfigViewState { - pub fn pre_update(&mut self, jobs: &mut JobQueue, config: &AppConfigRef) { + pub fn pre_update(&mut self, jobs: &mut JobQueue, state: &AppStateRef) { jobs.results.retain_mut(|result| { if let JobResult::CheckUpdate(result) = result { self.check_update = take(result); @@ -71,21 +71,21 @@ impl ConfigViewState { match self.file_dialog_state.poll() { FileDialogResult::None => {} FileDialogResult::ProjectDir(path) => { - let mut guard = config.write().unwrap(); + let mut guard = state.write().unwrap(); guard.set_project_dir(path.to_path_buf()); } FileDialogResult::TargetDir(path) => { - let mut guard = config.write().unwrap(); + let mut guard = state.write().unwrap(); guard.set_target_obj_dir(path.to_path_buf()); } FileDialogResult::BaseDir(path) => { - let mut guard = config.write().unwrap(); + let mut guard = state.write().unwrap(); guard.set_base_obj_dir(path.to_path_buf()); } FileDialogResult::Object(path) => { - let mut guard = config.write().unwrap(); + let mut guard = state.write().unwrap(); if let (Some(base_dir), Some(target_dir)) = - (&guard.base_obj_dir, &guard.target_obj_dir) + (&guard.config.base_obj_dir, &guard.config.target_obj_dir) { if let Ok(obj_path) = path.strip_prefix(base_dir) { let target_path = target_dir.join(obj_path); @@ -113,11 +113,11 @@ impl ConfigViewState { } } - pub fn post_update(&mut self, ctx: &egui::Context, jobs: &mut JobQueue, config: &AppConfigRef) { + pub fn post_update(&mut self, ctx: &egui::Context, jobs: &mut JobQueue, state: &AppStateRef) { if self.queue_build { self.queue_build = false; - if let Ok(mut config) = config.write() { - config.queue_build = true; + if let Ok(mut state) = state.write() { + state.queue_build = true; } } @@ -167,42 +167,40 @@ fn fetch_wsl2_distros() -> Vec { pub fn config_ui( ui: &mut egui::Ui, - config: &AppConfigRef, + state: &AppStateRef, show_config_window: &mut bool, - state: &mut ConfigViewState, + config_state: &mut ConfigViewState, appearance: &Appearance, ) { - let mut config_guard = config.write().unwrap(); - let AppConfig { - target_obj_dir, - base_obj_dir, - selected_obj, - auto_update_check, + let mut state_guard = state.write().unwrap(); + let AppState { + config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. }, objects, object_nodes, .. - } = &mut *config_guard; + } = &mut *state_guard; ui.heading("Updates"); ui.checkbox(auto_update_check, "Check for updates on startup"); - if ui.add_enabled(!state.check_update_running, egui::Button::new("Check now")).clicked() { - state.queue_check_update = true; + if ui.add_enabled(!config_state.check_update_running, egui::Button::new("Check now")).clicked() + { + config_state.queue_check_update = true; } ui.label(format!("Current version: {}", env!("CARGO_PKG_VERSION"))); - if let Some(result) = &state.check_update { + if let Some(result) = &config_state.check_update { ui.label(format!("Latest version: {}", result.latest_release.version)); if result.update_available { ui.colored_label(appearance.insert_color, "Update available"); ui.horizontal(|ui| { if let Some(bin_name) = &result.found_binary { if ui - .add_enabled(!state.update_running, egui::Button::new("Automatic")) + .add_enabled(!config_state.update_running, egui::Button::new("Automatic")) .on_hover_text_at_pointer( "Automatically download and replace the current build", ) .clicked() { - state.queue_update = Some(bin_name.clone()); + config_state.queue_update = Some(bin_name.clone()); } } if ui @@ -231,7 +229,7 @@ pub fn config_ui( if objects.is_empty() { if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) { if ui.button("Select object").clicked() { - state.file_dialog_state.queue( + config_state.file_dialog_state.queue( || { Box::pin( rfd::AsyncFileDialog::new() @@ -254,8 +252,8 @@ pub fn config_ui( ui.colored_label(appearance.delete_color, "Missing project settings"); } } else { - let had_search = !state.object_search.is_empty(); - egui::TextEdit::singleline(&mut state.object_search).hint_text("Filter").ui(ui); + let had_search = !config_state.object_search.is_empty(); + egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui); let mut root_open = None; let mut node_open = NodeOpen::Default; @@ -277,19 +275,22 @@ pub fn config_ui( node_open = NodeOpen::Object; } let mut filters_text = RichText::new("Filter ⏷"); - if state.filter_diffable || state.filter_incomplete || state.show_hidden { + if config_state.filter_diffable + || config_state.filter_incomplete + || config_state.show_hidden + { filters_text = filters_text.color(appearance.replace_color); } egui::menu::menu_button(ui, filters_text, |ui| { - ui.checkbox(&mut state.filter_diffable, "Diffable") + ui.checkbox(&mut config_state.filter_diffable, "Diffable") .on_hover_text_at_pointer("Only show objects with a source file"); - ui.checkbox(&mut state.filter_incomplete, "Incomplete") + ui.checkbox(&mut config_state.filter_incomplete, "Incomplete") .on_hover_text_at_pointer("Only show objects not marked complete"); - ui.checkbox(&mut state.show_hidden, "Hidden") + ui.checkbox(&mut config_state.show_hidden, "Hidden") .on_hover_text_at_pointer("Show hidden (auto-generated) objects"); }); }); - if state.object_search.is_empty() { + if config_state.object_search.is_empty() { if had_search { root_open = Some(true); node_open = NodeOpen::Object; @@ -306,15 +307,15 @@ pub fn config_ui( .open(root_open) .default_open(true) .show(ui, |ui| { - let search = state.object_search.to_ascii_lowercase(); + let search = config_state.object_search.to_ascii_lowercase(); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); for node in object_nodes.iter().filter_map(|node| { filter_node( node, &search, - state.filter_diffable, - state.filter_incomplete, - state.show_hidden, + config_state.filter_diffable, + config_state.filter_incomplete, + config_state.show_hidden, ) }) { display_node(ui, &mut new_selected_obj, &node, appearance, node_open); @@ -324,13 +325,13 @@ pub fn config_ui( if new_selected_obj != *selected_obj { if let Some(obj) = new_selected_obj { // Will set obj_changed, which will trigger a rebuild - config_guard.set_selected_obj(obj); + state_guard.set_selected_obj(obj); } } - if config_guard.selected_obj.is_some() - && ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() + if state_guard.config.selected_obj.is_some() + && ui.add_enabled(!config_state.build_running, egui::Button::new("Build")).clicked() { - state.queue_build = true; + config_state.queue_build = true; } ui.separator(); @@ -523,33 +524,33 @@ fn pick_folder_ui( pub fn project_window( ctx: &egui::Context, - config: &AppConfigRef, + state: &AppStateRef, show: &mut bool, - state: &mut ConfigViewState, + config_state: &mut ConfigViewState, appearance: &Appearance, ) { - let mut config_guard = config.write().unwrap(); + let mut state_guard = state.write().unwrap(); egui::Window::new("Project").open(show).show(ctx, |ui| { - split_obj_config_ui(ui, &mut config_guard, state, appearance); + split_obj_config_ui(ui, &mut state_guard, config_state, appearance); }); - if let Some(error) = &state.load_error { + if let Some(error) = &config_state.load_error { let mut open = true; egui::Window::new("Error").open(&mut open).show(ctx, |ui| { ui.label("Failed to load project config:"); ui.colored_label(appearance.delete_color, error); }); if !open { - state.load_error = None; + config_state.load_error = None; } } } fn split_obj_config_ui( ui: &mut egui::Ui, - config: &mut AppConfig, - state: &mut ConfigViewState, + state: &mut AppState, + config_state: &mut ConfigViewState, appearance: &Appearance, ) { let text_format = TextFormat::simple(appearance.ui_font.clone(), appearance.text_color); @@ -560,7 +561,7 @@ fn split_obj_config_ui( let response = pick_folder_ui( ui, - &config.project_dir, + &state.config.project_dir, "Project directory", |ui| { let mut job = LayoutJob::default(); @@ -576,7 +577,7 @@ fn split_obj_config_ui( true, ); if response.clicked() { - state.file_dialog_state.queue( + config_state.file_dialog_state.queue( || Box::pin(rfd::AsyncFileDialog::new().pick_folder()), FileDialogResult::ProjectDir, ); @@ -605,33 +606,35 @@ fn split_obj_config_ui( ui.label(job); }); }); - let mut custom_make_str = config.custom_make.clone().unwrap_or_default(); + let mut custom_make_str = state.config.custom_make.clone().unwrap_or_default(); if ui .add_enabled( - config.project_config_info.is_none(), + state.project_config_info.is_none(), egui::TextEdit::singleline(&mut custom_make_str).hint_text("make"), ) .on_disabled_hover_text(CONFIG_DISABLED_TEXT) .changed() { if custom_make_str.is_empty() { - config.custom_make = None; + state.config.custom_make = None; } else { - config.custom_make = Some(custom_make_str); + state.config.custom_make = Some(custom_make_str); } } #[cfg(all(windows, feature = "wsl"))] { - if state.available_wsl_distros.is_none() { - state.available_wsl_distros = Some(fetch_wsl2_distros()); + if config_state.available_wsl_distros.is_none() { + config_state.available_wsl_distros = Some(fetch_wsl2_distros()); } egui::ComboBox::from_label("Run in WSL2") - .selected_text(config.selected_wsl_distro.as_ref().unwrap_or(&"Disabled".to_string())) + .selected_text( + state.config.selected_wsl_distro.as_ref().unwrap_or(&"Disabled".to_string()), + ) .show_ui(ui, |ui| { - ui.selectable_value(&mut config.selected_wsl_distro, None, "Disabled"); - for distro in state.available_wsl_distros.as_ref().unwrap() { + ui.selectable_value(&mut state.config.selected_wsl_distro, None, "Disabled"); + for distro in config_state.available_wsl_distros.as_ref().unwrap() { ui.selectable_value( - &mut config.selected_wsl_distro, + &mut state.config.selected_wsl_distro, Some(distro.clone()), distro, ); @@ -640,10 +643,10 @@ fn split_obj_config_ui( } ui.separator(); - if let Some(project_dir) = config.project_dir.clone() { + if let Some(project_dir) = state.config.project_dir.clone() { let response = pick_folder_ui( ui, - &config.target_obj_dir, + &state.config.target_obj_dir, "Target build directory", |ui| { let mut job = LayoutJob::default(); @@ -660,17 +663,17 @@ fn split_obj_config_ui( ui.label(job); }, appearance, - config.project_config_info.is_none(), + state.project_config_info.is_none(), ); if response.clicked() { - state.file_dialog_state.queue( + config_state.file_dialog_state.queue( || Box::pin(rfd::AsyncFileDialog::new().set_directory(&project_dir).pick_folder()), FileDialogResult::TargetDir, ); } ui.add_enabled( - config.project_config_info.is_none(), - egui::Checkbox::new(&mut config.build_target, "Build target objects"), + state.project_config_info.is_none(), + egui::Checkbox::new(&mut state.config.build_target, "Build target objects"), ) .on_disabled_hover_text(CONFIG_DISABLED_TEXT) .on_hover_ui(|ui| { @@ -704,7 +707,7 @@ fn split_obj_config_ui( let response = pick_folder_ui( ui, - &config.base_obj_dir, + &state.config.base_obj_dir, "Base build directory", |ui| { let mut job = LayoutJob::default(); @@ -716,17 +719,17 @@ fn split_obj_config_ui( ui.label(job); }, appearance, - config.project_config_info.is_none(), + state.project_config_info.is_none(), ); if response.clicked() { - state.file_dialog_state.queue( + config_state.file_dialog_state.queue( || Box::pin(rfd::AsyncFileDialog::new().set_directory(&project_dir).pick_folder()), FileDialogResult::BaseDir, ); } ui.add_enabled( - config.project_config_info.is_none(), - egui::Checkbox::new(&mut config.build_base, "Build base objects"), + state.project_config_info.is_none(), + egui::Checkbox::new(&mut state.config.build_base, "Build base objects"), ) .on_disabled_hover_text(CONFIG_DISABLED_TEXT) .on_hover_ui(|ui| { @@ -757,7 +760,7 @@ fn split_obj_config_ui( subheading(ui, "Watch settings", appearance); let response = - ui.checkbox(&mut config.rebuild_on_changes, "Rebuild on changes").on_hover_ui(|ui| { + ui.checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes").on_hover_ui(|ui| { let mut job = LayoutJob::default(); job.append( "Automatically re-run the build & diff when files change.", @@ -767,23 +770,23 @@ fn split_obj_config_ui( ui.label(job); }); if response.changed() { - config.watcher_change = true; + state.watcher_change = true; }; ui.horizontal(|ui| { ui.label(RichText::new("File patterns").color(appearance.text_color)); if ui - .add_enabled(config.project_config_info.is_none(), egui::Button::new("Reset")) + .add_enabled(state.project_config_info.is_none(), egui::Button::new("Reset")) .on_disabled_hover_text(CONFIG_DISABLED_TEXT) .clicked() { - config.watch_patterns = + state.config.watch_patterns = DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(); - config.watcher_change = true; + state.watcher_change = true; } }); let mut remove_at: Option = None; - for (idx, glob) in config.watch_patterns.iter().enumerate() { + for (idx, glob) in state.config.watch_patterns.iter().enumerate() { ui.horizontal(|ui| { ui.label( RichText::new(format!("{}", glob)) @@ -791,7 +794,7 @@ fn split_obj_config_ui( .family(FontFamily::Monospace), ); if ui - .add_enabled(config.project_config_info.is_none(), egui::Button::new("-").small()) + .add_enabled(state.project_config_info.is_none(), egui::Button::new("-").small()) .on_disabled_hover_text(CONFIG_DISABLED_TEXT) .clicked() { @@ -800,24 +803,24 @@ fn split_obj_config_ui( }); } if let Some(idx) = remove_at { - config.watch_patterns.remove(idx); - config.watcher_change = true; + state.config.watch_patterns.remove(idx); + state.watcher_change = true; } ui.horizontal(|ui| { ui.add_enabled( - config.project_config_info.is_none(), - egui::TextEdit::singleline(&mut state.watch_pattern_text).desired_width(100.0), + state.project_config_info.is_none(), + egui::TextEdit::singleline(&mut config_state.watch_pattern_text).desired_width(100.0), ) .on_disabled_hover_text(CONFIG_DISABLED_TEXT); if ui - .add_enabled(config.project_config_info.is_none(), egui::Button::new("+").small()) + .add_enabled(state.project_config_info.is_none(), egui::Button::new("+").small()) .on_disabled_hover_text(CONFIG_DISABLED_TEXT) .clicked() { - if let Ok(glob) = Glob::new(&state.watch_pattern_text) { - config.watch_patterns.push(glob); - config.watcher_change = true; - state.watch_pattern_text.clear(); + if let Ok(glob) = Glob::new(&config_state.watch_pattern_text) { + state.config.watch_patterns.push(glob); + state.watcher_change = true; + config_state.watch_pattern_text.clear(); } } }); @@ -825,131 +828,131 @@ fn split_obj_config_ui( pub fn arch_config_window( ctx: &egui::Context, - config: &AppConfigRef, + state: &AppStateRef, show: &mut bool, appearance: &Appearance, ) { - let mut config_guard = config.write().unwrap(); + let mut state_guard = state.write().unwrap(); egui::Window::new("Arch Settings").open(show).show(ctx, |ui| { - arch_config_ui(ui, &mut config_guard, appearance); + arch_config_ui(ui, &mut state_guard, appearance); }); } -fn arch_config_ui(ui: &mut egui::Ui, config: &mut AppConfig, _appearance: &Appearance) { +fn arch_config_ui(ui: &mut egui::Ui, state: &mut AppState, _appearance: &Appearance) { ui.heading("x86"); egui::ComboBox::new("x86_formatter", "Format") - .selected_text(config.diff_obj_config.x86_formatter.get_message().unwrap()) + .selected_text(state.config.diff_obj_config.x86_formatter.get_message().unwrap()) .show_ui(ui, |ui| { for &formatter in X86Formatter::VARIANTS { if ui .selectable_label( - config.diff_obj_config.x86_formatter == formatter, + state.config.diff_obj_config.x86_formatter == formatter, formatter.get_message().unwrap(), ) .clicked() { - config.diff_obj_config.x86_formatter = formatter; - config.queue_reload = true; + state.config.diff_obj_config.x86_formatter = formatter; + state.queue_reload = true; } } }); ui.separator(); ui.heading("MIPS"); egui::ComboBox::new("mips_abi", "ABI") - .selected_text(config.diff_obj_config.mips_abi.get_message().unwrap()) + .selected_text(state.config.diff_obj_config.mips_abi.get_message().unwrap()) .show_ui(ui, |ui| { for &abi in MipsAbi::VARIANTS { if ui .selectable_label( - config.diff_obj_config.mips_abi == abi, + state.config.diff_obj_config.mips_abi == abi, abi.get_message().unwrap(), ) .clicked() { - config.diff_obj_config.mips_abi = abi; - config.queue_reload = true; + state.config.diff_obj_config.mips_abi = abi; + state.queue_reload = true; } } }); egui::ComboBox::new("mips_instr_category", "Instruction Category") - .selected_text(config.diff_obj_config.mips_instr_category.get_message().unwrap()) + .selected_text(state.config.diff_obj_config.mips_instr_category.get_message().unwrap()) .show_ui(ui, |ui| { for &category in MipsInstrCategory::VARIANTS { if ui .selectable_label( - config.diff_obj_config.mips_instr_category == category, + state.config.diff_obj_config.mips_instr_category == category, category.get_message().unwrap(), ) .clicked() { - config.diff_obj_config.mips_instr_category = category; - config.queue_reload = true; + state.config.diff_obj_config.mips_instr_category = category; + state.queue_reload = true; } } }); ui.separator(); ui.heading("ARM"); egui::ComboBox::new("arm_arch_version", "Architecture Version") - .selected_text(config.diff_obj_config.arm_arch_version.get_message().unwrap()) + .selected_text(state.config.diff_obj_config.arm_arch_version.get_message().unwrap()) .show_ui(ui, |ui| { for &version in ArmArchVersion::VARIANTS { if ui .selectable_label( - config.diff_obj_config.arm_arch_version == version, + state.config.diff_obj_config.arm_arch_version == version, version.get_message().unwrap(), ) .clicked() { - config.diff_obj_config.arm_arch_version = version; - config.queue_reload = true; + state.config.diff_obj_config.arm_arch_version = version; + state.queue_reload = true; } } }); let response = ui - .checkbox(&mut config.diff_obj_config.arm_unified_syntax, "Unified syntax") + .checkbox(&mut state.config.diff_obj_config.arm_unified_syntax, "Unified syntax") .on_hover_text("Disassemble as unified assembly language (UAL)."); if response.changed() { - config.queue_reload = true; + state.queue_reload = true; } let response = ui - .checkbox(&mut config.diff_obj_config.arm_av_registers, "Use A/V registers") + .checkbox(&mut state.config.diff_obj_config.arm_av_registers, "Use A/V registers") .on_hover_text("Display R0-R3 as A1-A4 and R4-R11 as V1-V8"); if response.changed() { - config.queue_reload = true; + state.queue_reload = true; } egui::ComboBox::new("arm_r9_usage", "Display R9 as") - .selected_text(config.diff_obj_config.arm_r9_usage.get_message().unwrap()) + .selected_text(state.config.diff_obj_config.arm_r9_usage.get_message().unwrap()) .show_ui(ui, |ui| { for &usage in ArmR9Usage::VARIANTS { if ui .selectable_label( - config.diff_obj_config.arm_r9_usage == usage, + state.config.diff_obj_config.arm_r9_usage == usage, usage.get_message().unwrap(), ) .on_hover_text(usage.get_detailed_message().unwrap()) .clicked() { - config.diff_obj_config.arm_r9_usage = usage; - config.queue_reload = true; + state.config.diff_obj_config.arm_r9_usage = usage; + state.queue_reload = true; } } }); let response = ui - .checkbox(&mut config.diff_obj_config.arm_sl_usage, "Display R10 as SL") + .checkbox(&mut state.config.diff_obj_config.arm_sl_usage, "Display R10 as SL") .on_hover_text("Used for explicit stack limits."); if response.changed() { - config.queue_reload = true; + state.queue_reload = true; } let response = ui - .checkbox(&mut config.diff_obj_config.arm_fp_usage, "Display R11 as FP") + .checkbox(&mut state.config.diff_obj_config.arm_fp_usage, "Display R11 as FP") .on_hover_text("Used for frame pointers."); if response.changed() { - config.queue_reload = true; + state.queue_reload = true; } let response = ui - .checkbox(&mut config.diff_obj_config.arm_ip_usage, "Display R12 as IP") + .checkbox(&mut state.config.diff_obj_config.arm_ip_usage, "Display R12 as IP") .on_hover_text("Used for interworking and long branches."); if response.changed() { - config.queue_reload = true; + state.queue_reload = true; } } diff --git a/objdiff-gui/src/views/symbol_diff.rs b/objdiff-gui/src/views/symbol_diff.rs index a8c3f5a..65deef2 100644 --- a/objdiff-gui/src/views/symbol_diff.rs +++ b/objdiff-gui/src/views/symbol_diff.rs @@ -13,7 +13,7 @@ use objdiff_core::{ use regex::{Regex, RegexBuilder}; use crate::{ - app::AppConfigRef, + app::AppStateRef, jobs::{ create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult}, objdiff::{BuildStatus, ObjDiffResult}, @@ -64,7 +64,7 @@ pub struct SymbolViewState { } impl DiffViewState { - pub fn pre_update(&mut self, jobs: &mut JobQueue, config: &AppConfigRef) { + pub fn pre_update(&mut self, jobs: &mut JobQueue, state: &AppStateRef) { jobs.results.retain_mut(|result| match result { JobResult::ObjDiff(result) => { self.build = take(result); @@ -80,26 +80,26 @@ impl DiffViewState { self.scratch_running = jobs.is_running(Job::CreateScratch); self.symbol_state.disable_reverse_fn_order = false; - if let Ok(config) = config.read() { - if let Some(obj_config) = &config.selected_obj { + if let Ok(state) = state.read() { + if let Some(obj_config) = &state.config.selected_obj { if let Some(value) = obj_config.reverse_fn_order { self.symbol_state.reverse_fn_order = value; self.symbol_state.disable_reverse_fn_order = true; } } - self.scratch_available = CreateScratchConfig::is_available(&config); + self.scratch_available = CreateScratchConfig::is_available(&state.config); } } - pub fn post_update(&mut self, ctx: &egui::Context, jobs: &mut JobQueue, config: &AppConfigRef) { + pub fn post_update(&mut self, ctx: &egui::Context, jobs: &mut JobQueue, state: &AppStateRef) { if let Some(result) = take(&mut self.scratch) { ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url))); } if self.queue_build { self.queue_build = false; - if let Ok(mut config) = config.write() { - config.queue_build = true; + if let Ok(mut state) = state.write() { + state.queue_build = true; } } @@ -108,8 +108,8 @@ impl DiffViewState { if let Some(function_name) = self.symbol_state.selected_symbol.as_ref().map(|sym| sym.symbol_name.clone()) { - if let Ok(config) = config.read() { - match CreateScratchConfig::from_config(&config, function_name) { + if let Ok(state) = state.read() { + match CreateScratchConfig::from_config(&state.config, function_name) { Ok(config) => { jobs.push_once(Job::CreateScratch, || { start_create_scratch(ctx, config)