Move config reloading to separate thread

This feature was previously shoved into the renderer due to initial
proof of concept. Now, providing config updates to other systems is
possible. This will be especially important for key bindings!
This commit is contained in:
Joe Wilm 2016-10-27 10:05:04 -07:00
parent 0958c0f0f2
commit 06ea6c8e56
3 changed files with 111 additions and 62 deletions

View File

@ -7,11 +7,13 @@ use std::env;
use std::fs;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use std::sync::mpsc;
use ::Rgb;
use font::Size;
use serde_yaml;
use serde::{self, Error as SerdeError};
use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op};
/// Top-level config type
#[derive(Debug, Deserialize, Default)]
@ -232,7 +234,7 @@ impl Config {
///
/// 1. `$HOME/.config/alacritty.yml`
/// 2. `$HOME/.alacritty.yml`
pub fn load() -> Result<Config> {
pub fn load() -> Result<(Config, PathBuf)> {
let home = env::var("HOME")?;
// First path
@ -240,15 +242,17 @@ impl Config {
path.push(".config");
path.push("alacritty.yml");
// Fallback path
let mut alt_path = PathBuf::from(&home);
alt_path.push(".alacritty.yml");
match Config::load_from(&path) {
match Config::load_from(path) {
Ok(c) => Ok(c),
Err(e) => {
match e {
Error::NotFound => Config::load_from(&alt_path),
Error::NotFound => {
// Fallback path
let mut alt_path = PathBuf::from(&home);
alt_path.push(".alacritty.yml");
Config::load_from(alt_path)
},
_ => Err(e),
}
}
@ -315,9 +319,10 @@ impl Config {
self.render_timer
}
fn load_from<P: AsRef<Path>>(path: P) -> Result<Config> {
let raw = Config::read_file(path)?;
Ok(serde_yaml::from_str(&raw[..])?)
fn load_from<P: Into<PathBuf>>(path: P) -> Result<(Config, PathBuf)> {
let path = path.into();
let raw = Config::read_file(path.as_path())?;
Ok((serde_yaml::from_str(&raw[..])?, path))
}
fn read_file<P: AsRef<Path>>(path: P) -> Result<String> {
@ -525,3 +530,46 @@ impl Default for Font {
}
}
}
pub struct Watcher(::std::thread::JoinHandle<()>);
pub trait OnConfigReload {
fn on_config_reload(&mut self, Config);
}
impl Watcher {
pub fn new<H: OnConfigReload + Send + 'static>(path: PathBuf, mut handler: H) -> Watcher {
Watcher(::util::thread::spawn_named("config watcher", move || {
let (tx, rx) = mpsc::channel();
let mut watcher = FileWatcher::new(tx).unwrap();
watcher.watch(&path).expect("watch alacritty yml");
let config_path = path.as_path();
loop {
let event = rx.recv().expect("watcher event");
let ::notify::Event { path, op } = event;
if let Ok(op) = op {
if op.contains(op::RENAME) {
continue;
}
if op.contains(op::IGNORED) {
if let Some(path) = path.as_ref() {
if let Err(err) = watcher.watch(&path) {
err_println!("failed to establish watch on {:?}: {:?}", path, err);
}
if path == config_path {
if let Ok((config, _)) = Config::load() {
handler.on_config_reload(config);
};
}
}
}
}
}
}))
}
}

View File

@ -118,14 +118,14 @@ mod gl {
fn main() {
// Load configuration
let config = match Config::load() {
let (config, config_path) = match Config::load() {
Err(err) => match err {
// Use default config when not found
config::Error::NotFound => Config::default(),
config::Error::NotFound => (Config::default(), None),
// Exit when there's a problem with it
_ => die!("{}", err),
},
Ok(config) => config,
Ok((config, path)) => (config, Some(path)),
};
let font = config.font();
@ -225,11 +225,25 @@ fn main() {
tx
);
let (config_tx, config_rx) = mpsc::channel();
// create a config watcher when config is loaded from disk
let _config_reloader = config_path.map(|config_path| {
config::Watcher::new(config_path, ConfigHandler {
tx: config_tx,
window: window.create_window_proxy(),
})
});
// Main loop
loop {
// Wait for something to happen
processor.process_events(&window);
if let Ok(config) = config_rx.try_recv() {
display.update_config(&config);
}
// Maybe draw the terminal
let terminal = terminal.lock();
signal_flag.set(false);
@ -242,11 +256,31 @@ fn main() {
}
}
// FIXME need file watcher to work with custom delegates before
// joining config reloader is possible
// config_reloader.join().ok();
// shutdown
event_loop_handle.join().ok();
println!("Goodbye");
}
struct ConfigHandler {
tx: mpsc::Sender<config::Config>,
window: ::glutin::WindowProxy,
}
impl config::OnConfigReload for ConfigHandler {
fn on_config_reload(&mut self, config: Config) {
if let Err(..) = self.tx.send(config) {
err_println!("Failed to notify of new config");
return;
}
self.window.wakeup_event_loop();
}
}
struct Display {
window: Arc<glutin::Window>,
renderer: QuadRenderer,
@ -257,6 +291,10 @@ struct Display {
}
impl Display {
pub fn update_config(&mut self, config: &Config) {
self.renderer.update_config(config);
}
pub fn new(window: Arc<glutin::Window>,
renderer: QuadRenderer,
glyph_cache: GlyphCache,

View File

@ -42,37 +42,9 @@ pub trait LoadGlyph {
}
enum Msg {
ConfigReload(Config),
ShaderReload,
}
/// Colors!
///
/// FIXME this is obviously bad; need static for reload logic for now. Hacking something in with
/// minimal effort and will improve later.
///
/// Only renderer is allowed to access this to prevent race conditions
static mut COLORS: [Rgb; 18] = [
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
];
/// Text drawing program
///
/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
@ -278,6 +250,7 @@ pub struct RenderApi<'a> {
batch: &'a mut Batch,
atlas: &'a mut Vec<Atlas>,
program: &'a mut ShaderProgram,
colors: &'a [Rgb; 18],
}
#[derive(Debug)]
@ -500,7 +473,6 @@ impl QuadRenderer {
let mut watcher = Watcher::new(tx).unwrap();
watcher.watch(TEXT_SHADER_F_PATH).expect("watch fragment shader");
watcher.watch(TEXT_SHADER_V_PATH).expect("watch vertex shader");
watcher.watch("/home/jwilm/.alacritty.yml").expect("watch alacritty yml");
loop {
let event = rx.recv().expect("watcher event");
@ -517,15 +489,8 @@ impl QuadRenderer {
println!("failed to establish watch on {:?}: {:?}", path, err);
}
if path == ::std::path::Path::new("/home/jwilm/.alacritty.yml") {
if let Ok(config) = Config::load() {
msg_tx.send(Msg::ConfigReload(config))
.expect("msg send ok");
};
} else {
msg_tx.send(Msg::ShaderReload)
.expect("msg send ok");
}
msg_tx.send(Msg::ShaderReload)
.expect("msg send ok");
}
}
}
@ -545,27 +510,24 @@ impl QuadRenderer {
rx: msg_rx,
};
unsafe {
COLORS = config.color_list();
}
let atlas = Atlas::new(ATLAS_SIZE);
renderer.atlas.push(atlas);
renderer
}
pub fn update_config(&mut self, config: &Config) {
self.colors = config.color_list();
self.program.activate();
self.program.set_color_uniforms(&self.colors);
self.program.deactivate();
}
pub fn with_api<F, T>(&mut self, props: &term::SizeInfo, func: F) -> T
where F: FnOnce(RenderApi) -> T
{
while let Ok(msg) = self.rx.try_recv() {
match msg {
Msg::ConfigReload(config) => {
self.colors = config.color_list();
self.program.activate();
self.program.set_color_uniforms(&self.colors);
self.program.deactivate();
},
Msg::ShaderReload => {
self.reload_shaders(props.width as u32, props.height as u32);
}
@ -587,6 +549,7 @@ impl QuadRenderer {
batch: &mut self.batch,
atlas: &mut self.atlas,
program: &mut self.program,
colors: &self.colors,
});
unsafe {
@ -732,7 +695,7 @@ impl<'a> RenderApi<'a> {
glyph_cache: &mut GlyphCache
) {
// TODO should be built into renderer
let color = unsafe { COLORS[::ansi::Color::Background as usize] };
let color = self.colors[::ansi::Color::Background as usize];
unsafe {
gl::ClearColor(
color.r as f32 / 255.0,