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:
co-authored by
Mikayla Maki
Anthony Eid
Anthony
parent
10989c702c
commit
fe2aa3f4cb
@@ -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),
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user