2022-09-08 14:19:20 -07:00
|
|
|
use std::{
|
|
|
|
sync::{
|
|
|
|
atomic::{AtomicUsize, Ordering},
|
|
|
|
mpsc::{Receiver, Sender, TryRecvError},
|
|
|
|
Arc, RwLock,
|
|
|
|
},
|
|
|
|
thread::JoinHandle,
|
|
|
|
};
|
|
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
2023-08-09 18:53:04 -07:00
|
|
|
use crate::jobs::{check_update::CheckUpdateResult, objdiff::ObjDiffResult, update::UpdateResult};
|
2022-09-08 14:19:20 -07:00
|
|
|
|
2022-12-06 14:53:32 -08:00
|
|
|
pub mod check_update;
|
2022-09-13 16:52:25 -07:00
|
|
|
pub mod objdiff;
|
2022-12-06 14:53:32 -08:00
|
|
|
pub mod update;
|
2022-09-08 14:19:20 -07:00
|
|
|
|
|
|
|
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
|
|
|
|
pub enum Job {
|
2022-09-13 16:52:25 -07:00
|
|
|
ObjDiff,
|
2022-12-06 14:53:32 -08:00
|
|
|
CheckUpdate,
|
|
|
|
Update,
|
2022-09-08 14:19:20 -07:00
|
|
|
}
|
|
|
|
pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
|
2023-08-09 16:39:06 -07:00
|
|
|
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct JobQueue {
|
|
|
|
pub jobs: Vec<JobState>,
|
2023-08-12 11:18:09 -07:00
|
|
|
pub results: Vec<JobResult>,
|
2023-08-09 16:39:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl JobQueue {
|
|
|
|
/// Adds a job to the queue.
|
2023-08-12 11:18:09 -07:00
|
|
|
#[inline]
|
2023-08-09 16:39:06 -07:00
|
|
|
pub fn push(&mut self, state: JobState) { self.jobs.push(state); }
|
|
|
|
|
2023-08-12 11:18:09 -07:00
|
|
|
/// 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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-09 16:39:06 -07:00
|
|
|
/// 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.
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
#[allow(dead_code)]
|
2023-08-09 16:39:06 -07:00
|
|
|
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.should_remove
|
|
|
|
&& job.handle.is_none()
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
&& job.context.status.read().unwrap().error.is_none())
|
2023-08-09 16:39:06 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Removes a job from the queue given its ID.
|
|
|
|
pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
|
|
|
|
}
|
|
|
|
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct JobContext {
|
|
|
|
pub status: Arc<RwLock<JobStatus>>,
|
|
|
|
pub egui: egui::Context,
|
|
|
|
}
|
2023-08-12 11:18:09 -07:00
|
|
|
|
2022-09-08 14:19:20 -07:00
|
|
|
pub struct JobState {
|
|
|
|
pub id: usize,
|
2023-08-09 16:39:06 -07:00
|
|
|
pub kind: Job,
|
2022-09-08 14:19:20 -07:00
|
|
|
pub handle: Option<JoinHandle<JobResult>>,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
pub context: JobContext,
|
2022-09-08 14:19:20 -07:00
|
|
|
pub cancel: Sender<()>,
|
|
|
|
pub should_remove: bool,
|
|
|
|
}
|
2023-08-09 16:39:06 -07:00
|
|
|
|
2022-09-08 14:19:20 -07:00
|
|
|
#[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>,
|
|
|
|
}
|
2023-08-09 16:39:06 -07:00
|
|
|
|
2022-09-08 14:19:20 -07:00
|
|
|
pub enum JobResult {
|
|
|
|
None,
|
2023-08-12 11:18:09 -07:00
|
|
|
ObjDiff(Option<Box<ObjDiffResult>>),
|
|
|
|
CheckUpdate(Option<Box<CheckUpdateResult>>),
|
2022-12-06 14:53:32 -08:00
|
|
|
Update(Box<UpdateResult>),
|
2022-09-08 14:19:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn should_cancel(rx: &Receiver<()>) -> bool {
|
|
|
|
match rx.try_recv() {
|
|
|
|
Ok(_) | Err(TryRecvError::Disconnected) => true,
|
|
|
|
Err(_) => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-09 16:39:06 -07:00
|
|
|
fn start_job(
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
ctx: &egui::Context,
|
2022-12-06 14:53:32 -08:00
|
|
|
title: &str,
|
2023-08-09 16:39:06 -07:00
|
|
|
kind: Job,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
run: impl FnOnce(JobContext, Receiver<()>) -> Result<JobResult> + Send + 'static,
|
2022-09-08 14:19:20 -07:00
|
|
|
) -> JobState {
|
|
|
|
let status = Arc::new(RwLock::new(JobStatus {
|
2022-12-06 14:53:32 -08:00
|
|
|
title: title.to_string(),
|
2022-09-08 14:19:20 -07:00
|
|
|
progress_percent: 0.0,
|
|
|
|
progress_items: None,
|
2022-12-06 14:53:32 -08:00
|
|
|
status: String::new(),
|
2022-09-08 14:19:20 -07:00
|
|
|
error: None,
|
|
|
|
}));
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
let context = JobContext { status: status.clone(), egui: ctx.clone() };
|
|
|
|
let context_inner = JobContext { status: status.clone(), egui: ctx.clone() };
|
2022-09-08 14:19:20 -07:00
|
|
|
let (tx, rx) = std::sync::mpsc::channel();
|
|
|
|
let handle = std::thread::spawn(move || {
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
return match run(context_inner, rx) {
|
2022-09-08 14:19:20 -07:00
|
|
|
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);
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
JobState { id, kind, handle: Some(handle), context, cancel: tx, should_remove: true }
|
2022-09-08 14:19:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn update_status(
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
context: &JobContext,
|
2022-09-08 14:19:20 -07:00
|
|
|
str: String,
|
|
|
|
count: u32,
|
|
|
|
total: u32,
|
|
|
|
cancel: &Receiver<()>,
|
|
|
|
) -> Result<()> {
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
let mut w =
|
|
|
|
context.status.write().map_err(|_| anyhow::Error::msg("Failed to lock job status"))?;
|
2022-09-08 14:19:20 -07:00
|
|
|
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;
|
|
|
|
}
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 11:34:26 -08:00
|
|
|
drop(w);
|
|
|
|
context.egui.request_repaint();
|
2022-09-08 14:19:20 -07:00
|
|
|
Ok(())
|
|
|
|
}
|