bevy_asset/io/
gated.rs

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