diff --git a/.github/workflows/pr_check.yaml b/.github/workflows/pr_check.yaml new file mode 100644 index 0000000..8dd7c6d --- /dev/null +++ b/.github/workflows/pr_check.yaml @@ -0,0 +1,47 @@ +name: cargo-code-check + +on: + pull_request: + branches: ["main"] + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-D warnings" + +jobs: + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - run: cargo fmt --all --check + + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: cargo check --workspace + + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo clippy --workspace -- -D warnings + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy + - uses: Swatinem/rust-cache@v2 + - run: cargo test --workspace diff --git a/Cargo.lock b/Cargo.lock index 0c4c9c0..5f4f62a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "phasetida-core" -version = "0.1.15" +version = "0.1.16" dependencies = [ "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 874954f..563d8a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "phasetida-core" -version = "0.1.15" +version = "0.1.16" edition = "2024" [lib] diff --git a/src/chart.rs b/src/chart.rs index b47c980..22afdf5 100644 --- a/src/chart.rs +++ b/src/chart.rs @@ -79,10 +79,10 @@ impl TryFrom for NoteType { fn try_from(value: i32) -> Result { match value { - 1 => Ok(NoteType::Tap), - 2 => Ok(NoteType::Drag), - 3 => Ok(NoteType::Hold), - 4 => Ok(NoteType::Flick), + 1 => Ok(Self::Tap), + 2 => Ok(Self::Drag), + 3 => Ok(Self::Hold), + 4 => Ok(Self::Flick), _ => Err(()), } } @@ -246,12 +246,12 @@ impl<'de> Deserialize<'de> for ChartRaw { let version = value .get("formatVersion") .and_then(serde_json::Value::as_i64) - .ok_or(serde::de::Error::missing_field("formatVersion"))?; + .ok_or_else(|| serde::de::Error::missing_field("formatVersion"))?; match version { - 1 => Ok(ChartRaw::V1( + 1 => Ok(Self::V1( serde_json::from_value::(value).map_err(serde::de::Error::custom)?, )), - 3 => Ok(ChartRaw::V3( + 3 => Ok(Self::V3( serde_json::from_value::(value).map_err(serde::de::Error::custom)?, )), _ => Err(serde::de::Error::custom(format!( @@ -266,7 +266,7 @@ impl ChartRaw { #[must_use] pub fn convert_to_v3(self) -> Chart { match self { - ChartRaw::V1(v1) => Chart { + Self::V1(v1) => Chart { offset: v1.offset, judge_line_list: v1 .judge_line_list @@ -274,7 +274,7 @@ impl ChartRaw { .map(std::convert::Into::into) .collect(), }, - ChartRaw::V3(v3) => v3, + Self::V3(v3) => v3, } } } @@ -299,7 +299,7 @@ impl From for JudgeLine { } }) .collect(); - JudgeLine { + Self { bpm: value.bpm, notes_above: value.notes_above, notes_below: value.notes_below, diff --git a/src/draw.rs b/src/draw.rs index d4df6ef..c90f3eb 100644 --- a/src/draw.rs +++ b/src/draw.rs @@ -1,13 +1,13 @@ use crate::chart::{Note, NoteType}; -use crate::math::{self, Point}; +use crate::math::{self, Point, Rect}; use crate::renders::{ self, Dense, RendClickEffect, RendNote, RendPoint, RendSound, RendSplashEffect, RendStatistics, }; -use crate::states::{LineState, NoteScore, NoteState}; +use crate::states::{LineData, LineState, NoteScore, NoteState}; use crate::states_effect::{HitEffect, SoundEffect, SplashEffect}; use crate::{ CHART_STATISTICS, DRAW_IMAGE_OFFSET, HIT_EFFECT_POOL, LINE_STATES, SOUND_POOL, - SPLASH_EFFECT_POOL, TOUCH_STATES, + SPLASH_EFFECT_POOL, TOUCH_STATES, WORLD_RECT, }; #[allow(clippy::struct_field_names)] @@ -29,7 +29,7 @@ pub trait BufferWithCursor { impl Default for DrawImageOffset { fn default() -> Self { - DrawImageOffset { + Self { hold_head_height: 0.0, hold_head_highlight_height: 0.0, hold_end_height: 0.0, @@ -61,49 +61,51 @@ pub fn load_image_offset( /// /// The state is written by calling `write` on the provided `BufferWithCursor`. pub fn process_state_to_drawable(wrapped_buffer: &mut impl BufferWithCursor) { - CHART_STATISTICS.with_borrow(|statistics| { - wrapped_buffer.write( - RendStatistics { - rend_type: 5, - combo: statistics.combo, - max_combo: statistics.max_combo, - score: statistics.score as f32, - accurate: statistics.accurate as f32, - } - .to_bytes(), - ); - }); - LINE_STATES.with_borrow(|states| { - DRAW_IMAGE_OFFSET.with_borrow(|offset| { - for it in states { - write_line(wrapped_buffer, it); - } - write_notes(wrapped_buffer, states.as_ref(), offset); - }); - }); - HIT_EFFECT_POOL.with_borrow(|effects| { - write_click_effects(wrapped_buffer, effects); - }); - SPLASH_EFFECT_POOL.with_borrow(|effects| { - write_splash_effects(wrapped_buffer, effects); - }); - SOUND_POOL.with_borrow(|effects| write_sound_effects(wrapped_buffer, effects)); - TOUCH_STATES.with_borrow(|touches| { - for it in touches { - if !it.enable { - continue; - } + WORLD_RECT.with_borrow(|world_rect| { + CHART_STATISTICS.with_borrow(|statistics| { wrapped_buffer.write( - RendPoint { - rend_type: 4, - x: it.x, - y: it.y, + RendStatistics { + rend_type: 5, + combo: statistics.combo, + max_combo: statistics.max_combo, + score: statistics.score as f32, + accurate: statistics.accurate as f32, } .to_bytes(), ); - } + }); + LINE_STATES.with_borrow(|states| { + DRAW_IMAGE_OFFSET.with_borrow(|offset| { + for it in states { + write_line(wrapped_buffer, it, world_rect); + } + write_notes(wrapped_buffer, states.as_ref(), offset, world_rect); + }); + }); + HIT_EFFECT_POOL.with_borrow(|effects| { + write_click_effects(wrapped_buffer, effects); + }); + SPLASH_EFFECT_POOL.with_borrow(|effects| { + write_splash_effects(wrapped_buffer, effects); + }); + SOUND_POOL.with_borrow(|effects| write_sound_effects(wrapped_buffer, effects)); + TOUCH_STATES.with_borrow(|touches| { + for it in touches { + if !it.enable { + continue; + } + wrapped_buffer.write( + RendPoint { + rend_type: 4, + x: it.x, + y: it.y, + } + .to_bytes(), + ); + } + }); + wrapped_buffer.write(&[0]); }); - wrapped_buffer.write(&[0]); } fn write_sound_effects(wrapped_buffer: &mut impl BufferWithCursor, states: &SoundEffect) { @@ -154,24 +156,33 @@ fn write_click_effects(wrapped_buffer: &mut impl BufferWithCursor, states: &[Hit } } -fn write_line(wrapped_buffer: &mut impl BufferWithCursor, state: &LineState) { +fn write_line(wrapped_buffer: &mut impl BufferWithCursor, state: &LineData, world_rect: &Rect) { fn eq(a: f64, b: f64) -> bool { (a - b).abs() <= f64::EPSILON } - let p1 = math::get_cross_point_with_screen(state.x, state.y, math::fix_degree(state.rotate)); - let p2 = - math::get_cross_point_with_screen(state.x, state.y, math::fix_degree(state.rotate + 180.0)); + let p1 = math::get_cross_point_with_screen( + state.x, + state.y, + math::fix_degree(state.rotate), + world_rect, + ); + let p2 = math::get_cross_point_with_screen( + state.x, + state.y, + math::fix_degree(state.rotate + 180.0), + world_rect, + ); if state.alpha <= 0.0 { return; } - if (((eq(p1.x, 0.0) && eq(p2.x, math::WORLD_WIDTH)) - || (eq(p2.x, 0.0) && eq(p1.x, math::WORLD_WIDTH))) + if (((eq(p1.x, 0.0) && eq(p2.x, world_rect.width)) + || (eq(p2.x, 0.0) && eq(p1.x, world_rect.width))) && ((p1.y <= 0.0 && p2.y <= 0.0) - || (p1.y >= math::WORLD_HEIGHT && p2.y >= math::WORLD_HEIGHT))) - || (((eq(p1.y, 0.0) && eq(p2.y, math::WORLD_HEIGHT)) - || (eq(p2.y, 0.0) && eq(p1.y, math::WORLD_HEIGHT))) + || (p1.y >= world_rect.height && p2.y >= world_rect.height))) + || (((eq(p1.y, 0.0) && eq(p2.y, world_rect.height)) + || (eq(p2.y, 0.0) && eq(p1.y, world_rect.height))) && ((p1.x <= 0.0 && p2.x <= 0.0) - || (p1.x >= math::WORLD_WIDTH && p2.x >= math::WORLD_WIDTH))) + || (p1.x >= world_rect.width && p2.x >= world_rect.width))) { return; } @@ -189,13 +200,14 @@ fn write_line(wrapped_buffer: &mut impl BufferWithCursor, state: &LineState) { fn write_notes( wrapped_buffer: &mut impl BufferWithCursor, - states: &[LineState], + states: &[LineData], offset: &DrawImageOffset, + world_rect: &Rect, ) { let notes = states .iter() .fold((Vec::new(), Vec::new()), |(v1, v2), it| { - process_notes(it, offset, v1, v2) + process_notes(it, offset, world_rect, v1, v2) }); notes .1 @@ -205,8 +217,9 @@ fn write_notes( } fn process_notes( - state: &LineState, + state: &LineData, offset: &DrawImageOffset, + world_rect: &Rect, mut vec: Vec, mut hold_vec: Vec, ) -> (Vec, Vec) { @@ -215,6 +228,7 @@ fn process_notes( offset, &state.notes_above_state, false, + world_rect, &mut vec, &mut hold_vec, ); @@ -223,6 +237,7 @@ fn process_notes( offset, &state.notes_below_state, true, + world_rect, &mut vec, &mut hold_vec, ); @@ -238,6 +253,7 @@ fn process_notes_half( offset: &DrawImageOffset, notes: &[NoteState], reverse: bool, + world_rect: &Rect, out: &mut Vec, out_hold: &mut Vec, ) { @@ -255,9 +271,11 @@ fn process_notes_half( } match note_type { NoteType::Tap | NoteType::Drag | NoteType::Flick => { - process_normal_note(reverse, line_state, note_state, out); + process_normal_note(reverse, line_state, note_state, world_rect, out); } - NoteType::Hold => process_hold_note(reverse, line_state, note_state, offset, out_hold), + NoteType::Hold => process_hold_note( + reverse, line_state, note_state, offset, world_rect, out_hold, + ), } } } @@ -266,6 +284,7 @@ fn process_normal_note( reverse: bool, line_state: &LineState, note_state: &NoteState, + world_rect: &Rect, out: &mut Vec, ) { let LineState { @@ -294,12 +313,12 @@ fn process_normal_note( return; } let Point { x: raw_x, y: raw_y } = - math::get_pos_out_of_line(*x, *y, *rotate, position_x * math::UNIT_WIDTH); + math::get_pos_out_of_line(*x, *y, *rotate, position_x * world_rect.get_unit_width()); let Point { x, y } = math::get_pos_out_of_line( raw_x, raw_y, *rotate + if reverse { 90.0 } else { -90.0 }, - delta_y * math::UNIT_HEIGHT * speed, + delta_y * world_rect.get_unit_height() * speed, ); if !check_in_bound(x, y) { return; @@ -321,6 +340,7 @@ fn process_hold_note( line_state: &LineState, note_state: &NoteState, offset: &DrawImageOffset, + world_rect: &Rect, out_hold: &mut Vec, ) { let LineState { @@ -348,7 +368,7 @@ fn process_hold_note( let should_high_light = i8::from(*highlight); let seconds_per_tick = 60.0 / bpm / 32.0; let head_position = floor_position - line_y; - let body_height = hold_time * speed * seconds_per_tick - 0.0f64.max(-head_position); + let body_height = (hold_time * speed).mul_add(seconds_per_tick, -0.0f64.max(-head_position)); let body_position = floor_position + body_height / 2.0 - line_y + 0.0f64.max(-head_position); if *time + *hold_time as i32 <= *tick_time as i32 { return; @@ -359,23 +379,25 @@ fn process_hold_note( let Point { x: temp_x, y: temp_y, - } = math::get_pos_out_of_line(*x, *y, *rotate, position_x * math::UNIT_WIDTH); + } = math::get_pos_out_of_line(*x, *y, *rotate, position_x * world_rect.get_unit_width()); let Point { x: hx, y: hy } = math::get_pos_out_of_line( temp_x, temp_y, math::fix_degree(rotate + if reverse { 90.0 } else { -90.0 }), - head_position * math::UNIT_HEIGHT - - (if *highlight { + head_position.mul_add( + world_rect.get_unit_height(), + -(if *highlight { offset.hold_head_highlight_height / 2.0 } else { offset.hold_head_height / 2.0 }), + ), ); let Point { x: bx, y: by } = math::get_pos_out_of_line( temp_x, temp_y, math::fix_degree(rotate + if reverse { 90.0 } else { -90.0 }), - body_position * math::UNIT_HEIGHT + body_position * world_rect.get_unit_height() + if body_position <= 0.0 { body_height / 2.0 } else { @@ -385,23 +407,25 @@ fn process_hold_note( let hold_rect = math::Rect { cx: bx, cy: by, - width: math::WORLD_WIDTH / 4.0, - height: body_height * math::UNIT_HEIGHT, + width: world_rect.width / 4.0, + height: body_height * world_rect.get_unit_height(), rotate: rotate.to_radians(), }; - if !math::check_rectangles_overlap(&math::WORLD_RECT, &hold_rect) { + if !math::check_rectangles_overlap(world_rect, &hold_rect) { return; } let Point { x: ex, y: ey } = math::get_pos_out_of_line( temp_x, temp_y, math::fix_degree(rotate + if reverse { 90.0 } else { -90.0 }), - (body_position + body_height / 2.0) * math::UNIT_HEIGHT - + (if *highlight { + (body_position + body_height / 2.0).mul_add( + world_rect.get_unit_height(), + if *highlight { offset.hold_end_highlight_height / 2.0 } else { offset.hold_end_height / 2.0 - }), + }, + ), ); out_hold.push(RendNote { rend_type: 2, @@ -418,7 +442,7 @@ fn process_hold_note( x: bx as f32, y: by as f32, rotate: math::fix_degree(*rotate + if reverse { 180.0 } else { 0.0 }) as f32, - height: (body_height * math::UNIT_HEIGHT) as f32, + height: (body_height * world_rect.get_unit_height()) as f32, high_light: should_high_light, }); if *time > *tick_time as i32 { diff --git a/src/input.rs b/src/input.rs index 8501fda..2b8fa68 100644 --- a/src/input.rs +++ b/src/input.rs @@ -9,7 +9,7 @@ pub struct TouchInfo { impl Default for TouchInfo { fn default() -> Self { - TouchInfo { + Self { enable: false, x: 0.0, y: 0.0, @@ -22,15 +22,15 @@ impl Default for TouchInfo { impl TouchInfo { pub fn length(&self) -> f32 { - ((self.x - self.init_x).powi(2) + (self.y - self.init_y).powi(2)).sqrt() + (self.x - self.init_x).hypot(self.y - self.init_y) } - pub fn reset_length(&mut self) { + pub const fn reset_length(&mut self) { self.init_x = self.x; self.init_y = self.y; } - pub fn touch_down(&mut self, x: f32, y: f32) { + pub const fn touch_down(&mut self, x: f32, y: f32) { self.enable = true; self.touch_valid = true; self.init_x = x; @@ -38,12 +38,12 @@ impl TouchInfo { self.touch_move(x, y); } - pub fn touch_move(&mut self, x: f32, y: f32) { + pub const fn touch_move(&mut self, x: f32, y: f32) { self.x = x; self.y = y; } - pub fn touch_up(&mut self) { + pub const fn touch_up(&mut self) { self.enable = false; } } diff --git a/src/lib.rs b/src/lib.rs index dcc27f0..e532fef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ //! #![deny(clippy::pedantic)] +#![deny(clippy::nursery)] #![deny(missing_docs)] #![allow(clippy::cast_possible_truncation)] @@ -31,12 +32,13 @@ mod states_statistics; thread_local! { pub(crate) static DRAW_IMAGE_OFFSET:RefCell = RefCell::new(draw::DrawImageOffset::default()); pub(crate) static FLATTEN_NOTE_INDEX:RefCell>= const{RefCell::new(Vec::<_>::new())}; - pub(crate) static LINE_STATES: RefCell<[states::LineState;50]> = RefCell::new(std::array::from_fn(|_|states::LineState::default())); + pub(crate) static LINE_STATES: RefCell<[states::LineData;50]> = RefCell::new(std::array::from_fn(|_|states::LineData::default())); pub(crate) static TOUCH_STATES: RefCell<[input::TouchInfo; 30]> = RefCell::new(std::array::from_fn(|_|input::TouchInfo::default())); pub(crate) static HIT_EFFECT_POOL: RefCell<[states_effect::HitEffect; 64]> = RefCell::new(std::array::from_fn(|_|states_effect::HitEffect::default())); pub(crate) static SPLASH_EFFECT_POOL : RefCell<[states_effect::SplashEffect;256]> = RefCell::new(std::array::from_fn(|_|states_effect::SplashEffect::default())); pub(crate) static CHART_STATISTICS: RefCell = RefCell::new(states_statistics::ChartStatistics::default()); pub(crate) static SOUND_POOL: RefCell = RefCell::new(states_effect::SoundEffect::default()); + pub(crate) static WORLD_RECT: RefCell = RefCell::new(math::Rect::world_default()); } pub use chart::Chart; @@ -50,6 +52,7 @@ pub use draw::process_state_to_drawable; pub use states_initializing::clear_states; pub use states_initializing::init_line_states; pub use states_initializing::init_line_states_from_json; +pub use states_initializing::init_world_rect; pub use states_input::clear_touch; pub use states_input::set_touch_down; diff --git a/src/math.rs b/src/math.rs index 5411f64..8789b39 100644 --- a/src/math.rs +++ b/src/math.rs @@ -13,6 +13,30 @@ pub struct Rect { pub rotate: f64, } +impl Rect { + pub fn centered(width: f64, height: f64) -> Self { + Self { + cx: width / 2.0, + cy: height / 2.0, + width, + height, + rotate: 0.0, + } + } + + pub fn world_default() -> Self { + Self::centered(1920.0, 1080.0) + } + + pub fn get_unit_width(&self) -> f64 { + self.width / 18.0 + } + + pub fn get_unit_height(&self) -> f64 { + self.height * 0.6 + } +} + enum Gx { I, Ii, @@ -20,19 +44,6 @@ enum Gx { Iv, } -pub static WORLD_WIDTH: f64 = 1920.0; -pub static WORLD_HEIGHT: f64 = 1080.0; -pub static UNIT_WIDTH: f64 = WORLD_WIDTH / 18.0; -pub static UNIT_HEIGHT: f64 = WORLD_HEIGHT * 0.6; - -pub static WORLD_RECT: Rect = Rect { - cx: WORLD_WIDTH / 2.0, - cy: WORLD_HEIGHT / 2.0, - width: WORLD_WIDTH, - height: WORLD_HEIGHT, - rotate: 0.0, -}; - fn get_gx(valid_degree: f64) -> Gx { match valid_degree { 315.0..=360.0 | 0.0..=45.0 => Gx::I, @@ -43,7 +54,12 @@ fn get_gx(valid_degree: f64) -> Gx { } } -pub fn get_cross_point_with_screen(line_x: f64, line_y: f64, valid_degree: f64) -> Point { +pub fn get_cross_point_with_screen( + line_x: f64, + line_y: f64, + valid_degree: f64, + world_rect: &Rect, +) -> Point { let gx = get_gx(valid_degree); let rad = valid_degree.to_radians(); let sin = rad.sin(); @@ -54,19 +70,19 @@ pub fn get_cross_point_with_screen(line_x: f64, line_y: f64, valid_degree: f64) }; match gx { Gx::I => Point { - x: WORLD_WIDTH, - y: line_y + (WORLD_WIDTH - line_x) * tan_cot, + x: world_rect.width, + y: (world_rect.width - line_x).mul_add(tan_cot, line_y), }, Gx::Ii => Point { - x: line_x + tan_cot * (WORLD_HEIGHT - line_y), - y: WORLD_HEIGHT, + x: tan_cot.mul_add(world_rect.height - line_y, line_x), + y: world_rect.height, }, Gx::Iii => Point { x: 0.0, - y: line_y - line_x * tan_cot, + y: line_x.mul_add(-tan_cot, line_y), }, Gx::Iv => Point { - x: line_x - line_y * tan_cot, + x: line_y.mul_add(-tan_cot, line_x), y: 0.0, }, } @@ -146,29 +162,29 @@ pub fn get_pos_point_vertical_in_line( match gx { Gx::I | Gx::Iii => { let tan = sin / cos; - let tmp = point_y - line_y - (point_x - line_x) * tan; + let tmp = (point_x - line_x).mul_add(-tan, point_y - line_y); Point { - x: point_x + tmp * cos * sin, - y: point_y - tmp * cos * cos, + x: (tmp * cos).mul_add(sin, point_x), + y: (tmp * cos).mul_add(-cos, point_y), } } Gx::Ii | Gx::Iv => { let cot = cos / sin; - let tmp = point_x - line_x - (point_y - line_y) * cot; + let tmp = (point_y - line_y).mul_add(-cot, point_x - line_x); Point { - x: point_x - tmp * sin * sin, - y: point_y + tmp * sin * cos, + x: (tmp * sin).mul_add(-sin, point_x), + y: (tmp * sin).mul_add(cos, point_y), } } } } fn dot_product(x1: f64, y1: f64, x2: f64, y2: f64) -> f64 { - x1 * x2 + y1 * y2 + x1.mul_add(x2, y1 * y2) } fn get_projection_interval(rect: &Rect, axis_x: f64, axis_y: f64) -> (f64, f64) { - let center_proj = rect.cx * axis_x + rect.cy * axis_y; + let center_proj = rect.cx.mul_add(axis_x, rect.cy * axis_y); let ux = rect.rotate.cos(); let uy = rect.rotate.sin(); let vx = -uy; diff --git a/src/states.rs b/src/states.rs index d7e77b4..153735d 100644 --- a/src/states.rs +++ b/src/states.rs @@ -1,11 +1,25 @@ +use std::ops::{Deref, DerefMut}; + use serde::Serialize; use crate::{ - LINE_STATES, + LINE_STATES, WORLD_RECT, chart::{self}, states_effect, states_judge, states_lines, states_statistics, }; +#[derive(Default)] +pub struct LineData { + pub line_state: LineState, + pub notes_above_state: Vec, + pub notes_below_state: Vec, + pub speed_events: Vec, + pub move_events: Vec, + pub rotate_events: Vec, + pub alpha_events: Vec, +} + +#[derive(Debug, Clone, Copy)] pub struct LineState { pub enable: bool, pub x: f64, @@ -19,15 +33,43 @@ pub struct LineState { pub event_move_index_cache: i64, pub event_rotate_index_cache: i64, pub event_alpha_index_cache: i64, - pub notes_above_state: Vec, - pub notes_below_state: Vec, - pub speed_events: Vec, - pub move_events: Vec, - pub rotate_events: Vec, - pub alpha_events: Vec, pub bpm: f64, } +impl Default for LineState { + fn default() -> Self { + Self { + enable: false, + x: 0.0, + y: 0.0, + rotate: 0.0, + alpha: 0.0, + speed: 1.0, + line_y: 0.0, + tick_time: 0.0, + event_speed_index_cache: 0, + event_move_index_cache: 0, + event_rotate_index_cache: 0, + event_alpha_index_cache: 0, + bpm: 0.0, + } + } +} + +impl Deref for LineData { + type Target = LineState; + + fn deref(&self) -> &Self::Target { + &self.line_state + } +} + +impl DerefMut for LineData { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.line_state + } +} + #[derive(Clone, Copy, PartialEq, Eq)] pub enum NoteScore { Perfect, @@ -58,35 +100,9 @@ pub struct Metadata { pub format_version: i32, } -impl Default for LineState { - fn default() -> Self { - LineState { - enable: false, - x: 0.0, - y: 0.0, - rotate: 0.0, - alpha: 0.0, - speed: 1.0, - line_y: 0.0, - tick_time: 0.0, - event_speed_index_cache: 0, - event_move_index_cache: 0, - event_rotate_index_cache: 0, - event_alpha_index_cache: 0, - notes_above_state: vec![], - notes_below_state: vec![], - speed_events: vec![], - move_events: vec![], - alpha_events: vec![], - rotate_events: vec![], - bpm: 0.0, - } - } -} - impl Default for NoteState { fn default() -> Self { - NoteState { + Self { highlight: false, score: NoteScore::None, hold_cool_down: 0.0, @@ -138,9 +154,11 @@ pub fn reset_note_state(before_time_in_second: f64) { /// Ticking all states, including lines, judges and chart statistics pub fn tick_all(time_in_second: f64, delta_time_in_second: f64, auto: bool) { - states_lines::tick_lines(time_in_second); - states_effect::tick_effect(delta_time_in_second); - if states_judge::tick_lines_judge(delta_time_in_second, auto) { - states_statistics::refresh_chart_statistics(); - } + WORLD_RECT.with_borrow(|world_rect| { + states_lines::tick_lines(time_in_second, world_rect); + states_effect::tick_effect(delta_time_in_second); + if states_judge::tick_lines_judge(delta_time_in_second, auto, world_rect) { + states_statistics::refresh_chart_statistics(); + } + }); } diff --git a/src/states_effect.rs b/src/states_effect.rs index 01570d6..420d033 100644 --- a/src/states_effect.rs +++ b/src/states_effect.rs @@ -29,7 +29,7 @@ pub struct SoundEffect { impl Default for HitEffect { fn default() -> Self { - HitEffect { + Self { enable: false, x: 0.0, y: 0.0, @@ -41,7 +41,7 @@ impl Default for HitEffect { impl Default for SplashEffect { fn default() -> Self { - SplashEffect { + Self { enable: false, x: 0.0, y: 0.0, @@ -59,9 +59,9 @@ pub struct Rng { } impl Rng { - pub fn new(seed: u64) -> Rng { + pub const fn new(seed: u64) -> Self { let seed = if seed == 0 { 0xdead_beef } else { seed }; - Rng { state: seed } + Self { state: seed } } pub fn next(&mut self) -> f64 { @@ -73,17 +73,17 @@ impl Rng { } pub fn range(&mut self, min: f64, max: f64) -> f64 { - min + (max - min) * self.next() + (max - min).mul_add(self.next(), min) } } const RATE: f64 = 2.0; -pub(crate) fn tick_effect(delta_time_in_second: f64) { +pub fn tick_effect(delta_time_in_second: f64) { HIT_EFFECT_POOL.with_borrow_mut(|pool| { for it in pool.iter_mut() { if it.enable { - it.progress += delta_time_in_second.max(0.0) * RATE; + it.progress = delta_time_in_second.max(0.0).mul_add(RATE, it.progress); if it.progress >= 1.0 { it.enable = false; } @@ -93,14 +93,14 @@ pub(crate) fn tick_effect(delta_time_in_second: f64) { SPLASH_EFFECT_POOL.with_borrow_mut(|pool| { for it in pool.iter_mut() { if it.enable { - it.progress += delta_time_in_second.max(0.0) * RATE; + it.progress = delta_time_in_second.max(0.0).mul_add(RATE, it.progress); if it.progress >= 1.0 { it.enable = false; continue; } it.speed -= (it.speed * 7.0 * delta_time_in_second.max(0.0)).max(0.0); - it.x += it.speed * it.x_vec * delta_time_in_second.max(0.0); - it.y += it.speed * it.y_vec * delta_time_in_second.max(0.0); + it.x = (it.speed * it.x_vec).mul_add(delta_time_in_second.max(0.0), it.x); + it.y = (it.speed * it.y_vec).mul_add(delta_time_in_second.max(0.0), it.y); } } }); diff --git a/src/states_initializing.rs b/src/states_initializing.rs index cdd2f96..73284e9 100644 --- a/src/states_initializing.rs +++ b/src/states_initializing.rs @@ -2,10 +2,11 @@ use std::{collections::HashSet, default::Default}; use crate::{ CHART_STATISTICS, FLATTEN_NOTE_INDEX, HIT_EFFECT_POOL, LINE_STATES, SOUND_POOL, - SPLASH_EFFECT_POOL, TOUCH_STATES, + SPLASH_EFFECT_POOL, TOUCH_STATES, WORLD_RECT, chart::{self, ChartRaw, JudgeLine, WithTimeRange}, input::TouchInfo, - states::{LineState, Metadata, NoteState, get_seconds_per_tick}, + math::Rect, + states::{LineData, LineState, Metadata, NoteState, get_seconds_per_tick}, states_effect::{HitEffect, SoundEffect, SplashEffect}, states_statistics::{self, ChartStatistics}, }; @@ -32,13 +33,13 @@ pub fn init_line_states(chart_raw: chart::ChartRaw) -> Metadata { .judge_line_list .into_iter() .map(|mut line| { - line.notes_above.sort_by(|a, b| (a.time).cmp(&b.time)); - line.notes_below.sort_by(|a, b| (a.time).cmp(&b.time)); + line.notes_above.sort_by_key(|a| a.time); + line.notes_below.sort_by_key(|a| a.time); line }) .collect::>(); let metadata = LINE_STATES.with_borrow_mut(|states| { - *states = std::array::from_fn(|_| LineState::default()); + *states = std::array::from_fn(|_| LineData::default()); let available_len = chart.judge_line_list.len(); for (i, it) in chart.judge_line_list.into_iter().enumerate() { let JudgeLine { @@ -50,9 +51,12 @@ pub fn init_line_states(chart_raw: chart::ChartRaw) -> Metadata { rotate_events, alpha_events, } = it; - states[i] = LineState { - enable: true, - bpm, + states[i] = LineData { + line_state: LineState { + enable: true, + bpm, + ..Default::default() + }, move_events, alpha_events, speed_events, @@ -73,7 +77,6 @@ pub fn init_line_states(chart_raw: chart::ChartRaw) -> Metadata { ..Default::default() }) .collect(), - ..Default::default() } } states @@ -91,10 +94,17 @@ pub fn init_line_states(chart_raw: chart::ChartRaw) -> Metadata { metadata } +/// Initialize world rect from width and height +pub fn init_world_rect(width: f64, height: f64) { + WORLD_RECT.with_borrow_mut(|world_height| { + *world_height = Rect::centered(width, height); + }); +} + /// Clear the states of lines pub fn clear_states() { FLATTEN_NOTE_INDEX.with_borrow_mut(std::vec::Vec::clear); - LINE_STATES.with_borrow_mut(|it| *it = std::array::from_fn(|_| LineState::default())); + LINE_STATES.with_borrow_mut(|it| *it = std::array::from_fn(|_| LineData::default())); TOUCH_STATES.with_borrow_mut(|it| *it = std::array::from_fn(|_| TouchInfo::default())); HIT_EFFECT_POOL.with_borrow_mut(|it| *it = std::array::from_fn(|_| HitEffect::default())); SPLASH_EFFECT_POOL.with_borrow_mut(|it| *it = std::array::from_fn(|_| SplashEffect::default())); @@ -102,7 +112,7 @@ pub fn clear_states() { SOUND_POOL.with_borrow_mut(|it| *it = SoundEffect::default()); } -fn process_highlight(judge_line_states: &mut [LineState]) { +fn process_highlight(judge_line_states: &mut [LineData]) { let mut set1 = HashSet::::new(); let mut set2 = HashSet::::new(); for it in judge_line_states.iter() { @@ -143,7 +153,7 @@ fn process_highlight(judge_line_states: &mut [LineState]) { } } -fn get_estimated_length(state: &[LineState]) -> f64 { +fn get_estimated_length(state: &[LineData]) -> f64 { let note_max_time = state.iter().fold(0.0, |last, it| { let seconds_per_tick = get_seconds_per_tick(it.bpm); let get_time = |note: &NoteState| -> f64 { diff --git a/src/states_judge.rs b/src/states_judge.rs index aa6727f..7430a0c 100644 --- a/src/states_judge.rs +++ b/src/states_judge.rs @@ -2,13 +2,13 @@ use crate::{ FLATTEN_NOTE_INDEX, LINE_STATES, TOUCH_STATES, chart::{Note, NoteType}, input::TouchInfo, - math::{self, Point}, - states::{LineState, NoteScore, NoteState}, + math::{self, Point, Rect}, + states::{LineData, LineState, NoteScore, NoteState}, states_effect, states_statistics::NoteIndex, }; -pub(crate) fn tick_lines_judge(delta_time_in_second: f64, auto: bool) -> bool { +pub fn tick_lines_judge(delta_time_in_second: f64, auto: bool, world_rect: &Rect) -> bool { states_effect::clear_sound_effect(); TOUCH_STATES.with_borrow_mut(|touches| { LINE_STATES.with_borrow_mut(|lines| { @@ -19,6 +19,7 @@ pub(crate) fn tick_lines_judge(delta_time_in_second: f64, auto: bool) -> bool { touches.as_mut(), lines.as_mut(), auto, + world_rect, ) }) }) @@ -29,8 +30,9 @@ fn tick_line_judge( delta_time_in_second: f64, flatten_note_index: &[NoteIndex], touches: &mut [TouchInfo], - lines: &mut [LineState], + lines: &mut [LineData], auto: bool, + world_rect: &Rect, ) -> bool { let mut judged = false; for note_index in flatten_note_index { @@ -41,9 +43,7 @@ fn tick_line_judge( continue; } let current_tick = line.tick_time; - let line_x = line.x; - let line_y = line.y; - let line_rotate = line.rotate; + let state = line.line_state; let bpm = line.bpm; let Some(note) = note_index.find_mut_note(line) else { continue; @@ -56,52 +56,32 @@ fn tick_line_judge( current_tick, note, touches, - line_x, - line_y, - line_rotate, + &state, bpm, + world_rect, ), - _ => tick_normal_note_auto(current_tick, note, line_x, line_y, line_rotate, bpm), + _ => tick_normal_note_auto(current_tick, note, &state, bpm, world_rect), } } else { match note_type { - NoteType::Tap => tick_tap_note( - current_tick, - note, - touches, - line_x, - line_y, - line_rotate, - bpm, - ), - NoteType::Drag => tick_drag_note( - current_tick, - note, - touches, - line_x, - line_y, - line_rotate, - bpm, - ), + NoteType::Tap => { + tick_tap_note(current_tick, note, touches, &state, bpm, world_rect) + } + NoteType::Drag => { + tick_drag_note(current_tick, note, touches, &state, bpm, world_rect) + } NoteType::Hold => tick_hold_note( delta_time_in_second, current_tick, note, touches, - line_x, - line_y, - line_rotate, - bpm, - ), - NoteType::Flick => tick_flick_note( - current_tick, - note, - touches, - line_x, - line_y, - line_rotate, + &state, bpm, + world_rect, ), + NoteType::Flick => { + tick_flick_note(current_tick, note, touches, &state, bpm, world_rect) + } } }; judged |= local_judged; @@ -115,9 +95,7 @@ fn tick_line_judge( } fn check_point_in_judge_range( - line_x: f64, - line_y: f64, - line_rotate: f64, + line_state: &LineState, Note { position_x: note_position_x, .. @@ -127,7 +105,14 @@ fn check_point_in_judge_range( y: touch_y, .. }: &TouchInfo, + world_rect: &Rect, ) -> (bool, (f64, f64)) { + let LineState { + x: line_x, + y: line_y, + rotate: line_rotate, + .. + } = *line_state; let Point { x: root_x, y: root_y, @@ -135,7 +120,7 @@ fn check_point_in_judge_range( line_x, line_y, line_rotate, - *note_position_x * math::UNIT_WIDTH, + *note_position_x * world_rect.get_unit_width(), ); let Point { x: touch_root_x, @@ -188,11 +173,16 @@ fn create_splash(seed: f64, x: f64, y: f64, note_score: NoteScore) { fn tick_normal_note_auto( current_tick: f64, note: &mut NoteState, - line_x: f64, - line_y: f64, - line_rotate: f64, + line_state: &LineState, bpm: f64, + world_rect: &Rect, ) -> bool { + let LineState { + x: line_x, + y: line_y, + rotate: line_rotate, + .. + } = *line_state; if note.score != NoteScore::None { return false; } @@ -205,7 +195,7 @@ fn tick_normal_note_auto( line_x, line_y, line_rotate, - note.note.position_x * math::UNIT_WIDTH, + note.note.position_x * world_rect.get_unit_width(), ); note.score = NoteScore::Perfect; create_splash(current_tick, root_x, root_y, NoteScore::Perfect); @@ -219,11 +209,16 @@ fn tick_flick_note( current_tick: f64, note: &mut NoteState, touches: &mut [TouchInfo], - line_x: f64, - line_y: f64, - line_rotate: f64, + line_state: &LineState, bpm: f64, + world_rect: &Rect, ) -> bool { + let LineState { + x: line_x, + y: line_y, + rotate: line_rotate, + .. + } = *line_state; if note.score != NoteScore::None { return false; } @@ -240,7 +235,7 @@ fn tick_flick_note( line_x, line_y, line_rotate, - note.note.position_x * math::UNIT_WIDTH, + note.note.position_x * world_rect.get_unit_width(), ); note.score = NoteScore::Perfect; create_splash(current_tick, root_x, root_y, NoteScore::Perfect); @@ -258,7 +253,7 @@ fn tick_flick_note( continue; } let (is_in_judge_range, _) = - check_point_in_judge_range(line_x, line_y, line_rotate, ¬e.note, touch); + check_point_in_judge_range(line_state, ¬e.note, touch, world_rect); if is_in_judge_range && touch.length() >= 50.0 { note.extra_score = NoteScore::Perfect; touch.reset_length(); @@ -268,16 +263,14 @@ fn tick_flick_note( false } -#[allow(clippy::too_many_arguments)] fn tick_hold_note_auto( delta_time_in_second: f64, current_tick: f64, note: &mut NoteState, - touches: &mut [TouchInfo], - line_x: f64, - line_y: f64, - line_rotate: f64, + touches: &[TouchInfo], + line_state: &LineState, bpm: f64, + world_rect: &Rect, ) -> bool { if note.score != NoteScore::None { return false; @@ -292,11 +285,10 @@ fn tick_hold_note_auto( current_tick, note, touches, - line_x, - line_y, - line_rotate, + line_state, bpm, true, + world_rect, ) .1 } @@ -306,13 +298,18 @@ fn tick_hold_note_common( delta_time_in_second: f64, current_tick: f64, note: &mut NoteState, - touches: &mut [TouchInfo], - line_x: f64, - line_y: f64, - line_rotate: f64, + touches: &[TouchInfo], + line_state: &LineState, bpm: f64, auto: bool, + world_rect: &Rect, ) -> (bool, bool) { + let LineState { + x: line_x, + y: line_y, + rotate: line_rotate, + .. + } = *line_state; if note.extra_score != NoteScore::None { let seconds_per_tick = 60.0 / bpm / 32.0; let delta_tick = delta_time_in_second / seconds_per_tick; @@ -326,12 +323,12 @@ fn tick_hold_note_common( line_x, line_y, line_rotate, - note.note.position_x * math::UNIT_WIDTH, + note.note.position_x * world_rect.get_unit_width(), ); if auto || touches.iter().any(|touch| { let (is_in_judge_range, _) = - check_point_in_judge_range(line_x, line_y, line_rotate, ¬e.note, touch); + check_point_in_judge_range(line_state, ¬e.note, touch, world_rect); is_in_judge_range && touch.enable }) || note.note.hold_time + f64::from(note.note.time) - 16.0 <= current_tick @@ -356,16 +353,14 @@ fn tick_hold_note_common( (false, false) } -#[allow(clippy::too_many_arguments)] fn tick_hold_note( delta_time_in_second: f64, current_tick: f64, note: &mut NoteState, touches: &mut [TouchInfo], - line_x: f64, - line_y: f64, - line_rotate: f64, + line_state: &LineState, bpm: f64, + world_rect: &Rect, ) -> bool { if note.score != NoteScore::None { return false; @@ -375,11 +370,10 @@ fn tick_hold_note( current_tick, note, touches, - line_x, - line_y, - line_rotate, + line_state, bpm, false, + world_rect, ); if hold { return hold_judged; @@ -397,7 +391,7 @@ fn tick_hold_note( continue; } let (is_in_judge_range, _) = - check_point_in_judge_range(line_x, line_y, line_rotate, ¬e.note, touch); + check_point_in_judge_range(line_state, ¬e.note, touch, world_rect); if is_in_judge_range && touch.touch_valid { if judge_result != NoteScore::Perfect && judge_result != NoteScore::Good { continue; @@ -415,11 +409,16 @@ fn tick_drag_note( current_tick: f64, note: &mut NoteState, touches: &mut [TouchInfo], - line_x: f64, - line_y: f64, - line_rotate: f64, + line_state: &LineState, bpm: f64, + world_rect: &Rect, ) -> bool { + let LineState { + x: line_x, + y: line_y, + rotate: line_rotate, + .. + } = *line_state; if note.score != NoteScore::None { return false; } @@ -436,7 +435,7 @@ fn tick_drag_note( line_x, line_y, line_rotate, - note.note.position_x * math::UNIT_WIDTH, + note.note.position_x * world_rect.get_unit_width(), ); note.score = NoteScore::Perfect; states_effect::new_sound_effect(NoteType::Drag); @@ -454,7 +453,7 @@ fn tick_drag_note( continue; } let (is_in_judge_range, _) = - check_point_in_judge_range(line_x, line_y, line_rotate, ¬e.note, touch); + check_point_in_judge_range(line_state, ¬e.note, touch, world_rect); if is_in_judge_range { note.extra_score = NoteScore::Perfect; return false; @@ -467,10 +466,9 @@ fn tick_tap_note( current_tick: f64, note: &mut NoteState, touches: &mut [TouchInfo], - line_x: f64, - line_y: f64, - line_rotate: f64, + line_state: &LineState, bpm: f64, + world_rect: &Rect, ) -> bool { if note.score != NoteScore::None { return false; @@ -489,7 +487,7 @@ fn tick_tap_note( continue; } let (is_in_judge_range, (root_x, root_y)) = - check_point_in_judge_range(line_x, line_y, line_rotate, ¬e.note, touch); + check_point_in_judge_range(line_state, ¬e.note, touch, world_rect); if is_in_judge_range && touch.touch_valid { touch.touch_valid = false; note.score = judge_result; diff --git a/src/states_lines.rs b/src/states_lines.rs index ba63f00..80b178d 100644 --- a/src/states_lines.rs +++ b/src/states_lines.rs @@ -1,19 +1,19 @@ use crate::{ LINE_STATES, chart::{self, TimeState, WithTimeRange, WithValue}, - math, - states::LineState, + math::{self, Rect}, + states::LineData, }; -pub(crate) fn tick_lines(time_in_second: f64) { +pub fn tick_lines(time_in_second: f64, world_rect: &Rect) { LINE_STATES.with_borrow_mut(|x| { for state in x.iter_mut() { - tick_line_state(time_in_second, state); + tick_line_state(time_in_second, state, world_rect); } }); } -fn get_line_y(tick_time: f64, line: &LineState) -> f64 { +fn get_line_y(tick_time: f64, line: &LineData) -> f64 { let mut t = 0.0; let seconds_per_tick = 60.0 / line.bpm / 32.0; let speed_events = &line.speed_events; @@ -24,18 +24,18 @@ fn get_line_y(tick_time: f64, line: &LineState) -> f64 { if event.start_time < tick_time && tick_time < event.end_time { let duration = event.end_time - event.start_time; let percent = (tick_time - event.start_time) / duration; - t += duration * percent * event.value; + t = (duration * percent).mul_add(event.value, t); break; } if event.end_time < tick_time { - t += (event.end_time - event.start_time) * event.value; + t = (event.end_time - event.start_time).mul_add(event.value, t); } } t * seconds_per_tick } #[allow(clippy::similar_names)] -fn tick_line_state(time_in_second: f64, state: &mut LineState) { +fn tick_line_state(time_in_second: f64, state: &mut LineData, world_rect: &Rect) { let seconds_per_tick = 60.0 / state.bpm / 32.0; let tick_time = time_in_second / seconds_per_tick; let ((speed_value, _), _, speed_new_index) = get_current_value_for_event( @@ -50,7 +50,7 @@ fn tick_line_state(time_in_second: f64, state: &mut LineState) { &state.alpha_events, state.event_alpha_index_cache, ); - state.alpha = alpha_start + (alpha_end - alpha_start) * alpha_percent; + state.alpha = (alpha_end - alpha_start).mul_add(alpha_percent, alpha_start); state.event_alpha_index_cache = alpha_new_index; let ((rotate_start, rotate_end), rotate_percent, rotate_new_index) = get_current_value_for_event( @@ -59,13 +59,13 @@ fn tick_line_state(time_in_second: f64, state: &mut LineState) { state.event_rotate_index_cache, ); state.rotate = - math::fix_degree(360.0 - (rotate_start + (rotate_end - rotate_start) * rotate_percent)); + math::fix_degree(360.0 - (rotate_end - rotate_start).mul_add(rotate_percent, rotate_start)); state.event_rotate_index_cache = rotate_new_index; let (((line_x_start, line_x_end), (line_y_start, line_y_end)), line_percent, line_new_index) = get_current_value_for_event(tick_time, &state.move_events, state.event_move_index_cache); - state.x = math::WORLD_WIDTH * (line_x_start + (line_x_end - line_x_start) * line_percent); + state.x = world_rect.width * (line_x_end - line_x_start).mul_add(line_percent, line_x_start); state.y = - math::WORLD_HEIGHT * (1.0 - (line_y_start + (line_y_end - line_y_start) * line_percent)); + world_rect.height * (1.0 - (line_y_end - line_y_start).mul_add(line_percent, line_y_start)); state.event_move_index_cache = line_new_index; state.line_y = get_line_y(tick_time, state); state.tick_time = tick_time; @@ -120,11 +120,9 @@ where if i <= 0 { return None; } - return if let Some(x) = events.last() { - Some((x, i64::from(events.len() as u32) - 1, 1.0)) - } else { - None - }; + return events + .last() + .map(|x| (x, i64::from(events.len() as u32) - 1, 1.0)); } } } diff --git a/src/states_statistics.rs b/src/states_statistics.rs index db0289b..c24a588 100644 --- a/src/states_statistics.rs +++ b/src/states_statistics.rs @@ -1,6 +1,6 @@ use crate::{ CHART_STATISTICS, FLATTEN_NOTE_INDEX, LINE_STATES, - states::{self, LineState, NoteState}, + states::{self, LineData, NoteState}, }; pub struct NoteIndex { @@ -19,7 +19,7 @@ pub struct ChartStatistics { impl Default for ChartStatistics { fn default() -> Self { - ChartStatistics { + Self { combo: 0, max_combo: 0, score: 0.0, @@ -29,7 +29,7 @@ impl Default for ChartStatistics { } impl NoteIndex { - pub fn find_note<'a>(&self, line_states: &'a [LineState]) -> Option<&'a NoteState> { + pub fn find_note<'a>(&self, line_states: &'a [LineData]) -> Option<&'a NoteState> { line_states.get(self.index_in_line).and_then(|line_state| { (if self.above { &line_state.notes_above_state @@ -40,11 +40,11 @@ impl NoteIndex { }) } - pub fn find_mut_line<'a>(&self, line_states: &'a mut [LineState]) -> Option<&'a mut LineState> { + pub fn find_mut_line<'a>(&self, line_states: &'a mut [LineData]) -> Option<&'a mut LineData> { line_states.get_mut(self.index_in_line) } - pub fn find_mut_note<'a>(&self, line_state: &'a mut LineState) -> Option<&'a mut NoteState> { + pub fn find_mut_note<'a>(&self, line_state: &'a mut LineData) -> Option<&'a mut NoteState> { (if self.above { &mut line_state.notes_above_state } else { @@ -62,7 +62,7 @@ pub fn init_flatten_line_state() { }); } -fn internal_init_flatten_line_state(line_states: &[LineState], flatten_index: &mut Vec) { +fn internal_init_flatten_line_state(line_states: &[LineData], flatten_index: &mut Vec) { let mut o = line_states .iter() .enumerate() @@ -94,7 +94,7 @@ fn internal_init_flatten_line_state(line_states: &[LineState], flatten_index: &m *flatten_index = o; } -pub(crate) fn refresh_chart_statistics() { +pub fn refresh_chart_statistics() { LINE_STATES.with_borrow(|line_states| { FLATTEN_NOTE_INDEX.with_borrow(|flatten_index| { CHART_STATISTICS.with_borrow_mut(|chart_statistics| { @@ -105,7 +105,7 @@ pub(crate) fn refresh_chart_statistics() { } fn internal_refresh_chart_statistics( - line_states: &[LineState], + line_states: &[LineData], flatten_index: &[NoteIndex], chart_statistics: &mut ChartStatistics, ) { @@ -129,22 +129,19 @@ fn internal_refresh_chart_statistics( } let max_combo = combos.iter().max().copied().unwrap_or(0u32); let current_combo = combos.last().copied().unwrap_or(0u32); - let judge_results = - flatten_index - .iter() - .fold((0, 0), |score, it| match it.find_note(line_states) { - None => score, - Some(state) => match state.score { - states::NoteScore::Perfect => (score.0 + 1, score.1), - states::NoteScore::Good => (score.0, score.1 + 1), - _ => score, - }, - }); + let judge_results = flatten_index.iter().fold((0, 0), |score, it| { + it.find_note(line_states) + .map_or(score, |state| match state.score { + states::NoteScore::Perfect => (score.0 + 1, score.1), + states::NoteScore::Good => (score.0, score.1 + 1), + _ => score, + }) + }); let total_notes = flatten_index.len(); let accurate = (f64::from(judge_results.0) + f64::from(judge_results.1) * 0.65) / f64::from(total_notes as u32); - let score = - (f64::from(max_combo) / f64::from(total_notes as u32) * 100_000.0) + (accurate * 900_000.0); + let score = (f64::from(max_combo) / f64::from(total_notes as u32)) + .mul_add(100_000.0, accurate * 900_000.0); *chart_statistics = ChartStatistics { combo: current_combo, max_combo,