perf: eliminate account dropdown micro-delays with global avatar caching and dev optimization

This commit is contained in:
katR 2026-08-29 19:21:11 +05:30
parent 005e7ce77b
commit 2a040f8e91
4 changed files with 45 additions and 17 deletions

View file

@ -140,6 +140,9 @@ freya = {version = "=0.5.0-rc.4", features = ["engine", "winit", "router", "remo
freya-skia-safe = "=0.99.1" # required for the SKSL player renderer freya-skia-safe = "=0.99.1" # required for the SKSL player renderer
image = {version = "=0.25.10", default-features = false, features = ["png", "jpeg"]} image = {version = "=0.25.10", default-features = false, features = ["png", "jpeg"]}
[profile.dev.package."*"]
opt-level = 3
[profile.release] [profile.release]
codegen-units = 1 codegen-units = 1
debug = "line-tables-only" debug = "line-tables-only"

View file

@ -90,8 +90,7 @@ impl Component for AccountPanel {
.spacing(8.) .spacing(8.)
.opacity(progress) .opacity(progress)
.margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.)) .margin(Gaps::new((1.0 - progress) * -8.0, 0., 0., 0.))
.background(colors::page_elevated().with_a(220)) .background(colors::page_elevated())
.blur(12.)
.corner_radius(CornerRadius::new_all(12.)) .corner_radius(CornerRadius::new_all(12.))
.border( .border(
Border::new() Border::new()

View file

@ -1,3 +1,6 @@
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
use bytes::Bytes; use bytes::Bytes;
use freya::elements::image::{ImageHandle, image}; use freya::elements::image::{ImageHandle, image};
use freya::engine::prelude::{Paint, SkData, SkImage, raster_n32_premul}; use freya::engine::prelude::{Paint, SkData, SkImage, raster_n32_premul};
@ -10,6 +13,8 @@ const FACE: f32 = 8.;
const HEAD_FACE: (f32, f32) = (8., 8.); const HEAD_FACE: (f32, f32) = (8., 8.);
const HEAD_OVERLAY: (f32, f32) = (40., 8.); const HEAD_OVERLAY: (f32, f32) = (40., 8.);
static AVATAR_HEAD_CACHE: OnceLock<Mutex<HashMap<usize, ImageHandle>>> = OnceLock::new();
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub struct Avatar { pub struct Avatar {
uuid: String, uuid: String,
@ -40,20 +45,7 @@ impl Component for Avatar {
fn render(&self) -> impl IntoElement { fn render(&self) -> impl IntoElement {
let (skin_bytes, _is_slim) = use_player_skin(self.uuid.clone()); let (skin_bytes, _is_slim) = use_player_skin(self.uuid.clone());
let head = get_or_compose_head(&skin_bytes);
let mut cache = use_state(|| None::<(usize, ImageHandle)>);
let src_ptr = skin_bytes.as_ptr() as usize;
let cached = cache.read().clone();
let head = match cached {
Some((ptr, holder)) if ptr == src_ptr => Some(holder),
_ => {
let holder = compose_head(&skin_bytes);
if let Some(holder) = &holder {
cache.set(Some((src_ptr, holder.clone())));
}
holder
}
};
rect() rect()
.width(Size::px(AVATAR_SIZE)) .width(Size::px(AVATAR_SIZE))
@ -71,6 +63,27 @@ impl Component for Avatar {
} }
} }
fn get_or_compose_head(skin_bytes: &Bytes) -> Option<ImageHandle> {
if skin_bytes.is_empty() {
return None;
}
let src_ptr = skin_bytes.as_ptr() as usize;
let cache = AVATAR_HEAD_CACHE.get_or_init(|| Mutex::new(HashMap::new()));
{
if let Ok(guard) = cache.lock() {
if let Some(handle) = guard.get(&src_ptr) {
return Some(handle.clone());
}
}
}
let composed = compose_head(skin_bytes)?;
if let Ok(mut guard) = cache.lock() {
guard.insert(src_ptr, composed.clone());
}
Some(composed)
}
fn compose_head(skin_bytes: &Bytes) -> Option<ImageHandle> { fn compose_head(skin_bytes: &Bytes) -> Option<ImageHandle> {
let data = unsafe { SkData::new_bytes(skin_bytes) }; let data = unsafe { SkData::new_bytes(skin_bytes) };
let skin = SkImage::from_encoded(data)?; let skin = SkImage::from_encoded(data)?;

View file

@ -22,8 +22,21 @@ impl QueryCapability for FetchPlayerProfileQuery {
type Keys = PlayerProfileQueryKeys; type Keys = PlayerProfileQueryKeys;
async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> { async fn run(&self, keys: &Self::Keys) -> Result<Self::Ok, Self::Err> {
let access_token = keys.access_token.clone(); let uuid_str = &keys.uuid;
if uuid_str.is_empty() {
return Ok(PlayerProfileView::default());
}
if let Ok(u) = uuid::Uuid::parse_str(uuid_str) {
if u.get_version() == Some(uuid::Version::Md5) || u.is_nil() {
return Ok(PlayerProfileView {
uuid: uuid_str.clone(),
..Default::default()
});
}
}
let access_token = keys.access_token.clone();
let state = crate::launcher::state()?; let state = crate::launcher::state()?;
let client = &state.services.requester; let client = &state.services.requester;