Something went wrong. Try again.
silly little cli tool and UI I overengineered for an event clxxiii.tngl.io/pprace
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163use std::collections::HashMap;
use chrono::{DateTime, Utc};use rosu_v2::prelude::User as RosuUser;use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default)]pub struct Event { pub users: Vec<User>, pub end_date: Option<DateTime<Utc>>, pub snapshot_times: Vec<DateTime<Utc>>,}
impl Event { pub fn add_snapshot(&mut self, snapshot: Snapshot) -> Result<(), ()> { let first_user = match self.users.first() { Some(x) => x, None => { println!("Event has no users"); return Err(()); } };
let expected_id = first_user.pp_history.len(); if expected_id != snapshot.id { println!("Expected snapshot ID {}", expected_id); return Err(()); }
self.snapshot_times.push(snapshot.time);
let pp_map = snapshot.into_map(); for user in &mut self.users { let Some(pp) = pp_map.get(&user.id) else { println!("Snapshot doesn't have user {}", &user.username); return Err(()); }; user.pp_history.push(*pp); }
Ok(()) }
pub fn get_snapshot(&self, id: usize) -> Result<Snapshot, ()> { let Some(time) = self.snapshot_times.get(id) else { println!("Snapshot doesn't exist"); return Err(()); };
let mut pairs = Vec::new(); for user in &self.users { let pp = user .pp_history .get(id) .expect("id already validated to exist"); pairs.push((user.id, *pp)); }
Ok(Snapshot { id, time: *time, id_pp_pairs: pairs, }) }}
#[derive(Serialize, Deserialize)]pub struct User { pub id: u32, pub username: String, pub avatar_url: String, pub initial_rank: Option<u32>, pub initial_pp: f32, pub pp_history: Vec<f32>,}
// Using a reference here prevents us from cloning the entire object// when converting using .iter().map() later onimpl From<&RosuUser> for User { fn from(value: &RosuUser) -> Self { User { id: value.user_id, username: value.username.clone().into_string(), avatar_url: value.avatar_url.clone(), pp_history: vec![], initial_rank: value .statistics_modes .osu .as_ref() .and_then(|x| x.global_rank), initial_pp: value .statistics_modes .osu .as_ref() .and_then(|x| Some(x.pp)) .unwrap_or(0.0), } }}
/// A intermediary struct to convert user arrays to/// sorted sets in redis. Just two parallel arrays#[derive(Serialize)]pub struct Snapshot { id: usize, time: DateTime<Utc>, id_pp_pairs: Vec<(u32, f32)>,}
impl Snapshot { pub fn id(&self) -> &usize { &self.id } pub fn create(event_users: &[User], user_updates: &[RosuUser]) -> Result<Self, ()> { let first_user = match event_users.first() { Some(x) => x, None => { println!("Event has no users"); return Err(()); } };
let id = first_user.pp_history.len();
let pp_map: HashMap<u32, f32> = HashMap::from_iter(user_updates.iter().map(|x| { ( x.user_id, x.statistics_modes .osu .as_ref() .expect("user should have osu! statistics") .pp, ) })); let mut pairs = Vec::new(); for user in event_users { let Some(current_pp) = pp_map.get(&user.id) else { println!( "User {} does not have an entry in fetched data", &user.username ); return Err(()); };
let pp_growth = current_pp - user.initial_pp;
pairs.push((user.id, pp_growth)) }
Ok(Self { id, time: Utc::now(), id_pp_pairs: pairs, }) } pub fn sorted_set_pairs(&self) -> Vec<(f32, u32)> { self.id_pp_pairs.iter().map(|x| (x.1, x.0)).collect() } pub fn into_map(self) -> HashMap<u32, f32> { HashMap::from_iter(self.id_pp_pairs) }}