bevy_mod_progress/
lib.rs

1#![doc=include_str!("../README.md")]
2#![forbid(unsafe_code)]
3#![warn(missing_docs)]
4
5use std::marker::PhantomData;
6use bevy_app::prelude::*;
7use bevy_ecs::{prelude::*, schedule::{ScheduleLabel, InternedScheduleLabel}};
8
9/// Adds progress tracking for `T` (as a resource).
10pub struct ResourceProgressTrackingPlugin<T: ?Sized> {
11    /// The schedule in which the progress value is checked.
12    pub check_schedule: InternedScheduleLabel,
13
14    /// The schedule in which the progress value is checked.
15    /// This should be the same as, or before, `check_schedule`.
16    pub reset_schedule: InternedScheduleLabel,
17
18    _p1: PhantomData<T>,
19}
20
21impl<T: ?Sized> Default for ResourceProgressTrackingPlugin<T> {
22    fn default() -> Self {
23        Self {
24            check_schedule: PostUpdate.intern(),
25            reset_schedule: Last.intern(),
26            _p1: PhantomData,
27        }
28    }
29}
30
31impl<T: Send + Sync + 'static> Plugin for ResourceProgressTrackingPlugin<T> {
32    fn build(&self, app: &mut App) {
33        app.add_systems(self.check_schedule, resource_progress_check_system::<T>
34            .in_set(ProgressSystems::Check));
35
36        app.add_systems(self.reset_schedule, resource_progress_reset_system::<T>
37            .in_set(ProgressSystems::Reset)
38            .after(ProgressSystems::Check));
39    }
40}
41
42fn resource_progress_check_system<T: ?Sized + Send + Sync + 'static>(
43    mut commands: Commands,
44    resource: Option<Res<Progress<T>>>,
45) {
46    let resource = match resource {
47        Some(v) => v,
48        None => return,
49    };
50
51    if !resource.done() { return }
52    commands.trigger(Done::<T> {
53        work: resource.total,
54        _p1: PhantomData,
55    });
56}
57
58fn resource_progress_reset_system<T: ?Sized + Send + Sync + 'static>(
59    resource: Option<ResMut<Progress<T>>>,
60) {
61    if let Some(mut resource) = resource {
62        resource.done = 0;
63        resource.total = 0;
64    }
65}
66
67/// Adds progress tracking for `T` (as a component).
68pub struct EntityProgressTrackingPlugin<T: ?Sized> {
69    /// The schedule in which the progress value is checked.
70    pub check_schedule: InternedScheduleLabel,
71
72    /// The schedule in which the progress value is checked.
73    /// This should be the same as, or before, `check_schedule`.
74    pub reset_schedule: InternedScheduleLabel,
75
76    _p1: PhantomData<T>,
77}
78
79impl<T: ?Sized> Default for EntityProgressTrackingPlugin<T> {
80    fn default() -> Self {
81        Self {
82            check_schedule: PostUpdate.intern(),
83            reset_schedule: Last.intern(),
84            _p1: PhantomData,
85        }
86    }
87}
88
89impl<T: Send + Sync + 'static> Plugin for EntityProgressTrackingPlugin<T> {
90    fn build(&self, app: &mut App) {
91        app.add_systems(self.check_schedule, entity_progress_check_system::<T>
92            .in_set(ProgressSystems::Check));
93
94        app.add_systems(self.reset_schedule, entity_progress_reset_system::<T>
95            .in_set(ProgressSystems::Reset)
96            .after(ProgressSystems::Check));
97    }
98}
99
100fn entity_progress_check_system<T: ?Sized + Send + Sync + 'static>(
101    mut commands: Commands,
102    query: Query<(Entity, &Progress<T>)>,
103) {
104    for (entity, tracker) in &query {
105        if !tracker.done() { continue }
106        commands.trigger_targets(Done::<T> {
107            work: tracker.total,
108            _p1: PhantomData,
109        }, [entity]);
110    }
111}
112
113fn entity_progress_reset_system<T: ?Sized + Send + Sync + 'static>(
114    mut query: Query<&mut Progress<T>>,
115) {
116    for mut tracker in &mut query {
117        tracker.done = 0;
118        tracker.total = 0;
119    }
120}
121
122/// Systems involved in progress tracking.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemSet)]
124pub enum ProgressSystems {
125    /// System(s) that check for completed trackers.
126    /// All progress should be recorded before this point.
127    Check,
128
129    /// Progress trackers are reset in preparation for the next tick.
130    /// Progress should not be read after this point.
131    Reset,
132}
133
134/// Progress state.
135/// 
136/// Can be inserted as a [`Resource`] to track global progress,
137/// or as a [`Component`] to track progress for a single entity.
138#[derive(Component, Resource)]
139pub struct Progress<T: ?Sized> {
140    done: u64,
141    total: u64,
142    _p1: PhantomData<T>,
143}
144
145impl<T: ?Sized> Progress<T> {
146    /// Creates a new [`Progress`] tracker.
147    pub fn new() -> Self {
148        Self {
149            done: 0,
150            total: 0,
151            _p1: PhantomData,
152        }
153    }
154}
155
156impl<T: ?Sized> Default for Progress<T> {
157    #[inline]
158    fn default() -> Self {
159        Self::new()
160    }
161}
162
163impl<T: ?Sized> Progress<T> {
164    /// Records progress, including its total work and done work.
165    pub fn track(&mut self, done: u32, total: u32) {
166        self.done += done as u64;
167        self.total += total as u64;
168    }
169
170    /// Returns the work that has been completed and the units of work 
171    pub fn work(&self) -> (u64, u64) {
172        (self.done, self.total)
173    }
174
175    /// Returns the progress as a fraction, from `0.0` (no work done) to `1.0` (all work done).
176    pub fn fract(&self) -> f32 {
177        let (done, total) = self.work();
178        return done as f32 / total as f32;
179    }
180
181    fn done(&self) -> bool {
182        let (done, total) = self.work();
183        return done >= total;
184    }
185}
186
187/// An observer event raised when a progress tracker completes.
188#[derive(Event)]
189pub struct Done<T: ?Sized> {
190    work: u64,
191    _p1: PhantomData<T>,
192}
193
194impl<T: ?Sized> Done<T> {
195    /// Returns the amount of work done.
196    #[inline]
197    pub fn work(&self) -> u64 {
198        self.work
199    }
200}