iyes_progress/
tracker.rs

1//! Storing and tracking progress
2
3use std::marker::PhantomData;
4use std::sync::atomic::{AtomicUsize, Ordering};
5
6use bevy_ecs::prelude::*;
7use bevy_ecs::system::SystemParam;
8use bevy_state::state::FreelyMutableState;
9use bevy_platform::collections::HashMap;
10use parking_lot::Mutex;
11
12use crate::prelude::*;
13
14static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
15
16/// An opaque ID for accessing data stored in the [`ProgressTracker`].
17///
18/// The ID can be used with the [`ProgressTracker`] resource
19/// (for any state type) to record [`Progress`] and [`HiddenProgress`].
20///
21/// Normally, `iyes_progress` will automatically manage these IDs for you
22/// under the hood, if you use the [`ProgressEntry`] system param or
23/// write systems that return progress values.
24///
25/// However, for some advanced use cases, you might want to do it manually.
26/// You can create a new unique ID at any time by calling
27/// [`ProgressEntryId::new()`]. Store that ID and then use it to update the
28/// values in the [`ProgressTracker`].
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
30pub struct ProgressEntryId(usize);
31
32impl ProgressEntryId {
33    /// Create a new unique ID
34    pub fn new() -> ProgressEntryId {
35        let next_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
36        ProgressEntryId(next_id)
37    }
38}
39
40/// The resource where all the progress information is stored.
41///
42/// You can get information about the overall accumulated progress
43/// from here. You can also manage the progress values associated
44/// with specific [`ProgressEntryId`]s.
45///
46/// The internal data is behind a mutex, to allow shared access.
47/// Bevy systems only need `Res`, not `ResMut`, allowing systems
48/// that use this resource to run in parallel.
49///
50/// All stored values are cleared automatically when entering a
51/// state configured for progress tracking. You can reset everything
52/// manually by calling [`clear`](Self::clear).
53#[derive(Resource)]
54pub struct ProgressTracker<S: FreelyMutableState> {
55    inner: Mutex<GlobalProgressTrackerInner>,
56    #[cfg(feature = "async")]
57    pub(crate) chan: Option<(Sender, Receiver)>,
58    _pd: PhantomData<S>,
59}
60
61impl<S: FreelyMutableState> Default for ProgressTracker<S> {
62    fn default() -> Self {
63        Self {
64            inner: Default::default(),
65            #[cfg(feature = "async")]
66            chan: None,
67            _pd: PhantomData,
68        }
69    }
70}
71
72#[derive(Default)]
73struct GlobalProgressTrackerInner {
74    entries: HashMap<ProgressEntryId, (Progress, HiddenProgress)>,
75    sum_entities: (Progress, HiddenProgress),
76    sum_entries: (Progress, HiddenProgress),
77}
78
79impl<S: FreelyMutableState> ProgressTracker<S> {
80    /// Clear all stored progress values.
81    pub fn clear(&mut self) {
82        self.inner = Default::default();
83        #[cfg(feature = "async")]
84        {
85            self.chan = None;
86        }
87    }
88
89    /// Create an entry for a background task/thread.
90    ///
91    /// Returns a [`ProgressSender`], which is the "handle" that
92    /// can be used to update the progress stored for the new entry ID.
93    #[cfg(feature = "async")]
94    pub fn new_async_entry(&mut self) -> ProgressSender {
95        if let Some((tx, _)) = &self.chan {
96            ProgressSender {
97                id: ProgressEntryId::new(),
98                sender: tx.clone(),
99            }
100        } else {
101            let chan = crossbeam_channel::unbounded();
102            let r = ProgressSender {
103                id: ProgressEntryId::new(),
104                sender: chan.0.clone(),
105            };
106            self.chan = Some(chan);
107            r
108        }
109    }
110
111    /// Call a closure on each entry stored in the tracker.
112    ///
113    /// This allows you to inspect or mutate anything stored in the tracker,
114    /// which can be useful for debugging or for advanced use cases.
115    pub fn foreach_entry(
116        &self,
117        mut f: impl FnMut(ProgressEntryId, &mut Progress, &mut HiddenProgress),
118    ) {
119        let mut inner = self.inner.lock();
120        for (k, v) in inner.entries.iter_mut() {
121            f(*k, &mut v.0, &mut v.1);
122        }
123    }
124
125    /// Check if there is any progress data stored for a given ID.
126    pub fn contains_id(&self, id: ProgressEntryId) -> bool {
127        self.inner.lock().entries.contains_key(&id)
128    }
129
130    /// Check if all progress is complete.
131    ///
132    /// This accounts for both visible progress and hidden progress.
133    pub fn is_ready(&self) -> bool {
134        self.get_global_combined_progress().is_ready()
135    }
136
137    /// Check if the progress for a specific ID is complete.
138    ///
139    /// This accounts for both visible progress and hidden progress.
140    pub fn is_id_ready(&self, id: ProgressEntryId) -> bool {
141        let inner = self.inner.lock();
142        inner
143            .entries
144            .get(&id)
145            .map(|x| (x.0 + x.1 .0).is_ready())
146            .unwrap_or_default()
147    }
148
149    pub(crate) fn set_sum_entities(&self, v: Progress, h: HiddenProgress) {
150        let mut inner = self.inner.lock();
151        inner.sum_entities.0 = v;
152        inner.sum_entities.1 = h;
153    }
154
155    /// Get the overall visible progress.
156    ///
157    /// This is what you should use to display a progress bar or
158    /// other user-facing indicator.
159    pub fn get_global_progress(&self) -> Progress {
160        let inner = self.inner.lock();
161        inner.sum_entries.0 + inner.sum_entities.0
162    }
163
164    /// Get the overall hidden progress.
165    pub fn get_global_hidden_progress(&self) -> HiddenProgress {
166        let inner = self.inner.lock();
167        inner.sum_entries.1 + inner.sum_entities.1
168    }
169
170    /// Get the overall visible+hidden progress.
171    ///
172    /// This is what you should use to determine if all work is complete.
173    pub fn get_global_combined_progress(&self) -> Progress {
174        let inner = self.inner.lock();
175        inner.sum_entries.0 + inner.sum_entries.1 .0 +
176        inner.sum_entities.0 + inner.sum_entities.1 .0
177    }
178
179    /// Get the visible progress stored for a specific ID.
180    pub fn get_progress(&self, id: ProgressEntryId) -> Progress {
181        let inner = self.inner.lock();
182        inner.entries.get(&id).copied().unwrap_or_default().0
183    }
184
185    /// Get the hidden progress stored for a specific ID.
186    pub fn get_hidden_progress(&self, id: ProgressEntryId) -> HiddenProgress {
187        let inner = self.inner.lock();
188        inner.entries.get(&id).copied().unwrap_or_default().1
189    }
190
191    /// Get the visible+hidden progress stored for a specific ID.
192    pub fn get_combined_progress(&self, id: ProgressEntryId) -> Progress {
193        let inner = self.inner.lock();
194        inner
195            .entries
196            .get(&id)
197            .map(|x| x.0 + x.1 .0)
198            .unwrap_or_default()
199    }
200
201    /// Get the (visible) expected work item count for a specific ID.
202    pub fn get_total(&self, id: ProgressEntryId) -> u32 {
203        let inner = self.inner.lock();
204        inner.entries.get(&id).copied().unwrap_or_default().0.total
205    }
206
207    /// Get the (visible) completed work item count for a specific ID.
208    pub fn get_done(&self, id: ProgressEntryId) -> u32 {
209        let inner = self.inner.lock();
210        inner.entries.get(&id).copied().unwrap_or_default().0.done
211    }
212
213    /// Get the (hidden) expected work item count for a specific ID.
214    pub fn get_hidden_total(&self, id: ProgressEntryId) -> u32 {
215        let inner = self.inner.lock();
216        inner.entries.get(&id).copied().unwrap_or_default().1.total
217    }
218
219    /// Get the (hidden) completed work item count for a specific ID.
220    pub fn get_hidden_done(&self, id: ProgressEntryId) -> u32 {
221        let inner = self.inner.lock();
222        inner.entries.get(&id).copied().unwrap_or_default().1.done
223    }
224
225    /// Overwrite the stored visible progress for a specific ID.
226    ///
227    /// Use this when you want to overwrite both the `total` and `done` at once.
228    pub fn set_progress(&self, id: ProgressEntryId, done: u32, total: u32) {
229        let inner = &mut *self.inner.lock();
230        if let Some(p) = inner.entries.get_mut(&id) {
231            if p.0.total < total {
232                let diff = total - p.0.total;
233                inner.sum_entries.0.total += diff;
234            }
235            if p.0.total > total {
236                let diff = p.0.total - total;
237                inner.sum_entries.0.total -= diff;
238            }
239            if p.0.done < done {
240                let diff = done - p.0.done;
241                inner.sum_entries.0.done += diff;
242            }
243            if p.0.done > done {
244                let diff = p.0.done - done;
245                inner.sum_entries.0.done -= diff;
246            }
247            p.0 = Progress { done, total };
248        } else {
249            inner.entries.insert(
250                id,
251                (Progress { done, total }, HiddenProgress::default()),
252            );
253            inner.sum_entries.0.total += total;
254            inner.sum_entries.0.done += done;
255        }
256    }
257
258    /// Overwrite the stored hidden progress for a specific ID.
259    ///
260    /// Use this when you want to overwrite both the `total` and `done` at once.
261    pub fn set_hidden_progress(
262        &self,
263        id: ProgressEntryId,
264        done: u32,
265        total: u32,
266    ) {
267        let inner = &mut *self.inner.lock();
268        if let Some(p) = inner.entries.get_mut(&id) {
269            if p.1.total < total {
270                let diff = total - p.1.total;
271                inner.sum_entries.1.total += diff;
272            }
273            if p.1.total > total {
274                let diff = p.1.total - total;
275                inner.sum_entries.1.total -= diff;
276            }
277            if p.1.done < done {
278                let diff = done - p.1.done;
279                inner.sum_entries.1.done += diff;
280            }
281            if p.1.done > done {
282                let diff = p.1.done - done;
283                inner.sum_entries.1.done -= diff;
284            }
285            p.1 = Progress { done, total }.into();
286        } else {
287            inner.entries.insert(
288                id,
289                (Progress::default(), Progress { done, total }.into()),
290            );
291            inner.sum_entries.1.total += total;
292            inner.sum_entries.1.done += done;
293        }
294    }
295
296    /// Overwrite the stored (visible) expected work items for a specific ID.
297    pub fn set_total(&self, id: ProgressEntryId, total: u32) {
298        let inner = &mut *self.inner.lock();
299        if let Some(p) = inner.entries.get_mut(&id) {
300            if p.0.total < total {
301                let diff = total - p.0.total;
302                inner.sum_entries.0.total += diff;
303            }
304            if p.0.total > total {
305                let diff = p.0.total - total;
306                inner.sum_entries.0.total -= diff;
307            }
308            p.0.total = total;
309        } else {
310            inner.entries.insert(
311                id,
312                (Progress { done: 0, total }, HiddenProgress::default()),
313            );
314            inner.sum_entries.0.total += total;
315        }
316    }
317
318    /// Overwrite the stored (visible) completed work items for a specific ID.
319    pub fn set_done(&self, id: ProgressEntryId, done: u32) {
320        let inner = &mut *self.inner.lock();
321        if let Some(p) = inner.entries.get_mut(&id) {
322            if p.0.done < done {
323                let diff = done - p.0.done;
324                inner.sum_entries.0.done += diff;
325            }
326            if p.0.done > done {
327                let diff = p.0.done - done;
328                inner.sum_entries.0.done -= diff;
329            }
330            p.0.done = done;
331        } else {
332            inner.entries.insert(
333                id,
334                (Progress { done, total: 0 }, HiddenProgress::default()),
335            );
336            inner.sum_entries.0.done += done;
337        }
338    }
339
340    /// Overwrite the stored (hidden) expected work items for a specific ID.
341    pub fn set_hidden_total(&self, id: ProgressEntryId, total: u32) {
342        let inner = &mut *self.inner.lock();
343        if let Some(p) = inner.entries.get_mut(&id) {
344            if p.1.total < total {
345                let diff = total - p.1.total;
346                inner.sum_entries.1.total += diff;
347            }
348            if p.1.total > total {
349                let diff = p.1.total - total;
350                inner.sum_entries.1.total -= diff;
351            }
352            p.1.total = total;
353        } else {
354            inner.entries.insert(
355                id,
356                (Progress::default(), Progress { done: 0, total }.into()),
357            );
358            inner.sum_entries.1.total += total;
359        }
360    }
361
362    /// Overwrite the stored (hidden) completed work items for a specific ID.
363    pub fn set_hidden_done(&self, id: ProgressEntryId, done: u32) {
364        let inner = &mut *self.inner.lock();
365        if let Some(p) = inner.entries.get_mut(&id) {
366            if p.1.done < done {
367                let diff = done - p.1.done;
368                inner.sum_entries.1.done += diff;
369            }
370            if p.1.done > done {
371                let diff = p.1.done - done;
372                inner.sum_entries.1.done -= diff;
373            }
374            p.1.done = done;
375        } else {
376            inner.entries.insert(
377                id,
378                (Progress::default(), Progress { done, total: 0 }.into()),
379            );
380            inner.sum_entries.1.done += done;
381        }
382    }
383
384    /// Add more (visible) work items to the previously stored progress for a
385    /// specific ID.
386    ///
387    /// Use this when you want to add to both the `total` and `done` at once.
388    pub fn add_progress(&self, id: ProgressEntryId, done: u32, total: u32) {
389        let inner = &mut *self.inner.lock();
390        if let Some(p) = inner.entries.get_mut(&id) {
391            p.0.done += done;
392            p.0.total += total;
393        } else {
394            inner.entries.insert(
395                id,
396                (Progress { done, total }, HiddenProgress::default()),
397            );
398        }
399        inner.sum_entries.0.total += total;
400        inner.sum_entries.0.done += done;
401    }
402
403    /// Add more (visible) expected work items to the previously stored value
404    /// for a specific ID.
405    pub fn add_total(&self, id: ProgressEntryId, total: u32) {
406        let inner = &mut *self.inner.lock();
407        if let Some(p) = inner.entries.get_mut(&id) {
408            p.0.total += total;
409        } else {
410            inner.entries.insert(
411                id,
412                (Progress { done: 0, total }, HiddenProgress::default()),
413            );
414        }
415        inner.sum_entries.0.total += total;
416    }
417
418    /// Add more (visible) completed work items to the previously stored value
419    /// for a specific ID.
420    pub fn add_done(&self, id: ProgressEntryId, done: u32) {
421        let inner = &mut *self.inner.lock();
422        if let Some(p) = inner.entries.get_mut(&id) {
423            p.0.done += done;
424        } else {
425            inner.entries.insert(
426                id,
427                (Progress { done, total: 0 }, HiddenProgress::default()),
428            );
429        }
430        inner.sum_entries.0.done += done;
431    }
432
433    /// Add more (hidden) work items to the previously stored progress for a
434    /// specific ID.
435    ///
436    /// Use this when you want to add to both the `total` and `done` at once.
437    pub fn add_hidden_progress(
438        &self,
439        id: ProgressEntryId,
440        done: u32,
441        total: u32,
442    ) {
443        let inner = &mut *self.inner.lock();
444        if let Some(p) = inner.entries.get_mut(&id) {
445            p.1.done += done;
446            p.1.total += total;
447        } else {
448            inner.entries.insert(
449                id,
450                (Progress::default(), Progress { done, total }.into()),
451            );
452        }
453        inner.sum_entries.1.total += total;
454        inner.sum_entries.1.done += done;
455    }
456
457    /// Add more (hidden) expected work items to the previously stored value for
458    /// a specific ID.
459    pub fn add_hidden_total(&self, id: ProgressEntryId, total: u32) {
460        let inner = &mut *self.inner.lock();
461        if let Some(p) = inner.entries.get_mut(&id) {
462            p.1.total += total;
463        } else {
464            inner.entries.insert(
465                id,
466                (Progress::default(), Progress { done: 0, total }.into()),
467            );
468        }
469        inner.sum_entries.1.total += total;
470    }
471
472    /// Add more (hidden) completed work items to the previously stored value
473    /// for a specific ID.
474    pub fn add_hidden_done(&self, id: ProgressEntryId, done: u32) {
475        let inner = &mut *self.inner.lock();
476        if let Some(p) = inner.entries.get_mut(&id) {
477            p.1.done += done;
478        } else {
479            inner.entries.insert(
480                id,
481                (Progress::default(), Progress { done, total: 0 }.into()),
482            );
483        }
484        inner.sum_entries.1.done += done;
485    }
486}
487
488/// Because we don't want to impl Default for ProgressEntryId, to prevent user
489/// footguns.
490struct ProgressEntryIdWrapper(ProgressEntryId);
491
492impl Default for ProgressEntryIdWrapper {
493    fn default() -> Self {
494        Self(ProgressEntryId::new())
495    }
496}
497
498/// System param to manage a progress entry in the [`ProgressTracker`].
499///
500/// You can use this in your systems to report progress to be tracked.
501///
502/// Each instance of this system param will create an entry in the
503/// [`ProgressTracker`] for itself and allow you to access the
504/// associated value. The ID is managed internally.
505#[derive(SystemParam)]
506pub struct ProgressEntry<'w, 's, S: FreelyMutableState> {
507    global: Res<'w, ProgressTracker<S>>,
508    my_id: Local<'s, ProgressEntryIdWrapper>,
509}
510
511impl<S: FreelyMutableState> ProgressEntry<'_, '_, S> {
512    /// Get the ID of the [`ProgressTracker`] entry managed by this system param
513    pub fn id(&self) -> ProgressEntryId {
514        self.my_id.0
515    }
516
517    /// Get the overall visible progress.
518    ///
519    /// This is what you should use to display a progress bar or
520    /// other user-facing indicator.
521    pub fn get_global_progress(&self) -> Progress {
522        self.global.get_global_progress()
523    }
524
525    /// Get the overall hidden progress.
526    pub fn get_global_hidden_progress(&self) -> HiddenProgress {
527        self.global.get_global_hidden_progress()
528    }
529
530    /// Get the overall visible+hidden progress.
531    ///
532    /// This is what you should use to determine if all work is complete.
533    pub fn get_global_combined_progress(&self) -> Progress {
534        self.global.get_global_combined_progress()
535    }
536
537    /// Check if everything is ready.
538    pub fn is_global_ready(&self) -> bool {
539        self.global.is_ready()
540    }
541
542    /// Check if the progress associated with this system param is ready.
543    pub fn is_ready(&self) -> bool {
544        self.global.is_id_ready(self.my_id.0)
545    }
546
547    /// Get the visible+hidden progress associated with this system param.
548    pub fn get_combined_progress(&self) -> Progress {
549        self.global.get_combined_progress(self.my_id.0)
550    }
551
552    /// Get the visible progress associated with this system param.
553    pub fn get_progress(&self) -> Progress {
554        self.global.get_progress(self.my_id.0)
555    }
556
557    /// Get the (visible) expected work items associated with this system param.
558    pub fn get_total(&self) -> u32 {
559        self.global.get_total(self.my_id.0)
560    }
561
562    /// Get the (visible) completed work items associated with this system
563    /// param.
564    pub fn get_done(&self) -> u32 {
565        self.global.get_done(self.my_id.0)
566    }
567
568    /// Overwrite the visible progress associated with this system param.
569    ///
570    /// Use this if you want to set both the `done` and `total` at once.
571    pub fn set_progress(&self, done: u32, total: u32) {
572        self.global.set_progress(self.my_id.0, done, total)
573    }
574
575    /// Overwrite the (visible) expected work items associated with this system
576    /// param.
577    pub fn set_total(&self, total: u32) {
578        self.global.set_total(self.my_id.0, total)
579    }
580
581    /// Overwrite the (visible) completed work items associated with this system
582    /// param.
583    pub fn set_done(&self, done: u32) {
584        self.global.set_done(self.my_id.0, done)
585    }
586
587    /// Add to the visible progress associated with this system param.
588    ///
589    /// Use this if you want to add to both the `done` and `total` at once.
590    pub fn add_progress(&self, done: u32, total: u32) {
591        self.global.add_progress(self.my_id.0, done, total)
592    }
593
594    /// Add more (visible) expected work items associated with this system
595    /// param.
596    pub fn add_total(&self, total: u32) {
597        self.global.add_total(self.my_id.0, total)
598    }
599
600    /// Add more (visible) completed work items associated with this system
601    /// param.
602    pub fn add_done(&self, done: u32) {
603        self.global.add_done(self.my_id.0, done)
604    }
605
606    /// Get the hidden progress associated with this system param.
607    pub fn get_hidden_progress(&self) -> HiddenProgress {
608        self.global.get_hidden_progress(self.my_id.0)
609    }
610
611    /// Get the (hidden) expected work items associated with this system param.
612    pub fn get_hidden_total(&self) -> u32 {
613        self.global.get_hidden_total(self.my_id.0)
614    }
615
616    /// Get the (hidden) completed work items associated with this system param.
617    pub fn get_hidden_done(&self) -> u32 {
618        self.global.get_hidden_done(self.my_id.0)
619    }
620
621    /// Overwrite the hidden progress associated with this system param.
622    ///
623    /// Use this if you want to set both the `done` and `total` at once.
624    pub fn set_hidden_progress(&self, done: u32, total: u32) {
625        self.global.set_hidden_progress(self.my_id.0, done, total)
626    }
627
628    /// Overwrite the (hidden) expected work items associated with this system
629    /// param.
630    pub fn set_hidden_total(&self, total: u32) {
631        self.global.set_hidden_total(self.my_id.0, total)
632    }
633
634    /// Overwrite the (hidden) completed work items associated with this system
635    /// param.
636    pub fn set_hidden_done(&self, done: u32) {
637        self.global.set_hidden_done(self.my_id.0, done)
638    }
639
640    /// Add to the hidden progress associated with this system param.
641    ///
642    /// Use this if you want to add to both the `done` and `total` at once.
643    pub fn add_hidden_progress(&self, done: u32, total: u32) {
644        self.global.add_hidden_progress(self.my_id.0, done, total)
645    }
646
647    /// Add more (hidden) expected work items associated with this system param.
648    pub fn add_hidden_total(&self, total: u32) {
649        self.global.add_hidden_total(self.my_id.0, total)
650    }
651
652    /// Add more (hidden) completed work items associated with this system
653    /// param.
654    pub fn add_hidden_done(&self, done: u32) {
655        self.global.add_hidden_done(self.my_id.0, done)
656    }
657}
658
659pub(crate) trait ApplyProgress: Sized {
660    fn apply_progress<S: FreelyMutableState>(
661        self,
662        tracker: &ProgressTracker<S>,
663        id: ProgressEntryId,
664    );
665}
666
667impl ApplyProgress for Progress {
668    fn apply_progress<S: FreelyMutableState>(
669        self,
670        tracker: &ProgressTracker<S>,
671        id: ProgressEntryId,
672    ) {
673        tracker.set_progress(id, self.done, self.total);
674    }
675}
676
677impl ApplyProgress for HiddenProgress {
678    fn apply_progress<S: FreelyMutableState>(
679        self,
680        tracker: &ProgressTracker<S>,
681        id: ProgressEntryId,
682    ) {
683        tracker.set_hidden_progress(id, self.0.done, self.0.total);
684    }
685}
686
687impl<T1: ApplyProgress, T2: ApplyProgress> ApplyProgress for (T1, T2) {
688    fn apply_progress<S: FreelyMutableState>(
689        self,
690        tracker: &ProgressTracker<S>,
691        id: ProgressEntryId,
692    ) {
693        self.0.apply_progress(tracker, id);
694        self.1.apply_progress(tracker, id);
695    }
696}