1
1
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:
Wez Furlong 2021-09-06 10:48:00 -07:00
parent 41866a0b5b
commit 10b64abec8

View File

@ -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>> {