1use std::{
2 sync::{
3 Arc, RwLock,
4 atomic::{AtomicUsize, Ordering},
5 mpsc::{Receiver, Sender, TryRecvError},
6 },
7 task::Waker,
8 thread::JoinHandle,
9};
10
11use anyhow::Result;
12
13use crate::jobs::{
14 check_update::CheckUpdateResult, create_scratch::CreateScratchResult, objdiff::ObjDiffResult,
15 update::UpdateResult,
16};
17
18pub mod check_update;
19pub mod create_scratch;
20pub mod objdiff;
21pub mod update;
22
23#[derive(Debug, Eq, PartialEq, Copy, Clone)]
24pub enum Job {
25 ObjDiff,
26 CheckUpdate,
27 Update,
28 CreateScratch,
29}
30pub static JOB_ID: AtomicUsize = AtomicUsize::new(0);
31
32#[derive(Default)]
33pub struct JobQueue {
34 pub jobs: Vec<JobState>,
35 pub results: Vec<JobResult>,
36}
37
38impl JobQueue {
39 #[inline]
41 pub fn push(&mut self, state: JobState) { self.jobs.push(state); }
42
43 #[inline]
45 pub fn push_once(&mut self, job: Job, func: impl FnOnce() -> JobState) {
46 if !self.is_running(job) {
47 self.push(func());
48 }
49 }
50
51 pub fn is_running(&self, kind: Job) -> bool {
53 self.jobs.iter().any(|j| j.kind == kind && j.handle.is_some())
54 }
55
56 pub fn any_running(&self) -> bool {
58 self.jobs.iter().any(|job| {
59 if let Some(handle) = &job.handle {
60 return !handle.is_finished();
61 }
62 false
63 })
64 }
65
66 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut JobState> + '_ { self.jobs.iter_mut() }
68
69 pub fn iter_finished(
71 &mut self,
72 ) -> impl Iterator<Item = (&mut JobState, std::thread::Result<JobResult>)> + '_ {
73 self.jobs.iter_mut().filter_map(|job| {
74 if let Some(handle) = &job.handle {
75 if !handle.is_finished() {
76 return None;
77 }
78 let result = job.handle.take().unwrap().join();
79 return Some((job, result));
80 }
81 None
82 })
83 }
84
85 pub fn clear_finished(&mut self) {
87 self.jobs.retain(|job| {
88 !(job.handle.is_none() && job.context.status.read().unwrap().error.is_none())
89 });
90 }
91
92 pub fn clear_errored(&mut self) {
94 self.jobs.retain(|job| job.context.status.read().unwrap().error.is_none());
95 }
96
97 pub fn remove(&mut self, id: usize) { self.jobs.retain(|job| job.id != id); }
99
100 pub fn collect_results(&mut self) {
102 let mut results = vec![];
103 for (job, result) in self.iter_finished() {
104 match result {
105 Ok(result) => {
106 match result {
107 JobResult::None => {
108 }
110 _ => results.push(result),
111 }
112 }
113 Err(err) => {
114 let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
115 anyhow::Error::msg(*msg)
116 } else if let Some(msg) = err.downcast_ref::<String>() {
117 anyhow::Error::msg(msg.clone())
118 } else {
119 anyhow::Error::msg("Thread panicked")
120 };
121 let result = job.context.status.write();
122 if let Ok(mut guard) = result {
123 guard.error = Some(err);
124 } else {
125 drop(result);
126 job.context.status = Arc::new(RwLock::new(JobStatus {
127 title: "Error".to_string(),
128 progress_percent: 0.0,
129 progress_items: None,
130 status: String::new(),
131 error: Some(err),
132 }));
133 }
134 }
135 }
136 }
137 self.results.append(&mut results);
138 self.clear_finished();
139 }
140}
141
142#[derive(Clone)]
143pub struct JobContext {
144 pub status: Arc<RwLock<JobStatus>>,
145 pub waker: Waker,
146}
147
148pub struct JobState {
149 pub id: usize,
150 pub kind: Job,
151 pub handle: Option<JoinHandle<JobResult>>,
152 pub context: JobContext,
153 pub cancel: Sender<()>,
154}
155
156#[derive(Default)]
157pub struct JobStatus {
158 pub title: String,
159 pub progress_percent: f32,
160 pub progress_items: Option<[u32; 2]>,
161 pub status: String,
162 pub error: Option<anyhow::Error>,
163}
164
165pub enum JobResult {
166 None,
167 ObjDiff(Option<Box<ObjDiffResult>>),
168 CheckUpdate(Option<Box<CheckUpdateResult>>),
169 Update(Box<UpdateResult>),
170 CreateScratch(Option<Box<CreateScratchResult>>),
171}
172
173fn start_job(
174 waker: Waker,
175 title: &str,
176 kind: Job,
177 run: impl FnOnce(JobContext, Receiver<()>) -> Result<JobResult> + Send + 'static,
178) -> JobState {
179 let status = Arc::new(RwLock::new(JobStatus {
180 title: title.to_string(),
181 progress_percent: 0.0,
182 progress_items: None,
183 status: String::new(),
184 error: None,
185 }));
186 let context = JobContext { status: status.clone(), waker: waker.clone() };
187 let context_inner = JobContext { status: status.clone(), waker };
188 let (tx, rx) = std::sync::mpsc::channel();
189 let handle = std::thread::spawn(move || match run(context_inner, rx) {
190 Ok(state) => state,
191 Err(e) => {
192 if let Ok(mut w) = status.write() {
193 w.error = Some(e);
194 }
195 JobResult::None
196 }
197 });
198 let id = JOB_ID.fetch_add(1, Ordering::Relaxed);
199 JobState { id, kind, handle: Some(handle), context, cancel: tx }
200}
201
202fn update_status(
203 context: &JobContext,
204 str: String,
205 count: u32,
206 total: u32,
207 cancel: &Receiver<()>,
208) -> Result<()> {
209 let mut w =
210 context.status.write().map_err(|_| anyhow::Error::msg("Failed to lock job status"))?;
211 w.progress_items = Some([count, total]);
212 w.progress_percent = count as f32 / total as f32;
213 if should_cancel(cancel) {
214 w.status = "Cancelled".to_string();
215 return Err(anyhow::Error::msg("Cancelled"));
216 } else {
217 w.status = str;
218 }
219 drop(w);
220 context.waker.wake_by_ref();
221 Ok(())
222}
223
224fn should_cancel(rx: &Receiver<()>) -> bool {
225 match rx.try_recv() {
226 Ok(_) | Err(TryRecvError::Disconnected) => true,
227 Err(_) => false,
228 }
229}