use std::cmp::Ordering;
use std::ops;
use std::time::Duration;
use bevy_ecs::prelude::*;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub enum TimeBound {
Inclusive(Duration),
Exclusive(Duration),
}
impl TimeBound {
pub fn duration(&self) -> Duration {
match self {
TimeBound::Inclusive(d) | TimeBound::Exclusive(d) => *d,
}
}
}
impl Default for TimeBound {
fn default() -> Self {
TimeBound::Inclusive(Duration::ZERO)
}
}
#[derive(Debug)]
pub enum NewTimeSpanError {
NotTime {
#[allow(missing_docs)]
min: TimeBound,
#[allow(missing_docs)]
max: TimeBound,
},
MinGreaterThanMax {
#[allow(missing_docs)]
min: TimeBound,
#[allow(missing_docs)]
max: TimeBound,
},
}
impl std::error::Error for NewTimeSpanError {}
impl std::fmt::Display for NewTimeSpanError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
NewTimeSpanError::NotTime { min, max } => {
write!(
f,
"This span does not contain any time: min {min:?} max {max:?}"
)
}
NewTimeSpanError::MinGreaterThanMax { min, max } => {
write!(
f,
"This span has min greater than max: min {min:?} max {max:?}"
)
}
}
}
}
#[derive(Debug, Component, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Component))]
pub struct TimeSpan {
min: TimeBound,
max: TimeBound,
}
impl TimeSpan {
pub(crate) fn new_unchecked(min: TimeBound, max: TimeBound) -> TimeSpan {
TimeSpan { min, max }
}
pub fn new(min: TimeBound, max: TimeBound) -> Result<TimeSpan, NewTimeSpanError> {
if matches!(
(min, max),
(TimeBound::Exclusive(_), TimeBound::Exclusive(_))
) && min.duration() == max.duration()
{
return Err(NewTimeSpanError::NotTime { min, max });
} else if min.duration() > max.duration() {
return Err(NewTimeSpanError::MinGreaterThanMax { min, max });
}
Ok(Self::new_unchecked(min, max))
}
pub(crate) fn quotient(&self, secs: f32) -> DurationQuotient {
let after_min = match self.min {
TimeBound::Inclusive(min) => secs >= min.as_secs_f32(),
TimeBound::Exclusive(min) => secs > min.as_secs_f32(),
};
let before_max = match self.max {
TimeBound::Inclusive(max) => secs <= max.as_secs_f32(),
TimeBound::Exclusive(max) => secs < max.as_secs_f32(),
};
match (after_min, before_max) {
(true, true) => DurationQuotient::Inside,
(true, false) => DurationQuotient::After,
(false, true) => DurationQuotient::Before,
(false, false) => unreachable!(),
}
}
pub fn min(&self) -> TimeBound {
self.min
}
pub fn max(&self) -> TimeBound {
self.max
}
pub fn length(&self) -> Duration {
self.max.duration() - self.min.duration()
}
}
impl Default for TimeSpan {
fn default() -> Self {
TimeSpan::try_from(Duration::ZERO..Duration::ZERO).unwrap()
}
}
impl TryFrom<ops::Range<Duration>> for TimeSpan {
type Error = NewTimeSpanError;
fn try_from(range: ops::Range<Duration>) -> Result<Self, Self::Error> {
TimeSpan::new(
TimeBound::Inclusive(range.start),
TimeBound::Exclusive(range.end),
)
}
}
impl TryFrom<ops::RangeInclusive<Duration>> for TimeSpan {
type Error = NewTimeSpanError;
fn try_from(range: ops::RangeInclusive<Duration>) -> Result<Self, Self::Error> {
TimeSpan::new(
TimeBound::Inclusive(*range.start()),
TimeBound::Inclusive(*range.end()),
)
}
}
impl TryFrom<ops::RangeTo<Duration>> for TimeSpan {
type Error = NewTimeSpanError;
fn try_from(range: ops::RangeTo<Duration>) -> Result<Self, Self::Error> {
TimeSpan::new(
TimeBound::Inclusive(Duration::ZERO),
TimeBound::Exclusive(range.end),
)
}
}
impl TryFrom<ops::RangeToInclusive<Duration>> for TimeSpan {
type Error = NewTimeSpanError;
fn try_from(range: ops::RangeToInclusive<Duration>) -> Result<Self, Self::Error> {
TimeSpan::new(
TimeBound::Inclusive(Duration::ZERO),
TimeBound::Inclusive(range.end),
)
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum DurationQuotient {
Before,
Inside,
After,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Component))]
pub struct TimeSpanProgress {
pub now_percentage: f32,
pub now: f32,
pub previous_percentage: f32,
pub previous: f32,
}
impl TimeSpanProgress {
pub fn direction(&self) -> Option<TimeDirection> {
match self.now.total_cmp(&self.previous) {
Ordering::Greater => Some(TimeDirection::Forward),
Ordering::Less => Some(TimeDirection::Backward),
Ordering::Equal => None,
}
}
pub(crate) fn update(&mut self, now: f32, now_percentage: f32) {
self.previous_percentage = self.now_percentage;
self.previous = self.now;
self.now_percentage = now_percentage;
self.now = now;
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub enum TimeDirection {
#[default]
#[allow(missing_docs)]
Forward,
#[allow(missing_docs)]
Backward,
}