use std::{sync::Arc, time::Duration};
use bevy::prelude::*;
use crate::{
CRATE_NAME,
animation::AnimationDirection,
animator::cache::{AnimationCache, AnimationCacheEvent, CacheFrame},
clip::ClipId,
components::spritesheet_animation::AnimationProgress,
events::Marker,
};
#[derive(Debug, Clone, Reflect)]
#[reflect(Debug)]
pub(crate) struct IteratorFrame {
pub atlas_index: usize,
pub duration: Duration,
pub clip_id: ClipId,
pub clip_repetition: usize,
pub animation_repetition: usize,
pub events: Vec<AnimationIteratorEvent>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect)]
#[reflect(Debug, PartialEq, Hash)]
pub(crate) enum AnimationIteratorEvent {
MarkerHit {
marker: Marker,
animation_repetition: usize,
clip_id: ClipId,
clip_repetition: usize,
},
ClipRepetitionEnd {
clip_id: ClipId,
clip_repetition: usize,
},
ClipEnd {
clip_id: ClipId,
},
AnimationRepetitionEnd {
animation_repetition: usize,
},
}
#[derive(Debug, Reflect)]
#[reflect(Debug)]
pub(crate) struct AnimationIterator {
cache: Arc<AnimationCache>,
next_frame_progress: AnimationProgress,
repetition_just_ended: Option<CacheFrame>,
}
impl AnimationIterator {
pub fn new(cache: Arc<AnimationCache>) -> Self {
Self {
cache,
next_frame_progress: AnimationProgress::default(),
repetition_just_ended: None,
}
}
pub fn to(&mut self, progress: AnimationProgress) -> bool {
if progress.frame >= self.cache.frames.len() {
error!(
"{CRATE_NAME}: invalid frame {} in {}-frame animation, cannot update progress",
progress.frame,
self.cache.frames.len().saturating_sub(1)
);
false
} else if let Some(repetitions) = self
.cache
.repetitions
.filter(|repetitions| progress.repetition >= *repetitions)
{
error!(
"{CRATE_NAME}: invalid repetition {} in {}-repetition animation, cannot update progress",
progress.frame,
repetitions.saturating_sub(1)
);
false
} else {
self.next_frame_progress = progress;
self.repetition_just_ended = None;
true
}
}
fn promote_events(
animation_events: &[AnimationCacheEvent],
animation_repetition: usize,
) -> Vec<AnimationIteratorEvent> {
animation_events
.iter()
.map(|event| match event {
AnimationCacheEvent::MarkerHit {
marker,
clip_id,
clip_repetition,
} => AnimationIteratorEvent::MarkerHit {
marker: *marker,
animation_repetition,
clip_id: *clip_id,
clip_repetition: *clip_repetition,
},
AnimationCacheEvent::ClipRepetitionEnd {
clip_id,
clip_repetition,
} => AnimationIteratorEvent::ClipRepetitionEnd {
clip_id: *clip_id,
clip_repetition: *clip_repetition,
},
AnimationCacheEvent::ClipEnd { clip_id } => {
AnimationIteratorEvent::ClipEnd { clip_id: *clip_id }
}
})
.collect()
}
}
impl Iterator for AnimationIterator {
type Item = (IteratorFrame, AnimationProgress);
fn next(&mut self) -> Option<Self::Item> {
let cached_frames = if let Some(frames_pong) = &self.cache.frames_pong {
if self.next_frame_progress.repetition.is_multiple_of(2) {
&self.cache.frames
} else {
frames_pong
}
} else {
&self.cache.frames
};
cached_frames
.get(self.next_frame_progress.frame)
.map(|cached_frame| {
let current_frame_progress = self.next_frame_progress;
let mut frame = IteratorFrame {
atlas_index: cached_frame.atlas_index,
duration: cached_frame.duration,
clip_id: cached_frame.clip_id,
clip_repetition: cached_frame.clip_repetition,
animation_repetition: current_frame_progress.repetition,
events: Self::promote_events(
&cached_frame.events,
current_frame_progress.repetition,
),
};
if let Some(previous_frame) = &self.repetition_just_ended {
frame
.events
.push(AnimationIteratorEvent::ClipRepetitionEnd {
clip_id: previous_frame.clip_id,
clip_repetition: previous_frame.clip_repetition,
});
frame.events.push(AnimationIteratorEvent::ClipEnd {
clip_id: previous_frame.clip_id,
});
frame
.events
.push(AnimationIteratorEvent::AnimationRepetitionEnd {
animation_repetition: current_frame_progress
.repetition
.saturating_sub(1),
});
self.repetition_just_ended = None;
}
self.next_frame_progress.frame += 1;
if self.next_frame_progress.frame >= self.cache.frames.len() {
self.next_frame_progress.repetition += 1;
self.repetition_just_ended = Some(cached_frame.clone());
if self
.cache
.repetitions
.map(|repetitions| self.next_frame_progress.repetition < repetitions)
.unwrap_or(true)
{
self.next_frame_progress.frame = if matches!(
self.cache.animation_direction,
AnimationDirection::PingPong
) {
1
} else {
0
};
}
}
(frame, current_frame_progress)
})
}
}