From a9ea5669915f4ac6b0d9e7d826d1ee341a2e3c80 Mon Sep 17 00:00:00 2001 From: Jonas Maier <> Date: Sat, 23 May 2026 22:45:12 +0200 Subject: allow registration of variable watchers --- src/run/var.rs | 127 ---------------------------------- src/run/var/mod.rs | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/run/var/watch.rs | 61 +++++++++++++++++ 3 files changed, 249 insertions(+), 127 deletions(-) delete mode 100644 src/run/var.rs create mode 100644 src/run/var/mod.rs create mode 100644 src/run/var/watch.rs (limited to 'src') diff --git a/src/run/var.rs b/src/run/var.rs deleted file mode 100644 index 8e22f5c..0000000 --- a/src/run/var.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; -use std::os::unix::ffi::OsStrExt; -use std::sync::LazyLock; -use std::time::Instant; - -use crate::BString; -use crate::bstr; - -pub struct Vars { - simple: HashMap, - magic: HashMap BString>, - all: HashSet, - export: HashSet, -} - -impl Vars { - fn new( - mut simple: HashMap, - magic: HashMap BString>, - ) -> Self { - for (var, val) in std::env::vars_os() { - // explicitly added variables get priority. - if !simple.contains_key(var.as_bytes()) { - simple.insert(var.into_encoded_bytes(), val.into_encoded_bytes()); - } - } - let all = simple - .keys() - .cloned() - .chain(magic.keys().cloned()) - .collect(); - let export = std::env::vars_os() - .map(|x| x.0.into_encoded_bytes()) - .collect(); - Self { - simple, - magic, - all, - export, - } - } - - pub fn set(&mut self, var: BString, val: BString) { - self.simple.insert(var.clone(), val); - self.all.insert(var); - } - - pub fn lookup<'a>(&'a self, var: &bstr) -> Option> { - if let Some(fun) = self.magic.get(var) { - return Some(Cow::Owned(fun())); - } - - if let Some(val) = self.simple.get(var) { - return Some(Cow::Borrowed(val)); - } - - None - } - - pub fn allow_export(&mut self, var: &bstr, allow: bool) { - if allow { - self.export.insert(var.to_vec()); - } else { - self.export.remove(var); - } - } - - pub fn export<'a>(&'a self) -> HashMap<&'a bstr, Cow<'a, bstr>> { - self.export - .iter() - .filter_map(|var| { - let val = self.lookup(var)?; - Some((var.as_ref(), val)) - }) - .collect() - } - - pub fn vars(&self) -> &HashSet { - &self.all - } -} - -macro_rules! map { - ($($key:expr => $val:expr),* $(,)?) => {{ - let mut map = HashMap::::new(); - $(map.insert($key.into(), $val);)* - map - }}; -} - -impl Default for Vars { - fn default() -> Self { - use crate::basedir::xdg; - - let simple = map! { - b"PISH_VERSION" => crate::consts::PISH_VERSION.as_bytes().to_vec(), - b"PISH_COMMIT" => crate::consts::PISH_COMMIT.as_bytes().to_vec(), - b"PISH_DIRTY" => vec![crate::consts::PISH_DIRTY as u8 + b'0'], - xdg::DATA_HOME => xdg::data_home().as_os_str().as_bytes().to_vec(), - xdg::STATE_HOME => xdg::state_home().as_os_str().as_bytes().to_vec(), - xdg::CONFIG_HOME => xdg::config_home().as_os_str().as_bytes().to_vec(), - }; - - let magic = map! { - b"CWD_PRETTY" => crate::pretty_cwd as _, - b"SECONDS" => seconds_since_startup as _, - }; - - // call it once such that lazylock gets initialized - seconds_since_startup(); - - let mut this = Self::new(simple.clone(), magic); - for var in simple.keys() { - if var.starts_with(b"XDG_") { - this.allow_export(var, true); - } - } - this - } -} - -static START: LazyLock = LazyLock::new(Instant::now); - -fn seconds_since_startup() -> BString { - format!("{}", START.elapsed().as_secs_f64() as u64).into_bytes() -} diff --git a/src/run/var/mod.rs b/src/run/var/mod.rs new file mode 100644 index 0000000..1ccc195 --- /dev/null +++ b/src/run/var/mod.rs @@ -0,0 +1,188 @@ +use std::borrow::Cow; +use std::collections::{HashMap, HashSet}; +use std::os::unix::ffi::OsStrExt; +use std::sync::LazyLock; +use std::time::Instant; + +use crate::BString; +use crate::bstr; + +mod watch; +pub use watch::WatchId; +use watch::{WatchState, WeakWatchId}; + +pub struct Vars { + simple: HashMap, + magic: HashMap BString>, + all: HashSet, + export: HashSet, + watches: HashMap, + watched: HashMap>, +} + +impl Vars { + fn new( + mut simple: HashMap, + magic: HashMap BString>, + ) -> Self { + for (var, val) in std::env::vars_os() { + // explicitly added variables get priority. + if !simple.contains_key(var.as_bytes()) { + simple.insert(var.into_encoded_bytes(), val.into_encoded_bytes()); + } + } + let all = simple + .keys() + .cloned() + .chain(magic.keys().cloned()) + .collect(); + let export = std::env::vars_os() + .map(|x| x.0.into_encoded_bytes()) + .collect(); + Self { + simple, + magic, + all, + export, + watches: HashMap::new(), + watched: HashMap::new(), + } + } + + pub fn watch(&mut self, vars: Vec) -> WatchId { + let watch = WatchId::new(); + let special = vars.iter().any(|v| self.magic.contains_key(v)); + self.watches.insert( + watch.weak(), + WatchState { + special, + dirty: true, + }, + ); + for var in vars { + self.watched + .entry(var) + .or_insert_with(Vec::new) + .push(watch.weak()); + } + watch + } + + pub fn peek_dirty(&self, watch_set: &WatchId) -> bool { + if let Some(w) = self.watches.get(&watch_set.weak()) { + w.dirty || w.special + } else { + false + } + } + + pub fn pop_dirty(&mut self, watch_set: &WatchId) -> bool { + if let Some(w) = self.watches.get_mut(&watch_set.weak()) { + let dirty = w.dirty || w.special; + w.dirty = false; + dirty + } else { + false + } + } + + pub fn set(&mut self, var: BString, val: BString) { + if let Some(watchers) = self.watched.get(&var) { + let mut dead = false; + for watcher in watchers { + if watcher.is_dead() { + dead = true; + self.watches.remove(watcher); + } else { + self.watches.get_mut(watcher).unwrap().dirty = true; + } + } + + if dead { + self.watched.get_mut(&var).unwrap().retain(|w| !w.is_dead()); + } + } + + self.simple.insert(var.clone(), val); + self.all.insert(var); + } + + pub fn lookup<'a>(&'a self, var: &bstr) -> Option> { + if let Some(fun) = self.magic.get(var) { + return Some(Cow::Owned(fun())); + } + + if let Some(val) = self.simple.get(var) { + return Some(Cow::Borrowed(val)); + } + + None + } + + pub fn allow_export(&mut self, var: &bstr, allow: bool) { + if allow { + self.export.insert(var.to_vec()); + } else { + self.export.remove(var); + } + } + + pub fn export<'a>(&'a self) -> HashMap<&'a bstr, Cow<'a, bstr>> { + self.export + .iter() + .filter_map(|var| { + let val = self.lookup(var)?; + Some((var.as_ref(), val)) + }) + .collect() + } + + pub fn vars(&self) -> &HashSet { + &self.all + } +} + +macro_rules! map { + ($($key:expr => $val:expr),* $(,)?) => {{ + let mut map = HashMap::::new(); + $(map.insert($key.into(), $val);)* + map + }}; +} + +impl Default for Vars { + fn default() -> Self { + use crate::basedir::xdg; + + let simple = map! { + b"PISH_VERSION" => crate::consts::PISH_VERSION.as_bytes().to_vec(), + b"PISH_COMMIT" => crate::consts::PISH_COMMIT.as_bytes().to_vec(), + b"PISH_DIRTY" => vec![crate::consts::PISH_DIRTY as u8 + b'0'], + xdg::DATA_HOME => xdg::data_home().as_os_str().as_bytes().to_vec(), + xdg::STATE_HOME => xdg::state_home().as_os_str().as_bytes().to_vec(), + xdg::CONFIG_HOME => xdg::config_home().as_os_str().as_bytes().to_vec(), + }; + + let magic = map! { + b"CWD_PRETTY" => crate::pretty_cwd as _, + b"SECONDS" => seconds_since_startup as _, + }; + + // call it once such that lazylock gets initialized + seconds_since_startup(); + + let mut this = Self::new(simple.clone(), magic); + for var in simple.keys() { + if var.starts_with(b"XDG_") { + this.allow_export(var, true); + } + } + this + } +} + +static START: LazyLock = LazyLock::new(Instant::now); + +fn seconds_since_startup() -> BString { + format!("{}", START.elapsed().as_secs_f64() as u64).into_bytes() +} diff --git a/src/run/var/watch.rs b/src/run/var/watch.rs new file mode 100644 index 0000000..22ac638 --- /dev/null +++ b/src/run/var/watch.rs @@ -0,0 +1,61 @@ +use std::{ + hash::Hash, + sync::{Arc, Weak, atomic::AtomicU32}, +}; + +#[derive(Clone)] +pub struct WatchId { + id: u32, + count: Arc<()>, +} + +#[derive(Clone)] +pub struct WeakWatchId { + id: u32, + count: Weak<()>, +} + +impl PartialEq for WeakWatchId { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for WeakWatchId {} + +impl Hash for WeakWatchId { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl WatchId { + pub(super) fn new() -> Self { + static GEN: AtomicU32 = AtomicU32::new(0); + Self { + id: GEN.fetch_add(1, std::sync::atomic::Ordering::SeqCst), + count: Arc::new(()), + } + } + + pub(super) fn weak(&self) -> WeakWatchId { + WeakWatchId { + id: self.id, + count: Arc::downgrade(&self.count), + } + } +} + +impl WeakWatchId { + pub fn is_dead(&self) -> bool { + self.count.upgrade().is_none() + } +} + +pub struct WatchState { + /// if set, essentially always dirty + pub special: bool, + + /// has the set of watched variables changed? + pub dirty: bool, +} -- cgit v1.2.3