bevy_asset/io/
gated.rs

1use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
2use bevy_utils::HashMap;
3use crossbeam_channel::{Receiver, Sender};
4use parking_lot::RwLock;
5use std::{path::Path, sync::Arc};
6
7/// A "gated" reader that will prevent asset reads from returning until
8/// a given path has been "opened" using [`GateOpener`].
9///
10/// This is built primarily for unit tests.
11pub struct GatedReader<R: AssetReader> {
12    reader: R,
13    gates: Arc<RwLock<HashMap<Box<Path>, (Sender<()>, Receiver<()>)>>>,
14}
15
16impl<R: AssetReader + Clone> Clone for GatedReader<R> {
17    fn clone(&self) -> Self {
18        Self {
19            reader: self.reader.clone(),
20            gates: self.gates.clone(),
21        }
22    }
23}
24
25/// Opens path "gates" for a [`GatedReader`].
26pub struct GateOpener {
27    gates: Arc<RwLock<HashMap<Box<Path>, (Sender<()>, Receiver<()>)>>>,
28}
29
30impl GateOpener {
31    /// Opens the `path` "gate", allowing a _single_ [`AssetReader`] operation to return for that path.
32    /// If multiple operations are expected, call `open` the expected number of calls.
33    pub fn open<P: AsRef<Path>>(&self, path: P) {
34        let mut gates = self.gates.write();
35        let gates = gates
36            .entry_ref(path.as_ref())
37            .or_insert_with(crossbeam_channel::unbounded);
38        gates.0.send(()).unwrap();
39    }
40}
41
42impl<R: AssetReader> GatedReader<R> {
43    /// Creates a new [`GatedReader`], which wraps the given `reader`. Also returns a [`GateOpener`] which
44    /// can be used to open "path gates" for this [`GatedReader`].
45    pub fn new(reader: R) -> (Self, GateOpener) {
46        let gates = Arc::new(RwLock::new(HashMap::new()));
47        (
48            Self {
49                reader,
50                gates: gates.clone(),
51            },
52            GateOpener { gates },
53        )
54    }
55}
56
57impl<R: AssetReader> AssetReader for GatedReader<R> {
58    async fn read<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
59        let receiver = {
60            let mut gates = self.gates.write();
61            let gates = gates
62                .entry_ref(path.as_ref())
63                .or_insert_with(crossbeam_channel::unbounded);
64            gates.1.clone()
65        };
66        receiver.recv().unwrap();
67        let result = self.reader.read(path).await?;
68        Ok(result)
69    }
70
71    async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Reader<'a>>, AssetReaderError> {
72        self.reader.read_meta(path).await
73    }
74
75    async fn read_directory<'a>(
76        &'a self,
77        path: &'a Path,
78    ) -> Result<Box<PathStream>, AssetReaderError> {
79        self.reader.read_directory(path).await
80    }
81
82    async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
83        self.reader.is_directory(path).await
84    }
85}