perf: eliminate account dropdown micro-delays with global avatar caching and dev optimization
This commit is contained in:
parent
005e7ce77b
commit
2a040f8e91
4 changed files with 45 additions and 17 deletions
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)?;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue