game_toolkit_assets/
lib.rs1#![forbid(unsafe_code)]
11
12use std::collections::HashSet;
13use std::path::{Path, PathBuf};
14use std::sync::mpsc::{Receiver, channel};
15
16use anyhow::{Context, Result};
17use notify::{Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
18
19pub struct Assets {
20 root: PathBuf,
21 watcher: Option<RecommendedWatcher>,
22 rx: Option<Receiver<PathBuf>>,
23 pending: HashSet<PathBuf>,
24}
25
26impl Assets {
27 pub fn new(root: impl AsRef<Path>) -> Result<Self> {
30 let root = root.as_ref();
31 let canon = root
32 .canonicalize()
33 .with_context(|| format!("asset root {} not found", root.display()))?;
34 Ok(Self {
35 root: canon,
36 watcher: None,
37 rx: None,
38 pending: HashSet::new(),
39 })
40 }
41
42 pub fn root(&self) -> &Path {
43 &self.root
44 }
45
46 pub fn resolve(&self, rel: impl AsRef<Path>) -> PathBuf {
47 let rel = rel.as_ref();
48 if rel.is_absolute() {
49 rel.to_path_buf()
50 } else {
51 self.root.join(rel)
52 }
53 }
54
55 pub fn read(&self, rel: impl AsRef<Path>) -> Result<Vec<u8>> {
56 let p = self.resolve(rel.as_ref());
57 std::fs::read(&p).with_context(|| format!("read {}", p.display()))
58 }
59
60 pub fn watch(&mut self) -> Result<()> {
62 if self.watcher.is_some() {
63 return Ok(());
64 }
65 let (tx, rx) = channel::<PathBuf>();
66 let mut watcher =
67 notify::recommended_watcher(move |res: notify::Result<Event>| match res {
68 Ok(event) => {
69 if matches!(
70 event.kind,
71 EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)
72 ) {
73 for p in event.paths {
74 let _ = tx.send(p);
75 }
76 }
77 }
78 Err(e) => log::warn!("notify error: {e}"),
79 })?;
80 watcher.watch(&self.root, RecursiveMode::Recursive)?;
81 self.watcher = Some(watcher);
82 self.rx = Some(rx);
83 Ok(())
84 }
85
86 pub fn drain_changes(&mut self) -> Vec<PathBuf> {
89 if let Some(rx) = self.rx.as_ref() {
90 while let Ok(p) = rx.try_recv() {
91 let canon = p.canonicalize().unwrap_or(p);
92 self.pending.insert(canon);
93 }
94 }
95 self.pending.drain().collect()
96 }
97}