mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-12 14:46:12 +00:00
Experimental objdiff-cli diff auto-rebuild
This commit is contained in:
@@ -11,24 +11,22 @@ use std::{
|
||||
};
|
||||
|
||||
use filetime::FileTime;
|
||||
use globset::{Glob, GlobSet};
|
||||
use notify::{RecursiveMode, Watcher};
|
||||
use globset::Glob;
|
||||
use objdiff_core::{
|
||||
build::watcher::{create_watcher, Watcher},
|
||||
config::{
|
||||
build_globset, save_project_config, ProjectConfig, ProjectConfigInfo, ProjectObject,
|
||||
ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
||||
build_globset, default_watch_patterns, save_project_config, ProjectConfig,
|
||||
ProjectConfigInfo, ProjectObject, ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
||||
},
|
||||
diff::DiffObjConfig,
|
||||
jobs::{Job, JobQueue, JobResult},
|
||||
};
|
||||
use time::UtcOffset;
|
||||
|
||||
use crate::{
|
||||
app_config::{deserialize_config, AppConfigVersion},
|
||||
config::{load_project_config, ProjectObjectNode},
|
||||
jobs::{
|
||||
objdiff::{start_build, ObjDiffConfig},
|
||||
Job, JobQueue, JobResult, JobStatus,
|
||||
},
|
||||
jobs::{create_objdiff_config, egui_waker, start_build},
|
||||
views::{
|
||||
appearance::{appearance_window, Appearance},
|
||||
config::{
|
||||
@@ -121,11 +119,6 @@ impl From<&ProjectObject> for ObjectConfig {
|
||||
#[inline]
|
||||
fn bool_true() -> bool { true }
|
||||
|
||||
#[inline]
|
||||
fn default_watch_patterns() -> Vec<Glob> {
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub config: AppConfig,
|
||||
pub objects: Vec<ProjectObject>,
|
||||
@@ -399,7 +392,7 @@ pub struct App {
|
||||
view_state: ViewState,
|
||||
state: AppStateRef,
|
||||
modified: Arc<AtomicBool>,
|
||||
watcher: Option<notify::RecommendedWatcher>,
|
||||
watcher: Option<Watcher>,
|
||||
app_path: Option<PathBuf>,
|
||||
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
||||
should_relaunch: bool,
|
||||
@@ -474,53 +467,17 @@ impl App {
|
||||
|
||||
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
|
||||
|
||||
let mut results = vec![];
|
||||
for (job, result) in jobs.iter_finished() {
|
||||
match result {
|
||||
Ok(result) => {
|
||||
log::info!("Job {} finished", job.id);
|
||||
match result {
|
||||
JobResult::None => {
|
||||
if let Some(err) = &job.context.status.read().unwrap().error {
|
||||
log::error!("{:?}", err);
|
||||
}
|
||||
}
|
||||
JobResult::Update(state) => {
|
||||
if let Ok(mut guard) = self.relaunch_path.lock() {
|
||||
*guard = Some(state.exe_path);
|
||||
self.should_relaunch = true;
|
||||
}
|
||||
}
|
||||
_ => results.push(result),
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
|
||||
anyhow::Error::msg(*msg)
|
||||
} else if let Some(msg) = err.downcast_ref::<String>() {
|
||||
anyhow::Error::msg(msg.clone())
|
||||
} else {
|
||||
anyhow::Error::msg("Thread panicked")
|
||||
};
|
||||
let result = job.context.status.write();
|
||||
if let Ok(mut guard) = result {
|
||||
guard.error = Some(err);
|
||||
} else {
|
||||
drop(result);
|
||||
job.context.status = Arc::new(RwLock::new(JobStatus {
|
||||
title: "Error".to_string(),
|
||||
progress_percent: 0.0,
|
||||
progress_items: None,
|
||||
status: String::new(),
|
||||
error: Some(err),
|
||||
}));
|
||||
}
|
||||
jobs.collect_results();
|
||||
jobs.results.retain(|result| match result {
|
||||
JobResult::Update(state) => {
|
||||
if let Ok(mut guard) = self.relaunch_path.lock() {
|
||||
*guard = Some(state.exe_path.clone());
|
||||
self.should_relaunch = true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
jobs.results.append(&mut results);
|
||||
jobs.clear_finished();
|
||||
|
||||
_ => true,
|
||||
});
|
||||
diff_state.pre_update(jobs, &self.state);
|
||||
config_state.pre_update(jobs, &self.state);
|
||||
debug_assert!(jobs.results.is_empty());
|
||||
@@ -572,7 +529,7 @@ impl App {
|
||||
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)
|
||||
create_watcher(self.modified.clone(), project_dir, globset, egui_waker(ctx))
|
||||
.map_err(anyhow::Error::new)
|
||||
}) {
|
||||
Ok(watcher) => self.watcher = Some(watcher),
|
||||
@@ -619,15 +576,15 @@ impl App {
|
||||
&& state.config.selected_obj.is_some()
|
||||
&& !jobs.is_running(Job::ObjDiff)
|
||||
{
|
||||
jobs.push(start_build(ctx, ObjDiffConfig::from_state(state)));
|
||||
start_build(ctx, jobs, create_objdiff_config(state));
|
||||
state.queue_build = false;
|
||||
state.queue_reload = false;
|
||||
} else if state.queue_reload && !jobs.is_running(Job::ObjDiff) {
|
||||
let mut diff_config = ObjDiffConfig::from_state(state);
|
||||
let mut diff_config = create_objdiff_config(state);
|
||||
// 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));
|
||||
start_build(ctx, jobs, diff_config);
|
||||
state.queue_reload = false;
|
||||
}
|
||||
|
||||
@@ -854,40 +811,6 @@ impl eframe::App for App {
|
||||
}
|
||||
}
|
||||
|
||||
fn create_watcher(
|
||||
ctx: egui::Context,
|
||||
modified: Arc<AtomicBool>,
|
||||
project_dir: &Path,
|
||||
patterns: GlobSet,
|
||||
) -> notify::Result<notify::RecommendedWatcher> {
|
||||
let base_dir = project_dir.to_owned();
|
||||
let mut watcher =
|
||||
notify::recommended_watcher(move |res: notify::Result<notify::Event>| match res {
|
||||
Ok(event) => {
|
||||
if matches!(
|
||||
event.kind,
|
||||
notify::EventKind::Modify(..)
|
||||
| notify::EventKind::Create(..)
|
||||
| notify::EventKind::Remove(..)
|
||||
) {
|
||||
for path in &event.paths {
|
||||
let Ok(path) = path.strip_prefix(&base_dir) else {
|
||||
continue;
|
||||
};
|
||||
if patterns.is_match(path) {
|
||||
log::info!("File modified: {}", path.display());
|
||||
modified.store(true, Ordering::Relaxed);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("watch error: {e:?}"),
|
||||
})?;
|
||||
watcher.watch(project_dir, RecursiveMode::Recursive)?;
|
||||
Ok(watcher)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn file_modified(path: &Path, last_ts: FileTime) -> bool {
|
||||
if let Ok(metadata) = fs::metadata(path) {
|
||||
|
||||
138
objdiff-gui/src/jobs.rs
Normal file
138
objdiff-gui/src/jobs.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use std::{
|
||||
sync::Arc,
|
||||
task::{Wake, Waker},
|
||||
};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use jobs::create_scratch;
|
||||
use objdiff_core::{
|
||||
build::BuildConfig,
|
||||
jobs,
|
||||
jobs::{check_update::CheckUpdateConfig, objdiff, update::UpdateConfig, Job, JobQueue},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
app::{AppConfig, AppState},
|
||||
update::{build_updater, BIN_NAME_NEW, BIN_NAME_OLD},
|
||||
};
|
||||
|
||||
struct EguiWaker(egui::Context);
|
||||
|
||||
impl Wake for EguiWaker {
|
||||
fn wake(self: Arc<Self>) { self.0.request_repaint(); }
|
||||
|
||||
fn wake_by_ref(self: &Arc<Self>) { self.0.request_repaint(); }
|
||||
}
|
||||
|
||||
pub fn egui_waker(ctx: &egui::Context) -> Waker { Waker::from(Arc::new(EguiWaker(ctx.clone()))) }
|
||||
|
||||
pub fn is_create_scratch_available(config: &AppConfig) -> bool {
|
||||
let Some(selected_obj) = &config.selected_obj else {
|
||||
return false;
|
||||
};
|
||||
selected_obj.target_path.is_some() && selected_obj.scratch.is_some()
|
||||
}
|
||||
|
||||
pub fn start_create_scratch(
|
||||
ctx: &egui::Context,
|
||||
jobs: &mut JobQueue,
|
||||
state: &AppState,
|
||||
function_name: String,
|
||||
) {
|
||||
match create_scratch_config(state, function_name) {
|
||||
Ok(config) => {
|
||||
jobs.push_once(Job::CreateScratch, || {
|
||||
create_scratch::start_create_scratch(egui_waker(ctx), config)
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to create scratch config: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_scratch_config(
|
||||
state: &AppState,
|
||||
function_name: String,
|
||||
) -> Result<create_scratch::CreateScratchConfig> {
|
||||
let Some(selected_obj) = &state.config.selected_obj else {
|
||||
bail!("No object selected");
|
||||
};
|
||||
let Some(target_path) = &selected_obj.target_path else {
|
||||
bail!("No target path for {}", selected_obj.name);
|
||||
};
|
||||
let Some(scratch_config) = &selected_obj.scratch else {
|
||||
bail!("No scratch configuration for {}", selected_obj.name);
|
||||
};
|
||||
Ok(create_scratch::CreateScratchConfig {
|
||||
build_config: BuildConfig::from(&state.config),
|
||||
context_path: scratch_config.ctx_path.clone(),
|
||||
build_context: scratch_config.build_ctx.unwrap_or(false),
|
||||
compiler: scratch_config.compiler.clone().unwrap_or_default(),
|
||||
platform: scratch_config.platform.clone().unwrap_or_default(),
|
||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
||||
function_name,
|
||||
target_obj: target_path.to_path_buf(),
|
||||
preset_id: scratch_config.preset_id,
|
||||
})
|
||||
}
|
||||
|
||||
impl From<&AppConfig> for BuildConfig {
|
||||
fn from(config: &AppConfig) -> Self {
|
||||
Self {
|
||||
project_dir: config.project_dir.clone(),
|
||||
custom_make: config.custom_make.clone(),
|
||||
custom_args: config.custom_args.clone(),
|
||||
selected_wsl_distro: config.selected_wsl_distro.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_objdiff_config(state: &AppState) -> objdiff::ObjDiffConfig {
|
||||
objdiff::ObjDiffConfig {
|
||||
build_config: BuildConfig::from(&state.config),
|
||||
build_base: state.config.build_base,
|
||||
build_target: state.config.build_target,
|
||||
target_path: state
|
||||
.config
|
||||
.selected_obj
|
||||
.as_ref()
|
||||
.and_then(|obj| obj.target_path.as_ref())
|
||||
.cloned(),
|
||||
base_path: state
|
||||
.config
|
||||
.selected_obj
|
||||
.as_ref()
|
||||
.and_then(|obj| obj.base_path.as_ref())
|
||||
.cloned(),
|
||||
diff_obj_config: state.config.diff_obj_config.clone(),
|
||||
symbol_mappings: state
|
||||
.config
|
||||
.selected_obj
|
||||
.as_ref()
|
||||
.map(|obj| &obj.symbol_mappings)
|
||||
.cloned()
|
||||
.unwrap_or_default(),
|
||||
selecting_left: state.selecting_left.clone(),
|
||||
selecting_right: state.selecting_right.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_build(ctx: &egui::Context, jobs: &mut JobQueue, config: objdiff::ObjDiffConfig) {
|
||||
jobs.push_once(Job::ObjDiff, || objdiff::start_build(egui_waker(ctx), config));
|
||||
}
|
||||
|
||||
pub fn start_check_update(ctx: &egui::Context, jobs: &mut JobQueue) {
|
||||
jobs.push_once(Job::Update, || {
|
||||
jobs::check_update::start_check_update(egui_waker(ctx), CheckUpdateConfig {
|
||||
build_updater,
|
||||
bin_names: vec![BIN_NAME_NEW.to_string(), BIN_NAME_OLD.to_string()],
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn start_update(ctx: &egui::Context, jobs: &mut JobQueue, bin_name: String) {
|
||||
jobs.push_once(Job::Update, || {
|
||||
jobs::update::start_update(egui_waker(ctx), UpdateConfig { build_updater, bin_name })
|
||||
});
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use std::sync::mpsc::Receiver;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use self_update::{cargo_crate_version, update::Release};
|
||||
|
||||
use crate::{
|
||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||
update::{build_updater, BIN_NAME_NEW, BIN_NAME_OLD},
|
||||
};
|
||||
|
||||
pub struct CheckUpdateResult {
|
||||
pub update_available: bool,
|
||||
pub latest_release: Release,
|
||||
pub found_binary: Option<String>,
|
||||
}
|
||||
|
||||
fn run_check_update(context: &JobContext, cancel: Receiver<()>) -> Result<Box<CheckUpdateResult>> {
|
||||
update_status(context, "Fetching latest release".to_string(), 0, 1, &cancel)?;
|
||||
let updater = build_updater().context("Failed to create release updater")?;
|
||||
let latest_release = updater.get_latest_release()?;
|
||||
let update_available =
|
||||
self_update::version::bump_is_greater(cargo_crate_version!(), &latest_release.version)?;
|
||||
// Find the binary name in the release assets
|
||||
let found_binary = latest_release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|a| a.name == BIN_NAME_NEW)
|
||||
.or_else(|| latest_release.assets.iter().find(|a| a.name == BIN_NAME_OLD))
|
||||
.map(|a| a.name.clone());
|
||||
|
||||
update_status(context, "Complete".to_string(), 1, 1, &cancel)?;
|
||||
Ok(Box::new(CheckUpdateResult { update_available, latest_release, found_binary }))
|
||||
}
|
||||
|
||||
pub fn start_check_update(ctx: &egui::Context) -> JobState {
|
||||
start_job(ctx, "Check for updates", Job::CheckUpdate, move |context, cancel| {
|
||||
run_check_update(&context, cancel).map(|result| JobResult::CheckUpdate(Some(result)))
|
||||
})
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
use std::{fs, path::PathBuf, sync::mpsc::Receiver};
|
||||
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use const_format::formatcp;
|
||||
|
||||
use crate::{
|
||||
app::AppConfig,
|
||||
jobs::{
|
||||
objdiff::{run_make, BuildConfig, BuildStatus},
|
||||
start_job, update_status, Job, JobContext, JobResult, JobState,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CreateScratchConfig {
|
||||
pub build_config: BuildConfig,
|
||||
pub context_path: Option<PathBuf>,
|
||||
pub build_context: bool,
|
||||
|
||||
// Scratch fields
|
||||
pub compiler: String,
|
||||
pub platform: String,
|
||||
pub compiler_flags: String,
|
||||
pub function_name: String,
|
||||
pub target_obj: PathBuf,
|
||||
pub preset_id: Option<u32>,
|
||||
}
|
||||
|
||||
impl CreateScratchConfig {
|
||||
pub(crate) fn from_config(config: &AppConfig, function_name: String) -> Result<Self> {
|
||||
let Some(selected_obj) = &config.selected_obj else {
|
||||
bail!("No object selected");
|
||||
};
|
||||
let Some(target_path) = &selected_obj.target_path else {
|
||||
bail!("No target path for {}", selected_obj.name);
|
||||
};
|
||||
let Some(scratch_config) = &selected_obj.scratch else {
|
||||
bail!("No scratch configuration for {}", selected_obj.name);
|
||||
};
|
||||
Ok(Self {
|
||||
build_config: BuildConfig::from_config(config),
|
||||
context_path: scratch_config.ctx_path.clone(),
|
||||
build_context: scratch_config.build_ctx.unwrap_or(false),
|
||||
compiler: scratch_config.compiler.clone().unwrap_or_default(),
|
||||
platform: scratch_config.platform.clone().unwrap_or_default(),
|
||||
compiler_flags: scratch_config.c_flags.clone().unwrap_or_default(),
|
||||
function_name,
|
||||
target_obj: target_path.to_path_buf(),
|
||||
preset_id: scratch_config.preset_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_available(config: &AppConfig) -> bool {
|
||||
let Some(selected_obj) = &config.selected_obj else {
|
||||
return false;
|
||||
};
|
||||
selected_obj.target_path.is_some() && selected_obj.scratch.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct CreateScratchResult {
|
||||
pub scratch_url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, serde::Deserialize)]
|
||||
struct CreateScratchResponse {
|
||||
pub slug: String,
|
||||
pub claim_token: String,
|
||||
}
|
||||
|
||||
const API_HOST: &str = "https://decomp.me";
|
||||
|
||||
fn run_create_scratch(
|
||||
status: &JobContext,
|
||||
cancel: Receiver<()>,
|
||||
config: CreateScratchConfig,
|
||||
) -> Result<Box<CreateScratchResult>> {
|
||||
let project_dir =
|
||||
config.build_config.project_dir.as_ref().ok_or_else(|| anyhow!("Missing project dir"))?;
|
||||
|
||||
let mut context = None;
|
||||
if let Some(context_path) = &config.context_path {
|
||||
if config.build_context {
|
||||
update_status(status, "Building context".to_string(), 0, 2, &cancel)?;
|
||||
match run_make(&config.build_config, context_path) {
|
||||
BuildStatus { success: true, .. } => {}
|
||||
BuildStatus { success: false, stdout, stderr, .. } => {
|
||||
bail!("Failed to build context:\n{stdout}\n{stderr}")
|
||||
}
|
||||
}
|
||||
}
|
||||
let context_path = project_dir.join(context_path);
|
||||
context = Some(
|
||||
fs::read_to_string(&context_path)
|
||||
.map_err(|e| anyhow!("Failed to read {}: {}", context_path.display(), e))?,
|
||||
);
|
||||
}
|
||||
|
||||
update_status(status, "Creating scratch".to_string(), 1, 2, &cancel)?;
|
||||
let diff_flags = [format!("--disassemble={}", config.function_name)];
|
||||
let diff_flags = serde_json::to_string(&diff_flags).unwrap();
|
||||
let obj_path = project_dir.join(&config.target_obj);
|
||||
let file = reqwest::blocking::multipart::Part::file(&obj_path)
|
||||
.with_context(|| format!("Failed to open {}", obj_path.display()))?;
|
||||
let mut form = reqwest::blocking::multipart::Form::new()
|
||||
.text("compiler", config.compiler.clone())
|
||||
.text("platform", config.platform.clone())
|
||||
.text("compiler_flags", config.compiler_flags.clone())
|
||||
.text("diff_label", config.function_name.clone())
|
||||
.text("diff_flags", diff_flags)
|
||||
.text("context", context.unwrap_or_default())
|
||||
.text("source_code", "// Move related code from Context tab to here");
|
||||
if let Some(preset) = config.preset_id {
|
||||
form = form.text("preset", preset.to_string());
|
||||
}
|
||||
form = form.part("target_obj", file);
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = client
|
||||
.post(formatcp!("{API_HOST}/api/scratch"))
|
||||
.multipart(form)
|
||||
.send()
|
||||
.map_err(|e| anyhow!("Failed to send request: {}", e))?;
|
||||
if !response.status().is_success() {
|
||||
return Err(anyhow!("Failed to create scratch: {}", response.text()?));
|
||||
}
|
||||
let body: CreateScratchResponse = response.json().context("Failed to parse response")?;
|
||||
let scratch_url = format!("{API_HOST}/scratch/{}/claim?token={}", body.slug, body.claim_token);
|
||||
|
||||
update_status(status, "Complete".to_string(), 2, 2, &cancel)?;
|
||||
Ok(Box::from(CreateScratchResult { scratch_url }))
|
||||
}
|
||||
|
||||
pub fn start_create_scratch(ctx: &egui::Context, config: CreateScratchConfig) -> JobState {
|
||||
start_job(ctx, "Create scratch", Job::CreateScratch, move |context, cancel| {
|
||||
run_create_scratch(&context, cancel, config)
|
||||
.map(|result| JobResult::CreateScratch(Some(result)))
|
||||
})
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc::{Receiver, Sender, TryRecvError},
|
||||
Arc, RwLock,
|
||||
},
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::jobs::{
|
||||
check_update::CheckUpdateResult, create_scratch::CreateScratchResult, objdiff::ObjDiffResult,
|
||||
update::UpdateResult,
|
||||
};
|
||||
|
||||
pub mod check_update;
|
||||
pub mod create_scratch;
|
||||
pub mod objdiff;
|
||||
pub mod update;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
||||
pub enum Job {
|
||||
ObjDiff,
|
||||
CheckUpdate,
|
||||
Update,
|
||||
CreateScratch,
|
||||
}
|
||||
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JobQueue {
|
||||
pub jobs: Vec<JobState>,
|
||||
pub results: Vec<JobResult>,
|
||||
}
|
||||
|
||||
impl JobQueue {
|
||||
/// Adds a job to the queue.
|
||||
#[inline]
|
||||
pub fn push(&mut self, state: JobState) { self.jobs.push(state); }
|
||||
|
||||
/// Adds a job to the queue if a job of the given kind is not already running.
|
||||
#[inline]
|
||||
pub fn push_once(&mut self, job: Job, func: impl FnOnce() -> JobState) {
|
||||
if !self.is_running(job) {
|
||||
self.push(func());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether a job of the given kind is running.
|
||||
pub fn is_running(&self, kind: Job) -> bool {
|
||||
self.jobs.iter().any(|j| j.kind == kind && j.handle.is_some())
|
||||
}
|
||||
|
||||
/// Returns whether any job is running.
|
||||
#[expect(dead_code)]
|
||||
pub fn any_running(&self) -> bool {
|
||||
self.jobs.iter().any(|job| {
|
||||
if let Some(handle) = &job.handle {
|
||||
return !handle.is_finished();
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Iterates over all jobs mutably.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut JobState> + '_ { self.jobs.iter_mut() }
|
||||
|
||||
/// Iterates over all finished jobs, returning the job state and the result.
|
||||
pub fn iter_finished(
|
||||
&mut self,
|
||||
) -> impl Iterator<Item = (&mut JobState, std::thread::Result<JobResult>)> + '_ {
|
||||
self.jobs.iter_mut().filter_map(|job| {
|
||||
if let Some(handle) = &job.handle {
|
||||
if !handle.is_finished() {
|
||||
return None;
|
||||
}
|
||||
let result = job.handle.take().unwrap().join();
|
||||
return Some((job, result));
|
||||
}
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Clears all finished jobs.
|
||||
pub fn clear_finished(&mut self) {
|
||||
self.jobs.retain(|job| {
|
||||
!(job.handle.is_none() && job.context.status.read().unwrap().error.is_none())
|
||||
});
|
||||
}
|
||||
|
||||
/// Clears all errored jobs.
|
||||
pub fn clear_errored(&mut self) {
|
||||
self.jobs.retain(|job| job.context.status.read().unwrap().error.is_none());
|
||||
}
|
||||
|
||||
/// Removes a job from the queue given its ID.
|
||||
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct JobContext {
|
||||
pub status: Arc<RwLock<JobStatus>>,
|
||||
pub egui: egui::Context,
|
||||
}
|
||||
|
||||
pub struct JobState {
|
||||
pub id: usize,
|
||||
pub kind: Job,
|
||||
pub handle: Option<JoinHandle<JobResult>>,
|
||||
pub context: JobContext,
|
||||
pub cancel: Sender<()>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct JobStatus {
|
||||
pub title: String,
|
||||
pub progress_percent: f32,
|
||||
pub progress_items: Option<[u32; 2]>,
|
||||
pub status: String,
|
||||
pub error: Option<anyhow::Error>,
|
||||
}
|
||||
|
||||
pub enum JobResult {
|
||||
None,
|
||||
ObjDiff(Option<Box<ObjDiffResult>>),
|
||||
CheckUpdate(Option<Box<CheckUpdateResult>>),
|
||||
Update(Box<UpdateResult>),
|
||||
CreateScratch(Option<Box<CreateScratchResult>>),
|
||||
}
|
||||
|
||||
fn should_cancel(rx: &Receiver<()>) -> bool {
|
||||
match rx.try_recv() {
|
||||
Ok(_) | Err(TryRecvError::Disconnected) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_job(
|
||||
ctx: &egui::Context,
|
||||
title: &str,
|
||||
kind: Job,
|
||||
run: impl FnOnce(JobContext, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
||||
) -> JobState {
|
||||
let status = Arc::new(RwLock::new(JobStatus {
|
||||
title: title.to_string(),
|
||||
progress_percent: 0.0,
|
||||
progress_items: None,
|
||||
status: String::new(),
|
||||
error: None,
|
||||
}));
|
||||
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let handle = std::thread::spawn(move || match run(context_inner, rx) {
|
||||
Ok(state) => state,
|
||||
Err(e) => {
|
||||
if let Ok(mut w) = status.write() {
|
||||
w.error = Some(e);
|
||||
}
|
||||
JobResult::None
|
||||
}
|
||||
});
|
||||
let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
|
||||
log::info!("Started job {}", id);
|
||||
JobState { id, kind, handle: Some(handle), context, cancel: tx }
|
||||
}
|
||||
|
||||
fn update_status(
|
||||
context: &JobContext,
|
||||
str: String,
|
||||
count: u32,
|
||||
total: u32,
|
||||
cancel: &Receiver<()>,
|
||||
) -> Result<()> {
|
||||
let mut w =
|
||||
context.status.write().map_err(|_| anyhow::Error::msg("Failed to lock job status"))?;
|
||||
w.progress_items = Some([count, total]);
|
||||
w.progress_percent = count as f32 / total as f32;
|
||||
if should_cancel(cancel) {
|
||||
w.status = "Cancelled".to_string();
|
||||
return Err(anyhow::Error::msg("Cancelled"));
|
||||
} else {
|
||||
w.status = str;
|
||||
}
|
||||
drop(w);
|
||||
context.egui.request_repaint();
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,328 +0,0 @@
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::mpsc::Receiver,
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Error, Result};
|
||||
use objdiff_core::{
|
||||
diff::{diff_objs, DiffObjConfig, MappingConfig, ObjDiff},
|
||||
obj::{read, ObjInfo},
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
use crate::{
|
||||
app::{AppConfig, AppState, ObjectConfig},
|
||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||
};
|
||||
|
||||
pub struct BuildStatus {
|
||||
pub success: bool,
|
||||
pub cmdline: String,
|
||||
pub stdout: String,
|
||||
pub stderr: String,
|
||||
}
|
||||
|
||||
impl Default for BuildStatus {
|
||||
fn default() -> Self {
|
||||
BuildStatus {
|
||||
success: true,
|
||||
cmdline: String::new(),
|
||||
stdout: String::new(),
|
||||
stderr: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BuildConfig {
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub custom_make: Option<String>,
|
||||
pub custom_args: Option<Vec<String>>,
|
||||
#[allow(unused)]
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
}
|
||||
|
||||
impl BuildConfig {
|
||||
pub(crate) fn from_config(config: &AppConfig) -> Self {
|
||||
Self {
|
||||
project_dir: config.project_dir.clone(),
|
||||
custom_make: config.custom_make.clone(),
|
||||
custom_args: config.custom_args.clone(),
|
||||
selected_wsl_distro: config.selected_wsl_distro.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ObjDiffConfig {
|
||||
pub build_config: BuildConfig,
|
||||
pub build_base: bool,
|
||||
pub build_target: bool,
|
||||
pub selected_obj: Option<ObjectConfig>,
|
||||
pub diff_obj_config: DiffObjConfig,
|
||||
pub selecting_left: Option<String>,
|
||||
pub selecting_right: Option<String>,
|
||||
}
|
||||
|
||||
impl ObjDiffConfig {
|
||||
pub(crate) fn from_state(state: &AppState) -> Self {
|
||||
Self {
|
||||
build_config: BuildConfig::from_config(&state.config),
|
||||
build_base: state.config.build_base,
|
||||
build_target: state.config.build_target,
|
||||
selected_obj: state.config.selected_obj.clone(),
|
||||
diff_obj_config: state.config.diff_obj_config.clone(),
|
||||
selecting_left: state.selecting_left.clone(),
|
||||
selecting_right: state.selecting_right.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ObjDiffResult {
|
||||
pub first_status: BuildStatus,
|
||||
pub second_status: BuildStatus,
|
||||
pub first_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub second_obj: Option<(ObjInfo, ObjDiff)>,
|
||||
pub time: OffsetDateTime,
|
||||
}
|
||||
|
||||
pub(crate) fn run_make(config: &BuildConfig, arg: &Path) -> BuildStatus {
|
||||
let Some(cwd) = &config.project_dir else {
|
||||
return BuildStatus {
|
||||
success: false,
|
||||
stderr: "Missing project dir".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
};
|
||||
let make = config.custom_make.as_deref().unwrap_or("make");
|
||||
let make_args = config.custom_args.as_deref().unwrap_or(&[]);
|
||||
#[cfg(not(windows))]
|
||||
let mut command = {
|
||||
let mut command = Command::new(make);
|
||||
command.current_dir(cwd).args(make_args).arg(arg);
|
||||
command
|
||||
};
|
||||
#[cfg(windows)]
|
||||
let mut command = {
|
||||
use std::os::windows::process::CommandExt;
|
||||
|
||||
use path_slash::PathExt;
|
||||
let mut command = if config.selected_wsl_distro.is_some() {
|
||||
Command::new("wsl")
|
||||
} else {
|
||||
Command::new(make)
|
||||
};
|
||||
if let Some(distro) = &config.selected_wsl_distro {
|
||||
// Strip distro root prefix \\wsl.localhost\{distro}
|
||||
let wsl_path_prefix = format!("\\\\wsl.localhost\\{}", distro);
|
||||
let cwd = match cwd.strip_prefix(wsl_path_prefix) {
|
||||
Ok(new_cwd) => format!("/{}", new_cwd.to_slash_lossy().as_ref()),
|
||||
Err(_) => cwd.to_string_lossy().to_string(),
|
||||
};
|
||||
|
||||
command
|
||||
.arg("--cd")
|
||||
.arg(cwd)
|
||||
.arg("-d")
|
||||
.arg(distro)
|
||||
.arg("--")
|
||||
.arg(make)
|
||||
.args(make_args)
|
||||
.arg(arg.to_slash_lossy().as_ref());
|
||||
} else {
|
||||
command.current_dir(cwd).args(make_args).arg(arg.to_slash_lossy().as_ref());
|
||||
}
|
||||
command.creation_flags(winapi::um::winbase::CREATE_NO_WINDOW);
|
||||
command
|
||||
};
|
||||
let mut cmdline = shell_escape::escape(command.get_program().to_string_lossy()).into_owned();
|
||||
for arg in command.get_args() {
|
||||
cmdline.push(' ');
|
||||
cmdline.push_str(shell_escape::escape(arg.to_string_lossy()).as_ref());
|
||||
}
|
||||
let output = match command.output() {
|
||||
Ok(output) => output,
|
||||
Err(e) => {
|
||||
return BuildStatus {
|
||||
success: false,
|
||||
cmdline,
|
||||
stdout: Default::default(),
|
||||
stderr: e.to_string(),
|
||||
};
|
||||
}
|
||||
};
|
||||
// Try from_utf8 first to avoid copying the buffer if it's valid, then fall back to from_utf8_lossy
|
||||
let stdout = String::from_utf8(output.stdout)
|
||||
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
|
||||
let stderr = String::from_utf8(output.stderr)
|
||||
.unwrap_or_else(|e| String::from_utf8_lossy(e.as_bytes()).into_owned());
|
||||
BuildStatus { success: output.status.success(), cmdline, stdout, stderr }
|
||||
}
|
||||
|
||||
fn run_build(
|
||||
context: &JobContext,
|
||||
cancel: Receiver<()>,
|
||||
mut config: ObjDiffConfig,
|
||||
) -> Result<Box<ObjDiffResult>> {
|
||||
let obj_config = config.selected_obj.ok_or_else(|| Error::msg("Missing obj path"))?;
|
||||
// Use the per-object symbol mappings, we don't set mappings globally
|
||||
config.diff_obj_config.symbol_mappings = MappingConfig {
|
||||
mappings: obj_config.symbol_mappings,
|
||||
selecting_left: config.selecting_left,
|
||||
selecting_right: config.selecting_right,
|
||||
};
|
||||
|
||||
let project_dir = config
|
||||
.build_config
|
||||
.project_dir
|
||||
.as_ref()
|
||||
.ok_or_else(|| Error::msg("Missing project dir"))?;
|
||||
let target_path_rel = if let Some(target_path) = &obj_config.target_path {
|
||||
Some(target_path.strip_prefix(project_dir).map_err(|_| {
|
||||
anyhow!(
|
||||
"Target path '{}' doesn't begin with '{}'",
|
||||
target_path.display(),
|
||||
project_dir.display()
|
||||
)
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let base_path_rel = if let Some(base_path) = &obj_config.base_path {
|
||||
Some(base_path.strip_prefix(project_dir).map_err(|_| {
|
||||
anyhow!(
|
||||
"Base path '{}' doesn't begin with '{}'",
|
||||
base_path.display(),
|
||||
project_dir.display()
|
||||
)
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut total = 1;
|
||||
if config.build_target && target_path_rel.is_some() {
|
||||
total += 1;
|
||||
}
|
||||
if config.build_base && base_path_rel.is_some() {
|
||||
total += 1;
|
||||
}
|
||||
if target_path_rel.is_some() {
|
||||
total += 1;
|
||||
}
|
||||
if base_path_rel.is_some() {
|
||||
total += 1;
|
||||
}
|
||||
|
||||
let mut step_idx = 0;
|
||||
let mut first_status = match target_path_rel {
|
||||
Some(target_path_rel) if config.build_target => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Building target {}", target_path_rel.display()),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
run_make(&config.build_config, target_path_rel)
|
||||
}
|
||||
_ => BuildStatus::default(),
|
||||
};
|
||||
|
||||
let mut second_status = match base_path_rel {
|
||||
Some(base_path_rel) if config.build_base => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Building base {}", base_path_rel.display()),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
run_make(&config.build_config, base_path_rel)
|
||||
}
|
||||
_ => BuildStatus::default(),
|
||||
};
|
||||
|
||||
let time = OffsetDateTime::now_utc();
|
||||
|
||||
let first_obj = match &obj_config.target_path {
|
||||
Some(target_path) if first_status.success => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Loading target {}", target_path_rel.unwrap().display()),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
match read::read(target_path, &config.diff_obj_config) {
|
||||
Ok(obj) => Some(obj),
|
||||
Err(e) => {
|
||||
first_status = BuildStatus {
|
||||
success: false,
|
||||
stdout: format!("Loading object '{}'", target_path.display()),
|
||||
stderr: format!("{:#}", e),
|
||||
..Default::default()
|
||||
};
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
step_idx += 1;
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let second_obj = match &obj_config.base_path {
|
||||
Some(base_path) if second_status.success => {
|
||||
update_status(
|
||||
context,
|
||||
format!("Loading base {}", base_path_rel.unwrap().display()),
|
||||
step_idx,
|
||||
total,
|
||||
&cancel,
|
||||
)?;
|
||||
step_idx += 1;
|
||||
match read::read(base_path, &config.diff_obj_config) {
|
||||
Ok(obj) => Some(obj),
|
||||
Err(e) => {
|
||||
second_status = BuildStatus {
|
||||
success: false,
|
||||
stdout: format!("Loading object '{}'", base_path.display()),
|
||||
stderr: format!("{:#}", e),
|
||||
..Default::default()
|
||||
};
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(_) => {
|
||||
step_idx += 1;
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
update_status(context, "Performing diff".to_string(), step_idx, total, &cancel)?;
|
||||
step_idx += 1;
|
||||
let result = diff_objs(&config.diff_obj_config, first_obj.as_ref(), second_obj.as_ref(), None)?;
|
||||
|
||||
update_status(context, "Complete".to_string(), step_idx, total, &cancel)?;
|
||||
Ok(Box::new(ObjDiffResult {
|
||||
first_status,
|
||||
second_status,
|
||||
first_obj: first_obj.and_then(|o| result.left.map(|d| (o, d))),
|
||||
second_obj: second_obj.and_then(|o| result.right.map(|d| (o, d))),
|
||||
time,
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn start_build(ctx: &egui::Context, config: ObjDiffConfig) -> JobState {
|
||||
start_job(ctx, "Build", Job::ObjDiff, move |context, cancel| {
|
||||
run_build(&context, cancel, config).map(|result| JobResult::ObjDiff(Some(result)))
|
||||
})
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
use std::{
|
||||
env::{current_dir, current_exe},
|
||||
fs::File,
|
||||
path::PathBuf,
|
||||
sync::mpsc::Receiver,
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::{
|
||||
jobs::{start_job, update_status, Job, JobContext, JobResult, JobState},
|
||||
update::build_updater,
|
||||
};
|
||||
|
||||
pub struct UpdateResult {
|
||||
pub exe_path: PathBuf,
|
||||
}
|
||||
|
||||
fn run_update(
|
||||
status: &JobContext,
|
||||
cancel: Receiver<()>,
|
||||
bin_name: String,
|
||||
) -> Result<Box<UpdateResult>> {
|
||||
update_status(status, "Fetching latest release".to_string(), 0, 3, &cancel)?;
|
||||
let updater = build_updater().context("Failed to create release updater")?;
|
||||
let latest_release = updater.get_latest_release()?;
|
||||
let asset = latest_release
|
||||
.assets
|
||||
.iter()
|
||||
.find(|a| a.name == bin_name)
|
||||
.ok_or_else(|| anyhow::Error::msg(format!("No release asset for {bin_name}")))?;
|
||||
|
||||
update_status(status, "Downloading release".to_string(), 1, 3, &cancel)?;
|
||||
let tmp_dir = tempfile::Builder::new().prefix("update").tempdir_in(current_dir()?)?;
|
||||
let tmp_path = tmp_dir.path().join(&asset.name);
|
||||
let tmp_file = File::create(&tmp_path)?;
|
||||
self_update::Download::from_url(&asset.download_url)
|
||||
.set_header(reqwest::header::ACCEPT, "application/octet-stream".parse()?)
|
||||
.download_to(tmp_file)?;
|
||||
|
||||
update_status(status, "Extracting release".to_string(), 2, 3, &cancel)?;
|
||||
let tmp_file = tmp_dir.path().join("replacement_tmp");
|
||||
let target_file = current_exe()?;
|
||||
self_update::Move::from_source(&tmp_path)
|
||||
.replace_using_temp(&tmp_file)
|
||||
.to_dest(&target_file)?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::{fs, os::unix::fs::PermissionsExt};
|
||||
let mut perms = fs::metadata(&target_file)?.permissions();
|
||||
perms.set_mode(0o755);
|
||||
fs::set_permissions(&target_file, perms)?;
|
||||
}
|
||||
tmp_dir.close()?;
|
||||
|
||||
update_status(status, "Complete".to_string(), 3, 3, &cancel)?;
|
||||
Ok(Box::from(UpdateResult { exe_path: target_file }))
|
||||
}
|
||||
|
||||
pub fn start_update(ctx: &egui::Context, bin_name: String) -> JobState {
|
||||
start_job(ctx, "Update app", Job::Update, move |context, cancel| {
|
||||
run_update(&context, cancel, bin_name).map(JobResult::Update)
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use anyhow::Result;
|
||||
use cfg_if::cfg_if;
|
||||
use const_format::formatcp;
|
||||
use objdiff_core::jobs::update::self_update;
|
||||
use self_update::{cargo_crate_version, update::ReleaseUpdate};
|
||||
|
||||
pub const OS: &str = std::env::consts::OS;
|
||||
@@ -26,8 +28,8 @@ pub const BIN_NAME_OLD: &str = formatcp!("objdiff-{}-{}{}", OS, ARCH, std::env::
|
||||
pub const RELEASE_URL: &str =
|
||||
formatcp!("https://github.com/{}/{}/releases/latest", GITHUB_USER, GITHUB_REPO);
|
||||
|
||||
pub fn build_updater() -> self_update::errors::Result<Box<dyn ReleaseUpdate>> {
|
||||
self_update::backends::github::Update::configure()
|
||||
pub fn build_updater() -> Result<Box<dyn ReleaseUpdate>> {
|
||||
Ok(self_update::backends::github::Update::configure()
|
||||
.repo_owner(GITHUB_USER)
|
||||
.repo_name(GITHUB_REPO)
|
||||
// bin_name is required, but unused?
|
||||
@@ -35,5 +37,5 @@ pub fn build_updater() -> self_update::errors::Result<Box<dyn ReleaseUpdate>> {
|
||||
.no_confirm(true)
|
||||
.show_output(false)
|
||||
.current_version(cargo_crate_version!())
|
||||
.build()
|
||||
.build()?)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use globset::Glob;
|
||||
use objdiff_core::{
|
||||
config::{ProjectObject, DEFAULT_WATCH_PATTERNS},
|
||||
diff::{ArmArchVersion, ArmR9Usage, MipsAbi, MipsInstrCategory, X86Formatter},
|
||||
jobs::{check_update::CheckUpdateResult, Job, JobQueue, JobResult},
|
||||
};
|
||||
use strum::{EnumMessage, VariantArray};
|
||||
|
||||
@@ -22,11 +23,7 @@ use crate::{
|
||||
app::{AppConfig, AppState, AppStateRef, ObjectConfig},
|
||||
config::ProjectObjectNode,
|
||||
hotkeys,
|
||||
jobs::{
|
||||
check_update::{start_check_update, CheckUpdateResult},
|
||||
update::start_update,
|
||||
Job, JobQueue, JobResult,
|
||||
},
|
||||
jobs::{start_check_update, start_update},
|
||||
update::RELEASE_URL,
|
||||
views::{
|
||||
appearance::Appearance,
|
||||
@@ -119,11 +116,11 @@ impl ConfigViewState {
|
||||
|
||||
if self.queue_check_update {
|
||||
self.queue_check_update = false;
|
||||
jobs.push_once(Job::CheckUpdate, || start_check_update(ctx));
|
||||
start_check_update(ctx, jobs);
|
||||
}
|
||||
|
||||
if let Some(bin_name) = self.queue_update.take() {
|
||||
jobs.push_once(Job::Update, || start_update(ctx, bin_name));
|
||||
start_update(ctx, jobs, bin_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use egui::{ProgressBar, RichText, Widget};
|
||||
use objdiff_core::jobs::{JobQueue, JobStatus};
|
||||
|
||||
use crate::{
|
||||
jobs::{JobQueue, JobStatus},
|
||||
views::appearance::Appearance,
|
||||
};
|
||||
use crate::views::appearance::Appearance;
|
||||
|
||||
pub fn jobs_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance) {
|
||||
if ui.button("Clear").clicked() {
|
||||
|
||||
@@ -6,7 +6,9 @@ use egui::{
|
||||
};
|
||||
use objdiff_core::{
|
||||
arch::ObjArch,
|
||||
build::BuildStatus,
|
||||
diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff},
|
||||
jobs::{create_scratch::CreateScratchResult, objdiff::ObjDiffResult, Job, JobQueue, JobResult},
|
||||
obj::{
|
||||
ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags, SymbolRef, SECTION_COMMON,
|
||||
},
|
||||
@@ -16,11 +18,7 @@ use regex::{Regex, RegexBuilder};
|
||||
use crate::{
|
||||
app::AppStateRef,
|
||||
hotkeys,
|
||||
jobs::{
|
||||
create_scratch::{start_create_scratch, CreateScratchConfig, CreateScratchResult},
|
||||
objdiff::{BuildStatus, ObjDiffResult},
|
||||
Job, JobQueue, JobResult,
|
||||
},
|
||||
jobs::{is_create_scratch_available, start_create_scratch},
|
||||
views::{
|
||||
appearance::Appearance,
|
||||
column_layout::{render_header, render_strips},
|
||||
@@ -182,7 +180,7 @@ impl DiffViewState {
|
||||
} else {
|
||||
self.source_path_available = false;
|
||||
}
|
||||
self.scratch_available = CreateScratchConfig::is_available(&state.config);
|
||||
self.scratch_available = is_create_scratch_available(&state.config);
|
||||
self.object_name =
|
||||
state.config.selected_obj.as_ref().map(|o| o.name.clone()).unwrap_or_default();
|
||||
}
|
||||
@@ -270,14 +268,7 @@ impl DiffViewState {
|
||||
let Ok(state) = state.read() else {
|
||||
return;
|
||||
};
|
||||
match CreateScratchConfig::from_config(&state.config, function_name) {
|
||||
Ok(config) => {
|
||||
jobs.push_once(Job::CreateScratch, || start_create_scratch(ctx, config));
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to create scratch config: {err}");
|
||||
}
|
||||
}
|
||||
start_create_scratch(ctx, jobs, &state, function_name);
|
||||
}
|
||||
DiffViewAction::OpenSourcePath => {
|
||||
let Ok(state) = state.read() else {
|
||||
|
||||
Reference in New Issue
Block a user