use etterna::prelude::*;
#[derive(Debug, PartialEq, Clone, Default)]
#[cfg_attr(
feature = "serde",
serde(crate = "serde_"),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct Replay {
pub notes: Vec<ReplayNote>,
}
impl Replay {
pub fn split_into_lanes(&self) -> Option<[NoteAndHitSeconds; 4]> {
let mut lanes = [
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![],
},
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![],
},
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![],
},
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![],
},
];
for note in self.notes.iter() {
if note.lane? >= 4 {
continue;
}
if !(note.note_type? == etterna::NoteType::Tap
|| note.note_type? == etterna::NoteType::HoldHead)
{
continue;
}
lanes[note.lane? as usize].note_seconds.push(note.time);
if let etterna::Hit::Hit { deviation } = note.hit {
lanes[note.lane? as usize]
.hit_seconds
.push(note.time + deviation);
}
}
Some(lanes)
}
pub fn split_into_notes_and_hits(&self) -> Option<NoteAndHitSeconds> {
let mut result = NoteAndHitSeconds {
note_seconds: Vec::with_capacity(self.notes.len()),
hit_seconds: Vec::with_capacity(self.notes.len()),
};
for note in self.notes.iter() {
if !(note.note_type? == etterna::NoteType::Tap
|| note.note_type? == etterna::NoteType::HoldHead)
{
continue;
}
result.note_seconds.push(note.time);
if let etterna::Hit::Hit { deviation } = note.hit {
result.hit_seconds.push(note.time + deviation);
}
}
Some(result)
}
}
impl etterna::SimpleReplay for Replay {
fn iter_hits(&self) -> Box<dyn '_ + Iterator<Item = etterna::Hit>> {
Box::new(self.notes.iter().map(|note| note.hit))
}
}
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(
feature = "serde",
serde(crate = "serde_"),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct ReplayNote {
pub time: f32,
pub hit: etterna::Hit,
pub lane: Option<u8>,
pub note_type: Option<NoteType>,
pub tick: Option<u32>,
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default)]
#[cfg_attr(
feature = "serde",
serde(crate = "serde_"),
derive(serde::Serialize, serde::Deserialize)
)]
pub struct FileSize {
bytes: u64,
}
impl FileSize {
pub fn from_bytes(bytes: u64) -> Self {
Self { bytes }
}
pub fn bytes(self) -> u64 {
self.bytes
}
pub fn kb(self) -> u64 {
self.bytes / 1_000
}
pub fn mb(self) -> u64 {
self.bytes / 1_000_000
}
pub fn gb(self) -> u64 {
self.bytes / 1_000_000_000
}
pub fn tb(self) -> u64 {
self.bytes / 1_000_000_000_000
}
}
thiserror_lite::err_enum! {
#[derive(Debug)]
pub enum FileSizeParseError {
#[error("Given string was empty")]
EmptyString,
#[error("Error while parsing the filesize number")]
InvalidNumber(#[from] std::num::ParseFloatError),
#[error("No KB/MB/... ending")]
NoEnding,
#[error("Unknown ending (the KB/MB/... thingy)")]
UnexpectedEnding(String),
}
}
impl std::str::FromStr for FileSize {
type Err = FileSizeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut token_iter = s.split_whitespace();
let number: f64 = token_iter
.next()
.ok_or(FileSizeParseError::EmptyString)?
.parse()
.map_err(FileSizeParseError::InvalidNumber)?;
let ending = token_iter.next().ok_or(FileSizeParseError::NoEnding)?;
let ending = ending.to_lowercase();
let multiplier: u64 = match &ending as &str {
"b" => 1,
"kb" => 1000,
"kib" => 1024,
"mb" => 1000 * 1000,
"mib" => 1024 * 1024,
"gb" => 1000 * 1000 * 1000,
"gib" => 1024 * 1024 * 1024,
"tb" => 1000 * 1000 * 1000 * 1000,
"tib" => 1024 * 1024 * 1024 * 1024,
_ => return Err(FileSizeParseError::UnexpectedEnding(ending)),
};
Ok(Self::from_bytes((number * multiplier as f64) as u64))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_split_replay() {
let replay = Replay {
notes: vec![
ReplayNote {
time: 0.0,
hit: etterna::Hit::Hit { deviation: 0.15 },
lane: Some(0),
note_type: Some(NoteType::Tap),
tick: None,
},
ReplayNote {
time: 1.0,
hit: etterna::Hit::Hit { deviation: -0.03 },
lane: Some(1),
note_type: Some(NoteType::Tap),
tick: None,
},
ReplayNote {
time: 2.0,
hit: etterna::Hit::Miss,
lane: Some(2),
note_type: Some(NoteType::Tap),
tick: None,
},
ReplayNote {
time: 3.0,
hit: etterna::Hit::Hit { deviation: 0.50 },
lane: Some(3),
note_type: Some(NoteType::Tap),
tick: None,
},
ReplayNote {
time: 4.0,
hit: etterna::Hit::Hit { deviation: 0.15 },
lane: Some(0),
note_type: Some(NoteType::Tap),
tick: None,
},
],
};
assert_eq!(
replay.split_into_notes_and_hits(),
Some(NoteAndHitSeconds {
note_seconds: vec![0.0, 1.0, 2.0, 3.0, 4.0],
hit_seconds: vec![0.15, 0.97, 3.5, 4.15],
})
);
assert_eq!(
replay.split_into_lanes(),
Some([
NoteAndHitSeconds {
note_seconds: vec![0.0, 4.0],
hit_seconds: vec![0.15, 4.15],
},
NoteAndHitSeconds {
note_seconds: vec![1.0],
hit_seconds: vec![0.97],
},
NoteAndHitSeconds {
note_seconds: vec![2.0],
hit_seconds: vec![],
},
NoteAndHitSeconds {
note_seconds: vec![3.0],
hit_seconds: vec![3.5],
},
])
);
assert_eq!(
Replay { notes: vec![] }.split_into_notes_and_hits(),
Some(NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![],
})
);
assert_eq!(
Replay { notes: vec![] }.split_into_lanes(),
Some([
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![]
},
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![]
},
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![]
},
NoteAndHitSeconds {
note_seconds: vec![],
hit_seconds: vec![]
},
])
);
}
}