mirror of
https://github.com/wez/wezterm.git
synced 2024-11-23 23:21:08 +03:00
fonts: constrain to a single thread for fallback resolution
@H-M-H noticed and suggested this; rather than spawning a thread for potentially every cluster of graphemes that are being displayed before we've located a font, constrain things to a single thread so that we don't burn CPU trying to process the same results in an excessive number of threads.
This commit is contained in:
parent
41866a0b5b
commit
10b64abec8
@ -11,6 +11,7 @@ use rangeset::RangeSet;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::rc::{Rc, Weak};
|
||||
use std::sync::mpsc::{channel, Sender};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
use termwiz::cell::Presentation;
|
||||
@ -172,6 +173,150 @@ impl LoadedFont {
|
||||
}
|
||||
}
|
||||
|
||||
struct FallbackResolveInfo {
|
||||
no_glyphs: Vec<char>,
|
||||
pending: Arc<Mutex<Vec<ParsedFont>>>,
|
||||
completion: Box<dyn FnOnce() + Send + Sync>,
|
||||
font_dirs: Arc<FontDatabase>,
|
||||
built_in: Arc<FontDatabase>,
|
||||
locator: Arc<dyn FontLocator + Send + Sync>,
|
||||
config: ConfigHandle,
|
||||
}
|
||||
|
||||
impl FallbackResolveInfo {
|
||||
fn process(self) {
|
||||
let fallback_str = self.no_glyphs.iter().collect::<String>();
|
||||
let mut extra_handles = vec![];
|
||||
|
||||
log::trace!(
|
||||
"Looking for {} in fallback fonts",
|
||||
fallback_str.escape_unicode()
|
||||
);
|
||||
|
||||
match self.locator.locate_fallback_for_codepoints(&self.no_glyphs) {
|
||||
Ok(ref mut handles) => extra_handles.append(handles),
|
||||
Err(err) => log::error!(
|
||||
"Error: {:#} while resolving fallback for {} from font-locator",
|
||||
err,
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
}
|
||||
|
||||
if self.config.search_font_dirs_for_fallback {
|
||||
match self
|
||||
.font_dirs
|
||||
.locate_fallback_for_codepoints(&self.no_glyphs)
|
||||
{
|
||||
Ok(ref mut handles) => extra_handles.append(handles),
|
||||
Err(err) => log::error!(
|
||||
"Error: {:#} while resolving fallback for {} from font_dirs",
|
||||
err,
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
match self
|
||||
.built_in
|
||||
.locate_fallback_for_codepoints(&self.no_glyphs)
|
||||
{
|
||||
Ok(ref mut handles) => extra_handles.append(handles),
|
||||
Err(err) => log::error!(
|
||||
"Error: {:#} while resolving fallback for {} for built-in fonts",
|
||||
err,
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
}
|
||||
|
||||
let mut wanted = RangeSet::new();
|
||||
for c in self.no_glyphs {
|
||||
wanted.add(c as u32);
|
||||
}
|
||||
log::trace!(
|
||||
"Fallback fonts that match {} before sorting are: {:#?}",
|
||||
fallback_str.escape_unicode(),
|
||||
extra_handles
|
||||
);
|
||||
|
||||
if wanted.len() > 1 && self.config.sort_fallback_fonts_by_coverage {
|
||||
// Sort by ascending coverage
|
||||
extra_handles.sort_by_cached_key(|p| {
|
||||
p.coverage_intersection(&wanted)
|
||||
.map(|r| r.len())
|
||||
.unwrap_or(0)
|
||||
});
|
||||
// Re-arrange to descending coverage
|
||||
extra_handles.reverse();
|
||||
log::trace!(
|
||||
"Fallback fonts that match {} after sorting are: {:#?}",
|
||||
fallback_str.escape_unicode(),
|
||||
extra_handles
|
||||
);
|
||||
}
|
||||
|
||||
// iteratively reduce to just the fonts that we need
|
||||
extra_handles.retain(|p| match p.coverage_intersection(&wanted) {
|
||||
Ok(cov) if cov.is_empty() => false,
|
||||
Ok(cov) => {
|
||||
// Remove the matches from the set, so that we avoid
|
||||
// picking up multiple fonts for the same glyphs
|
||||
wanted = wanted.difference(&cov);
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
});
|
||||
|
||||
if !extra_handles.is_empty() {
|
||||
let mut pending = self.pending.lock().unwrap();
|
||||
pending.append(&mut extra_handles);
|
||||
(self.completion)();
|
||||
}
|
||||
|
||||
if !wanted.is_empty() {
|
||||
// There were some glyphs we couldn't resolve!
|
||||
let fallback_str = wanted
|
||||
.iter_values()
|
||||
.map(|c| std::char::from_u32(c).unwrap_or(' '))
|
||||
.collect::<String>();
|
||||
|
||||
if self.config.warn_about_missing_glyphs {
|
||||
let url = "https://wezfurlong.org/wezterm/config/fonts.html";
|
||||
log::warn!(
|
||||
"No fonts contain glyphs for these codepoints: {}.\n\
|
||||
Placeholder 'Last Resort' glyphs are being displayed instead.\n\
|
||||
You may wish to install additional fonts, or adjust your\n\
|
||||
configuration so that it can find them.\n\
|
||||
{} has more information about configuring fonts.\n\
|
||||
Set warn_about_missing_glyphs=false to suppress this message.",
|
||||
fallback_str.escape_unicode(),
|
||||
url,
|
||||
);
|
||||
|
||||
ToastNotification {
|
||||
title: "Font problem".to_string(),
|
||||
message: format!(
|
||||
"No fonts contain glyphs for these codepoints: {}.\n\
|
||||
Placeholder glyphs are being displayed instead.\n\
|
||||
You may wish to install additional fonts, or adjust\n\
|
||||
your configuration so that it can find them.\n\
|
||||
Set warn_about_missing_glyphs=false to suppress this\n\
|
||||
message.",
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
url: Some(url.to_string()),
|
||||
timeout: Some(Duration::from_secs(15)),
|
||||
}
|
||||
.show();
|
||||
} else {
|
||||
log::debug!(
|
||||
"No fonts contain glyphs for these codepoints: {}",
|
||||
fallback_str.escape_unicode()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FontConfigInner {
|
||||
fonts: RefCell<HashMap<TextStyle, Rc<LoadedFont>>>,
|
||||
metrics: RefCell<Option<FontMetrics>>,
|
||||
@ -183,6 +328,7 @@ struct FontConfigInner {
|
||||
built_in: RefCell<Arc<FontDatabase>>,
|
||||
no_glyphs: RefCell<HashSet<char>>,
|
||||
title_font: RefCell<Option<Rc<LoadedFont>>>,
|
||||
fallback_channel: RefCell<Option<Sender<FallbackResolveInfo>>>,
|
||||
}
|
||||
|
||||
/// Matches and loads fonts for a given input style
|
||||
@ -206,6 +352,7 @@ impl FontConfigInner {
|
||||
font_dirs: RefCell::new(Arc::new(FontDatabase::with_font_dirs(&config)?)),
|
||||
built_in: RefCell::new(Arc::new(FontDatabase::with_built_in()?)),
|
||||
no_glyphs: RefCell::new(HashSet::new()),
|
||||
fallback_channel: RefCell::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
@ -236,136 +383,33 @@ impl FontConfigInner {
|
||||
return;
|
||||
}
|
||||
|
||||
let font_dirs = Arc::clone(&*self.font_dirs.borrow());
|
||||
let built_in = Arc::clone(&*self.built_in.borrow());
|
||||
let locator = Arc::clone(&self.locator);
|
||||
let pending = Arc::clone(pending);
|
||||
let config = self.config.borrow().clone();
|
||||
std::thread::spawn(move || {
|
||||
let fallback_str = no_glyphs.iter().collect::<String>();
|
||||
let mut extra_handles = vec![];
|
||||
let info = FallbackResolveInfo {
|
||||
completion: Box::new(completion),
|
||||
no_glyphs,
|
||||
pending: Arc::clone(pending),
|
||||
font_dirs: Arc::clone(&*self.font_dirs.borrow()),
|
||||
built_in: Arc::clone(&*self.built_in.borrow()),
|
||||
locator: Arc::clone(&self.locator),
|
||||
config: self.config.borrow().clone(),
|
||||
};
|
||||
|
||||
log::trace!(
|
||||
"Looking for {} in fallback fonts",
|
||||
fallback_str.escape_unicode()
|
||||
);
|
||||
let mut fallback = self.fallback_channel.borrow_mut();
|
||||
|
||||
match locator.locate_fallback_for_codepoints(&no_glyphs) {
|
||||
Ok(ref mut handles) => extra_handles.append(handles),
|
||||
Err(err) => log::error!(
|
||||
"Error: {:#} while resolving fallback for {} from font-locator",
|
||||
err,
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
}
|
||||
if fallback.is_none() {
|
||||
let (tx, rx) = channel::<FallbackResolveInfo>();
|
||||
|
||||
if config.search_font_dirs_for_fallback {
|
||||
match font_dirs.locate_fallback_for_codepoints(&no_glyphs) {
|
||||
Ok(ref mut handles) => extra_handles.append(handles),
|
||||
Err(err) => log::error!(
|
||||
"Error: {:#} while resolving fallback for {} from font_dirs",
|
||||
err,
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
std::thread::spawn(move || {
|
||||
for info in rx {
|
||||
info.process();
|
||||
}
|
||||
}
|
||||
|
||||
match built_in.locate_fallback_for_codepoints(&no_glyphs) {
|
||||
Ok(ref mut handles) => extra_handles.append(handles),
|
||||
Err(err) => log::error!(
|
||||
"Error: {:#} while resolving fallback for {} for built-in fonts",
|
||||
err,
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
}
|
||||
|
||||
let mut wanted = RangeSet::new();
|
||||
for c in no_glyphs {
|
||||
wanted.add(c as u32);
|
||||
}
|
||||
log::trace!(
|
||||
"Fallback fonts that match {} before sorting are: {:#?}",
|
||||
fallback_str.escape_unicode(),
|
||||
extra_handles
|
||||
);
|
||||
|
||||
if wanted.len() > 1 && config.sort_fallback_fonts_by_coverage {
|
||||
// Sort by ascending coverage
|
||||
extra_handles.sort_by_cached_key(|p| {
|
||||
p.coverage_intersection(&wanted)
|
||||
.map(|r| r.len())
|
||||
.unwrap_or(0)
|
||||
});
|
||||
// Re-arrange to descending coverage
|
||||
extra_handles.reverse();
|
||||
log::trace!(
|
||||
"Fallback fonts that match {} after sorting are: {:#?}",
|
||||
fallback_str.escape_unicode(),
|
||||
extra_handles
|
||||
);
|
||||
}
|
||||
|
||||
// iteratively reduce to just the fonts that we need
|
||||
extra_handles.retain(|p| match p.coverage_intersection(&wanted) {
|
||||
Ok(cov) if cov.is_empty() => false,
|
||||
Ok(cov) => {
|
||||
// Remove the matches from the set, so that we avoid
|
||||
// picking up multiple fonts for the same glyphs
|
||||
wanted = wanted.difference(&cov);
|
||||
true
|
||||
}
|
||||
Err(_) => false,
|
||||
});
|
||||
|
||||
if !extra_handles.is_empty() {
|
||||
let mut pending = pending.lock().unwrap();
|
||||
pending.append(&mut extra_handles);
|
||||
completion();
|
||||
}
|
||||
fallback.replace(tx);
|
||||
}
|
||||
|
||||
if !wanted.is_empty() {
|
||||
// There were some glyphs we couldn't resolve!
|
||||
let fallback_str = wanted
|
||||
.iter_values()
|
||||
.map(|c| std::char::from_u32(c).unwrap_or(' '))
|
||||
.collect::<String>();
|
||||
|
||||
if config.warn_about_missing_glyphs {
|
||||
let url = "https://wezfurlong.org/wezterm/config/fonts.html";
|
||||
log::warn!(
|
||||
"No fonts contain glyphs for these codepoints: {}.\n\
|
||||
Placeholder 'Last Resort' glyphs are being displayed instead.\n\
|
||||
You may wish to install additional fonts, or adjust your\n\
|
||||
configuration so that it can find them.\n\
|
||||
{} has more information about configuring fonts.\n\
|
||||
Set warn_about_missing_glyphs=false to suppress this message.",
|
||||
fallback_str.escape_unicode(),
|
||||
url,
|
||||
);
|
||||
|
||||
ToastNotification {
|
||||
title: "Font problem".to_string(),
|
||||
message: format!(
|
||||
"No fonts contain glyphs for these codepoints: {}.\n\
|
||||
Placeholder glyphs are being displayed instead.\n\
|
||||
You may wish to install additional fonts, or adjust\n\
|
||||
your configuration so that it can find them.\n\
|
||||
Set warn_about_missing_glyphs=false to suppress this\n\
|
||||
message.",
|
||||
fallback_str.escape_unicode()
|
||||
),
|
||||
url: Some(url.to_string()),
|
||||
timeout: Some(Duration::from_secs(15)),
|
||||
}
|
||||
.show();
|
||||
} else {
|
||||
log::debug!(
|
||||
"No fonts contain glyphs for these codepoints: {}",
|
||||
fallback_str.escape_unicode()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
if let Err(err) = fallback.as_mut().expect("channel to exist").send(info) {
|
||||
log::error!("Failed to schedule font fallback resolve: {:#}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn title_font(&self, myself: &Rc<Self>) -> anyhow::Result<Rc<LoadedFont>> {
|
||||
|
Loading…
Reference in New Issue
Block a user