Skip to content

Commit

Permalink
feat!: Allow to define the frame rate from FPS (#94)
Browse files Browse the repository at this point in the history
Now `Animation::from_iter` and `Animation::from_range` take a `FrameRate` instead of a duration.

The `FrameRate` can be created from a *frame duration* or a from a *fps* (frame-per-second).
  • Loading branch information
jcornaz committed Aug 3, 2022
1 parent 6c83f98 commit 7eb14dd
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 36 deletions.
11 changes: 6 additions & 5 deletions examples/bevy.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::time::Duration;

use bevy::{prelude::*, render::texture::ImageSettings};

use benimator::FrameRate;

// Create the animation component
// Note: you may make the animation an asset instead of a component
#[derive(Component, Deref)]
Expand Down Expand Up @@ -29,9 +29,10 @@ fn spawn(
commands.spawn_bundle(Camera2dBundle::default());

// Create an animation
const FPS: f64 = 12.0;
let frame_duration: Duration = Duration::from_secs(1).div_f64(FPS);
let animation = Animation(benimator::Animation::from_range(0..=4, frame_duration));
let animation = Animation(benimator::Animation::from_range(
0..=4,
FrameRate::from_fps(12.0),
));

commands
// Spawn a bevy sprite-sheet
Expand Down
12 changes: 6 additions & 6 deletions src/animation/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,17 @@ impl Error for InvalidAnimation {}
mod tests {
use super::*;

use crate::{animation::Mode, Frame};
use crate::{animation::Mode, Frame, FrameRate};
use std::time::Duration;

#[rstest]
fn deserialize_serialize(
#[values(
Animation::from_range(0..=2, Duration::from_secs(1)),
Animation::from_range(0..=2, Duration::from_secs(1)).once(),
Animation::from_range(0..=2, Duration::from_secs(1)).repeat(),
Animation::from_range(0..=2, Duration::from_secs(1)).repeat_from(1),
Animation::from_range(0..=2, Duration::from_secs(1)).ping_pong(),
Animation::from_range(0..=2, FrameRate::from_fps(2.0)),
Animation::from_range(0..=2, FrameRate::from_fps(2.0)).once(),
Animation::from_range(0..=2, FrameRate::from_fps(2.0)).repeat(),
Animation::from_range(0..=2, FrameRate::from_fps(2.0)).repeat_from(1),
Animation::from_range(0..=2, FrameRate::from_fps(2.0)).ping_pong(),
)]
animation: Animation,
) {
Expand Down
62 changes: 55 additions & 7 deletions src/animation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ impl Animation {
///
/// Panics if the duration is zero
#[must_use]
pub fn from_range(index_range: RangeInclusive<usize>, frame_duration: Duration) -> Self {
Self::from_iter(index_range, frame_duration)
pub fn from_range(index_range: RangeInclusive<usize>, frame_rate: FrameRate) -> Self {
Self::from_iter(index_range, frame_rate)
}

/// Create a new animation from an index iterator, using the same frame duration for each frame.
Expand All @@ -85,20 +85,20 @@ impl Animation {
///
/// You may use this to create a reversed animation:
/// ```
/// # use benimator::Animation;
/// # use benimator::{Animation, FrameRate};
/// # use std::time::Duration;
/// let animation = Animation::from_iter((0..5).rev(), Duration::from_millis(100));
/// let animation = Animation::from_iter((0..5).rev(), FrameRate::from_fps(12.));
/// ```
///
/// For more granular configuration, see [`from_frames`](Animation::from_frames)
///
/// # Panics
///
/// Panics if the duration is zero
pub fn from_iter(indices: impl IntoIterator<Item = usize>, frame_duration: Duration) -> Self {
pub fn from_iter(indices: impl IntoIterator<Item = usize>, frame_rate: FrameRate) -> Self {
indices
.into_iter()
.map(|index| Frame::new(index, frame_duration))
.map(|index| Frame::new(index, frame_rate.frame_duration))
.collect()
}

Expand Down Expand Up @@ -181,10 +181,44 @@ impl Frame {
}
}

/// Frame-Rate definition
#[derive(Debug, Clone)]
#[must_use]
pub struct FrameRate {
frame_duration: Duration,
}

impl FrameRate {
/// Frame rate defined by the FPS (Frame-Per-Second)
///
/// # Panics
///
/// This function will panic if `fps` is negative, zero or not finite.
pub fn from_fps(fps: f64) -> Self {
assert!(fps.is_finite() && fps > 0.0, "Invalid FPS: ${fps}");
Self {
frame_duration: Duration::from_secs(1).div_f64(fps),
}
}

/// Frame rate defined by the duration of each frame
pub fn from_frame_duration(duration: Duration) -> Self {
Self {
frame_duration: duration,
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[rstest]
#[should_panic]
fn invalid_frame_rate_panics(#[values(0.0, -1.0, f64::NAN, f64::INFINITY)] fps: f64) {
let _ = FrameRate::from_fps(fps);
}

#[test]
#[should_panic]
fn panics_for_zero_duration() {
Expand All @@ -193,7 +227,10 @@ mod tests {

#[test]
fn extends() {
let mut anim = Animation::from_range(0..=0, Duration::from_secs(1));
let mut anim = Animation::from_range(
0..=0,
FrameRate::from_frame_duration(Duration::from_secs(1)),
);
anim.extend([Frame::new(2, Duration::from_secs(2))]);
assert_eq!(
anim,
Expand All @@ -203,4 +240,15 @@ mod tests {
])
);
}

#[test]
fn fps_frame_duration_equivalence() {
assert_eq!(
Animation::from_range(1..=3, FrameRate::from_fps(10.0)),
Animation::from_range(
1..=3,
FrameRate::from_frame_duration(Duration::from_millis(100))
),
);
}
}
7 changes: 3 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@
//!
//! ```
//! use std::time::Duration;
//! use benimator::{Animation, State};
//! use benimator::{Animation, FrameRate, State};
//!
//! // Create an animation
//! let frame_duration = Duration::from_millis(100);
//! let animation = Animation::from_range(0..=3, frame_duration);
//! let animation = Animation::from_range(0..=3, FrameRate::from_fps(10.0));
//!
//! // Get a new animation state
//! let mut state = State::default();
Expand All @@ -42,7 +41,7 @@
#[macro_use]
extern crate rstest;

pub use animation::{Animation, Frame};
pub use animation::{Animation, Frame, FrameRate};
pub use state::State;

mod animation;
Expand Down
34 changes: 20 additions & 14 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,26 @@ impl State {
#[cfg(test)]
mod tests {
use super::*;
use crate::FrameRate;

#[fixture]
fn frame_duration() -> Duration {
Duration::from_secs(1)
}

#[fixture]
fn frame_rate(frame_duration: Duration) -> FrameRate {
FrameRate::from_frame_duration(frame_duration)
}

#[fixture]
fn smaller_duration(frame_duration: Duration) -> Duration {
frame_duration - Duration::from_millis(1)
}

#[rstest]
fn sprite_index(frame_duration: Duration) {
let animation = Animation::from_range(3..=5, frame_duration);
fn sprite_index(frame_rate: FrameRate) {
let animation = Animation::from_range(3..=5, frame_rate);
let mut state = State::default();
state.update(&animation, Duration::ZERO);
assert_eq!(state.sprite_frame_index(), 3);
Expand Down Expand Up @@ -153,8 +159,8 @@ mod tests {
use super::*;

#[fixture]
fn animation(frame_duration: Duration) -> Animation {
Animation::from_range(0..=2, frame_duration)
fn animation(frame_rate: FrameRate) -> Animation {
Animation::from_range(0..=2, frame_rate)
}

#[fixture]
Expand All @@ -175,21 +181,21 @@ mod tests {
#[rstest]
fn updates_index_if_less_than_expected_index(
mut state: State,
frame_duration: Duration,
frame_rate: FrameRate,
smaller_duration: Duration,
) {
let animation = Animation::from_range(1..=3, frame_duration);
let animation = Animation::from_range(1..=3, frame_rate);
state.update(&animation, smaller_duration);
assert_eq!(state.sprite_frame_index(), 1);
}

#[rstest]
fn updates_index_if_greater_than_expected_index(
mut state: State,
frame_duration: Duration,
frame_rate: FrameRate,
smaller_duration: Duration,
) {
let animation = Animation::from_range(1..=3, frame_duration);
let animation = Animation::from_range(1..=3, frame_rate);
state.update(&animation, smaller_duration);
assert_eq!(state.sprite_frame_index(), 1);
}
Expand Down Expand Up @@ -341,8 +347,8 @@ mod tests {
use super::*;

#[fixture]
fn animation(frame_duration: Duration) -> Animation {
Animation::from_range(0..=1, frame_duration).ping_pong()
fn animation(frame_rate: FrameRate) -> Animation {
Animation::from_range(0..=1, frame_rate).ping_pong()
}

#[fixture]
Expand Down Expand Up @@ -379,8 +385,8 @@ mod tests {
use super::*;

#[fixture]
fn animation(frame_duration: Duration) -> Animation {
Animation::from_range(0..=2, frame_duration).ping_pong()
fn animation(frame_rate: FrameRate) -> Animation {
Animation::from_range(0..=2, frame_rate).ping_pong()
}

#[fixture]
Expand Down Expand Up @@ -409,8 +415,8 @@ mod tests {
use super::*;

#[fixture]
fn animation(frame_duration: Duration) -> Animation {
Animation::from_range(0..=1, frame_duration).once()
fn animation(frame_rate: FrameRate) -> Animation {
Animation::from_range(0..=1, frame_rate).once()
}

mod on_first_frame {
Expand Down

0 comments on commit 7eb14dd

Please sign in to comment.