veloren_common_assets/
plugin_cache.rs1use std::{path::PathBuf, sync::RwLock};
2
3use crate::Concatenate;
4
5use super::{ASSETS_PATH, fs::FileSystem};
6use assets_manager::{
7 AnyCache, AssetCache, BoxedError, Compound, Storable,
8 asset::DirLoadable,
9 hot_reloading::EventSender,
10 source::{FileContent, Source, Tar},
11};
12
13struct PluginEntry {
14 path: PathBuf,
15 cache: AssetCache<Tar>,
16}
17
18enum AssetSource {
20 FileSystem,
21 Plugin { index: usize },
22}
23
24struct SourceAndContents<'a>(AssetSource, FileContent<'a>);
25
26pub struct CombinedSource {
31 fs: AssetCache<FileSystem>,
32 plugin_list: RwLock<Vec<PluginEntry>>,
33}
34
35impl CombinedSource {
36 pub fn new() -> std::io::Result<Self> {
37 Ok(Self {
38 fs: AssetCache::with_source(FileSystem::new()?),
39 plugin_list: RwLock::new(Vec::new()),
40 })
41 }
42}
43
44impl CombinedSource {
45 fn read_multiple(&self, id: &str, ext: &str) -> Vec<SourceAndContents<'_>> {
47 let mut result = Vec::new();
48 if let Ok(file_entry) = self.fs.raw_source().read(id, ext) {
49 result.push(SourceAndContents(AssetSource::FileSystem, file_entry));
50 }
51 for (n, p) in self.plugin_list.read().unwrap().iter().enumerate() {
52 if let Ok(entry) = p.cache.raw_source().read(id, ext) {
53 result.push(SourceAndContents(
55 AssetSource::Plugin { index: n },
56 match entry {
57 FileContent::Slice(s) => FileContent::Buffer(Vec::from(s)),
58 FileContent::Buffer(b) => FileContent::Buffer(b),
59 FileContent::Owned(s) => {
60 FileContent::Buffer(Vec::from(s.as_ref().as_ref()))
61 },
62 },
63 ));
64 }
65 }
66 result
67 }
68
69 fn plugin_path(&self, index: &AssetSource) -> Option<PathBuf> {
71 match index {
72 AssetSource::FileSystem => Some(ASSETS_PATH.clone()),
73 AssetSource::Plugin { index } => self.plugin_list
74 .read()
75 .unwrap()
76 .get(*index)
77 .map(|plugin| plugin.path.clone()),
79 }
80 }
81}
82
83impl Source for CombinedSource {
84 fn read(&self, id: &str, ext: &str) -> std::io::Result<FileContent<'_>> {
85 let mut entries = self.read_multiple(id, ext);
87 if entries.is_empty() {
88 Err(std::io::ErrorKind::NotFound.into())
89 } else {
90 if entries.len() > 1 {
91 let patha = self.plugin_path(&entries[0].0);
92 let pathb = self.plugin_path(&entries[1].0);
93 tracing::error!("Duplicate asset {id} in {patha:?} and {pathb:?}");
94 }
95 Ok(entries.swap_remove(0).1)
97 }
98 }
99
100 fn read_dir(
101 &self,
102 id: &str,
103 f: &mut dyn FnMut(assets_manager::source::DirEntry),
104 ) -> std::io::Result<()> {
105 self.fs.raw_source().read_dir(id, f)
107 }
108
109 fn exists(&self, entry: assets_manager::source::DirEntry) -> bool {
110 self.fs.raw_source().exists(entry)
111 || self
112 .plugin_list
113 .read()
114 .unwrap()
115 .iter()
116 .any(|plugin| plugin.cache.raw_source().exists(entry))
117 }
118
119 fn make_source(&self) -> Option<Box<dyn Source + Send>> { self.fs.raw_source().make_source() }
121
122 fn configure_hot_reloading(&self, events: EventSender) -> Result<(), BoxedError> {
123 self.fs.raw_source().configure_hot_reloading(events)
124 }
125}
126
127pub struct CombinedCache(AssetCache<CombinedSource>);
129
130impl CombinedCache {
131 pub fn new() -> std::io::Result<Self> {
132 CombinedSource::new().map(|combined_source| Self(AssetCache::with_source(combined_source)))
133 }
134
135 #[doc(hidden)]
136 pub(crate) fn non_reloading_cache(&self) -> AnyCache<'_> {
139 self.0.raw_source().fs.as_any_cache()
140 }
141
142 pub fn combine<T: Concatenate>(
144 &self,
145 reloading_cache: AnyCache,
147 mut load_from: impl FnMut(AnyCache) -> Result<T, assets_manager::Error>,
148 ) -> Result<T, assets_manager::Error> {
149 let mut result = load_from(reloading_cache);
150 if let Err(ref fs_error) = result {
153 match fs_error
154 .reason()
155 .downcast_ref::<std::io::Error>()
156 .map(|io_error| io_error.kind())
157 {
158 Some(std::io::ErrorKind::NotFound) => (),
159 _ => tracing::error!("Filesystem asset load {fs_error:?}"),
160 }
161 }
162 for plugin in self.0.raw_source().plugin_list.read().unwrap().iter() {
163 match load_from(plugin.cache.as_any_cache()) {
164 Ok(b) => {
165 result = if let Ok(a) = result {
166 Ok(a.concatenate(b))
167 } else {
168 Ok(b)
169 };
170 },
171 Err(plugin_error) => {
173 match plugin_error
174 .reason()
175 .downcast_ref::<std::io::Error>()
176 .map(|io_error| io_error.kind())
177 {
178 Some(std::io::ErrorKind::NotFound) => (),
179 _ => tracing::error!(
180 "Loading from {:?} failed {plugin_error:?}",
181 plugin.path
182 ),
183 }
184 },
185 }
186 }
187 result
188 }
189
190 pub fn register_tar(&self, path: PathBuf) -> std::io::Result<()> {
193 let tar_source = Tar::open(&path)?;
194 let cache = AssetCache::with_source(tar_source);
195 self.0
196 .raw_source()
197 .plugin_list
198 .write()
199 .unwrap()
200 .push(PluginEntry { path, cache });
201 Ok(())
202 }
203
204 #[inline]
206 #[cfg(feature = "hot-reloading")]
207 pub fn enhance_hot_reloading(&'static self) { self.0.enhance_hot_reloading(); }
208
209 #[inline]
210 pub fn load_rec_dir<A: DirLoadable>(
211 &self,
212 id: &str,
213 ) -> Result<&assets_manager::Handle<assets_manager::RecursiveDirectory<A>>, assets_manager::Error>
214 {
215 self.0.load_rec_dir(id)
216 }
217
218 #[inline]
219 pub fn load<A: Compound>(
220 &self,
221 id: &str,
222 ) -> Result<&assets_manager::Handle<A>, assets_manager::Error> {
223 self.0.load(id)
224 }
225
226 #[inline]
227 pub fn get_or_insert<A: Storable>(&self, id: &str, a: A) -> &assets_manager::Handle<A> {
228 self.0.get_or_insert(id, a)
229 }
230
231 #[inline]
232 pub fn load_owned<A: Compound>(&self, id: &str) -> Result<A, assets_manager::Error> {
233 self.0.load_owned(id)
234 }
235}