From 5ba841518ba8df01054be867deab1c02065c8136 Mon Sep 17 00:00:00 2001 From: polina4096 Date: Sat, 24 Aug 2024 21:13:02 +0300 Subject: [PATCH] Migrate to beatmap hashes instead of paths for ids --- Cargo.lock | 19 ++- crates/apex-client/Cargo.toml | 1 + .../apex-client/src/client/action/select.rs | 4 +- crates/apex-client/src/client/client.rs | 51 +++--- crates/apex-client/src/client/event.rs | 21 ++- .../src/client/gameplay/beatmap.rs | 153 +++++++++++++++--- .../src/client/gameplay/beatmap_cache.rs | 50 +++--- .../src/client/gameplay/taiko_player.rs | 11 +- .../graphics/taiko_renderer/taiko_renderer.rs | 12 +- crates/apex-client/src/client/score/score.rs | 2 +- .../src/client/score/score_cache.rs | 21 +-- .../screen/gameplay_screen/gameplay_screen.rs | 15 +- .../screen/pause_screen/pause_screen.rs | 4 +- .../recording_screen/recording_screen.rs | 6 +- .../screen/result_screen/result_screen.rs | 29 ++-- .../selection_screen/selection_screen.rs | 8 +- .../ui/beatmap_selection/beatmap_card.rs | 17 +- .../ui/beatmap_selection/beatmap_list.rs | 6 +- .../ui/beatmap_selection/beatmap_scores.rs | 16 +- .../src/client/ui/beatmap_selection/mod.rs | 37 +++-- .../src/client/ui/play_results/mod.rs | 76 ++++----- .../src/client/ui/recording_panel/mod.rs | 4 +- 22 files changed, 348 insertions(+), 215 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb04a8d..2d3a595 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -165,6 +165,7 @@ dependencies = [ "ahash", "apex-framework", "atty", + "blake3", "bytemuck", "bytesize", "cfg-if", @@ -436,6 +437,19 @@ version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "415f8399438eb5e4b2f73ed3152a3448b98149dda642a957ee704e1daa5cf1d8" +[[package]] +name = "blake3" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block" version = "0.1.6" @@ -565,12 +579,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.7" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] diff --git a/crates/apex-client/Cargo.toml b/crates/apex-client/Cargo.toml index 6cf2bdd..c4bc614 100644 --- a/crates/apex-client/Cargo.toml +++ b/crates/apex-client/Cargo.toml @@ -61,6 +61,7 @@ instant = "0.1.12" # Data Structures ahash = "0.8.11" nohash-hasher = "0.2.0" +blake3 = "1.5.4" dashmap = "6.0.1" indexmap = "2.4.0" triomphe = "0.1.13" diff --git a/crates/apex-client/src/client/action/select.rs b/crates/apex-client/src/client/action/select.rs index bfc28e4..ecdfc67 100644 --- a/crates/apex-client/src/client/action/select.rs +++ b/crates/apex-client/src/client/action/select.rs @@ -12,12 +12,12 @@ impl Action for Select { match client.game_state { GameState::Selection => { let selected_idx = client.selection_screen.beatmap_selector().selected(); - #[rustfmt::skip] let Some((path, _)) = client.beatmap_cache.get_index(selected_idx) else { + #[rustfmt::skip] let Some((hash, _)) = client.beatmap_cache.get_index(selected_idx) else { log::error!("Failed to select beatmap, no beatmap with cache idx `{}` found.", selected_idx); return true; }; - client.event_bus.send(ClientEvent::PickBeatmap { path: path.clone() }); + client.event_bus.send(ClientEvent::PickBeatmap { beatmap_hash: hash }); return true; } diff --git a/crates/apex-client/src/client/client.rs b/crates/apex-client/src/client/client.rs index 45a897d..36ccfb0 100644 --- a/crates/apex-client/src/client/client.rs +++ b/crates/apex-client/src/client/client.rs @@ -47,7 +47,10 @@ use super::{ action::ClientAction, audio::game_audio::GameAudio, event::ClientEvent, - gameplay::beatmap_cache::{BeatmapCache, BeatmapInfo}, + gameplay::{ + beatmap::Beatmap, + beatmap_cache::{BeatmapCache, BeatmapInfo}, + }, graphics::{FrameLimiterOptions, RenderingBackend}, score::score_cache::ScoreCache, screen::{ @@ -198,7 +201,7 @@ impl App for Client { } GameState::Results => { - self.result_screen.prepare(core, &self.beatmap_cache, &self.score_cache); + self.result_screen.prepare(core); } } @@ -349,9 +352,12 @@ impl App for Client { fn dispatch(&mut self, core: &mut Core, event: ClientEvent) { match event { - ClientEvent::PickBeatmap { path } => { + ClientEvent::PickBeatmap { beatmap_hash } => { + let beatmap_info = self.beatmap_cache.get(beatmap_hash).unwrap(); + let beatmap = Beatmap::from_path(&beatmap_info.file_path); + + self.gameplay_screen.play(beatmap, &core.graphics, &mut self.audio); self.game_state = GameState::Playing; - self.gameplay_screen.play(&path, &core.graphics, &mut self.audio); } ClientEvent::SelectBeatmap => { @@ -366,15 +372,20 @@ impl App for Client { self.settings_screen.toggle(); } - ClientEvent::ShowResultScreen { path, score } => { - let score_id = self.score_cache.insert(path.clone(), score); - self.result_screen.set_score(&self.beatmap_cache, &self.score_cache, &path, score_id); - self.selection_screen.update_scores(&mut self.score_cache, &path); + ClientEvent::ShowResultScreen { beatmap_hash, score } => { + let beatmap_info = self.beatmap_cache.get(beatmap_hash).unwrap(); + let beatmap = Beatmap::from_path(&beatmap_info.file_path); + self.score_cache.insert(beatmap_hash, score.clone()); + self.result_screen.set_score(beatmap, score); + self.selection_screen.update_scores(&mut self.score_cache, beatmap_hash); self.game_state = GameState::Results; } - ClientEvent::ViewScore { path, score_id } => { - self.result_screen.set_score(&self.beatmap_cache, &self.score_cache, &path, score_id); + ClientEvent::ViewScore { beatmap_hash, score_id } => { + let beatmap_info = self.beatmap_cache.get(beatmap_hash).unwrap(); + let beatmap = Beatmap::from_path(&beatmap_info.file_path); + let score = self.score_cache.score_details(score_id); + self.result_screen.set_score(beatmap, score.clone()); self.game_state = GameState::Results; } @@ -454,7 +465,7 @@ impl Client { let score_cache = ScoreCache::new(conn); #[rustfmt::skip] let selection_screen = SelectionScreen::new(event_bus.clone(), &beatmap_cache, &mut audio, graphics, &settings); - #[rustfmt::skip] let result_screen = ResultScreen::new(event_bus.clone(), &score_cache); + #[rustfmt::skip] let result_screen = ResultScreen::new(); #[rustfmt::skip] let gameplay_screen = GameplayScreen::new(event_bus.clone(), graphics, &audio, &settings); #[rustfmt::skip] let settings_screen = SettingsScreen::new(); #[rustfmt::skip] let volume_screen = VolumeScreen::new(); @@ -525,24 +536,26 @@ impl Client { pub fn play_beatmap_audio(&mut self) { let selected = self.selection_screen.beatmap_selector().selected(); - let Some((path, beatmap)) = self.beatmap_cache.get_index(selected) else { + let Some((_, beatmap_info)) = self.beatmap_cache.get_index(selected) else { return; }; - if beatmap.audio_path != self.prev_audio_path || path.parent().unwrap() != self.prev_beatmap_path { - self.prev_beatmap_path = path.parent().unwrap().to_owned(); - self.prev_audio_path = beatmap.audio_path.clone(); + if beatmap_info.audio_path != self.prev_audio_path + || beatmap_info.file_path.parent().unwrap() != self.prev_beatmap_path + { + self.prev_beatmap_path = beatmap_info.file_path.parent().unwrap().to_owned(); + self.prev_audio_path = beatmap_info.audio_path.clone(); } else { return; } - Self::play_beatmap_audio_unchecked(&mut self.audio, path, beatmap); + Self::play_beatmap_audio_unchecked(&mut self.audio, &beatmap_info.file_path, beatmap_info); } - pub fn play_beatmap_audio_unchecked(audio: &mut GameAudio, path: &Path, beatmap: &BeatmapInfo) { + pub fn play_beatmap_audio_unchecked(audio: &mut GameAudio, path: &Path, beatmap_info: &BeatmapInfo) { use std::time::Duration; - let audio_path = path.parent().unwrap().join(&beatmap.audio_path); + let audio_path = path.parent().unwrap().join(&beatmap_info.audio_path); let file = BufReader::new(File::open(audio_path).unwrap()); let source = Decoder::new(file).unwrap(); @@ -557,7 +570,7 @@ impl Client { audio.set_playing(false); audio.set_source(source); - audio.set_position(Time::from_ms(beatmap.preview_time as f64)); + audio.set_position(Time::from_ms(beatmap_info.preview_time as f64)); audio.set_playing(true); } } diff --git a/crates/apex-client/src/client/event.rs b/crates/apex-client/src/client/event.rs index 9c2206f..cf53a4c 100644 --- a/crates/apex-client/src/client/event.rs +++ b/crates/apex-client/src/client/event.rs @@ -1,14 +1,23 @@ -use std::path::PathBuf; - -use super::score::{score::Score, score_cache::ScoreId}; +use super::{ + gameplay::beatmap::BeatmapHash, + score::{score::Score, score_cache::ScoreId}, +}; #[derive(Debug)] pub enum ClientEvent { RetryBeatmap, ToggleSettings, ToggleRecordingWindow, - ShowResultScreen { path: PathBuf, score: Score }, - ViewScore { path: PathBuf, score_id: ScoreId }, - PickBeatmap { path: PathBuf }, + ShowResultScreen { + beatmap_hash: BeatmapHash, + score: Score, + }, + ViewScore { + beatmap_hash: BeatmapHash, + score_id: ScoreId, + }, + PickBeatmap { + beatmap_hash: BeatmapHash, + }, SelectBeatmap, } diff --git a/crates/apex-client/src/client/gameplay/beatmap.rs b/crates/apex-client/src/client/gameplay/beatmap.rs index 7651652..4a0ba6b 100644 --- a/crates/apex-client/src/client/gameplay/beatmap.rs +++ b/crates/apex-client/src/client/gameplay/beatmap.rs @@ -1,35 +1,31 @@ -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use ahash::AHashMap; use intbits::Bits; use log::warn; use apex_framework::time::time::Time; +use smart_default::SmartDefault; use super::taiko_hit_object::{TaikoColor, TaikoHitObject}; -#[derive(Debug, Clone)] +// Beatmap timing-related structs +#[derive(SmartDefault, Debug, Clone)] pub struct TimingPoint { + #[default(Time::zero())] pub time: Time, - pub bpm: f64, -} -impl Default for TimingPoint { - fn default() -> Self { - return Self { time: Time::zero(), bpm: 60.0 }; - } + #[default = 60.0] + pub bpm: f64, } -#[derive(Debug, Clone)] +#[derive(SmartDefault, Debug, Clone)] pub struct VelocityPoint { + #[default(Time::zero())] pub time: Time, - pub velocity: f64, -} -impl Default for VelocityPoint { - fn default() -> Self { - return Self { time: Time::zero(), velocity: 1.0 }; - } + #[default = 1.0] + pub velocity: f64, } #[derive(Debug, Clone)] @@ -38,6 +34,38 @@ pub struct BreakPoint { pub end: Time, } +/// Unique local identifier for a beatmap. +/// +/// May change when the beatmap is changed, usually does not change with new game versions. Used to differentiate +/// beatmaps in client code, most commonly in the beatmap cache and in other clientside beatmap logic. Never rely on +/// this id to uniquely identify a beatmap across multiple game clients! If needed, use online ids with hash checks, or +/// anything else that is guaranteed to be invalidated on any changes to the file and allows updates. +/// +/// We can't rely on the beatmap's path because it can be changed by the user or between game versions easily without +/// affecting the beatmap itself. Also we can't go with random ids because they change every restart if not stored in +/// the beatmap files (which is a bad idea too). +/// +/// For that reason, we use a blake3 hash of certain beatmap data. Generally, the possible kinds of relevant beatmap +/// data should not change between game versions which means we, for the most part, can safely rely on it's hash as a +/// unique identifier. As a rule of thumb, we should not hash empty or default values, so the hashes are not affected +/// by new fields being added. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct BeatmapHash(blake3::Hash); + +impl ToString for BeatmapHash { + fn to_string(&self) -> String { + return format!("{}", self.0); + } +} + +impl Default for BeatmapHash { + fn default() -> Self { + let beatmap = Beatmap::default(); + return beatmap.hash(); + } +} + +// TODO: getters, hide hash field #[derive(Clone)] pub struct Beatmap { pub hit_objects: Vec, @@ -45,28 +73,85 @@ pub struct Beatmap { pub velocity_points: Vec, pub break_points: Vec, + pub title: String, + pub artist: String, + pub creator: String, + pub variant: String, + + pub hp_drain_rate: f32, pub overall_difficulty: f32, + pub velocity_multiplier: f32, - pub audio: PathBuf, + pub file_path: PathBuf, + pub audio_path: PathBuf, + pub bg_path: PathBuf, + + pub hash: Option, } impl Default for Beatmap { fn default() -> Self { return Self { hit_objects: Vec::new(), + timing_points: Vec::new(), velocity_points: Vec::new(), break_points: Vec::new(), + title: String::new(), + artist: String::new(), + creator: String::new(), + variant: String::new(), + + hp_drain_rate: 5.0, overall_difficulty: 5.0, + velocity_multiplier: 0.6, - audio: PathBuf::new(), + file_path: PathBuf::new(), + audio_path: PathBuf::new(), + bg_path: PathBuf::new(), + + hash: None, }; } } +impl Beatmap { + pub fn hash(&self) -> BeatmapHash { + return self.hash.unwrap_or_else(|| { + let mut hasher = blake3::Hasher::new(); + + for hit_object in &self.hit_objects { + hasher.update(&hit_object.time.to_seconds().to_le_bytes()); + hasher.update(&[hit_object.color.is_kat() as u8]); + hasher.update(&[hit_object.big as u8]); + } + + for timing_point in &self.timing_points { + hasher.update(&timing_point.time.to_seconds().to_le_bytes()); + hasher.update(&timing_point.bpm.to_le_bytes()); + } + + for velocity_point in &self.velocity_points { + hasher.update(&velocity_point.time.to_seconds().to_le_bytes()); + hasher.update(&velocity_point.velocity.to_le_bytes()); + } + + for break_point in &self.break_points { + hasher.update(&break_point.start.to_seconds().to_le_bytes()); + hasher.update(&break_point.end.to_seconds().to_le_bytes()); + } + + hasher.update(&self.overall_difficulty.to_le_bytes()); + hasher.update(&self.velocity_multiplier.to_le_bytes()); + + return BeatmapHash(hasher.finalize()); + }); + } +} + pub fn calc_hit_window_150(od: f32) -> Time { return Time::from_ms(if od <= 5.0 { 120.0 - 8.0 * od } else { 110.0 - 6.0 * od }); } @@ -76,7 +161,12 @@ pub fn calc_hit_window_300(od: f32) -> Time { } impl Beatmap { - pub fn parse>(data: T) -> Self { + pub fn from_path(path: impl AsRef) -> Self { + let data = std::fs::read_to_string(path.as_ref()).unwrap(); + return Self::parse(data, path.as_ref().to_owned()); + } + + pub fn parse>(data: T, file_path: PathBuf) -> Self { let data = data.as_ref(); let mut objects = Vec::::new(); @@ -84,6 +174,7 @@ impl Beatmap { let mut velocity_points = Vec::::new(); let mut break_points = Vec::::new(); + let mut bg_path = PathBuf::new(); let mut property_map = AHashMap::<&str, AHashMap<&str, &str>>::new(); let mut current_category = None::<&str>; @@ -135,6 +226,15 @@ impl Beatmap { } Some("[Events]") => { + if line.contains(".jpg") || line.contains(".jpeg") || line.contains(".png") { + let Some(file_bg_path) = line.split("\"").nth(1) else { + warn!("Failed to bg path at line {}", i); + continue; + }; + + bg_path = file_bg_path.into(); + } + let mut parts = line.split(','); let Some(event_type) = parts.next() else { warn!("Failed to parse event type at line {}", i); @@ -251,9 +351,22 @@ impl Beatmap { timing_points, velocity_points, break_points, - overall_difficulty: property_map["[Difficulty]"]["OverallDifficulty"].parse().expect("idk default OD, plz fix"), + + title: property_map["[Metadata]"]["Title"].to_owned(), + artist: property_map["[Metadata]"]["Artist"].to_owned(), + creator: property_map["[Metadata]"]["Creator"].to_owned(), + variant: property_map["[Metadata]"]["Version"].to_owned(), + + hp_drain_rate: property_map["[Difficulty]"]["HPDrainRate"].parse().unwrap_or(5.0), + overall_difficulty: property_map["[Difficulty]"]["OverallDifficulty"].parse().unwrap_or(5.0), + velocity_multiplier: property_map["[Difficulty]"]["SliderMultiplier"].parse().unwrap_or(0.6), - audio: PathBuf::from(property_map["[General]"]["AudioFilename"]), + + file_path, + audio_path: PathBuf::from(property_map["[General]"]["AudioFilename"]), + bg_path, + + hash: None, }; } } diff --git a/crates/apex-client/src/client/gameplay/beatmap_cache.rs b/crates/apex-client/src/client/gameplay/beatmap_cache.rs index bd657fc..dfd7198 100644 --- a/crates/apex-client/src/client/gameplay/beatmap_cache.rs +++ b/crates/apex-client/src/client/gameplay/beatmap_cache.rs @@ -9,14 +9,14 @@ use log::warn; use apex_framework::time::time::Time; +use super::beatmap::{Beatmap, BeatmapHash}; + #[derive(Debug, Default, Clone)] pub struct BeatmapInfo { pub title: String, pub artist: String, pub creator: String, pub variant: String, - pub bg_path: PathBuf, - pub audio_path: PathBuf, pub preview_time: u64, pub difficulty: f64, @@ -26,18 +26,25 @@ pub struct BeatmapInfo { pub hp_drain: f32, pub overall_difficulty: f32, + + pub file_path: PathBuf, + pub audio_path: PathBuf, + pub bg_path: PathBuf, } -impl> From for BeatmapInfo { - fn from(data: T) -> Self { +impl BeatmapInfo { + pub fn from_path(path: impl AsRef) -> Self { + let data = std::fs::read_to_string(path.as_ref()).unwrap(); + return Self::parse(data, path.as_ref().to_owned()); + } + + pub fn parse>(data: T, file_path: PathBuf) -> Self { let data = data.as_ref(); let mut beatmap_info = Self { title: String::new(), artist: String::new(), creator: String::new(), variant: String::new(), - bg_path: PathBuf::new(), - audio_path: PathBuf::new(), preview_time: 0, difficulty: 0.0, @@ -47,6 +54,10 @@ impl> From for BeatmapInfo { hp_drain: 0.0, overall_difficulty: 0.0, + + file_path, + audio_path: PathBuf::new(), + bg_path: PathBuf::new(), }; let r_beatmap = rosu_pp::Beatmap::from_str(data).unwrap(); @@ -124,7 +135,7 @@ impl> From for BeatmapInfo { } pub struct BeatmapCache { - cache: IndexMap, + cache: IndexMap, last_update: Instant, } @@ -182,30 +193,31 @@ impl BeatmapCache { continue; }; - let path = entry.path(); + let entry_path = entry.path(); - if path.is_file() { - if let Some(extension) = path.extension() { + if entry_path.is_file() { + if let Some(extension) = entry_path.extension() { if extension == "osu" { - let data = std::fs::read_to_string(&path).unwrap(); - let beatmap_info = BeatmapInfo::from(&data); - self.cache.insert(path, beatmap_info); + let data = std::fs::read_to_string(&entry_path).unwrap(); + let beatmap = Beatmap::parse(&data, path.to_owned()); + let beatmap_info = BeatmapInfo::parse(&data, entry_path.to_owned()); + self.cache.insert(beatmap.hash(), beatmap_info); } } } } } - pub fn get(&self, path: &Path) -> Option<&BeatmapInfo> { - return self.cache.get(path); + pub fn get(&self, hash: BeatmapHash) -> Option<&BeatmapInfo> { + return self.cache.get(&hash); } - pub fn get_index(&self, idx: usize) -> Option<(&PathBuf, &BeatmapInfo)> { - return self.cache.get_index(idx); + pub fn get_index(&self, idx: usize) -> Option<(BeatmapHash, &BeatmapInfo)> { + return self.cache.get_index(idx).map(|(hash, beatmap_info)| (*hash, beatmap_info)); } - pub fn iter(&self) -> impl Iterator { - return self.cache.iter(); + pub fn iter(&self) -> impl Iterator { + return self.cache.iter().map(|(hash, beatmap_info)| (*hash, beatmap_info)); } pub fn last_update(&self) -> Instant { diff --git a/crates/apex-client/src/client/gameplay/taiko_player.rs b/crates/apex-client/src/client/gameplay/taiko_player.rs index d7b1bc4..8feccb4 100644 --- a/crates/apex-client/src/client/gameplay/taiko_player.rs +++ b/crates/apex-client/src/client/gameplay/taiko_player.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use apex_framework::time::{clock::AbstractClock as _, time::Time}; use crate::client::{ @@ -46,7 +44,6 @@ pub enum BreakState { /// Logcial actions that a player can perform while playing taiko. pub struct TaikoPlayer { beatmap: Beatmap, - beatmap_path: PathBuf, // Memoized for performance reasons. hit_window_150: Time, @@ -60,7 +57,6 @@ impl TaikoPlayer { pub fn new() -> Self { return Self { beatmap: Beatmap::default(), - beatmap_path: PathBuf::new(), hit_window_150: Time::zero(), hit_window_300: Time::zero(), current_circle: 0, @@ -68,11 +64,10 @@ impl TaikoPlayer { }; } - pub fn play(&mut self, beatmap: Beatmap, beatmap_path: PathBuf) { + pub fn play(&mut self, beatmap: Beatmap) { self.reset(); self.beatmap = beatmap; - self.beatmap_path = beatmap_path; self.hit_window_150 = calc_hit_window_150(self.beatmap.overall_difficulty); self.hit_window_300 = calc_hit_window_300(self.beatmap.overall_difficulty); } @@ -86,10 +81,6 @@ impl TaikoPlayer { return &self.beatmap; } - pub fn beatmap_path(&self) -> &PathBuf { - return &self.beatmap_path; - } - pub fn has_ended(&self, time: Time, audio: &GameAudio) -> bool { return time >= audio.length() + audio.lead_out; } diff --git a/crates/apex-client/src/client/graphics/taiko_renderer/taiko_renderer.rs b/crates/apex-client/src/client/graphics/taiko_renderer/taiko_renderer.rs index 5addb96..24907fb 100644 --- a/crates/apex-client/src/client/graphics/taiko_renderer/taiko_renderer.rs +++ b/crates/apex-client/src/client/graphics/taiko_renderer/taiko_renderer.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, path::PathBuf}; +use std::collections::HashMap; use bytemuck::Zeroable; use glam::{vec2, vec3, vec4, Quat, Vec2, Vec4}; @@ -124,15 +124,7 @@ impl TaikoRenderer { let circle_overlay_texture = Texture::from_path("./assets/taikohitcircleoverlay.png", device, queue).unwrap(); let finisher_overlay_texture = Texture::from_path("./assets/taikobigcircleoverlay.png", device, queue).unwrap(); - let current_beatmap = Beatmap { - hit_objects: vec![], - timing_points: vec![], - velocity_points: vec![], - break_points: vec![], - overall_difficulty: 0.0, - velocity_multiplier: 0.0, - audio: PathBuf::new(), - }; + let current_beatmap = Beatmap::default(); let mut renderer = Self { scene, diff --git a/crates/apex-client/src/client/score/score.rs b/crates/apex-client/src/client/score/score.rs index 64c2f76..3c36740 100644 --- a/crates/apex-client/src/client/score/score.rs +++ b/crates/apex-client/src/client/score/score.rs @@ -5,7 +5,7 @@ use crate::client::gameplay::taiko_player::TaikoInput; use super::grades::Grade; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Score { pub(crate) date: Timestamp, pub(crate) username: String, diff --git a/crates/apex-client/src/client/score/score_cache.rs b/crates/apex-client/src/client/score/score_cache.rs index 17aa837..386d8cf 100644 --- a/crates/apex-client/src/client/score/score_cache.rs +++ b/crates/apex-client/src/client/score/score_cache.rs @@ -1,5 +1,4 @@ use std::fmt::Write as _; -use std::path::PathBuf; use ahash::AHashMap; use jiff::Timestamp; @@ -8,6 +7,8 @@ use tap::Tap; use apex_framework::time::time::Time; +use crate::client::gameplay::beatmap::BeatmapHash; + use super::{grades::Grade, score::Score}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -21,7 +22,7 @@ impl Default for ScoreId { pub struct ScoreCache { conn: Connection, - cache: AHashMap>, + cache: AHashMap>, scores: Vec, } @@ -54,10 +55,10 @@ impl ScoreCache { }; } - pub fn beatmap_scores(&mut self, path: &PathBuf) -> Option<&[ScoreId]> { + pub fn beatmap_scores(&mut self, beatmap: BeatmapHash) -> Option<&[ScoreId]> { // polonius when - if self.cache.get(path).is_some() { - return self.cache.get(path).map(|x| x.as_slice()); + if self.cache.get(&beatmap).is_some() { + return self.cache.get(&beatmap).map(|x| x.as_slice()); } let mut stmt = self @@ -70,7 +71,7 @@ impl ScoreCache { .unwrap(); let scores = stmt - .query_map((path.to_str().unwrap(),), |row| { + .query_map((beatmap.to_string(),), |row| { let result_300 = row.get::<_, i64>(3).unwrap() as usize; let result_150 = row.get::<_, i64>(4).unwrap() as usize; let result_miss = row.get::<_, i64>(5).unwrap() as usize; @@ -105,7 +106,7 @@ impl ScoreCache { }) .unwrap(); - let cache = self.cache.entry(path.clone()).or_default(); + let cache = self.cache.entry(beatmap).or_default(); for score in scores { let score = score.unwrap(); @@ -121,14 +122,14 @@ impl ScoreCache { return &self.scores[id.0]; } - pub fn insert(&mut self, path: PathBuf, score: Score) -> ScoreId { + pub fn insert(&mut self, beatmap: BeatmapHash, score: Score) -> ScoreId { let id = ScoreId(self.scores.len()); self.conn.execute( "insert into scores (path, date, username, score_points, result_300, result_150, result_miss, last_combo, max_combo, accuracy, hits) values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)", ( - path.to_str().unwrap(), + beatmap.to_string(), score.date().as_millisecond(), score.username(), score.score_points() as i64, @@ -148,7 +149,7 @@ impl ScoreCache { ).unwrap(); self.scores.push(score); - self.cache.entry(path).or_default().push(id); + self.cache.entry(beatmap).or_default().push(id); return id; } diff --git a/crates/apex-client/src/client/screen/gameplay_screen/gameplay_screen.rs b/crates/apex-client/src/client/screen/gameplay_screen/gameplay_screen.rs index 3daaf7e..8440c3e 100644 --- a/crates/apex-client/src/client/screen/gameplay_screen/gameplay_screen.rs +++ b/crates/apex-client/src/client/screen/gameplay_screen/gameplay_screen.rs @@ -1,4 +1,4 @@ -use std::{fs::File, io::BufReader, path::Path}; +use std::{fs::File, io::BufReader}; use glam::{vec2, Vec2}; use jiff::Timestamp; @@ -172,12 +172,9 @@ impl GameplayScreen { } } - pub fn play(&mut self, beatmap_path: &Path, graphics: &Graphics, audio: &mut GameAudio) { - let data = std::fs::read_to_string(beatmap_path).unwrap(); - let beatmap = Beatmap::parse(data); - + pub fn play(&mut self, beatmap: Beatmap, graphics: &Graphics, audio: &mut GameAudio) { let config = audio.device().default_output_config().unwrap(); - let audio_path = beatmap_path.parent().unwrap().join(&beatmap.audio); + let audio_path = beatmap.file_path.parent().unwrap().join(&beatmap.audio_path); let file = BufReader::new(File::open(audio_path).unwrap()); let source = Decoder::new(file).unwrap(); @@ -192,7 +189,7 @@ impl GameplayScreen { self.hit_result_display.reset(graphics, &mut self.sprite_renderer); self.taiko_renderer.load_beatmap(&graphics.device, beatmap.clone()); - self.taiko_player.play(beatmap, beatmap_path.to_owned()); + self.taiko_player.play(beatmap); std::mem::take(&mut self.score_processor); audio.set_position(Time::zero() - audio.lead_in); @@ -222,9 +219,9 @@ impl GameplayScreen { if self.taiko_player.has_ended(time, audio) { // Finish the play if beatmap is over. - let path = self.taiko_player.beatmap_path().clone(); + let beatmap_hash = self.taiko_player.beatmap().hash(); let score = self.score_processor.export(Timestamp::now(), settings.profile.user.username().clone()); - self.event_bus.send(ClientEvent::ShowResultScreen { path, score }); + self.event_bus.send(ClientEvent::ShowResultScreen { beatmap_hash, score }); } self.hit_result_display.prepare(&core.graphics, &mut self.sprite_renderer); diff --git a/crates/apex-client/src/client/screen/pause_screen/pause_screen.rs b/crates/apex-client/src/client/screen/pause_screen/pause_screen.rs index 183ddbf..5f8652c 100644 --- a/crates/apex-client/src/client/screen/pause_screen/pause_screen.rs +++ b/crates/apex-client/src/client/screen/pause_screen/pause_screen.rs @@ -129,8 +129,8 @@ impl PauseScreen { let delay_adjusted_position = delay_adjusted_position.max(Time::zero()); let selected = selection_screen.beatmap_selector().selected(); - if let Some((path, beatmap)) = beatmap_cache.get_index(selected) { - Client::play_beatmap_audio_unchecked(audio, path, beatmap); + if let Some((_, beatmap)) = beatmap_cache.get_index(selected) { + Client::play_beatmap_audio_unchecked(audio, &beatmap.file_path, beatmap); audio.set_position(delay_adjusted_position); }; }); diff --git a/crates/apex-client/src/client/screen/recording_screen/recording_screen.rs b/crates/apex-client/src/client/screen/recording_screen/recording_screen.rs index 8722493..6c34c3c 100644 --- a/crates/apex-client/src/client/screen/recording_screen/recording_screen.rs +++ b/crates/apex-client/src/client/screen/recording_screen/recording_screen.rs @@ -24,9 +24,9 @@ impl RecordingScreen { } pub fn prepare(&mut self, core: &Core, beatmap_idx: usize, beatmap_cache: &BeatmapCache) { - if let Some(path) = beatmap_cache.get_index(beatmap_idx).map(|x| x.0) { - if self.beatmap_path != *path { - self.beatmap_path = path.clone(); + if let Some((_, info)) = beatmap_cache.get_index(beatmap_idx) { + if self.beatmap_path != info.file_path { + self.beatmap_path = info.file_path.clone(); } } diff --git a/crates/apex-client/src/client/screen/result_screen/result_screen.rs b/crates/apex-client/src/client/screen/result_screen/result_screen.rs index e79f3cd..31102f5 100644 --- a/crates/apex-client/src/client/screen/result_screen/result_screen.rs +++ b/crates/apex-client/src/client/screen/result_screen/result_screen.rs @@ -1,13 +1,7 @@ -use std::path::{Path, PathBuf}; - -use apex_framework::{core::Core, event::EventBus}; +use apex_framework::core::Core; use crate::client::{ - client::Client, - event::ClientEvent, - gameplay::beatmap_cache::{BeatmapCache, BeatmapInfo}, - score::score_cache::{ScoreCache, ScoreId}, - ui::play_results::PlayResultsView, + client::Client, gameplay::beatmap::Beatmap, score::score::Score, ui::play_results::PlayResultsView, }; pub struct ResultScreen { @@ -15,25 +9,20 @@ pub struct ResultScreen { } impl ResultScreen { - pub fn new(_event_bus: EventBus, score_cache: &ScoreCache) -> Self { - let play_results = - PlayResultsView::new("", PathBuf::new().as_path(), BeatmapInfo::default(), ScoreId::default(), score_cache); + pub fn new() -> Self { + let play_results = PlayResultsView::new("", Beatmap::default(), Score::default()); return Self { play_results }; } - pub fn set_score(&mut self, beatmap_cache: &BeatmapCache, score_cache: &ScoreCache, path: &Path, score: ScoreId) { - let Some(beatmap) = beatmap_cache.get(path) else { - return; - }; - - let bg = path.parent().unwrap().join(&beatmap.bg_path); + pub fn set_score(&mut self, beatmap: Beatmap, score: Score) { + let bg = beatmap.file_path.parent().unwrap().join(&beatmap.bg_path); let bg = format!("file://{}", bg.to_str().unwrap()); - self.play_results = PlayResultsView::new(bg, path, beatmap.clone(), score, score_cache); + self.play_results = PlayResultsView::new(bg, beatmap, score); } - pub fn prepare(&mut self, core: &mut Core, _beatmap_cache: &BeatmapCache, score_cache: &ScoreCache) { - self.play_results.prepare(core, score_cache); + pub fn prepare(&mut self, core: &mut Core) { + self.play_results.prepare(core); } } diff --git a/crates/apex-client/src/client/screen/selection_screen/selection_screen.rs b/crates/apex-client/src/client/screen/selection_screen/selection_screen.rs index 4bf0667..68c92f6 100644 --- a/crates/apex-client/src/client/screen/selection_screen/selection_screen.rs +++ b/crates/apex-client/src/client/screen/selection_screen/selection_screen.rs @@ -1,9 +1,7 @@ -use std::path::PathBuf; - use crate::client::{ client::Client, event::ClientEvent, - gameplay::{beatmap_cache::BeatmapCache, beatmap_selector::BeatmapSelector}, + gameplay::{beatmap::BeatmapHash, beatmap_cache::BeatmapCache, beatmap_selector::BeatmapSelector}, score::score_cache::ScoreCache, settings::Settings, ui::beatmap_selection::BeatmapSelectionView, @@ -48,8 +46,8 @@ impl SelectionScreen { self.beatmap_selection.scroll_to_selected(); } - pub fn update_scores(&mut self, score_cache: &mut ScoreCache, path: &PathBuf) { - self.beatmap_selection.update_scores(score_cache, path); + pub fn update_scores(&mut self, score_cache: &mut ScoreCache, beatmap_hash: BeatmapHash) { + self.beatmap_selection.update_scores(score_cache, beatmap_hash); } pub fn beatmap_selector(&self) -> &BeatmapSelector { diff --git a/crates/apex-client/src/client/ui/beatmap_selection/beatmap_card.rs b/crates/apex-client/src/client/ui/beatmap_selection/beatmap_card.rs index 3b6a20c..f6c46a6 100644 --- a/crates/apex-client/src/client/ui/beatmap_selection/beatmap_card.rs +++ b/crates/apex-client/src/client/ui/beatmap_selection/beatmap_card.rs @@ -1,9 +1,11 @@ -use std::path::Path; - use apex_framework::event::EventBus; use egui::Widget; -use crate::client::{event::ClientEvent, gameplay::beatmap_cache::BeatmapInfo, ui::card_component::CardComponent}; +use crate::client::{ + event::ClientEvent, + gameplay::{beatmap::BeatmapHash, beatmap_cache::BeatmapInfo}, + ui::card_component::CardComponent, +}; pub struct BeatmapCard { card: CardComponent, @@ -14,8 +16,8 @@ pub struct BeatmapCard { } impl BeatmapCard { - pub fn new(path: &Path, info: &BeatmapInfo) -> Self { - let bg = path.parent().unwrap().join(&info.bg_path); + pub fn new(info: &BeatmapInfo) -> Self { + let bg = info.file_path.parent().unwrap().join(&info.bg_path); let bg = format!("file://{}", bg.to_str().unwrap()); return Self { @@ -31,7 +33,7 @@ impl BeatmapCard { &mut self, ui: &mut egui::Ui, selected: bool, - path: &Path, + beatmap_hash: BeatmapHash, bus: &EventBus, ) -> egui::Response { self.card.selected = selected; @@ -74,8 +76,7 @@ impl BeatmapCard { let text = egui::RichText::new("Play").size(FONT_SIZE); if ui.button(text).clicked() { - let path = path.to_owned(); - bus.send(ClientEvent::PickBeatmap { path }); + bus.send(ClientEvent::PickBeatmap { beatmap_hash }); ui.close_menu(); } diff --git a/crates/apex-client/src/client/ui/beatmap_selection/beatmap_list.rs b/crates/apex-client/src/client/ui/beatmap_selection/beatmap_list.rs index 16a3b40..977a2bc 100644 --- a/crates/apex-client/src/client/ui/beatmap_selection/beatmap_list.rs +++ b/crates/apex-client/src/client/ui/beatmap_selection/beatmap_list.rs @@ -42,8 +42,8 @@ impl BeatmapList { self.last_update = Instant::now(); self.beatmap_cards.clear(); - for (path, info) in beatmap_cache.iter() { - let card = BeatmapCard::new(path, info); + for (_, info) in beatmap_cache.iter() { + let card = BeatmapCard::new(info); self.beatmap_cards.push(card); } } @@ -143,7 +143,7 @@ impl BeatmapList { self.event_bus.send(ClientEvent::SelectBeatmap); if is_selected && !clicked_secondary { - self.event_bus.send(ClientEvent::PickBeatmap { path: path.clone() }); + self.event_bus.send(ClientEvent::PickBeatmap { beatmap_hash: path.clone() }); } } }); diff --git a/crates/apex-client/src/client/ui/beatmap_selection/beatmap_scores.rs b/crates/apex-client/src/client/ui/beatmap_selection/beatmap_scores.rs index 6deb9ea..00e0c09 100644 --- a/crates/apex-client/src/client/ui/beatmap_selection/beatmap_scores.rs +++ b/crates/apex-client/src/client/ui/beatmap_selection/beatmap_scores.rs @@ -1,4 +1,4 @@ -use std::{fmt::Write as _, path::Path}; +use std::fmt::Write as _; use apex_framework::event::EventBus; use egui::Widget; @@ -7,6 +7,7 @@ use tap::Tap as _; use crate::client::{ event::ClientEvent, + gameplay::beatmap::BeatmapHash, score::{ score::Score, score_cache::{ScoreCache, ScoreId}, @@ -24,7 +25,13 @@ impl BeatmapScores { return Self { event_bus, buffer: String::new() }; } - pub fn prepare(&mut self, ui: &mut egui::Ui, score_cache: &ScoreCache, score_ids: &[ScoreId], path: &Path) { + pub fn prepare( + &mut self, + ui: &mut egui::Ui, + score_ids: &[ScoreId], + score_cache: &ScoreCache, + beatmap_hash: BeatmapHash, + ) { let color = egui::Color32::from_black_alpha(160); let rect = ui.cursor().tap_mut(|rect| { rect.min.y -= 2.0; @@ -85,10 +92,7 @@ impl BeatmapScores { for (i, (score_id, score)) in sorted.iter().copied().enumerate() { write!(&mut self.buffer, "{}", i + 1).unwrap(); if render_score(ui, score, &self.buffer).clicked() { - self.event_bus.send(ClientEvent::ViewScore { - path: path.to_owned(), - score_id: score_id, - }); + self.event_bus.send(ClientEvent::ViewScore { beatmap_hash, score_id: score_id }); } self.buffer.clear(); diff --git a/crates/apex-client/src/client/ui/beatmap_selection/mod.rs b/crates/apex-client/src/client/ui/beatmap_selection/mod.rs index af07836..761dedb 100644 --- a/crates/apex-client/src/client/ui/beatmap_selection/mod.rs +++ b/crates/apex-client/src/client/ui/beatmap_selection/mod.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use action_bar::ActionBar; use apex_framework::{ core::Core, @@ -17,7 +15,11 @@ use tap::Tap; use crate::client::{ client::Client, event::ClientEvent, - gameplay::{beatmap::Beatmap, beatmap_cache::BeatmapCache, beatmap_selector::BeatmapSelector}, + gameplay::{ + beatmap::{Beatmap, BeatmapHash}, + beatmap_cache::BeatmapCache, + beatmap_selector::BeatmapSelector, + }, score::score_cache::{ScoreCache, ScoreId}, settings::Settings, }; @@ -32,7 +34,7 @@ pub mod beatmap_scores; pub mod beatmap_stats; pub struct BeatmapSelectionView { - prev_beatmap: PathBuf, + prev_beatmap: BeatmapHash, score_ids: Vec, beatmap_bg: BackgroundComponent, @@ -52,13 +54,13 @@ impl BeatmapSelectionView { settings: &Settings, ) -> Self { let mut beatmap_cards = vec![]; - for (path, info) in beatmap_cache.iter() { - let card = BeatmapCard::new(path, info); + for (_, info) in beatmap_cache.iter() { + let card = BeatmapCard::new(info); beatmap_cards.push(card); } return Self { - prev_beatmap: PathBuf::new(), + prev_beatmap: BeatmapHash::default(), score_ids: Vec::new(), beatmap_bg: BackgroundComponent::new(""), @@ -157,24 +159,25 @@ impl BeatmapSelectionView { } let selected = selector.selected(); - let Some((path, info)) = beatmap_cache.get_index(selected) else { + let Some((beatmap_hash, info)) = beatmap_cache.get_index(selected) else { // TODO: Show error message no beatmaps found return; }; - if self.prev_beatmap != *path { - self.prev_beatmap = path.clone(); + if self.prev_beatmap != beatmap_hash { + self.prev_beatmap = beatmap_hash; - let bg_path = path.parent().unwrap().join(&info.bg_path); + let base_path = info.file_path.parent().unwrap(); + let bg_path = base_path.join(&info.bg_path); let bg = format!("file://{}", bg_path.to_str().unwrap()); self.beatmap_bg = BackgroundComponent::new(bg); - let data = std::fs::read_to_string(path).unwrap(); - let beatmap = Beatmap::parse(data); + let data = std::fs::read_to_string(&info.file_path).unwrap(); + let beatmap = Beatmap::parse(data, base_path.to_owned()); self.beatmap_preview.change_beatmap(&core.graphics, core.egui.renderer_mut(), &beatmap); - self.update_scores(score_cache, path); + self.update_scores(score_cache, beatmap_hash); } let (egui_ctx, egui_renderer) = core.egui.ctx_renderer_mut(); @@ -194,7 +197,7 @@ impl BeatmapSelectionView { ui.add_space(8.0); self.beatmap_preview.prepare(ui, clock, egui_renderer); ui.add_space(8.0); - self.beatmap_scores.prepare(ui, score_cache, &self.score_ids, path); + self.beatmap_scores.prepare(ui, &self.score_ids, score_cache, beatmap_hash); }); }); @@ -210,9 +213,9 @@ impl BeatmapSelectionView { }); } - pub fn update_scores(&mut self, score_cache: &mut ScoreCache, path: &PathBuf) { + pub fn update_scores(&mut self, score_cache: &mut ScoreCache, beatmap: BeatmapHash) { self.score_ids.clear(); - if let Some(score_ids) = score_cache.beatmap_scores(path) { + if let Some(score_ids) = score_cache.beatmap_scores(beatmap) { self.score_ids.extend(score_ids.iter()); } } diff --git a/crates/apex-client/src/client/ui/play_results/mod.rs b/crates/apex-client/src/client/ui/play_results/mod.rs index d2812a3..64702f7 100644 --- a/crates/apex-client/src/client/ui/play_results/mod.rs +++ b/crates/apex-client/src/client/ui/play_results/mod.rs @@ -1,5 +1,3 @@ -use std::path::Path; - use apex_framework::{core::Core, time::time::Time}; use egui::ImageSource; use jiff::fmt::strtime; @@ -14,7 +12,6 @@ use crate::client::{ score::{ judgement_processor::{check_hit, Judgement}, score::Score, - score_cache::{ScoreCache, ScoreId}, }, }; @@ -24,63 +21,58 @@ pub struct PlayResultsView { background: BackgroundComponent, beatmap_stats: BeatmapStats, beatmap_info: BeatmapInfo, - score_id: ScoreId, + score: Score, hits: Vec<(Time, Time, Judgement)>, } impl PlayResultsView { - pub fn new( - source: impl Into>, - beatmap: &Path, - beatmap_info: BeatmapInfo, - score_id: ScoreId, - score_cache: &ScoreCache, - ) -> Self { + pub fn new(source: impl Into>, beatmap: Beatmap, score: Score) -> Self { let image = source.into(); let background = BackgroundComponent::new(image.clone()); let beatmap_stats = BeatmapStats::new(); - let score = score_cache.score_details(score_id); let mut hits = Vec::with_capacity(score.hits.len()); - if let Ok(data) = std::fs::read_to_string(beatmap) { - let beatmap = Beatmap::parse(data); - - let mut current_circle = 0; - for (hit_time, hit_input) in score.hits.iter().copied() { - let hit_window_300 = calc_hit_window_300(beatmap.overall_difficulty); - let hit_window_150 = calc_hit_window_150(beatmap.overall_difficulty); + let mut current_circle = 0; + for (hit_time, hit_input) in score.hits.iter().copied() { + let hit_window_300 = calc_hit_window_300(beatmap.overall_difficulty); + let hit_window_150 = calc_hit_window_150(beatmap.overall_difficulty); - while let Some(hit_object) = beatmap.hit_objects.get(current_circle) { - let hit_window_end_time = hit_object.time + hit_window_150; + while let Some(hit_object) = beatmap.hit_objects.get(current_circle) { + let hit_window_end_time = hit_object.time + hit_window_150; - if hit_window_end_time >= hit_time { - if let Some(result) = check_hit(hit_time, hit_object, hit_input, hit_window_150, hit_window_300) { - hits.push((hit_time, result.hit_delta, result.judgement)); + if hit_window_end_time >= hit_time { + if let Some(result) = check_hit(hit_time, hit_object, hit_input, hit_window_150, hit_window_300) { + hits.push((hit_time, result.hit_delta, result.judgement)); - current_circle += 1; - break; - } + current_circle += 1; + break; } + } - // Unhit hit object which can not be hit anymore counts as a miss. - hits.push((hit_time, hit_object.time - hit_time, Judgement::Miss)); + // Unhit hit object which can not be hit anymore counts as a miss. + hits.push((hit_time, hit_object.time - hit_time, Judgement::Miss)); - current_circle += 1; - } + current_circle += 1; } } + // TODO: bad, please fix + let beatmap_info = if beatmap.file_path.exists() { + BeatmapInfo::from_path(beatmap.file_path) + } else { + BeatmapInfo::default() + }; + return Self { background, beatmap_stats, beatmap_info, - score_id, + score, hits, }; } - pub fn prepare(&mut self, core: &Core, score_cache: &ScoreCache) { - let score = score_cache.score_details(self.score_id); + pub fn prepare(&mut self, core: &Core) { egui::CentralPanel::default().frame(egui::Frame::none()).show(core.egui.ctx(), |ui| { self.background.prepare(ui); egui::Frame::none().show(ui, |ui| { @@ -125,7 +117,7 @@ impl PlayResultsView { ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.add(egui::Label::new( - egui::RichText::new(format!("{}", score.score_points())) // + egui::RichText::new(format!("{}", self.score.score_points())) // .size(16.0), )); }); @@ -135,13 +127,13 @@ impl PlayResultsView { ui.separator(); ui.add_space(8.0); - self.render_results_grid(ui, score); + self.render_results_grid(ui); height -= ui.cursor().min.y; }); }); strip.cell(|ui| { - self.render_general_info(ui, height.abs(), score); + self.render_general_info(ui, height.abs(), &self.score); }); }); }); @@ -294,7 +286,7 @@ impl PlayResultsView { }); } - fn render_results_grid(&mut self, ui: &mut egui::Ui, score: &Score) { + fn render_results_grid(&mut self, ui: &mut egui::Ui) { egui::Grid::new("results_grid") // .num_columns(2) .spacing([40.0, 4.0]) @@ -309,7 +301,7 @@ impl PlayResultsView { )); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.add(egui::Label::new(egui::RichText::new(format!("{}x", score.result_300s())).size(16.0).strong())); + ui.add(egui::Label::new(egui::RichText::new(format!("{}x", self.score.result_300s())).size(16.0).strong())); }); ui.end_row(); @@ -324,7 +316,7 @@ impl PlayResultsView { )); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.add(egui::Label::new(egui::RichText::new(format!("{}x", score.result_150s())).size(16.0).strong())); + ui.add(egui::Label::new(egui::RichText::new(format!("{}x", self.score.result_150s())).size(16.0).strong())); }); ui.end_row(); @@ -339,7 +331,9 @@ impl PlayResultsView { )); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.add(egui::Label::new(egui::RichText::new(format!("{}x", score.result_misses())).size(16.0).strong())); + ui.add(egui::Label::new( + egui::RichText::new(format!("{}x", self.score.result_misses())).size(16.0).strong(), + )); }); ui.end_row(); diff --git a/crates/apex-client/src/client/ui/recording_panel/mod.rs b/crates/apex-client/src/client/ui/recording_panel/mod.rs index ff01cfd..a894323 100644 --- a/crates/apex-client/src/client/ui/recording_panel/mod.rs +++ b/crates/apex-client/src/client/ui/recording_panel/mod.rs @@ -160,8 +160,8 @@ impl RecordingPanelView { ui.horizontal(|ui| { if ui.button("⏺ Record").clicked() { let data = std::fs::read_to_string(path).unwrap(); - let beatmap = Beatmap::parse(data); - let info = cache.get(path).unwrap(); + let beatmap = Beatmap::parse(data, path.to_owned()); + let info = cache.get(beatmap.hash()).unwrap(); let preview_time = info.preview_time; let audio_path = info.audio_path.clone(); let path = path.to_owned();