onboarding: Fix font loading frame delay (#37668)

Closes #ISSUE

Fixed an issue where the first frame of the `Editing` page in onboarding
would have a slight delay before rendering the first time it was
navigated to. This was caused by listing the OS fonts on the main
thread, blocking rendering. This PR fixes the issue by adding a new
method to the font family cache to prefill the cache on a background
thread.

Release Notes:

- N/A *or* Added/Fixed/Improved ...

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Ben Kunkle
2025-09-08 11:09:54 -04:00
committed by GitHub
co-authored by Mikayla Maki Anthony Eid Anthony
parent 10989c702c
commit fe2aa3f4cb
4 changed files with 77 additions and 23 deletions
+16 -16
View File
@@ -449,28 +449,28 @@ impl FontPickerDelegate {
) -> Self {
let font_family_cache = FontFamilyCache::global(cx);
let fonts: Vec<SharedString> = font_family_cache
.list_font_families(cx)
.into_iter()
.collect();
let fonts = font_family_cache
.try_list_font_families()
.unwrap_or_else(|| vec![current_font.clone()]);
let selected_index = fonts
.iter()
.position(|font| *font == current_font)
.unwrap_or(0);
let filtered_fonts = fonts
.iter()
.enumerate()
.map(|(index, font)| StringMatch {
candidate_id: index,
string: font.to_string(),
positions: Vec::new(),
score: 0.0,
})
.collect();
Self {
fonts: fonts.clone(),
filtered_fonts: fonts
.iter()
.enumerate()
.map(|(index, font)| StringMatch {
candidate_id: index,
string: font.to_string(),
positions: Vec::new(),
score: 0.0,
})
.collect(),
fonts,
filtered_fonts,
selected_index,
current_font,
on_font_changed: Arc::new(on_font_changed),
+19 -6
View File
@@ -242,12 +242,25 @@ struct Onboarding {
impl Onboarding {
fn new(workspace: &Workspace, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self {
workspace: workspace.weak_handle(),
focus_handle: cx.focus_handle(),
selected_page: SelectedPage::Basics,
user_store: workspace.user_store().clone(),
_settings_subscription: cx.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
let font_family_cache = theme::FontFamilyCache::global(cx);
cx.new(|cx| {
cx.spawn(async move |this, cx| {
font_family_cache.prefetch(cx).await;
this.update(cx, |_, cx| {
cx.notify();
})
})
.detach();
Self {
workspace: workspace.weak_handle(),
focus_handle: cx.focus_handle(),
selected_page: SelectedPage::Basics,
user_store: workspace.user_store().clone(),
_settings_subscription: cx
.observe_global::<SettingsStore>(move |_, cx| cx.notify()),
}
})
}
+1
View File
@@ -78,6 +78,7 @@ impl SettingsValue<serde_json::Value> {
let fs = <dyn Fs>::global(cx);
let rx = settings_store.update_settings_file_at_path(fs.clone(), path.as_slice(), value);
let path = path.clone();
cx.background_spawn(async move {
rx.await?
+41 -1
View File
@@ -16,7 +16,7 @@ struct FontFamilyCacheState {
/// so we do it once and then use the cached values each render.
#[derive(Default)]
pub struct FontFamilyCache {
state: RwLock<FontFamilyCacheState>,
state: Arc<RwLock<FontFamilyCacheState>>,
}
#[derive(Default)]
@@ -52,4 +52,44 @@ impl FontFamilyCache {
lock.font_families.clone()
}
/// Returns the list of font families if they have been loaded
pub fn try_list_font_families(&self) -> Option<Vec<SharedString>> {
self.state
.try_read()
.filter(|state| state.loaded_at.is_some())
.map(|state| state.font_families.clone())
}
/// Prefetch all font names in the background
pub async fn prefetch(&self, cx: &gpui::AsyncApp) {
if self
.state
.try_read()
.is_none_or(|state| state.loaded_at.is_some())
{
return;
}
let Ok(text_system) = cx.update(|cx| App::text_system(cx).clone()) else {
return;
};
let state = self.state.clone();
cx.background_executor()
.spawn(async move {
// We take this lock in the background executor to ensure that synchronous calls to `list_font_families` are blocked while we are prefetching,
// while not blocking the main thread and risking deadlocks
let mut lock = state.write();
let all_font_names = text_system
.all_font_names()
.into_iter()
.map(SharedString::from)
.collect();
lock.font_families = all_font_names;
lock.loaded_at = Some(Instant::now());
})
.await;
}
}