pre-warm cluster art

This commit is contained in:
Julian Chang 2026-09-30 21:01:26 -04:00
parent 50bf2c07ee
commit 4995276c51
No known key found for this signature in database
7 changed files with 113 additions and 15 deletions

View file

@ -50,7 +50,7 @@ pub use queries::{
mutation_error, mutation_is_pending, mutation_is_running, mutation_ok,
onboarding_bundles_items, package_meta_batch, package_updates, pick_version_metadata,
project_detail, provider_versions, query_error, query_is_busy, query_is_loading,
refresh_version_art_gallery, reset_login_code_dedup, resolve_art_url, search_items,
cluster_art_url, refresh_version_art_gallery, reset_login_code_dedup, resolve_art_url, search_items,
search_pending, search_total, settled_or_loading, stale_hashes, terms_document, terms_error,
terms_is_loading, try_account, try_accounts, try_cluster_analytics, try_cluster_logs,
try_cluster_screenshots, try_default_account, try_game_profile, try_global_analytics,

View file

@ -104,8 +104,8 @@ pub use storage::{
};
pub use tos::{TermsQuery, terms_document, terms_error, terms_is_loading, use_terms};
pub use version_metadata::{
pick_version_metadata, refresh_version_art_gallery, resolve_art_url, use_version_art,
use_version_art_gallery, use_version_metadata,
cluster_art_url, pick_version_metadata, refresh_version_art_gallery, resolve_art_url,
use_version_art, use_version_art_gallery, use_version_metadata,
};
pub use versions::{
GameVersion, JavaMajorsQuery, game_versions, java_majors,

View file

@ -1,6 +1,8 @@
use oneclient_common::VersionKey;
use oneclient_common::domain::GameLoader;
use oneclient_common::{VersionKey, parse_mc_version};
use oneclient_core::VersionMetadata;
use oneclient_core::clusters::Cluster;
use oneclient_core::versions::VersionArts;
use freya::query::QueriesStorage;
@ -107,6 +109,23 @@ pub fn resolve_art_url(
.or_else(|| candidates.fallback.clone())
}
pub fn cluster_art_url(
cluster: &Cluster,
list: &[VersionMetadata],
arts: &VersionArts,
) -> Option<String> {
let parsed = parse_mc_version(&cluster.mc_version);
let major = parsed.as_ref().map(|p| p.major);
let key = parsed.and_then(|p| p.key());
let curated =
major.and_then(|major| pick_version_metadata(list, major, key, Some(cluster.mc_loader)));
let candidates = VersionArtCandidates {
minor: arts.specific_art_url(major, key),
fallback: arts.fallback_art_url(major),
};
resolve_art_url(curated.as_ref(), &candidates)
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -27,6 +27,7 @@ use crate::hooks::{
use_provide_browser_state, use_provide_browser_type, use_provide_datapack_world, use_splash,
};
use crate::theme::colors;
use crate::utils::home_cluster;
use oneclient_events::LaunchStage;
use std::collections::HashMap;
use std::path::PathBuf;
@ -271,12 +272,6 @@ pub(crate) fn appshell_overlay(alpha: f32) -> Rect {
pub const HOME_BACKGROUND_ASSET: &str = "backgrounds/CavesAndCliffs.jpg";
fn home_cluster(clusters: &[Cluster], active: Option<ClusterId>) -> Option<&Cluster> {
active
.and_then(|id| clusters.iter().find(|c| c.id == id))
.or_else(|| clusters.first())
}
fn home_art(cluster: Option<&Cluster>) -> DynamicArt {
cluster
.map_or_else(DynamicArt::fallback, DynamicArt::for_cluster)

View file

@ -335,9 +335,14 @@ pub fn bundle_display_name(archive: &BundleArchive) -> String {
}
pub fn default_cluster(clusters: Vec<Cluster>, active: Option<i64>) -> Option<Cluster> {
home_cluster(&clusters, active).cloned()
}
pub fn home_cluster(clusters: &[Cluster], active: Option<i64>) -> Option<&Cluster> {
active
.and_then(|id| clusters.iter().find(|cluster| cluster.id == id).cloned())
.or_else(|| sort_clusters_for_home(clusters).into_iter().next())
.and_then(|id| clusters.iter().find(|cluster| cluster.id == id))
// `min_by` keeps the first of equals, matching the stable sort in `sort_clusters_for_home`
.or_else(|| clusters.iter().min_by(|a, b| compare_recent_activity(a, b)))
}
pub fn sort_clusters_for_home(mut clusters: Vec<Cluster>) -> Vec<Cluster> {
@ -444,6 +449,22 @@ mod tests {
assert_eq!(order, vec![2, 1]);
}
#[test]
fn home_background_matches_the_first_recents_card() {
let clusters = vec![
versioned(1, "1.8.9"),
versioned(2, "26.2"),
versioned(3, "1.21.1"),
];
let first_card = sort_clusters_for_home(clusters.clone())[0].id;
assert_eq!(
home_cluster(&clusters, None).map(|c| c.id),
Some(first_card)
);
assert_eq!(home_cluster(&clusters, Some(3)).map(|c| c.id), Some(3));
}
fn line(major: u32, minor: Option<u32>) -> ReleaseLine {
ReleaseLine { major, minor }
}

View file

@ -1,14 +1,18 @@
use freya::prelude::*;
use freya::router::RouterContext;
use oneclient_core::clusters::Cluster;
use oneclient_core::images::BACKGROUND_IMAGE_EDGE;
use oneclient_core::{BundleArchive, BundleFile, ImportTarget, MigrationSource, SentryExclusion};
use oneclient_db::models::OverrideType;
use crate::components::ART_PREVIEW_EDGE;
use crate::hooks::{
Actions, ClusterBundles, invalidate_cluster_queries, migration_detection,
Actions, ClusterBundles, cluster_art_url, invalidate_cluster_queries, migration_detection,
onboarding_bundles_items, try_default_account, use_current_account, use_dispatch,
use_migration, use_onboarding_bundles, use_onboarding_selection, use_settings_snapshot,
};
use crate::routes::Route;
use crate::utils::{home_cluster, sort_clusters_for_home};
use crate::view::onboarding::{matching_new_cluster_id, pkg_key};
mod view;
@ -302,6 +306,8 @@ fn finish_setup(
invalidate_cluster_queries().await;
let _ = tokio::time::timeout(std::time::Duration::from_secs(30), warm_home_art()).await;
for version in versions {
dispatch.record_seen_version(version);
}
@ -311,6 +317,59 @@ fn finish_setup(
});
}
const HOME_ART_CLUSTERS: usize = 3;
async fn warm_home_art() {
let Ok(state) = crate::launcher::state() else {
return;
};
let clusters = match state.clusters.list().await {
Ok(clusters) => clusters,
Err(err) => {
tracing::debug!("could not list clusters to warm home art: {err}");
return;
}
};
let meta_url_base = &state.services.requester.config().meta_url_base;
let (list, arts) = tokio::join!(
state.versions.metadata(meta_url_base),
state.versions.arts(meta_url_base),
);
let art_url = |cluster: &Cluster| {
cluster
.cover_file()
.is_none()
.then(|| cluster_art_url(cluster, &list, &arts))
.flatten()
};
let background = home_cluster(&clusters, None).and_then(art_url);
let mut warms: Vec<(String, &[u32])> = sort_clusters_for_home(clusters.clone())
.iter()
.take(HOME_ART_CLUSTERS)
.filter_map(art_url)
.filter(|url| Some(url) != background.as_ref())
.map(|url| (url, &[ART_PREVIEW_EDGE][..]))
.collect();
warms.sort();
warms.dedup();
warms.extend(background.map(|url| (url, &[ART_PREVIEW_EDGE, BACKGROUND_IMAGE_EDGE][..])));
let net = &state.services.requester;
let results = futures_util::future::join_all(
warms
.iter()
.map(|(url, edges)| state.images.prefetch(net, url, edges)),
)
.await;
for ((url, _), result) in warms.iter().zip(results) {
if let Err(err) = result {
tracing::debug!("home art warm failed for {url}: {err}");
}
}
}
async fn apply_overrides(plan: &ClusterPlan) -> oneclient_core::LauncherResult<()> {
let state = crate::launcher::state()?;
oneclient_core::set_bundle_package_overrides(

View file

@ -2,12 +2,16 @@ use std::collections::BTreeSet;
use futures_util::StreamExt;
use crate::images::{DEFAULT_IMAGE_EDGE, PREVIEW_IMAGE_EDGE};
use crate::images::{BACKGROUND_IMAGE_EDGE, DEFAULT_IMAGE_EDGE, PREVIEW_IMAGE_EDGE};
use crate::state::LauncherState;
const PREFETCH_CONCURRENCY: usize = 4;
const PREFETCH_EDGES: [u32; 2] = [PREVIEW_IMAGE_EDGE, DEFAULT_IMAGE_EDGE];
const PREFETCH_EDGES: [u32; 3] = [
PREVIEW_IMAGE_EDGE,
DEFAULT_IMAGE_EDGE,
BACKGROUND_IMAGE_EDGE,
];
#[tracing::instrument(level = "debug", skip(state))]
pub async fn prefetch_version_art(state: &LauncherState) {