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