font_family_cache.rs

 1use std::sync::Arc;
 2use std::time::Instant;
 3
 4use gpui::{App, Global, ReadGlobal, SharedString};
 5use parking_lot::RwLock;
 6
 7#[derive(Default)]
 8struct FontFamilyCacheState {
 9    loaded_at: Option<Instant>,
10    font_families: Vec<SharedString>,
11}
12
13/// A cache for the list of font families.
14///
15/// Listing the available font families from the text system is expensive,
16/// so we do it once and then use the cached values each render.
17#[derive(Default)]
18pub struct FontFamilyCache {
19    state: Arc<RwLock<FontFamilyCacheState>>,
20}
21
22#[derive(Default)]
23struct GlobalFontFamilyCache(Arc<FontFamilyCache>);
24
25impl Global for GlobalFontFamilyCache {}
26
27impl FontFamilyCache {
28    /// Initializes the global font family cache.
29    pub fn init_global(cx: &mut App) {
30        cx.default_global::<GlobalFontFamilyCache>();
31    }
32
33    /// Returns the global font family cache.
34    pub fn global(cx: &App) -> Arc<Self> {
35        GlobalFontFamilyCache::global(cx).0.clone()
36    }
37
38    /// Returns the list of font families.
39    pub fn list_font_families(&self, cx: &App) -> Vec<SharedString> {
40        if self.state.read().loaded_at.is_some() {
41            return self.state.read().font_families.clone();
42        }
43
44        let mut lock = self.state.write();
45        lock.font_families = cx
46            .text_system()
47            .all_font_names()
48            .into_iter()
49            .map(SharedString::from)
50            .collect();
51        lock.loaded_at = Some(Instant::now());
52
53        lock.font_families.clone()
54    }
55
56    /// Returns the list of font families if they have been loaded
57    pub fn try_list_font_families(&self) -> Option<Vec<SharedString>> {
58        self.state
59            .try_read()
60            .filter(|state| state.loaded_at.is_some())
61            .map(|state| state.font_families.clone())
62    }
63
64    /// Prefetch all font names in the background
65    pub async fn prefetch(&self, cx: &gpui::AsyncApp) {
66        if self
67            .state
68            .try_read()
69            .is_none_or(|state| state.loaded_at.is_some())
70        {
71            return;
72        }
73
74        let Ok(text_system) = cx.update(|cx| App::text_system(cx).clone()) else {
75            return;
76        };
77
78        let state = self.state.clone();
79
80        cx.background_executor()
81            .spawn(async move {
82                // We take this lock in the background executor to ensure that synchronous calls to `list_font_families` are blocked while we are prefetching,
83                // while not blocking the main thread and risking deadlocks
84                let mut lock = state.write();
85                let all_font_names = text_system
86                    .all_font_names()
87                    .into_iter()
88                    .map(SharedString::from)
89                    .collect();
90                lock.font_families = all_font_names;
91                lock.loaded_at = Some(Instant::now());
92            })
93            .await;
94    }
95}