1
Fork 0

Make everything build

This commit is contained in:
prescientmoon 2024-09-23 21:12:04 +02:00
parent 74f554e058
commit 92dbd181f2
Signed by: prescientmoon
SSH key fingerprint: SHA256:WFp/cO76nbarETAoQcQXuV+0h7XJsEsOCI0UsyPIy6U
21 changed files with 107 additions and 169 deletions

View file

@ -41,5 +41,5 @@ anyhow = "1.0.87"
sha2 = "0.10.8"
base16ct = { version = "0.2.0", features = ["alloc"] }
# [profile.dev.package."*"]
# opt-level = 3
[profile.dev.package."*"]
opt-level = 3

View file

@ -13,7 +13,12 @@
inputs.flake-utils.lib.eachSystem (with inputs.flake-utils.lib.system; [ x86_64-linux ]) (
system:
let
pkgs = inputs.nixpkgs.legacyPackages.${system}.extend (import inputs.rust-overlay);
pkgs = inputs.nixpkgs.legacyPackages.${system};
# pkgs = inputs.nixpkgs.legacyPackages.${system}.extend (import inputs.rust-overlay);
# pkgs = import inputs.nixpkgs {
# inherit system;
# overlays = [ (import inputs.rust-overlay) ];
# };
# toolchain = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default);
# toolchain = pkgs.rust-bin.stable.latest.default;
toolchain = inputs.fenix.packages.${system}.complete.toolchain;
@ -34,18 +39,17 @@
};
};
};
devShell = pkgs.mkShell {
devShell = pkgs.mkShell rec {
nativeBuildInputs = with pkgs; [
toolchain
# ruff
# imagemagick
ruff
imagemagick
pkg-config
# clang
# llvmPackages.clang
];
buildInputs = with pkgs; [
toolchain
freetype
fontconfig
leptonica
@ -54,7 +58,7 @@
sqlite
];
# LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;
# compilation of -sys packages requires manually setting LIBCLANG_PATH
# LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";

View file

@ -1,3 +1,4 @@
use std::path::Path;
// {{{ Imports
use std::{fmt::Display, num::NonZeroU16, path::PathBuf};
@ -216,7 +217,7 @@ pub struct Chart {
impl Chart {
#[inline]
pub fn jacket_path(&self, data_dir: &PathBuf) -> PathBuf {
pub fn jacket_path(&self, data_dir: &Path) -> PathBuf {
data_dir
.join("jackets")
.join(format!("{}-{}.jpg", self.song_id, self.id))
@ -241,10 +242,7 @@ impl CachedSong {
#[inline]
pub fn charts(&self) -> impl Iterator<Item = u32> {
self.chart_ids
.into_iter()
.filter_map(|i| i)
.map(|i| i.get() as u32)
self.chart_ids.into_iter().flatten().map(|i| i.get() as u32)
}
}
// }}}

View file

@ -55,6 +55,7 @@ impl ImageVec {
let r = (r as f64 / count).sqrt();
let g = (g as f64 / count).sqrt();
let b = (b as f64 / count).sqrt();
#[allow(clippy::identity_op)]
colors[i as usize * 3 + 0] = r as f32;
colors[i as usize * 3 + 1] = g as f32;
colors[i as usize * 3 + 2] = b as f32;

View file

@ -500,7 +500,7 @@ pub fn compute_b30_ptt(scoring_system: ScoringSystem, plays: &PlayCollection<'_>
}
// }}}
// {{{ Maintenance functions
pub async fn generate_missing_scores(ctx: &UserContext) -> Result<(), TaggedError> {
pub async fn generate_missing_scores(ctx: &UserContext) -> Result<(), Error> {
let conn = ctx.db.get()?;
let mut query = conn.prepare_cached(
"

View file

@ -226,16 +226,10 @@ impl Score {
// {{{ Compute score from note breakdown subpairs
let pf_score = Score::compute_naive(note_count, pures, fars);
let fl_score = Score::compute_naive(
note_count,
note_count.checked_sub(losts + fars).unwrap_or(0),
fars,
);
let lp_score = Score::compute_naive(
note_count,
pures,
note_count.checked_sub(losts + pures).unwrap_or(0),
);
let fl_score =
Score::compute_naive(note_count, note_count.saturating_sub(losts + fars), fars);
let lp_score =
Score::compute_naive(note_count, pures, note_count.saturating_sub(losts + pures));
// }}}
// {{{ Look for consensus among recomputed scores
// Lemma: if two computed scores agree, then so will the third

View file

@ -45,7 +45,7 @@ pub fn get_asset_dir() -> PathBuf {
fn get_font(name: &str) -> RefCell<Face> {
let face = FREETYPE_LIB.with(|lib| {
lib.new_face(get_asset_dir().join("fonts").join(name), 0)
.expect(&format!("Could not load {} font", name))
.unwrap_or_else(|_| panic!("Could not load {} font", name))
});
RefCell::new(face)
}

View file

@ -2,6 +2,7 @@
use std::path::PathBuf;
use crate::context::CliContext;
use shimmeringmoon::commands::discord::MessageContext;
use shimmeringmoon::commands::score::magic_impl;
use shimmeringmoon::context::{Error, UserContext};
// }}}
@ -13,6 +14,7 @@ pub struct Args {
pub async fn run(args: Args) -> Result<(), Error> {
let mut ctx = CliContext::new(UserContext::new().await?);
magic_impl(&mut ctx, &args.files).await?;
let res = magic_impl(&mut ctx, &args.files).await;
ctx.handle_error(res).await?;
Ok(())
}

View file

@ -3,10 +3,10 @@ use std::num::NonZeroU64;
use std::path::PathBuf;
use std::str::FromStr;
use poise::serenity_prelude::{CreateAttachment, CreateMessage};
extern crate shimmeringmoon;
use poise::CreateReply;
use shimmeringmoon::assets::get_var;
use shimmeringmoon::commands::discord::mock::ReplyEssence;
use shimmeringmoon::context::Error;
use shimmeringmoon::{commands::discord::MessageContext, context::UserContext};
// }}}
@ -53,12 +53,8 @@ impl MessageContext for CliContext {
Ok(())
}
async fn send_files(
&mut self,
_attachments: impl IntoIterator<Item = CreateAttachment>,
message: CreateMessage,
) -> Result<(), Error> {
let all = toml::to_string(&message).unwrap();
async fn send(&mut self, message: CreateReply) -> Result<(), Error> {
let all = toml::to_string(&ReplyEssence::from_reply(message)).unwrap();
println!("\n========== Message ==========");
println!("{all}");
Ok(())

View file

@ -1,6 +1,6 @@
use clap::Parser;
use command::{Cli, Command};
use shimmeringmoon::context::{Error, UserContext};
use shimmeringmoon::context::Error;
mod command;
mod commands;

View file

@ -1,5 +1,4 @@
use poise::serenity_prelude::{self as serenity};
extern crate shimmeringmoon;
use shimmeringmoon::arcaea::play::generate_missing_scores;
use shimmeringmoon::context::{Error, UserContext};
use shimmeringmoon::{commands, timed};

View file

@ -174,9 +174,11 @@ impl BitmapCanvas {
}
// {{{ Draw pixel
#[allow(clippy::identity_op)]
pub fn set_pixel(&mut self, pos: (u32, u32), color: Color) {
let index = 3 * (pos.1 * self.width + pos.0) as usize;
let alpha = color.3 as u32;
self.buffer[index + 0] =
((alpha * color.0 as u32 + (255 - alpha) * self.buffer[index + 0] as u32) / 255) as u8;
self.buffer[index + 1] =
@ -299,6 +301,7 @@ impl BitmapCanvas {
}
// }}}
// {{{ Draw text
#[allow(clippy::type_complexity)]
pub fn plan_text_rendering(
pos: Position,
faces: &mut [&mut Face],
@ -490,8 +493,8 @@ impl BitmapCanvas {
for dx in 0..iw {
for dy in 0..ih {
let x = pos.0 + dx as i32 + b_glyph.left();
let y = pos.1 + dy as i32 - b_glyph.top();
let x = pos.0 + dx + b_glyph.left();
let y = pos.1 + dy - b_glyph.top();
// TODO: gamma correction
if x >= 0 && (x as u32) < self.width && y >= 0 && (y as u32) < height {
@ -656,7 +659,7 @@ impl LayoutManager {
(
outer_id,
(0..amount.0 * amount.1).into_iter().map(move |i| {
(0..amount.0 * amount.1).map(move |i| {
let (y, x) = i.div_rem_euclid(&amount.0);
((x * inner.width) as i32, (y * inner.height) as i32)
}),
@ -689,7 +692,7 @@ impl LayoutManager {
#[inline]
pub fn position_relative_to(&self, id: LayoutBoxId, pos: Position) -> Position {
let current = self.lookup(id);
((pos.0 as i32 + current.x), (pos.1 as i32 + current.y))
((pos.0 + current.x), (pos.1 + current.y))
}
#[inline]

View file

@ -219,7 +219,7 @@ mod best_tests {
#[tokio::test]
async fn no_scores() -> Result<(), Error> {
with_test_ctx!("test/commands/chart/best/specify_difficulty", async |ctx| {
with_test_ctx!("test/commands/chart/best/no_scores", async |ctx| {
best_impl(ctx, "Pentiment").await?;
Ok(())
})
@ -228,9 +228,9 @@ mod best_tests {
#[tokio::test]
async fn pick_correct_score() -> Result<(), Error> {
with_test_ctx!(
"test/commands/chart/best/last_byd",
"test/commands/chart/best/pick_correct_score",
async |ctx: &mut MockContext| {
magic_impl(
let plays = magic_impl(
ctx,
&[
PathBuf::from_str("test/screenshots/fracture_ray_ex.jpg")?,
@ -243,6 +243,7 @@ mod best_tests {
let play = best_impl(ctx, "Fracture ray").await?;
assert_eq!(play.score(ScoringSystem::Standard).0, 9_805_651);
assert_eq!(plays[0], play);
Ok(())
}

View file

@ -122,24 +122,17 @@ impl<'a> MessageContext for poise::Context<'a, UserContext, Error> {
pub mod mock {
use std::{env, fs, path::PathBuf};
use anyhow::Context;
use poise::serenity_prelude::CreateEmbed;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use super::*;
/// A mock context usable for testing. Messages and attachments are
/// accumulated inside a vec, and can be used for golden testing
/// (see [MockContext::golden])
pub struct MockContext {
pub user_id: u64,
pub data: UserContext,
messages: Vec<ReplyEssence>,
}
// {{{ Message essences
/// Holds test-relevant data about an attachment.
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AttachmentEssence {
pub struct AttachmentEssence {
filename: String,
description: Option<String>,
/// SHA-256 hash of the file
@ -148,7 +141,7 @@ pub mod mock {
/// Holds test-relevant data about a reply.
#[derive(Debug, Clone, Serialize)]
struct ReplyEssence {
pub struct ReplyEssence {
reply: bool,
ephermal: Option<bool>,
content: Option<String>,
@ -156,6 +149,43 @@ pub mod mock {
attachments: Vec<AttachmentEssence>,
}
impl ReplyEssence {
pub fn from_reply(message: CreateReply) -> Self {
ReplyEssence {
reply: message.reply,
ephermal: message.ephemeral,
content: message.content,
embeds: message.embeds,
attachments: message
.attachments
.into_iter()
.map(|attachment| AttachmentEssence {
filename: attachment.filename,
description: attachment.description,
hash: {
let hash = Sha256::digest(&attachment.data);
let string = base16ct::lower::encode_string(&hash);
// We allocate twice, but it's only at the end of tests,
// so it should be fineeeeeeee
format!("sha256_{string}")
},
})
.collect(),
}
}
}
// }}}
// {{{ Mock context
/// A mock context usable for testing. Messages and attachments are
/// accumulated inside a vec, and can be used for golden testing
/// (see [MockContext::golden])
pub struct MockContext {
pub user_id: u64,
pub data: UserContext,
messages: Vec<ReplyEssence>,
}
impl MockContext {
pub fn new(data: UserContext) -> Self {
Self {
@ -223,28 +253,7 @@ pub mod mock {
}
async fn send(&mut self, message: CreateReply) -> Result<(), Error> {
self.messages.push(ReplyEssence {
reply: message.reply,
ephermal: message.ephemeral,
content: message.content,
embeds: message.embeds,
attachments: message
.attachments
.into_iter()
.map(|attachment| AttachmentEssence {
filename: attachment.filename,
description: attachment.description,
hash: {
let hash = Sha256::digest(&attachment.data);
let string = base16ct::lower::encode_string(&hash);
// We allocate twice, but it's only at the end of tests,
// so it should be fineeeeeeee
format!("sha256_{string}")
},
})
.collect(),
});
self.messages.push(ReplyEssence::from_reply(message));
Ok(())
}
@ -266,11 +275,14 @@ pub mod mock {
}
async fn download(&self, attachment: &Self::Attachment) -> Result<Vec<u8>, Error> {
let res = tokio::fs::read(attachment).await?;
let res = tokio::fs::read(attachment)
.await
.with_context(|| format!("Could not download attachment {attachment:?}"))?;
Ok(res)
}
// }}}
}
// }}}
}
// }}}
// {{{ Helpers

View file

@ -1,5 +1,3 @@
pub mod two_columns;
#[macro_export]
macro_rules! edit_reply {
($ctx:expr, $handle:expr, $($arg:tt)*) => {{

View file

@ -1,54 +0,0 @@
//! These functions have been copy-pasted from internal `poise` code.
use std::fmt::Write as _;
/// Convenience function to align descriptions behind commands
pub struct TwoColumnList(Vec<(String, Option<String>)>);
impl TwoColumnList {
/// Creates a new [`TwoColumnList`]
pub fn new() -> Self {
Self(Vec::new())
}
/// Add a line that needs the padding between the columns
pub fn push_two_colums(&mut self, command: String, description: String) {
self.0.push((command, Some(description)));
}
/// Add a line that doesn't influence the first columns's width
pub fn push_heading(&mut self, category: &str) {
if !self.0.is_empty() {
self.0.push(("".to_string(), None));
}
let mut category = category.to_string();
category += ":";
self.0.push((category, None));
}
/// Convert the list into a string with aligned descriptions
pub fn into_string(self) -> String {
let longest_command = self
.0
.iter()
.filter_map(|(command, description)| {
if description.is_some() {
Some(command.len())
} else {
None
}
})
.max()
.unwrap_or(0);
let mut text = String::new();
for (command, description) in self.0 {
if let Some(description) = description {
let padding = " ".repeat(longest_command - command.len() + 3);
writeln!(text, "{}{}{}", command, padding, description).unwrap();
} else {
writeln!(text, "{}", command).unwrap();
}
}
text
}
}

View file

@ -9,7 +9,6 @@ use std::sync::LazyLock;
use crate::arcaea::{chart::SongCache, jacket::JacketCache};
use crate::assets::{get_data_dir, EXO_FONT, GEOSANS_FONT, KAZESAWA_BOLD_FONT, KAZESAWA_FONT};
use crate::commands::discord::MessageContext;
use crate::recognition::{hyperglass::CharMeasurements, ui::UIMeasurements};
use crate::timed;
// }}}
@ -48,24 +47,6 @@ macro_rules! get_user_error {
}};
}
/// Handles a [TaggedError], showing user errors to the user,
/// and throwing away anything else.
pub async fn discord_error_handler<V>(
ctx: &mut impl MessageContext,
res: Result<V, TaggedError>,
) -> Result<Option<V>, Error> {
match res {
Ok(v) => Ok(Some(v)),
Err(e) => match e.kind {
ErrorKind::Internal => Err(e.error),
ErrorKind::User => {
ctx.reply(&format!("{}", e.error)).await?;
Ok(None)
}
},
}
}
impl<E: Into<Error>> From<E> for TaggedError {
fn from(value: E) -> Self {
Self::new(ErrorKind::Internal, value.into())

View file

@ -1,4 +1,6 @@
#![allow(async_fn_in_trait)]
#![allow(clippy::needless_range_loop)]
#![allow(clippy::redundant_closure)]
#![feature(iter_map_windows)]
#![feature(let_chains)]
#![feature(array_try_map)]
@ -7,6 +9,7 @@
#![feature(thread_local)]
#![feature(generic_arg_infer)]
#![feature(iter_collect_into)]
#![feature(stmt_expr_attributes)]
pub mod arcaea;
pub mod assets;

View file

@ -1,7 +1,8 @@
//! Hyperglass is my own specialized OCR system, created as a result of my
//! annoyance with how unreliable tesseract is. Assuming we know the font,
//! OCR should be almost perfect, even when faced with stange kerning. This is
//! what this module achieves!
//! Hyperglass is my own specialized OCR system.
//!
//! Hyperglass was created as a result of my annoyance with how unreliable
//! tesseract is. Assuming we know the font, OCR should be almost perfect,
//! even when faced with stange kerning. This is what this module achieves!
//!
//! The algorithm is pretty simple:
//! 1. Find the connected components (i.e., "black areas") in the image.
@ -202,10 +203,7 @@ impl ComponentsWithBounds {
for bound in &mut bounds {
if bound.map_or(false, |b| {
(b.x_max - b.x_min) as f32 >= max_sizes.0 * image.width() as f32
}) {
*bound = None;
} else if bound.map_or(false, |b| {
(b.y_max - b.y_min) as f32 >= max_sizes.1 * image.height() as f32
|| (b.y_max - b.y_min) as f32 >= max_sizes.1 * image.height() as f32
}) {
*bound = None;
}
@ -249,14 +247,14 @@ impl CharMeasurements {
weight,
};
let padding = (5, 5);
let planned = BitmapCanvas::plan_text_rendering(padding, &mut [face], style, &string)?;
let planned = BitmapCanvas::plan_text_rendering(padding, &mut [face], style, string)?;
let mut canvas = BitmapCanvas::new(
(planned.0 .0) as u32 + planned.1.width + 2 * padding.0 as u32,
(planned.0 .1) as u32 + planned.1.height + 2 * padding.0 as u32,
);
canvas.text(padding, &mut [face], style, &string)?;
canvas.text(padding, &mut [face], style, string)?;
let buffer = ImageBuffer::from_raw(canvas.width, canvas.height(), canvas.buffer.to_vec())
.ok_or_else(|| anyhow!("Failed to turn buffer into canvas"))?;
let image = DynamicImage::ImageRgb8(buffer);

View file

@ -111,14 +111,14 @@ impl UIMeasurements {
let i = i % (UI_RECT_COUNT + 2);
if i == 0 {
for (j, str) in line.split_whitespace().enumerate().take(2) {
measurement.dimensions[j] = u32::from_str_radix(str, 10)?;
measurement.dimensions[j] = str.parse()?;
}
} else if i == UI_RECT_COUNT + 1 {
measurements.push(measurement);
measurement = UIMeasurement::default();
} else {
for (j, str) in line.split_whitespace().enumerate().take(4) {
measurement.datapoints[(i - 1) * 4 + j] = u32::from_str_radix(str, 10)?;
measurement.datapoints[(i - 1) * 4 + j] = str.parse()?;
}
}
}

View file

@ -1,9 +1,11 @@
//! This file implements the "rotation as shearing" algorithm,
//! which can rotate images without making use of any trigonometric
//! functions (or working with floats altogether, if you don't care
//! This file implements the "rotation as shearing" algorithm.
//!
//! The algorithm can rotate images without making use of any trigonometric
//! functions (or working with floats altogether, assuming you don't care
//! about antialiasing).
//!
//! For more information, consult this article: https://www.ocf.berkeley.edu/~fricke/projects/israel/paeth/rotation_by_shearing.html
//! For more information, consult this article:
//! https://www.ocf.berkeley.edu/~fricke/projects/israel/paeth/rotation_by_shearing.html
use image::{DynamicImage, GenericImage, GenericImageView};
@ -57,7 +59,7 @@ pub fn yshear(image: &mut DynamicImage, rect: Rect, center: Position, shear: f32
}
}
/// Performs a rotation as a series of three shear operations
/// Performs a rotation as a series of three shear operations.
/// Does not perform anti-aliasing.
pub fn rotate(image: &mut DynamicImage, rect: Rect, center: Position, angle: f32) {
let alpha = -f32::tan(angle / 2.0);