use std::{
path::Path,
sync::{Mutex, OnceLock},
time::Duration,
};
use hashbrown::HashSet;
use notify_debouncer_mini::{
DebounceEventResult, DebouncedEventKind, Debouncer,
notify::{RecommendedWatcher, RecursiveMode},
};
use super::Id;
use crate::context::ContextInner;
const DEBOUNCE_DELAY: Duration = Duration::from_millis(100);
#[inline]
pub(crate) fn handle_changed_asset_files(ctx: &mut ContextInner) {
global_assets_updated()
.lock()
.unwrap()
.drain()
.for_each(|changed_asset| {
ctx.remove(&changed_asset);
});
}
#[must_use]
pub(crate) fn watch_assets_folder(assets_dir: impl AsRef<Path>) -> Debouncer<RecommendedWatcher> {
fn inner(assets_dir: &Path) -> Debouncer<RecommendedWatcher> {
let mut debouncer = {
let assets_dir = assets_dir.to_owned();
notify_debouncer_mini::new_debouncer(DEBOUNCE_DELAY, move |res: DebounceEventResult| {
match res {
Ok(events) => {
for event in events {
if event.kind != DebouncedEventKind::AnyContinuous {
continue;
}
let Some(id) = path_to_id(&event.path, &assets_dir) else {
eprintln!(
"Error converting changed file asset path {} to ID",
event.path.display()
);
continue;
};
global_assets_updated().lock().unwrap().insert(id);
}
}
Err(err) => eprintln!("Error while watching assets folder: {err}"),
}
})
.unwrap()
};
debouncer
.watcher()
.watch(assets_dir, RecursiveMode::Recursive)
.unwrap();
debouncer
}
inner(assets_dir.as_ref())
}
#[inline]
fn path_to_id(path: &Path, assets_dir: &Path) -> Option<Id> {
let extension = path.extension()?.to_string_lossy();
let relative_path = path.strip_prefix(assets_dir).ok()?;
let id = relative_path
.iter()
.map(|path| path.to_string_lossy())
.collect::<Vec<_>>()
.join(".");
let id = id.strip_suffix(&format!(".{extension}"))?;
Some(Id::new(id))
}
#[inline]
#[must_use]
fn global_assets_updated() -> &'static Mutex<HashSet<Id>> {
static MAP: OnceLock<Mutex<HashSet<Id>>> = OnceLock::new();
MAP.get_or_init(|| Mutex::new(HashSet::new()))
}