#![doc=include_str!("../README.md")]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
use std::marker::PhantomData;
use bevy_app::prelude::*;
use bevy_ecs::{prelude::*, schedule::{ScheduleLabel, InternedScheduleLabel}};
pub struct ResourceProgressTrackingPlugin<T: ?Sized> {
pub check_schedule: InternedScheduleLabel,
pub reset_schedule: InternedScheduleLabel,
_p1: PhantomData<T>,
}
impl<T: ?Sized> Default for ResourceProgressTrackingPlugin<T> {
fn default() -> Self {
Self {
check_schedule: PostUpdate.intern(),
reset_schedule: Last.intern(),
_p1: PhantomData,
}
}
}
impl<T: Send + Sync + 'static> Plugin for ResourceProgressTrackingPlugin<T> {
fn build(&self, app: &mut App) {
app.add_systems(self.check_schedule, resource_progress_check_system::<T>
.in_set(ProgressSystems::Check));
app.add_systems(self.reset_schedule, resource_progress_reset_system::<T>
.in_set(ProgressSystems::Reset)
.after(ProgressSystems::Check));
}
}
fn resource_progress_check_system<T: ?Sized + Send + Sync + 'static>(
mut commands: Commands,
resource: Option<Res<Progress<T>>>,
) {
let resource = match resource {
Some(v) => v,
None => return,
};
if !resource.done() { return }
commands.trigger(Done::<T> {
work: resource.total,
_p1: PhantomData,
});
}
fn resource_progress_reset_system<T: ?Sized + Send + Sync + 'static>(
resource: Option<ResMut<Progress<T>>>,
) {
if let Some(mut resource) = resource {
resource.done = 0;
resource.total = 0;
}
}
pub struct EntityProgressTrackingPlugin<T: ?Sized> {
pub check_schedule: InternedScheduleLabel,
pub reset_schedule: InternedScheduleLabel,
_p1: PhantomData<T>,
}
impl<T: ?Sized> Default for EntityProgressTrackingPlugin<T> {
fn default() -> Self {
Self {
check_schedule: PostUpdate.intern(),
reset_schedule: Last.intern(),
_p1: PhantomData,
}
}
}
impl<T: Send + Sync + 'static> Plugin for EntityProgressTrackingPlugin<T> {
fn build(&self, app: &mut App) {
app.add_systems(self.check_schedule, entity_progress_check_system::<T>
.in_set(ProgressSystems::Check));
app.add_systems(self.reset_schedule, entity_progress_reset_system::<T>
.in_set(ProgressSystems::Reset)
.after(ProgressSystems::Check));
}
}
fn entity_progress_check_system<T: ?Sized + Send + Sync + 'static>(
mut commands: Commands,
query: Query<(Entity, &Progress<T>)>,
) {
for (entity, tracker) in &query {
if !tracker.done() { continue }
commands.trigger_targets(Done::<T> {
work: tracker.total,
_p1: PhantomData,
}, [entity]);
}
}
fn entity_progress_reset_system<T: ?Sized + Send + Sync + 'static>(
mut query: Query<&mut Progress<T>>,
) {
for mut tracker in &mut query {
tracker.done = 0;
tracker.total = 0;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, SystemSet)]
pub enum ProgressSystems {
Check,
Reset,
}
#[derive(Component, Resource)]
pub struct Progress<T: ?Sized> {
done: u64,
total: u64,
_p1: PhantomData<T>,
}
impl<T: ?Sized> Progress<T> {
pub fn new() -> Self {
Self {
done: 0,
total: 0,
_p1: PhantomData,
}
}
}
impl<T: ?Sized> Default for Progress<T> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<T: ?Sized> Progress<T> {
pub fn track(&mut self, done: u32, total: u32) {
self.done += done as u64;
self.total += total as u64;
}
pub fn work(&self) -> (u64, u64) {
(self.done, self.total)
}
pub fn fract(&self) -> f32 {
let (done, total) = self.work();
return done as f32 / total as f32;
}
fn done(&self) -> bool {
let (done, total) = self.work();
return done >= total;
}
}
#[derive(Event)]
pub struct Done<T: ?Sized> {
work: u64,
_p1: PhantomData<T>,
}
impl<T: ?Sized> Done<T> {
#[inline]
pub fn work(&self) -> u64 {
self.work
}
}