warmy/
load.rs

1//! Load and reload resources.
2//!
3//! This module exposes traits, types and functions you need to use to load and reload objects.
4
5use any_cache::{Cache, HashCache};
6use notify::{self, DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
7use std::collections::{HashMap, HashSet};
8use std::fmt::{self, Display};
9use std::ops::{Deref, DerefMut};
10use std::path::{Path, PathBuf};
11use std::sync::mpsc::{channel, Receiver};
12use std::time::Duration;
13
14use crate::key::{Key, PrivateKey};
15use crate::res::Res;
16
17/// Class of types that can be loaded and reloaded.
18///
19/// The first type variable, `C`, represents the context of the loading. This will be accessed via
20/// a mutable reference when loading and reloading.
21///
22/// The second type variable, `K`, is the type of key that can be used to index resources. Some
23/// special resource keys exist:
24///
25///   - [`SimpleKey`]: such a key indexes a resource that lives either on the filesystem or as a
26///     logical resource (in-memory, on-the-fly, etc.)
27///
28/// A key type must implement the [`Key`] trait in order to be usable.
29///
30/// The last type variable, `Method`, is a tag-only value that is useful to implement several
31/// algorithms to load the same type with different methods.
32///
33/// [`SimpleKey`]: crate::key::SimpleKey
34pub trait Load<C, K, Method = ()>: 'static + Sized
35where K: Key,
36      Method: ?Sized {
37  /// Type of error that might happen while loading.
38  type Error: Display + 'static;
39
40  /// Load a resource.
41  ///
42  /// The [`Storage`] can be used to load additional resource dependencies.
43  ///
44  /// The result type is used to register for dependency events. If you do not need any, you can
45  /// lift your return value in [`Loaded`] with `your_value.into()`.
46  fn load(
47    key: K,
48    storage: &mut Storage<C, K>,
49    ctx: &mut C,
50  ) -> Result<Loaded<Self, K>, Self::Error>;
51
52  // FIXME: add support for redeclaring the dependencies?
53  /// Function called when a resource must be reloaded.
54  ///
55  /// The default implementation of that function calls [`Load::load`] and returns its result.
56  fn reload(
57    &self,
58    key: K,
59    storage: &mut Storage<C, K>,
60    ctx: &mut C,
61  ) -> Result<Self, Self::Error> {
62    Self::load(key, storage, ctx).map(|lr| lr.res)
63  }
64}
65
66/// Result of a resource loading.
67///
68/// This type enables you to register a resource for reloading events of other resources. Those are
69/// named dependencies. If you don’t need to run specific code on a dependency reloading, use
70/// the `.into()` function to lift your return value to [`Loaded`] or use the provided
71/// [`Loaded::without_dep`] function.
72pub struct Loaded<T, K> {
73  /// The loaded object.
74  pub res: T,
75  /// The list of dependencies to listen for events.
76  pub deps: Vec<K>,
77}
78
79impl<T, K> Loaded<T, K> {
80  /// Return a resource declaring no dependency at all.
81  pub fn without_dep(res: T) -> Self {
82    Loaded {
83      res,
84      deps: Vec::new(),
85    }
86  }
87
88  /// Return a resource along with its dependencies.
89  pub fn with_deps(res: T, deps: Vec<K>) -> Self {
90    Loaded { res, deps }
91  }
92}
93
94impl<T, K> From<T> for Loaded<T, K> {
95  fn from(res: T) -> Self {
96    Loaded::without_dep(res)
97  }
98}
99
100/// Metadata about a resource.
101struct ResMetaData<C, K> {
102  /// Function to call each time the resource must be reloaded.
103  on_reload: Box<dyn Fn(&mut Storage<C, K>, &mut C) -> Result<(), Box<dyn Display>>>,
104}
105
106impl<C, K> ResMetaData<C, K> {
107  fn new<F>(f: F) -> Self
108  where F: 'static + Fn(&mut Storage<C, K>, &mut C) -> Result<(), Box<dyn Display>> {
109    ResMetaData {
110      on_reload: Box::new(f),
111    }
112  }
113}
114
115/// Resource storage.
116///
117/// This type is responsible for storing resources, giving functions to look them up and update
118/// them whenever needed.
119pub struct Storage<C, K> {
120  // canonicalized root path (used for resources loaded from the file system)
121  canon_root: PathBuf,
122  // resource cache, containing all living resources
123  cache: HashCache,
124  // dependencies, mapping a dependency to its dependent resources
125  deps: HashMap<K, Vec<K>>,
126  // contains all metadata on resources (reload functions)
127  metadata: HashMap<K, ResMetaData<C, K>>,
128}
129
130impl<C, K> Storage<C, K> where K: Key {
131  fn new(canon_root: PathBuf) -> Self{
132    Storage {
133      canon_root,
134      cache: HashCache::new(),
135      deps: HashMap::new(),
136      metadata: HashMap::new(),
137    }
138  }
139
140  /// The canonicalized root the [`Storage`] is configured with.
141  pub fn root(&self) -> &Path {
142    &self.canon_root
143  }
144
145  /// Inject a new resource in the store.
146  ///
147  /// The resource might be refused for several reasons. Further information in the documentation of
148  /// the [`StoreError`] error type.
149  fn inject<T, M>(
150    &mut self,
151    key: K,
152    resource: T,
153    deps: Vec<K>,
154  ) -> Result<Res<T>, StoreError<K>>
155  where T: Load<C, K, M> {
156    // we forbid having two resources sharing the same key
157    if self.metadata.contains_key(&key) {
158      return Err(StoreError::AlreadyRegisteredKey(key.clone()));
159    }
160
161    // wrap the resource to make it shared mutably
162    let res = Res::new(resource);
163
164    // create the metadata for the resource
165    let res_ = res.clone();
166    let key_ = key.clone();
167    let metadata = ResMetaData::new(move |storage, ctx| {
168      let reloaded = <T as Load<C, K, M>>::reload(&res_.borrow(), key_.clone(), storage, ctx);
169
170      match reloaded {
171        Ok(r) => {
172          // replace the current resource with the freshly loaded one
173          *res_.borrow_mut() = r;
174          Ok(())
175        }
176        Err(e) => Err(Box::new(e)),
177      }
178    });
179
180    self.metadata.insert(key.clone(), metadata);
181
182    // register the resource as an observer of its dependencies in the dependencies graph
183    let root = &self.canon_root;
184    for dep in deps {
185      self
186        .deps
187        .entry(dep.clone().prepare_key(root))
188        .or_insert(Vec::new())
189        .push(key.clone());
190    }
191
192    // wrap the key in our private key so that we can use it in the cache
193    let pkey = PrivateKey::new(key);
194
195    // cache the resource
196    self.cache.save(pkey, res.clone());
197
198    Ok(res)
199  }
200
201  /// Get a resource from the [`Storage`] and return an error if its loading failed.
202  ///
203  /// This function uses the default loading method.
204  pub fn get<T>(&mut self, key: &K, ctx: &mut C) -> Result<Res<T>, StoreErrorOr<T, C, K>>
205  where T: Load<C, K> {
206    self.get_by(key, ctx, ())
207  }
208
209  /// Get a resource from the [`Storage`] by using a specific method and return and error if its
210  /// loading failed.
211  pub fn get_by<T, M>(
212    &mut self,
213    key: &K,
214    ctx: &mut C,
215    _: M,
216  ) -> Result<Res<T>, StoreErrorOr<T, C, K, M>>
217  where T: Load<C, K, M> {
218    let key = key.clone().prepare_key(self.root());
219
220    // move the key into pkey to prevent an allocation and remove it after use
221    let pkey = PrivateKey::<K, T>::new(key);
222    let x: Option<Res<T>> = self.cache.get(&pkey).cloned();
223    let key = pkey.0;
224
225    match x {
226      Some(resource) => Ok(resource),
227      None => {
228        let loaded =
229          <T as Load<C, K, M>>::load(key.clone(), self, ctx).map_err(StoreErrorOr::ResError)?;
230        self
231          .inject::<T, M>(key, loaded.res, loaded.deps)
232          .map_err(StoreErrorOr::StoreError)
233      }
234    }
235  }
236
237  /// Get a resource from the [`Storage`] for the given key. If it fails, a proxied version is used,
238  /// which will get replaced by the resource once it’s available and reloaded.
239  ///
240  /// This function uses the default loading method.
241  pub fn get_proxied<T, P>(
242    &mut self,
243    key: &K,
244    proxy: P,
245    ctx: &mut C,
246  ) -> Result<Res<T>, StoreError<K>>
247  where T: Load<C, K>,
248        P: FnOnce() -> T {
249    self
250      .get(key, ctx)
251      .or_else(|_| self.inject::<T, ()>(key.clone().into(), proxy(), Vec::new()))
252  }
253
254  /// Get a resource from the [`Storage`] for the given key by using a specific method. If it fails, a
255  /// proxied version is used, which will get replaced by the resource once it’s available and
256  /// reloaded.
257  pub fn get_proxied_by<T, M, P>(
258    &mut self,
259    key: &K,
260    proxy: P,
261    ctx: &mut C,
262    method: M,
263  ) -> Result<Res<T>, StoreError<K>>
264  where T: Load<C, K, M>,
265        P: FnOnce() -> T {
266    self
267      .get_by(key, ctx, method)
268      .or_else(|_| self.inject::<T, M>(key.clone().into(), proxy(), Vec::new()))
269  }
270}
271
272/// Error that might happen when handling a resource store around.
273#[derive(Clone, Debug, Eq, PartialEq)]
274pub enum StoreError<K> {
275  /// The root path for a filesystem resource was not found.
276  RootDoesNotExist(PathBuf),
277  /// The key associated with a resource already exists in the [`Store`].
278  ///
279  /// > Note: it is not currently possible to have two resources living in a [`Store`] and using an
280  /// > identical key at the same time.
281  AlreadyRegisteredKey(K),
282}
283
284impl<K> Display for StoreError<K> where K: Display {
285  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
286    match *self {
287      StoreError::RootDoesNotExist(ref path) => write!(f, "root {} doesn’t exist", path.display()),
288      StoreError::AlreadyRegisteredKey(ref dk) => write!(f, "already registered key: {}", dk),
289    }
290  }
291}
292
293/// Either a store error or a resource loading error.
294pub enum StoreErrorOr<T, C, K, M = ()> where T: Load<C, K, M>, K: Key {
295  /// A store error.
296  StoreError(StoreError<K>),
297  /// A resource error.
298  ResError(T::Error),
299}
300
301impl<T, C, K, M> Clone for StoreErrorOr<T, C, K, M>
302where T: Load<C, K, M>,
303      T::Error: Clone,
304      K: Key {
305  fn clone(&self) -> Self {
306    match *self {
307      StoreErrorOr::StoreError(ref e) => StoreErrorOr::StoreError(e.clone()),
308      StoreErrorOr::ResError(ref e) => StoreErrorOr::ResError(e.clone()),
309    }
310  }
311}
312
313impl<T, C, K, M> Eq for StoreErrorOr<T, C, K, M>
314where T: Load<C, K, M>,
315      T::Error: Eq,
316      K: Key {
317}
318
319impl<T, C, K, M> PartialEq for StoreErrorOr<T, C, K, M>
320where T: Load<C, K, M>,
321      T::Error: PartialEq,
322      K: Key {
323  fn eq(&self, rhs: &Self) -> bool {
324    match (self, rhs) {
325      (&StoreErrorOr::StoreError(ref a), &StoreErrorOr::StoreError(ref b)) => a == b,
326      (&StoreErrorOr::ResError(ref a), &StoreErrorOr::ResError(ref b)) => a == b,
327      _ => false,
328    }
329  }
330}
331
332impl<T, C, K, M> fmt::Debug for StoreErrorOr<T, C, K, M>
333where T: Load<C, K, M>,
334      T::Error: fmt::Debug,
335      K: Key + fmt::Debug {
336  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
337    match *self {
338      StoreErrorOr::StoreError(ref e) => f.debug_tuple("StoreError").field(e).finish(),
339      StoreErrorOr::ResError(ref e) => f.debug_tuple("ResError").field(e).finish(),
340    }
341  }
342}
343
344impl<T, C, K, M> Display for StoreErrorOr<T, C, K, M>
345where T: Load<C, K, M>,
346      T::Error: fmt::Debug,
347      K: Key + Display {
348  fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
349    match *self {
350      StoreErrorOr::StoreError(ref e) => e.fmt(f),
351      StoreErrorOr::ResError(ref e) => e.fmt(f),
352    }
353  }
354}
355
356/// Resource synchronizer.
357///
358/// An object of this type is responsible to synchronize resources living in a store. It keeps in
359/// internal, optimized state to perform correct and efficient synchronization.
360struct Synchronizer<C, K> {
361  // all the resources that must be reloaded; they’re mapped to the instant they were found updated
362  dirties: HashSet<K>,
363  // keep the watcher around so that we don’t have it disconnected
364  #[allow(dead_code)]
365  watcher: RecommendedWatcher,
366  // watcher receiver part of the channel
367  watcher_rx: Receiver<DebouncedEvent>,
368  // used to accept or ignore new discoveries
369  discovery: Discovery<C, K>
370}
371
372impl<C, K> Synchronizer<C, K> where K: Key {
373  fn new(
374    watcher: RecommendedWatcher,
375    watcher_rx: Receiver<DebouncedEvent>,
376    discovery: Discovery<C, K>
377  ) -> Self {
378    Synchronizer {
379      dirties: HashSet::new(),
380      watcher,
381      watcher_rx,
382      discovery
383    }
384  }
385
386  /// Dequeue any file system events.
387  fn dequeue_fs_events(&mut self, storage: &mut Storage<C, K>, ctx: &mut C) where K: for<'a> From<&'a Path> {
388    for event in self.watcher_rx.try_iter() {
389      match event {
390        DebouncedEvent::Write(ref path) | DebouncedEvent::Create(ref path) => {
391          let key = path.as_path().into();
392
393          if storage.metadata.contains_key(&key) {
394            self.dirties.insert(key);
395          } else {
396            self.discovery.discover(path, storage, ctx);
397          }
398        }
399
400        _ => (),
401      }
402    }
403  }
404
405  /// Reload any dirty resource that fulfill its time predicate.
406  fn reload_dirties(&mut self, storage: &mut Storage<C, K>, ctx: &mut C) {
407    self.dirties.retain(|dep_key| {
408      if let Some(metadata) = storage.metadata.remove(&dep_key) {
409        if (metadata.on_reload)(storage, ctx).is_ok() {
410          // if we have successfully reloaded the resource, notify the observers that this
411          // dependency has changed
412          if let Some(deps) = storage.deps.get(&dep_key).cloned() {
413            for dep in deps {
414              if let Some(obs_metadata) = storage.metadata.remove(&dep) {
415                // FIXME: decide what to do with the result (error?)
416                let _ = (obs_metadata.on_reload)(storage, ctx);
417
418                // reinject the dependency once afterwards
419                storage.metadata.insert(dep, obs_metadata);
420              }
421            }
422          }
423        }
424
425        storage.metadata.insert(dep_key.clone(), metadata);
426      }
427
428      false
429    });
430  }
431
432  /// Synchronize the [`Storage`] by updating the resources that ought to.
433  fn sync(&mut self, storage: &mut Storage<C, K>, ctx: &mut C) where K: for<'a> From<&'a Path> {
434    self.dequeue_fs_events(storage, ctx);
435    self.reload_dirties(storage, ctx);
436  }
437}
438
439/// Resource store. Responsible for holding and presenting resources.
440pub struct Store<C, K> {
441  storage: Storage<C, K>,
442  synchronizer: Synchronizer<C, K>,
443}
444
445impl<C, K> Store<C, K> where K: Key {
446  /// Create a new store.
447  ///
448  /// # Failures
449  ///
450  /// This function will fail if the root path in the [`StoreOpt`] doesn’t resolve to a correct
451  /// canonicalized path.
452  pub fn new(opt: StoreOpt<C, K>) -> Result<Self, StoreError<K>> {
453    // canonicalize the root because some platforms won’t correctly report file changes otherwise
454    let root = &opt.root;
455    let canon_root = root
456      .canonicalize()
457      .map_err(|_| StoreError::RootDoesNotExist(root.to_owned()))?;
458
459    // create the mpsc channel to communicate with the file watcher
460    let (wsx, wrx) = channel();
461    let mut watcher = notify::watcher(wsx, opt.debounce_duration).unwrap();
462
463    // spawn a new thread in which we look for events
464    let _ = watcher.watch(&canon_root, RecursiveMode::Recursive);
465
466    // create the storage
467    let storage = Storage::new(canon_root);
468
469    // create the synchronizer
470    let synchronizer = Synchronizer::new(watcher, wrx, opt.discovery);
471
472    let store = Store {
473      storage,
474      synchronizer,
475    };
476
477    Ok(store)
478  }
479
480  /// Synchronize the [`Store`] by updating the resources that ought to with a provided context.
481  pub fn sync(&mut self, ctx: &mut C) where K: for<'a> From<&'a Path> {
482    self.synchronizer.sync(&mut self.storage, ctx);
483  }
484}
485
486impl<C, K> Deref for Store<C, K> {
487  type Target = Storage<C, K>;
488
489  fn deref(&self) -> &Self::Target {
490    &self.storage
491  }
492}
493
494impl<C, K> DerefMut for Store<C, K> {
495  fn deref_mut(&mut self) -> &mut Self::Target {
496    &mut self.storage
497  }
498}
499
500/// Various options to customize a [`Store`].
501///
502/// Feel free to inspect all of its declared methods for further information.
503pub struct StoreOpt<C, K> {
504  root: PathBuf,
505  debounce_duration: Duration,
506  discovery: Discovery<C, K>
507}
508
509impl<C, K> Default for StoreOpt<C, K> {
510  fn default() -> Self {
511    StoreOpt {
512      root: PathBuf::from("."),
513      debounce_duration: Duration::from_millis(50),
514      discovery: Discovery::default()
515    }
516  }
517}
518
519impl<C, K> StoreOpt<C, K> {
520  /// Change the debounce duration used to determine whether a resource should be
521  /// reloaded or not.
522  ///
523  /// A [`Store`] will wait that amount of time before deciding an resource should be reloaded after
524  /// it has changed on the filesystem. That is required in order to cope with write streaming, that
525  /// generates a lot of write event.
526  ///
527  /// # Default
528  ///
529  /// Defaults to `50` milliseconds.
530  #[inline]
531  pub fn set_debounce_duration(self, duration: Duration) -> Self {
532    StoreOpt {
533      debounce_duration: duration,
534      ..self
535    }
536  }
537
538  /// Get the debounce duration.
539  #[inline]
540  pub fn debounce_duration(&self) -> Duration {
541    self.debounce_duration
542  }
543
544  /// Change the root directory from which the [`Store`] will be watching file changes.
545  ///
546  /// # Default
547  ///
548  /// Defaults to `"."`.
549  #[inline]
550  pub fn set_root<P>(self, root: P) -> Self
551  where P: AsRef<Path> {
552    StoreOpt {
553      root: root.as_ref().to_owned(),
554      ..self
555    }
556  }
557
558  /// Get root directory.
559  #[inline]
560  pub fn root(&self) -> &Path {
561    &self.root
562  }
563
564  /// Change the discovery mechanism.
565  ///
566  /// # Default
567  ///
568  /// Defaults to `Discovery::default()`.
569  #[inline]
570  pub fn set_discovery(self, discovery: Discovery<C, K>) -> Self {
571    StoreOpt {
572      discovery,
573      ..self
574    }
575  }
576
577  /// Get the discovery mechanism.
578  #[inline]
579  pub fn discovery(&self) -> &Discovery<C, K> {
580    &self.discovery
581  }
582}
583
584/// Discovery.
585///
586/// Such an object is called whenever a new resource is discovered and is relied on to decide what
587/// to do with the resource.
588///
589/// If you don’t care about discovering new resources, feel free to use the [`Default`] implementation.
590pub struct Discovery<C, K> {
591  closure: Box<dyn FnMut(&Path, &mut Storage<C, K>, &mut C)>,
592}
593
594impl<C, K> Discovery<C, K> {
595  /// Create an new filter.
596  ///
597  /// The closure is passed the path of the discovered resource along with the storage and the
598  /// context so that you can [`get`] that resource if you want. Keep in mind that the path is a raw
599  /// and absolute path: you’ll have to extract the key (according to the type of resource you
600  /// target) and pattern-match the extension / mime type on your own to choose which type of
601  /// resource you want to get. Or you’ll just go full one-way and use the same resource type for
602  /// all discovery, that’s also possible.
603  ///
604  /// [`get`]: crate::load::Storage::get
605  pub fn new<F>(f: F) -> Self where F: 'static + FnMut(&Path, &mut Storage<C, K>, &mut C) {
606    Discovery {
607      closure: Box::new(f)
608    }
609  }
610
611  /// Filter a discovery.
612  fn discover(&mut self, path: &Path, storage: &mut Storage<C, K>, ctx: &mut C) {
613    (self.closure)(path, storage, ctx)
614  }
615}
616
617/// The default filter.
618///
619///   - Ignores any discovery.
620impl<C, K> Default for Discovery<C, K> {
621  fn default() -> Self {
622    Discovery::new(|_, _, _| {})
623  }
624}