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.
This commit is contained in:
Luke Street 2024-09-28 10:55:22 -06:00
parent b0123b3f83
commit 8fc142d316
4 changed files with 273 additions and 257 deletions

View File

@ -7,6 +7,7 @@ use std::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Mutex, RwLock, Arc, Mutex, RwLock,
}, },
time::Instant,
}; };
use filetime::FileTime; use filetime::FileTime;
@ -82,6 +83,36 @@ fn default_watch_patterns() -> Vec<Glob> {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect() DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
} }
pub struct AppState {
pub config: AppConfig,
pub objects: Vec<ProjectObject>,
pub object_nodes: Vec<ProjectObjectNode>,
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<ProjectConfigInfo>,
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)] #[derive(Clone, serde::Deserialize, serde::Serialize)]
pub struct AppConfig { pub struct AppConfig {
// TODO: https://github.com/ron-rs/ron/pull/455 // TODO: https://github.com/ron-rs/ron/pull/455
@ -116,23 +147,6 @@ pub struct AppConfig {
pub recent_projects: Vec<PathBuf>, pub recent_projects: Vec<PathBuf>,
#[serde(default)] #[serde(default)]
pub diff_obj_config: DiffObjConfig, pub diff_obj_config: DiffObjConfig,
#[serde(skip)]
pub objects: Vec<ProjectObject>,
#[serde(skip)]
pub object_nodes: Vec<ProjectObjectNode>,
#[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<ProjectConfigInfo>,
} }
impl Default for AppConfig { 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(), watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
recent_projects: vec![], recent_projects: vec![],
diff_obj_config: Default::default(), 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) { pub fn set_project_dir(&mut self, path: PathBuf) {
self.recent_projects.retain(|p| p != &path); self.config.recent_projects.retain(|p| p != &path);
if self.recent_projects.len() > 9 { if self.config.recent_projects.len() > 9 {
self.recent_projects.truncate(9); self.config.recent_projects.truncate(9);
} }
self.recent_projects.insert(0, path.clone()); self.config.recent_projects.insert(0, path.clone());
self.project_dir = Some(path); self.config.project_dir = Some(path);
self.target_obj_dir = None; self.config.target_obj_dir = None;
self.base_obj_dir = None; self.config.base_obj_dir = None;
self.selected_obj = None; self.config.selected_obj = None;
self.build_target = false; self.config.build_target = false;
self.objects.clear(); self.objects.clear();
self.object_nodes.clear(); self.object_nodes.clear();
self.watcher_change = true; self.watcher_change = true;
@ -187,33 +193,33 @@ impl AppConfig {
} }
pub fn set_target_obj_dir(&mut self, path: PathBuf) { pub fn set_target_obj_dir(&mut self, path: PathBuf) {
self.target_obj_dir = Some(path); self.config.target_obj_dir = Some(path);
self.selected_obj = None; self.config.selected_obj = None;
self.obj_change = true; self.obj_change = true;
self.queue_build = false; self.queue_build = false;
} }
pub fn set_base_obj_dir(&mut self, path: PathBuf) { pub fn set_base_obj_dir(&mut self, path: PathBuf) {
self.base_obj_dir = Some(path); self.config.base_obj_dir = Some(path);
self.selected_obj = None; self.config.selected_obj = None;
self.obj_change = true; self.obj_change = true;
self.queue_build = false; self.queue_build = false;
} }
pub fn set_selected_obj(&mut self, object: ObjectConfig) { 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.obj_change = true;
self.queue_build = false; self.queue_build = false;
} }
} }
pub type AppConfigRef = Arc<RwLock<AppConfig>>; pub type AppStateRef = Arc<RwLock<AppState>>;
#[derive(Default)] #[derive(Default)]
pub struct App { pub struct App {
appearance: Appearance, appearance: Appearance,
view_state: ViewState, view_state: ViewState,
config: AppConfigRef, state: AppStateRef,
modified: Arc<AtomicBool>, modified: Arc<AtomicBool>,
watcher: Option<notify::RecommendedWatcher>, watcher: Option<notify::RecommendedWatcher>,
app_path: Option<PathBuf>, app_path: Option<PathBuf>,
@ -241,16 +247,17 @@ impl App {
if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) { if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) {
app.appearance = appearance; app.appearance = appearance;
} }
if let Some(mut config) = deserialize_config(storage) { if let Some(config) = deserialize_config(storage) {
if config.project_dir.is_some() { let mut state = AppState { config, ..Default::default() };
config.config_change = true; if state.config.project_dir.is_some() {
config.watcher_change = true; state.config_change = true;
state.watcher_change = true;
} }
if config.selected_obj.is_some() { if state.config.selected_obj.is_some() {
config.queue_build = true; state.queue_build = true;
} }
app.view_state.config_state.queue_check_update = config.auto_update_check; app.view_state.config_state.queue_check_update = state.config.auto_update_check;
app.config = Arc::new(RwLock::new(config)); app.state = Arc::new(RwLock::new(state));
} }
} }
app.appearance.init_fonts(&cc.egui_ctx); app.appearance.init_fonts(&cc.egui_ctx);
@ -336,8 +343,8 @@ impl App {
jobs.results.append(&mut results); jobs.results.append(&mut results);
jobs.clear_finished(); jobs.clear_finished();
diff_state.pre_update(jobs, &self.config); diff_state.pre_update(jobs, &self.state);
config_state.pre_update(jobs, &self.config); config_state.pre_update(jobs, &self.state);
debug_assert!(jobs.results.is_empty()); debug_assert!(jobs.results.is_empty());
} }
@ -345,23 +352,23 @@ impl App {
self.appearance.post_update(ctx); self.appearance.post_update(ctx);
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state; let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
config_state.post_update(ctx, jobs, &self.config); config_state.post_update(ctx, jobs, &self.state);
diff_state.post_update(ctx, jobs, &self.config); 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; 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) { if file_modified(&info.path, info.timestamp) {
config.config_change = true; state.config_change = true;
} }
} }
if config.config_change { if state.config_change {
config.config_change = false; state.config_change = false;
match load_project_config(config) { match load_project_config(state) {
Ok(()) => config_state.load_error = None, Ok(()) => config_state.load_error = None,
Err(e) => { Err(e) => {
log::error!("Failed to load project config: {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()); drop(self.watcher.take());
if let Some(project_dir) = &config.project_dir { if let Some(project_dir) = &state.config.project_dir {
match build_globset(&config.watch_patterns).map_err(anyhow::Error::new).and_then( match build_globset(&state.config.watch_patterns)
|globset| { .map_err(anyhow::Error::new)
.and_then(|globset| {
create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset) create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset)
.map_err(anyhow::Error::new) .map_err(anyhow::Error::new)
}, }) {
) {
Ok(watcher) => self.watcher = Some(watcher), Ok(watcher) => self.watcher = Some(watcher),
Err(e) => log::error!("Failed to create watcher: {e}"), 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(); *diff_state = Default::default();
if config.selected_obj.is_some() { if state.config.selected_obj.is_some() {
config.queue_build = true; state.queue_build = true;
} }
config.obj_change = false; state.obj_change = false;
} }
if self.modified.swap(false, Ordering::Relaxed) && config.rebuild_on_changes { if self.modified.swap(false, Ordering::Relaxed) && state.config.rebuild_on_changes {
config.queue_build = true; state.queue_build = true;
} }
if let Some(result) = &diff_state.build { if let Some(result) = &diff_state.build {
if let Some((obj, _)) = &result.first_obj { if state.last_mod_check.elapsed().as_millis() >= 500 {
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { state.last_mod_check = Instant::now();
if file_modified(path, timestamp) { if let Some((obj, _)) = &result.first_obj {
config.queue_reload = true; 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((obj, _)) = &result.second_obj { if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) { if file_modified(path, timestamp) {
if file_modified(path, timestamp) { state.queue_reload = true;
config.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 // 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. // 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) { if state.queue_build
jobs.push(start_build(ctx, ObjDiffConfig::from_config(config))); && state.config.selected_obj.is_some()
config.queue_build = false; && !jobs.is_running(Job::ObjDiff)
config.queue_reload = false; {
} else if config.queue_reload && !jobs.is_running(Job::ObjDiff) { jobs.push(start_build(ctx, ObjDiffConfig::from_config(&state.config)));
let mut diff_config = ObjDiffConfig::from_config(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 // Don't build, just reload the current files
diff_config.build_base = false; diff_config.build_base = false;
diff_config.build_target = false; diff_config.build_target = false;
jobs.push(start_build(ctx, diff_config)); jobs.push(start_build(ctx, diff_config));
config.queue_reload = false; state.queue_reload = false;
} }
if graphics_state.should_relaunch { if graphics_state.should_relaunch {
@ -453,7 +466,7 @@ impl eframe::App for App {
self.pre_update(ctx); self.pre_update(ctx);
let Self { config, appearance, view_state, .. } = self; let Self { state, appearance, view_state, .. } = self;
let ViewState { let ViewState {
jobs, jobs,
config_state, config_state,
@ -485,8 +498,8 @@ impl eframe::App for App {
*show_project_config = !*show_project_config; *show_project_config = !*show_project_config;
ui.close_menu(); ui.close_menu();
} }
let recent_projects = if let Ok(guard) = config.read() { let recent_projects = if let Ok(guard) = state.read() {
guard.recent_projects.clone() guard.config.recent_projects.clone()
} else { } else {
vec![] vec![]
}; };
@ -495,12 +508,12 @@ impl eframe::App for App {
} else { } else {
ui.menu_button("Recent Projects…", |ui| { ui.menu_button("Recent Projects…", |ui| {
if ui.button("Clear").clicked() { if ui.button("Clear").clicked() {
config.write().unwrap().recent_projects.clear(); state.write().unwrap().config.recent_projects.clear();
}; };
ui.separator(); ui.separator();
for path in recent_projects { for path in recent_projects {
if ui.button(format!("{}", path.display())).clicked() { if ui.button(format!("{}", path.display())).clicked() {
config.write().unwrap().set_project_dir(path); state.write().unwrap().set_project_dir(path);
ui.close_menu(); ui.close_menu();
} }
} }
@ -533,12 +546,12 @@ impl eframe::App for App {
*show_arch_config = !*show_arch_config; *show_arch_config = !*show_arch_config;
ui.close_menu(); ui.close_menu();
} }
let mut config = config.write().unwrap(); let mut state = state.write().unwrap();
let response = ui 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."); .on_hover_text("Automatically re-run the build & diff when files change.");
if response.changed() { if response.changed() {
config.watcher_change = true; state.watcher_change = true;
}; };
ui.add_enabled( ui.add_enabled(
!diff_state.symbol_state.disable_reverse_fn_order, !diff_state.symbol_state.disable_reverse_fn_order,
@ -554,7 +567,7 @@ impl eframe::App for App {
); );
if ui if ui
.checkbox( .checkbox(
&mut config.diff_obj_config.relax_reloc_diffs, &mut state.config.diff_obj_config.relax_reloc_diffs,
"Relax relocation diffs", "Relax relocation diffs",
) )
.on_hover_text( .on_hover_text(
@ -562,26 +575,26 @@ impl eframe::App for App {
) )
.changed() .changed()
{ {
config.queue_reload = true; state.queue_reload = true;
} }
if ui if ui
.checkbox( .checkbox(
&mut config.diff_obj_config.space_between_args, &mut state.config.diff_obj_config.space_between_args,
"Space between args", "Space between args",
) )
.changed() .changed()
{ {
config.queue_reload = true; state.queue_reload = true;
} }
if ui if ui
.checkbox( .checkbox(
&mut config.diff_obj_config.combine_data_sections, &mut state.config.diff_obj_config.combine_data_sections,
"Combine data sections", "Combine data sections",
) )
.on_hover_text("Combines data sections with equal names.") .on_hover_text("Combines data sections with equal names.")
.changed() .changed()
{ {
config.queue_reload = true; state.queue_reload = true;
} }
}); });
}); });
@ -603,7 +616,7 @@ impl eframe::App for App {
} else { } else {
egui::SidePanel::left("side_panel").show(ctx, |ui| { egui::SidePanel::left("side_panel").show(ctx, |ui| {
egui::ScrollArea::both().show(ui, |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); 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); appearance_window(ctx, show_appearance_config, appearance);
demangle_window(ctx, show_demangle, demangle_state, appearance); demangle_window(ctx, show_demangle, demangle_state, appearance);
rlwinm_decode_window(ctx, show_rlwinm_decode, rlwinm_decode_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); debug_window(ctx, show_debug, frame_history, appearance);
graphics_window(ctx, show_graphics, frame_history, graphics_state, 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. /// Called by the frame work to save state before shutdown.
fn save(&mut self, storage: &mut dyn eframe::Storage) { fn save(&mut self, storage: &mut dyn eframe::Storage) {
if let Ok(config) = self.config.read() { if let Ok(state) = self.state.read() {
eframe::set_value(storage, CONFIG_KEY, &*config); eframe::set_value(storage, CONFIG_KEY, &state.config);
} }
eframe::set_value(storage, APPEARANCE_KEY, &self.appearance); eframe::set_value(storage, APPEARANCE_KEY, &self.appearance);
} }

View File

@ -4,7 +4,7 @@ use anyhow::Result;
use globset::Glob; use globset::Glob;
use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS}; use objdiff_core::config::{try_project_config, ProjectObject, DEFAULT_WATCH_PATTERNS};
use crate::app::AppConfig; use crate::app::AppState;
#[derive(Clone)] #[derive(Clone)]
pub enum ProjectObjectNode { pub enum ProjectObjectNode {
@ -64,30 +64,30 @@ fn build_nodes(
nodes nodes
} }
pub fn load_project_config(config: &mut AppConfig) -> Result<()> { pub fn load_project_config(state: &mut AppState) -> Result<()> {
let Some(project_dir) = &config.project_dir else { let Some(project_dir) = &state.config.project_dir else {
return Ok(()); return Ok(());
}; };
if let Some((result, info)) = try_project_config(project_dir) { if let Some((result, info)) = try_project_config(project_dir) {
let project_config = result?; let project_config = result?;
config.custom_make = project_config.custom_make; state.config.custom_make = project_config.custom_make;
config.custom_args = project_config.custom_args; state.config.custom_args = project_config.custom_args;
config.target_obj_dir = project_config.target_dir.map(|p| project_dir.join(p)); state.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)); state.config.base_obj_dir = project_config.base_dir.map(|p| project_dir.join(p));
config.build_base = project_config.build_base; state.config.build_base = project_config.build_base;
config.build_target = project_config.build_target; state.config.build_target = project_config.build_target;
config.watch_patterns = project_config.watch_patterns.unwrap_or_else(|| { state.config.watch_patterns = project_config.watch_patterns.unwrap_or_else(|| {
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect() DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
}); });
config.watcher_change = true; state.watcher_change = true;
config.objects = project_config.objects; state.objects = project_config.objects;
config.object_nodes = build_nodes( state.object_nodes = build_nodes(
&config.objects, &state.objects,
project_dir, project_dir,
config.target_obj_dir.as_deref(), state.config.target_obj_dir.as_deref(),
config.base_obj_dir.as_deref(), state.config.base_obj_dir.as_deref(),
); );
config.project_config_info = Some(info); state.project_config_info = Some(info);
} }
Ok(()) Ok(())
} }

View File

@ -19,7 +19,7 @@ use objdiff_core::{
use strum::{EnumMessage, VariantArray}; use strum::{EnumMessage, VariantArray};
use crate::{ use crate::{
app::{AppConfig, AppConfigRef, ObjectConfig}, app::{AppConfig, AppState, AppStateRef, ObjectConfig},
config::ProjectObjectNode, config::ProjectObjectNode,
jobs::{ jobs::{
check_update::{start_check_update, CheckUpdateResult}, check_update::{start_check_update, CheckUpdateResult},
@ -54,7 +54,7 @@ pub struct ConfigViewState {
} }
impl 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| { jobs.results.retain_mut(|result| {
if let JobResult::CheckUpdate(result) = result { if let JobResult::CheckUpdate(result) = result {
self.check_update = take(result); self.check_update = take(result);
@ -71,21 +71,21 @@ impl ConfigViewState {
match self.file_dialog_state.poll() { match self.file_dialog_state.poll() {
FileDialogResult::None => {} FileDialogResult::None => {}
FileDialogResult::ProjectDir(path) => { FileDialogResult::ProjectDir(path) => {
let mut guard = config.write().unwrap(); let mut guard = state.write().unwrap();
guard.set_project_dir(path.to_path_buf()); guard.set_project_dir(path.to_path_buf());
} }
FileDialogResult::TargetDir(path) => { FileDialogResult::TargetDir(path) => {
let mut guard = config.write().unwrap(); let mut guard = state.write().unwrap();
guard.set_target_obj_dir(path.to_path_buf()); guard.set_target_obj_dir(path.to_path_buf());
} }
FileDialogResult::BaseDir(path) => { FileDialogResult::BaseDir(path) => {
let mut guard = config.write().unwrap(); let mut guard = state.write().unwrap();
guard.set_base_obj_dir(path.to_path_buf()); guard.set_base_obj_dir(path.to_path_buf());
} }
FileDialogResult::Object(path) => { FileDialogResult::Object(path) => {
let mut guard = config.write().unwrap(); let mut guard = state.write().unwrap();
if let (Some(base_dir), Some(target_dir)) = 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) { if let Ok(obj_path) = path.strip_prefix(base_dir) {
let target_path = target_dir.join(obj_path); 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 { if self.queue_build {
self.queue_build = false; self.queue_build = false;
if let Ok(mut config) = config.write() { if let Ok(mut state) = state.write() {
config.queue_build = true; state.queue_build = true;
} }
} }
@ -167,42 +167,40 @@ fn fetch_wsl2_distros() -> Vec<String> {
pub fn config_ui( pub fn config_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
config: &AppConfigRef, state: &AppStateRef,
show_config_window: &mut bool, show_config_window: &mut bool,
state: &mut ConfigViewState, config_state: &mut ConfigViewState,
appearance: &Appearance, appearance: &Appearance,
) { ) {
let mut config_guard = config.write().unwrap(); let mut state_guard = state.write().unwrap();
let AppConfig { let AppState {
target_obj_dir, config: AppConfig { target_obj_dir, base_obj_dir, selected_obj, auto_update_check, .. },
base_obj_dir,
selected_obj,
auto_update_check,
objects, objects,
object_nodes, object_nodes,
.. ..
} = &mut *config_guard; } = &mut *state_guard;
ui.heading("Updates"); ui.heading("Updates");
ui.checkbox(auto_update_check, "Check for updates on startup"); ui.checkbox(auto_update_check, "Check for updates on startup");
if ui.add_enabled(!state.check_update_running, egui::Button::new("Check now")).clicked() { if ui.add_enabled(!config_state.check_update_running, egui::Button::new("Check now")).clicked()
state.queue_check_update = true; {
config_state.queue_check_update = true;
} }
ui.label(format!("Current version: {}", env!("CARGO_PKG_VERSION"))); 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)); ui.label(format!("Latest version: {}", result.latest_release.version));
if result.update_available { if result.update_available {
ui.colored_label(appearance.insert_color, "Update available"); ui.colored_label(appearance.insert_color, "Update available");
ui.horizontal(|ui| { ui.horizontal(|ui| {
if let Some(bin_name) = &result.found_binary { if let Some(bin_name) = &result.found_binary {
if ui 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( .on_hover_text_at_pointer(
"Automatically download and replace the current build", "Automatically download and replace the current build",
) )
.clicked() .clicked()
{ {
state.queue_update = Some(bin_name.clone()); config_state.queue_update = Some(bin_name.clone());
} }
} }
if ui if ui
@ -231,7 +229,7 @@ pub fn config_ui(
if objects.is_empty() { if objects.is_empty() {
if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) { if let (Some(_base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
if ui.button("Select object").clicked() { if ui.button("Select object").clicked() {
state.file_dialog_state.queue( config_state.file_dialog_state.queue(
|| { || {
Box::pin( Box::pin(
rfd::AsyncFileDialog::new() rfd::AsyncFileDialog::new()
@ -254,8 +252,8 @@ pub fn config_ui(
ui.colored_label(appearance.delete_color, "Missing project settings"); ui.colored_label(appearance.delete_color, "Missing project settings");
} }
} else { } else {
let had_search = !state.object_search.is_empty(); let had_search = !config_state.object_search.is_empty();
egui::TextEdit::singleline(&mut state.object_search).hint_text("Filter").ui(ui); egui::TextEdit::singleline(&mut config_state.object_search).hint_text("Filter").ui(ui);
let mut root_open = None; let mut root_open = None;
let mut node_open = NodeOpen::Default; let mut node_open = NodeOpen::Default;
@ -277,19 +275,22 @@ pub fn config_ui(
node_open = NodeOpen::Object; node_open = NodeOpen::Object;
} }
let mut filters_text = RichText::new("Filter ⏷"); 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); filters_text = filters_text.color(appearance.replace_color);
} }
egui::menu::menu_button(ui, filters_text, |ui| { 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"); .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"); .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"); .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 { if had_search {
root_open = Some(true); root_open = Some(true);
node_open = NodeOpen::Object; node_open = NodeOpen::Object;
@ -306,15 +307,15 @@ pub fn config_ui(
.open(root_open) .open(root_open)
.default_open(true) .default_open(true)
.show(ui, |ui| { .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); ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
for node in object_nodes.iter().filter_map(|node| { for node in object_nodes.iter().filter_map(|node| {
filter_node( filter_node(
node, node,
&search, &search,
state.filter_diffable, config_state.filter_diffable,
state.filter_incomplete, config_state.filter_incomplete,
state.show_hidden, config_state.show_hidden,
) )
}) { }) {
display_node(ui, &mut new_selected_obj, &node, appearance, node_open); 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 new_selected_obj != *selected_obj {
if let Some(obj) = new_selected_obj { if let Some(obj) = new_selected_obj {
// Will set obj_changed, which will trigger a rebuild // 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() if state_guard.config.selected_obj.is_some()
&& ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() && ui.add_enabled(!config_state.build_running, egui::Button::new("Build")).clicked()
{ {
state.queue_build = true; config_state.queue_build = true;
} }
ui.separator(); ui.separator();
@ -523,33 +524,33 @@ fn pick_folder_ui(
pub fn project_window( pub fn project_window(
ctx: &egui::Context, ctx: &egui::Context,
config: &AppConfigRef, state: &AppStateRef,
show: &mut bool, show: &mut bool,
state: &mut ConfigViewState, config_state: &mut ConfigViewState,
appearance: &Appearance, 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| { 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; let mut open = true;
egui::Window::new("Error").open(&mut open).show(ctx, |ui| { egui::Window::new("Error").open(&mut open).show(ctx, |ui| {
ui.label("Failed to load project config:"); ui.label("Failed to load project config:");
ui.colored_label(appearance.delete_color, error); ui.colored_label(appearance.delete_color, error);
}); });
if !open { if !open {
state.load_error = None; config_state.load_error = None;
} }
} }
} }
fn split_obj_config_ui( fn split_obj_config_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
config: &mut AppConfig, state: &mut AppState,
state: &mut ConfigViewState, config_state: &mut ConfigViewState,
appearance: &Appearance, appearance: &Appearance,
) { ) {
let text_format = TextFormat::simple(appearance.ui_font.clone(), appearance.text_color); 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( let response = pick_folder_ui(
ui, ui,
&config.project_dir, &state.config.project_dir,
"Project directory", "Project directory",
|ui| { |ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
@ -576,7 +577,7 @@ fn split_obj_config_ui(
true, true,
); );
if response.clicked() { if response.clicked() {
state.file_dialog_state.queue( config_state.file_dialog_state.queue(
|| Box::pin(rfd::AsyncFileDialog::new().pick_folder()), || Box::pin(rfd::AsyncFileDialog::new().pick_folder()),
FileDialogResult::ProjectDir, FileDialogResult::ProjectDir,
); );
@ -605,33 +606,35 @@ fn split_obj_config_ui(
ui.label(job); 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 if ui
.add_enabled( .add_enabled(
config.project_config_info.is_none(), state.project_config_info.is_none(),
egui::TextEdit::singleline(&mut custom_make_str).hint_text("make"), egui::TextEdit::singleline(&mut custom_make_str).hint_text("make"),
) )
.on_disabled_hover_text(CONFIG_DISABLED_TEXT) .on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.changed() .changed()
{ {
if custom_make_str.is_empty() { if custom_make_str.is_empty() {
config.custom_make = None; state.config.custom_make = None;
} else { } else {
config.custom_make = Some(custom_make_str); state.config.custom_make = Some(custom_make_str);
} }
} }
#[cfg(all(windows, feature = "wsl"))] #[cfg(all(windows, feature = "wsl"))]
{ {
if state.available_wsl_distros.is_none() { if config_state.available_wsl_distros.is_none() {
state.available_wsl_distros = Some(fetch_wsl2_distros()); config_state.available_wsl_distros = Some(fetch_wsl2_distros());
} }
egui::ComboBox::from_label("Run in WSL2") 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| { .show_ui(ui, |ui| {
ui.selectable_value(&mut config.selected_wsl_distro, None, "Disabled"); ui.selectable_value(&mut state.config.selected_wsl_distro, None, "Disabled");
for distro in state.available_wsl_distros.as_ref().unwrap() { for distro in config_state.available_wsl_distros.as_ref().unwrap() {
ui.selectable_value( ui.selectable_value(
&mut config.selected_wsl_distro, &mut state.config.selected_wsl_distro,
Some(distro.clone()), Some(distro.clone()),
distro, distro,
); );
@ -640,10 +643,10 @@ fn split_obj_config_ui(
} }
ui.separator(); 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( let response = pick_folder_ui(
ui, ui,
&config.target_obj_dir, &state.config.target_obj_dir,
"Target build directory", "Target build directory",
|ui| { |ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
@ -660,17 +663,17 @@ fn split_obj_config_ui(
ui.label(job); ui.label(job);
}, },
appearance, appearance,
config.project_config_info.is_none(), state.project_config_info.is_none(),
); );
if response.clicked() { 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()), || Box::pin(rfd::AsyncFileDialog::new().set_directory(&project_dir).pick_folder()),
FileDialogResult::TargetDir, FileDialogResult::TargetDir,
); );
} }
ui.add_enabled( ui.add_enabled(
config.project_config_info.is_none(), state.project_config_info.is_none(),
egui::Checkbox::new(&mut config.build_target, "Build target objects"), egui::Checkbox::new(&mut state.config.build_target, "Build target objects"),
) )
.on_disabled_hover_text(CONFIG_DISABLED_TEXT) .on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.on_hover_ui(|ui| { .on_hover_ui(|ui| {
@ -704,7 +707,7 @@ fn split_obj_config_ui(
let response = pick_folder_ui( let response = pick_folder_ui(
ui, ui,
&config.base_obj_dir, &state.config.base_obj_dir,
"Base build directory", "Base build directory",
|ui| { |ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
@ -716,17 +719,17 @@ fn split_obj_config_ui(
ui.label(job); ui.label(job);
}, },
appearance, appearance,
config.project_config_info.is_none(), state.project_config_info.is_none(),
); );
if response.clicked() { 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()), || Box::pin(rfd::AsyncFileDialog::new().set_directory(&project_dir).pick_folder()),
FileDialogResult::BaseDir, FileDialogResult::BaseDir,
); );
} }
ui.add_enabled( ui.add_enabled(
config.project_config_info.is_none(), state.project_config_info.is_none(),
egui::Checkbox::new(&mut config.build_base, "Build base objects"), egui::Checkbox::new(&mut state.config.build_base, "Build base objects"),
) )
.on_disabled_hover_text(CONFIG_DISABLED_TEXT) .on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.on_hover_ui(|ui| { .on_hover_ui(|ui| {
@ -757,7 +760,7 @@ fn split_obj_config_ui(
subheading(ui, "Watch settings", appearance); subheading(ui, "Watch settings", appearance);
let response = 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(); let mut job = LayoutJob::default();
job.append( job.append(
"Automatically re-run the build & diff when files change.", "Automatically re-run the build & diff when files change.",
@ -767,23 +770,23 @@ fn split_obj_config_ui(
ui.label(job); ui.label(job);
}); });
if response.changed() { if response.changed() {
config.watcher_change = true; state.watcher_change = true;
}; };
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label(RichText::new("File patterns").color(appearance.text_color)); ui.label(RichText::new("File patterns").color(appearance.text_color));
if ui 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) .on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.clicked() .clicked()
{ {
config.watch_patterns = state.config.watch_patterns =
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(); DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
config.watcher_change = true; state.watcher_change = true;
} }
}); });
let mut remove_at: Option<usize> = None; let mut remove_at: Option<usize> = None;
for (idx, glob) in config.watch_patterns.iter().enumerate() { for (idx, glob) in state.config.watch_patterns.iter().enumerate() {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label( ui.label(
RichText::new(format!("{}", glob)) RichText::new(format!("{}", glob))
@ -791,7 +794,7 @@ fn split_obj_config_ui(
.family(FontFamily::Monospace), .family(FontFamily::Monospace),
); );
if ui 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) .on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.clicked() .clicked()
{ {
@ -800,24 +803,24 @@ fn split_obj_config_ui(
}); });
} }
if let Some(idx) = remove_at { if let Some(idx) = remove_at {
config.watch_patterns.remove(idx); state.config.watch_patterns.remove(idx);
config.watcher_change = true; state.watcher_change = true;
} }
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.add_enabled( ui.add_enabled(
config.project_config_info.is_none(), state.project_config_info.is_none(),
egui::TextEdit::singleline(&mut state.watch_pattern_text).desired_width(100.0), egui::TextEdit::singleline(&mut config_state.watch_pattern_text).desired_width(100.0),
) )
.on_disabled_hover_text(CONFIG_DISABLED_TEXT); .on_disabled_hover_text(CONFIG_DISABLED_TEXT);
if ui 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) .on_disabled_hover_text(CONFIG_DISABLED_TEXT)
.clicked() .clicked()
{ {
if let Ok(glob) = Glob::new(&state.watch_pattern_text) { if let Ok(glob) = Glob::new(&config_state.watch_pattern_text) {
config.watch_patterns.push(glob); state.config.watch_patterns.push(glob);
config.watcher_change = true; state.watcher_change = true;
state.watch_pattern_text.clear(); config_state.watch_pattern_text.clear();
} }
} }
}); });
@ -825,131 +828,131 @@ fn split_obj_config_ui(
pub fn arch_config_window( pub fn arch_config_window(
ctx: &egui::Context, ctx: &egui::Context,
config: &AppConfigRef, state: &AppStateRef,
show: &mut bool, show: &mut bool,
appearance: &Appearance, 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| { 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"); ui.heading("x86");
egui::ComboBox::new("x86_formatter", "Format") 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| { .show_ui(ui, |ui| {
for &formatter in X86Formatter::VARIANTS { for &formatter in X86Formatter::VARIANTS {
if ui if ui
.selectable_label( .selectable_label(
config.diff_obj_config.x86_formatter == formatter, state.config.diff_obj_config.x86_formatter == formatter,
formatter.get_message().unwrap(), formatter.get_message().unwrap(),
) )
.clicked() .clicked()
{ {
config.diff_obj_config.x86_formatter = formatter; state.config.diff_obj_config.x86_formatter = formatter;
config.queue_reload = true; state.queue_reload = true;
} }
} }
}); });
ui.separator(); ui.separator();
ui.heading("MIPS"); ui.heading("MIPS");
egui::ComboBox::new("mips_abi", "ABI") 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| { .show_ui(ui, |ui| {
for &abi in MipsAbi::VARIANTS { for &abi in MipsAbi::VARIANTS {
if ui if ui
.selectable_label( .selectable_label(
config.diff_obj_config.mips_abi == abi, state.config.diff_obj_config.mips_abi == abi,
abi.get_message().unwrap(), abi.get_message().unwrap(),
) )
.clicked() .clicked()
{ {
config.diff_obj_config.mips_abi = abi; state.config.diff_obj_config.mips_abi = abi;
config.queue_reload = true; state.queue_reload = true;
} }
} }
}); });
egui::ComboBox::new("mips_instr_category", "Instruction Category") 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| { .show_ui(ui, |ui| {
for &category in MipsInstrCategory::VARIANTS { for &category in MipsInstrCategory::VARIANTS {
if ui if ui
.selectable_label( .selectable_label(
config.diff_obj_config.mips_instr_category == category, state.config.diff_obj_config.mips_instr_category == category,
category.get_message().unwrap(), category.get_message().unwrap(),
) )
.clicked() .clicked()
{ {
config.diff_obj_config.mips_instr_category = category; state.config.diff_obj_config.mips_instr_category = category;
config.queue_reload = true; state.queue_reload = true;
} }
} }
}); });
ui.separator(); ui.separator();
ui.heading("ARM"); ui.heading("ARM");
egui::ComboBox::new("arm_arch_version", "Architecture Version") 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| { .show_ui(ui, |ui| {
for &version in ArmArchVersion::VARIANTS { for &version in ArmArchVersion::VARIANTS {
if ui if ui
.selectable_label( .selectable_label(
config.diff_obj_config.arm_arch_version == version, state.config.diff_obj_config.arm_arch_version == version,
version.get_message().unwrap(), version.get_message().unwrap(),
) )
.clicked() .clicked()
{ {
config.diff_obj_config.arm_arch_version = version; state.config.diff_obj_config.arm_arch_version = version;
config.queue_reload = true; state.queue_reload = true;
} }
} }
}); });
let response = ui 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)."); .on_hover_text("Disassemble as unified assembly language (UAL).");
if response.changed() { if response.changed() {
config.queue_reload = true; state.queue_reload = true;
} }
let response = ui 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"); .on_hover_text("Display R0-R3 as A1-A4 and R4-R11 as V1-V8");
if response.changed() { if response.changed() {
config.queue_reload = true; state.queue_reload = true;
} }
egui::ComboBox::new("arm_r9_usage", "Display R9 as") 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| { .show_ui(ui, |ui| {
for &usage in ArmR9Usage::VARIANTS { for &usage in ArmR9Usage::VARIANTS {
if ui if ui
.selectable_label( .selectable_label(
config.diff_obj_config.arm_r9_usage == usage, state.config.diff_obj_config.arm_r9_usage == usage,
usage.get_message().unwrap(), usage.get_message().unwrap(),
) )
.on_hover_text(usage.get_detailed_message().unwrap()) .on_hover_text(usage.get_detailed_message().unwrap())
.clicked() .clicked()
{ {
config.diff_obj_config.arm_r9_usage = usage; state.config.diff_obj_config.arm_r9_usage = usage;
config.queue_reload = true; state.queue_reload = true;
} }
} }
}); });
let response = ui 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."); .on_hover_text("Used for explicit stack limits.");
if response.changed() { if response.changed() {
config.queue_reload = true; state.queue_reload = true;
} }
let response = ui 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."); .on_hover_text("Used for frame pointers.");
if response.changed() { if response.changed() {
config.queue_reload = true; state.queue_reload = true;
} }
let response = ui 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."); .on_hover_text("Used for interworking and long branches.");
if response.changed() { if response.changed() {
config.queue_reload = true; state.queue_reload = true;
} }
} }

View File

@ -13,7 +13,7 @@ use objdiff_core::{
use regex::{Regex, RegexBuilder}; use regex::{Regex, RegexBuilder};
use crate::{ use crate::{
app::AppConfigRef, app::AppStateRef,
jobs::{ jobs::{
create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult}, create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult},
objdiff::{BuildStatus, ObjDiffResult}, objdiff::{BuildStatus, ObjDiffResult},
@ -64,7 +64,7 @@ pub struct SymbolViewState {
} }
impl DiffViewState { 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 { jobs.results.retain_mut(|result| match result {
JobResult::ObjDiff(result) => { JobResult::ObjDiff(result) => {
self.build = take(result); self.build = take(result);
@ -80,26 +80,26 @@ impl DiffViewState {
self.scratch_running = jobs.is_running(Job::CreateScratch); self.scratch_running = jobs.is_running(Job::CreateScratch);
self.symbol_state.disable_reverse_fn_order = false; self.symbol_state.disable_reverse_fn_order = false;
if let Ok(config) = config.read() { if let Ok(state) = state.read() {
if let Some(obj_config) = &config.selected_obj { if let Some(obj_config) = &state.config.selected_obj {
if let Some(value) = obj_config.reverse_fn_order { if let Some(value) = obj_config.reverse_fn_order {
self.symbol_state.reverse_fn_order = value; self.symbol_state.reverse_fn_order = value;
self.symbol_state.disable_reverse_fn_order = true; 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) { if let Some(result) = take(&mut self.scratch) {
ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url))); ctx.output_mut(|o| o.open_url = Some(OpenUrl::new_tab(result.scratch_url)));
} }
if self.queue_build { if self.queue_build {
self.queue_build = false; self.queue_build = false;
if let Ok(mut config) = config.write() { if let Ok(mut state) = state.write() {
config.queue_build = true; state.queue_build = true;
} }
} }
@ -108,8 +108,8 @@ impl DiffViewState {
if let Some(function_name) = if let Some(function_name) =
self.symbol_state.selected_symbol.as_ref().map(|sym| sym.symbol_name.clone()) self.symbol_state.selected_symbol.as_ref().map(|sym| sym.symbol_name.clone())
{ {
if let Ok(config) = config.read() { if let Ok(state) = state.read() {
match CreateScratchConfig::from_config(&config, function_name) { match CreateScratchConfig::from_config(&state.config, function_name) {
Ok(config) => { Ok(config) => {
jobs.push_once(Job::CreateScratch, || { jobs.push_once(Job::CreateScratch, || {
start_create_scratch(ctx, config) start_create_scratch(ctx, config)