diff --git a/.gitignore b/.gitignore index 3b2743f..885013f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,12 @@ -target .direnv .envrc -data/db.sqlite -data/jackets -data/songs + +shimmering/data +shimmering/logs +shimmering/assets/fonts +shimmering/assets/songs +shimmering/assets/b30_background.* + +target backups dump.sql -logs -cache diff --git a/data/assets/count_background.png b/shimmering/assets/count_background.png similarity index 100% rename from data/assets/count_background.png rename to shimmering/assets/count_background.png diff --git a/shimmering/assets/diff_byd.png b/shimmering/assets/diff_byd.png new file mode 100644 index 0000000..539955c Binary files /dev/null and b/shimmering/assets/diff_byd.png differ diff --git a/shimmering/assets/diff_etr.png b/shimmering/assets/diff_etr.png new file mode 100644 index 0000000..0ece003 Binary files /dev/null and b/shimmering/assets/diff_etr.png differ diff --git a/shimmering/assets/diff_ftr.png b/shimmering/assets/diff_ftr.png new file mode 100644 index 0000000..4ff6a82 Binary files /dev/null and b/shimmering/assets/diff_ftr.png differ diff --git a/shimmering/assets/diff_prs.png b/shimmering/assets/diff_prs.png new file mode 100644 index 0000000..c7d0f1e Binary files /dev/null and b/shimmering/assets/diff_prs.png differ diff --git a/shimmering/assets/diff_pst.png b/shimmering/assets/diff_pst.png new file mode 100644 index 0000000..8c9832f Binary files /dev/null and b/shimmering/assets/diff_pst.png differ diff --git a/data/assets/grade_background.png b/shimmering/assets/grade_background.png similarity index 100% rename from data/assets/grade_background.png rename to shimmering/assets/grade_background.png diff --git a/data/assets/name_background.png b/shimmering/assets/name_background.png similarity index 100% rename from data/assets/name_background.png rename to shimmering/assets/name_background.png diff --git a/data/assets/placeholder_jacket.jpg b/shimmering/assets/placeholder_jacket.jpg similarity index 100% rename from data/assets/placeholder_jacket.jpg rename to shimmering/assets/placeholder_jacket.jpg diff --git a/data/assets/ptt_emblem.png b/shimmering/assets/ptt_emblem.png similarity index 100% rename from data/assets/ptt_emblem.png rename to shimmering/assets/ptt_emblem.png diff --git a/data/assets/score_background.png b/shimmering/assets/score_background.png similarity index 100% rename from data/assets/score_background.png rename to shimmering/assets/score_background.png diff --git a/data/assets/status_background.png b/shimmering/assets/status_background.png similarity index 100% rename from data/assets/status_background.png rename to shimmering/assets/status_background.png diff --git a/data/assets/top_background.png b/shimmering/assets/top_background.png similarity index 100% rename from data/assets/top_background.png rename to shimmering/assets/top_background.png diff --git a/data/charts.csv b/shimmering/config/charts.csv similarity index 100% rename from data/charts.csv rename to shimmering/config/charts.csv diff --git a/data/shorthands.csv b/shimmering/config/shorthands.csv similarity index 100% rename from data/shorthands.csv rename to shimmering/config/shorthands.csv diff --git a/data/ui.txt b/shimmering/config/ui.txt similarity index 100% rename from data/ui.txt rename to shimmering/config/ui.txt diff --git a/src/arcaea/jacket.rs b/src/arcaea/jacket.rs index 0897cf7..dc91cfd 100644 --- a/src/arcaea/jacket.rs +++ b/src/arcaea/jacket.rs @@ -1,11 +1,11 @@ -use std::{fs, io::Cursor, path::PathBuf}; +use std::{fs, io::Cursor}; use image::{imageops::FilterType, GenericImageView, Rgba}; use num::Integer; use crate::{ arcaea::chart::{Difficulty, Jacket, SongCache}, - assets::{get_assets_dir, should_blur_jacket_art, should_skip_jacket_art}, + assets::{get_asset_dir, should_blur_jacket_art, should_skip_jacket_art}, context::Error, recognition::fuzzy_song_name::guess_chart_name, }; @@ -80,17 +80,9 @@ pub struct JacketCache { impl JacketCache { // {{{ Generate // This is a bit inefficient (using a hash set), but only runs once - pub fn new(data_dir: &PathBuf, song_cache: &mut SongCache) -> Result { - let jacket_dir = data_dir.join("jackets"); - - if jacket_dir.exists() { - fs::remove_dir_all(&jacket_dir).expect("Could not delete jacket dir"); - } - - fs::create_dir_all(&jacket_dir).expect("Could not create jacket dir"); - + pub fn new(song_cache: &mut SongCache) -> Result { let jacket_vectors = if should_skip_jacket_art() { - let path = get_assets_dir().join("placeholder_jacket.jpg"); + let path = get_asset_dir().join("placeholder_jacket.jpg"); let contents: &'static _ = fs::read(path)?.leak(); let image = image::load_from_memory(contents)?; let bitmap: &'static _ = Box::leak(Box::new( @@ -109,7 +101,7 @@ impl JacketCache { Vec::new() } else { let entries = - fs::read_dir(data_dir.join("songs")).expect("Couldn't read songs directory"); + fs::read_dir(get_asset_dir().join("songs")).expect("Couldn't read songs directory"); let mut jacket_vectors = vec![]; for entry in entries { diff --git a/src/assets.rs b/src/assets.rs index ccfadb3..7a6c91c 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -1,44 +1,56 @@ -use std::{cell::RefCell, env::var, path::PathBuf, str::FromStr, sync::OnceLock, thread::LocalKey}; +use std::{ + cell::RefCell, + env::var, + path::PathBuf, + str::FromStr, + sync::{LazyLock, OnceLock}, + thread::LocalKey, +}; use freetype::{Face, Library}; -use image::{ImageBuffer, Rgb, Rgba}; +use image::{DynamicImage, RgbaImage}; use crate::{arcaea::chart::Difficulty, timed}; +// {{{ Path helpers +#[inline] +pub fn get_var(name: &str) -> String { + var(name).unwrap_or_else(|_| panic!("Missing `{name}` environment variable")) +} + +#[inline] +pub fn get_path(name: &str) -> PathBuf { + PathBuf::from_str(&get_var(name)) + .unwrap_or_else(|_| panic!("`{name}` environment variable is not a valid path")) +} + #[inline] pub fn get_data_dir() -> PathBuf { - PathBuf::from_str(&var("SHIMMERING_DATA_DIR").expect("Missing `SHIMMERING_DATA_DIR` env var")) - .expect("`SHIMMERING_DATA_DIR` is not a valid path") + get_path("SHIMMERING_DATA_DIR") } #[inline] -pub fn get_assets_dir() -> PathBuf { - get_data_dir().join("assets") +pub fn get_config_dir() -> PathBuf { + get_path("SHIMMERING_CONFIG_DIR") } +#[inline] +pub fn get_asset_dir() -> PathBuf { + get_path("SHIMMERING_ASSET_DIR") +} +// }}} +// {{{ Font helpers #[inline] fn get_font(name: &str) -> RefCell { let face = timed!(format!("load font \"{name}\""), { FREETYPE_LIB.with(|lib| { - lib.new_face(get_assets_dir().join(name), 0) + lib.new_face(get_asset_dir().join("fonts").join(name), 0) .expect(&format!("Could not load {} font", name)) }) }); RefCell::new(face) } -thread_local! { -pub static FREETYPE_LIB: Library = Library::init().unwrap(); -pub static SAIRA_FONT: RefCell = get_font("saira-variable.ttf"); -pub static EXO_FONT: RefCell = get_font("exo-variable.ttf"); -pub static GEOSANS_FONT: RefCell = get_font("geosans-light.ttf"); -pub static KAZESAWA_FONT: RefCell = get_font("kazesawa-regular.ttf"); -pub static KAZESAWA_BOLD_FONT: RefCell = get_font("kazesawa-bold.ttf"); -pub static NOTO_SANS_FONT: RefCell = get_font("noto-sans.ttf"); -pub static ARIAL_FONT: RefCell = get_font("arial.ttf"); -pub static UNI_FONT: RefCell = get_font("unifont.otf"); -} - #[inline] pub fn with_font( primary: &'static LocalKey>, @@ -52,7 +64,21 @@ pub fn with_font( // }) }) } - +// }}} +// {{{ Font loading +thread_local! { +pub static FREETYPE_LIB: Library = Library::init().unwrap(); +pub static SAIRA_FONT: RefCell = get_font("saira-variable.ttf"); +pub static EXO_FONT: RefCell = get_font("exo-variable.ttf"); +pub static GEOSANS_FONT: RefCell = get_font("geosans-light.ttf"); +pub static KAZESAWA_FONT: RefCell = get_font("kazesawa-regular.ttf"); +pub static KAZESAWA_BOLD_FONT: RefCell = get_font("kazesawa-bold.ttf"); +pub static NOTO_SANS_FONT: RefCell = get_font("noto-sans.ttf"); +pub static ARIAL_FONT: RefCell = get_font("arial.ttf"); +pub static UNI_FONT: RefCell = get_font("unifont.otf"); +} +// }}} +// {{{ Asset art helpers #[inline] pub fn should_skip_jacket_art() -> bool { var("SHIMMERING_NO_JACKETS").unwrap_or_default() == "1" @@ -63,110 +89,49 @@ pub fn should_blur_jacket_art() -> bool { var("SHIMMERING_BLUR_JACKETS").unwrap_or_default() == "1" } -pub fn get_b30_background() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_b30_background", { - let raw_b30_background = image::open(get_assets_dir().join("b30_background.jpg")) - .expect("Could not open b30 background"); - - raw_b30_background.blur(7.0).into_rgb8() - }) - }) +macro_rules! get_asset { + ($name: ident, $path:expr) => { + get_asset!($name, $path, |d: DynamicImage| d); + }; + ($name: ident, $path:expr, $f:expr) => { + pub static $name: LazyLock = LazyLock::new(move || { + timed!($path, { + let image = image::open(get_asset_dir().join($path)) + .unwrap_or_else(|_| panic!("Could no read asset `{}`", $path)); + let f = $f; + f(image).into_rgba8() + }) + }); + }; } +// }}} +// {{{ Asset art loading +get_asset!(COUNT_BACKGROUND, "count_background.png"); +get_asset!(SCORE_BACKGROUND, "score_background.png"); +get_asset!(STATUS_BACKGROUND, "status_background.png"); +get_asset!(GRADE_BACKGROUND, "grade_background.png"); +get_asset!(TOP_BACKGROUND, "top_background.png"); +get_asset!(NAME_BACKGROUND, "name_background.png"); +get_asset!(PTT_EMBLEM, "ptt_emblem.png"); +get_asset!( + B30_BACKGROUND, + "b30_background.jpg", + |image: DynamicImage| image.blur(7.0) +); -pub fn get_count_background() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_count_backound", { - image::open(get_assets_dir().join("count_background.png")) - .expect("Could not open count background") - .into_rgba8() - }) - }) -} - -pub fn get_score_background() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_score_background", { - image::open(get_assets_dir().join("score_background.png")) - .expect("Could not open score background") - .into_rgba8() - }) - }) -} - -pub fn get_status_background() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_status_background", { - image::open(get_assets_dir().join("status_background.png")) - .expect("Could not open status background") - .into_rgba8() - }) - }) -} - -pub fn get_grade_background() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_grade_background", { - image::open(get_assets_dir().join("grade_background.png")) - .expect("Could not open grade background") - .into_rgba8() - }) - }) -} - -pub fn get_top_backgound() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_top_background", { - image::open(get_assets_dir().join("top_background.png")) - .expect("Could not open top background") - .into_rgb8() - }) - }) -} - -pub fn get_name_backgound() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_name_background", { - image::open(get_assets_dir().join("name_background.png")) - .expect("Could not open name background") - .into_rgb8() - }) - }) -} - -pub fn get_ptt_emblem() -> &'static ImageBuffer, Vec> { - static CELL: OnceLock, Vec>> = OnceLock::new(); - CELL.get_or_init(|| { - timed!("load_ptt_emblem", { - image::open(get_assets_dir().join("ptt_emblem.png")) - .expect("Could not open ptt emblem") - .into_rgba8() - }) - }) -} - -pub fn get_difficulty_background( - difficulty: Difficulty, -) -> &'static ImageBuffer, Vec> { - static CELL: OnceLock<[ImageBuffer, Vec>; 5]> = OnceLock::new(); +pub fn get_difficulty_background(difficulty: Difficulty) -> &'static RgbaImage { + static CELL: OnceLock<[RgbaImage; 5]> = OnceLock::new(); &CELL.get_or_init(|| { timed!("load_difficulty_background", { - let assets_dir = get_assets_dir(); + let assets_dir = get_asset_dir(); Difficulty::DIFFICULTY_SHORTHANDS.map(|shorthand| { image::open(assets_dir.join(format!("diff_{}.png", shorthand.to_lowercase()))) - .expect(&format!( - "Could not get background for difficulty {:?}", - shorthand - )) + .unwrap_or_else(|_| { + panic!("Could not get background for difficulty {shorthand:?}") + }) .into_rgba8() }) }) })[difficulty.to_index()] } +// }}} diff --git a/src/bitmap.rs b/src/bitmap.rs index 330026c..b2070cf 100644 --- a/src/bitmap.rs +++ b/src/bitmap.rs @@ -12,7 +12,7 @@ use freetype::{ ffi::{FT_Set_Var_Design_Coordinates, FT_GLYPH_BBOX_PIXELS}, Bitmap, BitmapGlyph, Face, Glyph, StrokerLineCap, StrokerLineJoin, }; -use image::GenericImage; +use image::{GenericImage, RgbImage, RgbaImage}; use num::traits::Euclid; use crate::{assets::FREETYPE_LIB, context::Error}; @@ -184,7 +184,8 @@ impl BitmapCanvas { ((alpha * color.2 as u32 + (255 - alpha) * self.buffer[index + 2] as u32) / 255) as u8; } // }}} - // {{{ Draw RBG image + // {{{ Draw RGB image + /// Draws a bitmap image with no alpha channel. pub fn blit_rbg(&mut self, pos: Position, (iw, ih): (u32, u32), src: &[u8]) { let iw = iw as i32; let ih = ih as i32; @@ -242,8 +243,8 @@ impl BitmapCanvas { } } // }}} - // {{{ Draw scaled up RBG image - pub fn blit_rbg_scaled_up( + // {{{ Draw scaled up RBGA image + pub fn blit_rbga_scaled_up( &mut self, pos: Position, (iw, ih): (u32, u32), @@ -269,11 +270,12 @@ impl BitmapCanvas { // but would not perform division. let dx = (x - pos.0) / scale; let dy = (y - pos.1) / scale; - let r = src[(dx + dy * iw) as usize * 3]; - let g = src[(dx + dy * iw) as usize * 3 + 1]; - let b = src[(dx + dy * iw) as usize * 3 + 2]; + let r = src[(dx + dy * iw) as usize * 4]; + let g = src[(dx + dy * iw) as usize * 4 + 1]; + let b = src[(dx + dy * iw) as usize * 4 + 2]; + let a = src[(dx + dy * iw) as usize * 4 + 3]; - let color = Color(r, g, b, 0xff); + let color = Color(r, g, b, a); self.set_pixel((x as u32, y as u32), color); } @@ -712,11 +714,21 @@ impl LayoutDrawer { self.canvas.set_pixel((pos.0 as u32, pos.1 as u32), color); } // }}} - // {{{ Draw RGB image + // {{{ Draw images + /// Draws a bitmap image taking with no alpha channel. #[inline] - pub fn blit_rbg(&mut self, id: LayoutBoxId, pos: Position, dims: (u32, u32), src: &[u8]) { + pub fn blit_rbg(&mut self, id: LayoutBoxId, pos: Position, image: &RgbImage) { let pos = self.layout.position_relative_to(id, pos); - self.canvas.blit_rbg(pos, dims, src); + self.canvas + .blit_rbg(pos, image.dimensions(), image.as_raw()); + } + + /// Draws a bitmap image taking care of the alpha channel. + #[inline] + pub fn blit_rbga(&mut self, id: LayoutBoxId, pos: Position, image: &RgbaImage) { + let pos = self.layout.position_relative_to(id, pos); + self.canvas + .blit_rbga(pos, image.dimensions(), image.as_raw()); } #[inline] @@ -729,15 +741,7 @@ impl LayoutDrawer { scale: u32, ) { let pos = self.layout.position_relative_to(id, pos); - self.canvas.blit_rbg_scaled_up(pos, dims, src, scale); - } - // }}} - // {{{ Draw RGBA image - /// Draws a bitmap image taking care of the alpha channel. - #[inline] - pub fn blit_rbga(&mut self, id: LayoutBoxId, pos: Position, dims: (u32, u32), src: &[u8]) { - let pos = self.layout.position_relative_to(id, pos); - self.canvas.blit_rbga(pos, dims, src); + self.canvas.blit_rbga_scaled_up(pos, dims, src, scale); } // }}} // {{{ Fill diff --git a/src/commands/stats.rs b/src/commands/stats.rs index 122f55a..09e9c6e 100644 --- a/src/commands/stats.rs +++ b/src/commands/stats.rs @@ -24,9 +24,9 @@ use crate::{ }, assert_is_pookie, assets::{ - get_b30_background, get_count_background, get_difficulty_background, get_grade_background, - get_name_backgound, get_ptt_emblem, get_score_background, get_status_background, - get_top_backgound, with_font, EXO_FONT, + get_difficulty_background, with_font, B30_BACKGROUND, COUNT_BACKGROUND, EXO_FONT, + GRADE_BACKGROUND, NAME_BACKGROUND, PTT_EMBLEM, SCORE_BACKGROUND, STATUS_BACKGROUND, + TOP_BACKGROUND, }, bitmap::{Align, BitmapCanvas, Color, LayoutDrawer, LayoutManager, Rect}, context::{Context, Error}, @@ -300,7 +300,7 @@ async fn best_plays( let mut drawer = LayoutDrawer::new(layout, canvas); // }}} // {{{ Render background - let bg = get_b30_background(); + let bg = &*B30_BACKGROUND; let scale = (drawer.layout.width(root) as f32 / bg.width() as f32) .max(drawer.layout.height(root) as f32 / bg.height() as f32) @@ -325,8 +325,8 @@ async fn best_plays( .layout .edit_to_relative(item_with_margin, item_grid, origin.0, origin.1); - let top_bg = get_top_backgound(); - drawer.blit_rbg(top_area, (0, 0), top_bg.dimensions(), top_bg); + let top_bg = &*TOP_BACKGROUND; + drawer.blit_rbga(top_area, (0, 0), top_bg); let (play, song, chart) = if let Some(item) = plays.get(i) { item @@ -335,11 +335,11 @@ async fn best_plays( }; // {{{ Display index - let bg = get_count_background(); + let bg = &*COUNT_BACKGROUND; let bg_center = Rect::from_image(bg).center(); // Draw background - drawer.blit_rbga(item_area, (-8, jacket_margin as i32), bg.dimensions(), bg); + drawer.blit_rbga(item_area, (-8, jacket_margin as i32), bg); with_font(&EXO_FONT, |faces| { drawer.text( item_area, @@ -359,8 +359,8 @@ async fn best_plays( // }}} // {{{ Display chart name // Draw background - let bg = get_name_backgound(); - drawer.blit_rbg(bottom_area, (0, 0), bg.dimensions(), bg.as_raw()); + let bg = &*NAME_BACKGROUND; + drawer.blit_rbga(bottom_area, (0, 0), bg); // Draw text with_font(&EXO_FONT, |faces| { @@ -403,12 +403,7 @@ async fn best_plays( })?; drawer.fill(jacket_with_border, Color::from_rgb_int(0x271E35)); - drawer.blit_rbg( - jacket_area, - (0, 0), - jacket.bitmap.dimensions(), - &jacket.bitmap.as_raw(), - ); + drawer.blit_rbg(jacket_area, (0, 0), jacket.bitmap); // }}} // {{{ Display difficulty background let diff_bg = get_difficulty_background(chart.difficulty); @@ -417,12 +412,7 @@ async fn best_plays( (drawer.layout.width(jacket_with_border) as i32, 0), ); - drawer.blit_rbga( - jacket_with_border, - diff_bg_area.top_left(), - diff_bg.dimensions(), - &diff_bg.as_raw(), - ); + drawer.blit_rbga(jacket_with_border, diff_bg_area.top_left(), diff_bg); // }}} // {{{ Display difficulty text let x_offset = if chart.level.ends_with("+") { @@ -453,7 +443,7 @@ async fn best_plays( })?; // }}} // {{{ Display score background - let score_bg = get_score_background(); + let score_bg = &*SCORE_BACKGROUND; let score_bg_pos = Rect::from_image(score_bg).align( (Align::End, Align::End), ( @@ -462,12 +452,7 @@ async fn best_plays( ), ); - drawer.blit_rbga( - jacket_area, - score_bg_pos, - score_bg.dimensions(), - &score_bg.as_raw(), - ); + drawer.blit_rbga(jacket_area, score_bg_pos, score_bg); // }}} // {{{ Display score text with_font(&EXO_FONT, |faces| { @@ -491,7 +476,7 @@ async fn best_plays( })?; // }}} // {{{ Display status background - let status_bg = get_status_background(); + let status_bg = &*STATUS_BACKGROUND; let status_bg_area = Rect::from_image(status_bg).align_whole( (Align::Center, Align::Center), ( @@ -500,12 +485,7 @@ async fn best_plays( ), ); - drawer.blit_rbga( - jacket_area, - status_bg_area.top_left(), - status_bg.dimensions(), - &status_bg.as_raw(), - ); + drawer.blit_rbga(jacket_area, status_bg_area.top_left(), status_bg); // }}} // {{{ Display status text with_font(&EXO_FONT, |faces| { @@ -543,18 +523,13 @@ async fn best_plays( // }}} // {{{ Display grade background let top_left_center = (drawer.layout.width(top_left_area) as i32 + jacket_margin) / 2; - let grade_bg = get_grade_background(); + let grade_bg = &*GRADE_BACKGROUND; let grade_bg_area = Rect::from_image(grade_bg).align_whole( (Align::Center, Align::Center), (top_left_center, jacket_margin + 140), ); - drawer.blit_rbga( - top_area, - grade_bg_area.top_left(), - grade_bg.dimensions(), - &grade_bg.as_raw(), - ); + drawer.blit_rbga(top_area, grade_bg_area.top_left(), grade_bg); // }}} // {{{ Display grade text with_font(&EXO_FONT, |faces| { @@ -614,13 +589,12 @@ async fn best_plays( })?; // }}} // {{{ Display ptt emblem - let ptt_emblem = get_ptt_emblem(); + let ptt_emblem = &*PTT_EMBLEM; drawer.blit_rbga( top_left_area, Rect::from_image(ptt_emblem) .align((Align::Center, Align::Center), (top_left_center, 115)), - ptt_emblem.dimensions(), - ptt_emblem.as_raw(), + ptt_emblem, ); // }}} } diff --git a/src/context.rs b/src/context.rs index d836d32..6089aab 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,10 +1,10 @@ -use std::{fs, path::PathBuf}; +use std::fs; use sqlx::SqlitePool; use crate::{ arcaea::{chart::SongCache, jacket::JacketCache}, - assets::{EXO_FONT, GEOSANS_FONT, KAZESAWA_BOLD_FONT, KAZESAWA_FONT}, + assets::{get_data_dir, EXO_FONT, GEOSANS_FONT, KAZESAWA_BOLD_FONT, KAZESAWA_FONT}, recognition::{hyperglass::CharMeasurements, ui::UIMeasurements}, }; @@ -14,9 +14,6 @@ pub type Context<'a> = poise::Context<'a, UserContext, Error>; // Custom user data passed to all command functions pub struct UserContext { - #[allow(dead_code)] - pub data_dir: PathBuf, - pub db: SqlitePool, pub song_cache: SongCache, pub jacket_cache: JacketCache, @@ -31,14 +28,14 @@ pub struct UserContext { impl UserContext { #[inline] - pub async fn new(data_dir: PathBuf, cache_dir: PathBuf, db: SqlitePool) -> Result { - fs::create_dir_all(&cache_dir)?; - fs::create_dir_all(&data_dir)?; + pub async fn new(db: SqlitePool) -> Result { + fs::create_dir_all(get_data_dir())?; let mut song_cache = SongCache::new(&db).await?; - let jacket_cache = JacketCache::new(&data_dir, &mut song_cache)?; - let ui_measurements = UIMeasurements::read(&data_dir)?; + let jacket_cache = JacketCache::new(&mut song_cache)?; + let ui_measurements = UIMeasurements::read()?; + // {{{ Font measurements static WHITELIST: &str = "0123456789'abcdefghklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUVWXYZ"; let geosans_measurements = GEOSANS_FONT @@ -49,11 +46,11 @@ impl UserContext { .with_borrow_mut(|font| CharMeasurements::from_text(font, WHITELIST, None))?; let exo_measurements = EXO_FONT .with_borrow_mut(|font| CharMeasurements::from_text(font, WHITELIST, Some(700)))?; + // }}} println!("Created user context"); Ok(Self { - data_dir, db, song_cache, jacket_cache, diff --git a/src/logs.rs b/src/logs.rs index a278ff1..745a6ba 100644 --- a/src/logs.rs +++ b/src/logs.rs @@ -6,11 +6,11 @@ //! allows for a convenient way to throw images into a `logs` directory with //! a simple env var. -use std::{env, ops::Deref, sync::OnceLock, time::Instant}; +use std::{env, ops::Deref, path::PathBuf, sync::OnceLock, time::Instant}; use image::{DynamicImage, EncodableLayout, ImageBuffer, PixelWithColorType}; -use crate::context::Error; +use crate::{assets::get_path, context::Error}; #[inline] fn should_save_debug_images() -> bool { @@ -19,6 +19,11 @@ fn should_save_debug_images() -> bool { .unwrap_or(false) } +#[inline] +fn get_log_dir() -> PathBuf { + get_path("SHIMMERING_LOG_DIR") +} + #[inline] fn get_startup_time() -> Instant { static CELL: OnceLock = OnceLock::new(); @@ -28,10 +33,10 @@ fn get_startup_time() -> Instant { #[inline] pub fn debug_image_log(image: &DynamicImage) -> Result<(), Error> { if should_save_debug_images() { - image.save(format!( - "./logs/{:0>15}.png", + image.save(get_log_dir().join(format!( + "{:0>15}.png", get_startup_time().elapsed().as_nanos() - ))?; + )))?; } Ok(()) @@ -45,10 +50,10 @@ where C: Deref, { if should_save_debug_images() { - image.save(format!( + image.save(get_log_dir().join(format!( "./logs/{:0>15}.png", get_startup_time().elapsed().as_nanos() - ))?; + )))?; } Ok(()) diff --git a/src/main.rs b/src/main.rs index 9e6acaa..445882d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ #![feature(array_try_map)] #![feature(async_closure)] #![feature(try_blocks)] +#![feature(thread_local)] mod arcaea; mod assets; @@ -21,7 +22,7 @@ use assets::get_data_dir; use context::{Error, UserContext}; use poise::serenity_prelude::{self as serenity}; use sqlx::sqlite::SqlitePoolOptions; -use std::{env::var, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; +use std::{env::var, sync::Arc, time::Duration}; // {{{ Error handler async fn on_error(error: poise::FrameworkError<'_, UserContext, Error>) { @@ -37,13 +38,10 @@ async fn on_error(error: poise::FrameworkError<'_, UserContext, Error>) { #[tokio::main] async fn main() { - let data_dir = get_data_dir(); - let cache_dir = var("SHIMMERING_CACHE_DIR").expect("Missing `SHIMMERING_CACHE_DIR` env var"); - let pool = SqlitePoolOptions::new() .connect(&format!( "sqlite://{}/db.sqlite", - data_dir.to_str().unwrap() + get_data_dir().to_str().unwrap() )) .await .unwrap(); @@ -89,7 +87,7 @@ async fn main() { Box::pin(async move { println!("Logged in as {}", _ready.user.name); poise::builtins::register_globally(ctx, &framework.options().commands).await?; - let ctx = UserContext::new(data_dir, PathBuf::from_str(&cache_dir)?, pool).await?; + let ctx = UserContext::new(pool).await?; Ok(ctx) }) diff --git a/src/recognition/ui.rs b/src/recognition/ui.rs index 2c6061a..8886820 100644 --- a/src/recognition/ui.rs +++ b/src/recognition/ui.rs @@ -1,8 +1,8 @@ -use std::{fs, path::PathBuf}; +use std::fs; use image::GenericImage; -use crate::{bitmap::Rect, context::Error}; +use crate::{assets::get_config_dir, bitmap::Rect, context::Error}; // {{{ Rects #[derive(Debug, Clone, Copy)] @@ -94,11 +94,11 @@ pub struct UIMeasurements { impl UIMeasurements { // {{{ Read - pub fn read(data_dir: &PathBuf) -> Result { + pub fn read() -> Result { let mut measurements = Vec::new(); let mut measurement = UIMeasurement::default(); - let path = data_dir.join("ui.txt"); + let path = get_config_dir().join("ui.txt"); let contents = fs::read_to_string(path)?; // {{{ Parse measurement file