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: FileSystem,
30 plugin_list: RwLock<Vec<PluginEntry>>,
31}
32
33impl CombinedSource {
34 pub fn new() -> std::io::Result<Self> {
35 Ok(Self {
36 fs: 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.fs.read(id, ext) {
47 result.push(SourceAndContents(AssetSource::FileSystem, file_entry));
48 }
49 for (n, p) in self.plugin_list.read().unwrap().iter().enumerate() {
50 if let Ok(entry) = p.cache.source().read(id, ext) {
51 result.push(SourceAndContents(
53 AssetSource::Plugin { index: n },
54 match entry {
55 FileContent::Slice(s) => FileContent::Buffer(Vec::from(s)),
56 FileContent::Buffer(b) => FileContent::Buffer(b),
57 FileContent::Owned(s) => {
58 FileContent::Buffer(Vec::from(s.as_ref().as_ref()))
59 },
60 },
61 ));
62 }
63 }
64 result
65 }
66
67 fn plugin_path(&self, index: &AssetSource) -> Option<PathBuf> {
69 match index {
70 AssetSource::FileSystem => Some(ASSETS_PATH.clone()),
71 AssetSource::Plugin { index } => self.plugin_list
72 .read()
73 .unwrap()
74 .get(*index)
75 .map(|plugin| plugin.path.clone()),
77 }
78 }
79}
80
81impl Source for CombinedSource {
82 fn read(&self, id: &str, ext: &str) -> std::io::Result<FileContent<'_>> {
83 let mut entries = self.read_multiple(id, ext);
85 if entries.is_empty() {
86 Err(std::io::ErrorKind::NotFound.into())
87 } else {
88 if entries.len() > 1 {
89 let patha = self.plugin_path(&entries[0].0);
90 let pathb = self.plugin_path(&entries[1].0);
91 tracing::error!("Duplicate asset {id} in {patha:?} and {pathb:?}");
92 }
93 Ok(entries.swap_remove(0).1)
95 }
96 }
97
98 fn read_dir(
99 &self,
100 id: &str,
101 f: &mut dyn FnMut(assets_manager::source::DirEntry),
102 ) -> std::io::Result<()> {
103 self.fs.read_dir(id, f)
105 }
106
107 fn exists(&self, entry: assets_manager::source::DirEntry) -> bool {
108 self.fs.exists(entry)
109 || self
110 .plugin_list
111 .read()
112 .unwrap()
113 .iter()
114 .any(|plugin| plugin.cache.source().exists(entry))
115 }
116
117 fn configure_hot_reloading(&self, events: EventSender) -> Result<(), BoxedError> {
119 self.fs.configure_hot_reloading(events)
120 }
121}
122
123pub struct CombinedCache(AssetCache);
125
126impl CombinedCache {
127 pub fn new() -> std::io::Result<Self> {
128 CombinedSource::new().map(|combined_source| Self(AssetCache::with_source(combined_source)))
129 }
130
131 pub fn as_cache(&self) -> &AssetCache { &self.0 }
132
133 pub fn combine<T: Concatenate>(
135 &self,
136 cache: &AssetCache,
138 mut load_from: impl FnMut(&AssetCache) -> Result<T, assets_manager::Error>,
139 ) -> Result<T, assets_manager::Error> {
140 let mut result = load_from(cache);
141 if let Err(ref fs_error) = result {
144 match fs_error
145 .reason()
146 .downcast_ref::<std::io::Error>()
147 .map(|io_error| io_error.kind())
148 {
149 Some(std::io::ErrorKind::NotFound) => (),
150 _ => tracing::error!("Filesystem asset load {fs_error:?}"),
151 }
152 }
153 for plugin in self
154 .0
155 .downcast_raw_source::<CombinedSource>()
156 .unwrap()
157 .plugin_list
158 .read()
159 .unwrap()
160 .iter()
161 {
162 match load_from(&plugin.cache) {
163 Ok(b) => {
164 result = if let Ok(a) = result {
165 Ok(a.concatenate(b))
166 } else {
167 Ok(b)
168 };
169 },
170 Err(plugin_error) => {
172 match plugin_error
173 .reason()
174 .downcast_ref::<std::io::Error>()
175 .map(|io_error| io_error.kind())
176 {
177 Some(std::io::ErrorKind::NotFound) => (),
178 _ => tracing::error!(
179 "Loading from {:?} failed {plugin_error:?}",
180 plugin.path
181 ),
182 }
183 },
184 }
185 }
186 result
187 }
188
189 pub fn register_tar(&self, path: PathBuf) -> std::io::Result<()> {
192 let tar_source = Tar::open(&path)?;
193 let cache = AssetCache::with_source(tar_source);
194 self.0
195 .downcast_raw_source::<CombinedSource>()
196 .unwrap()
197 .plugin_list
198 .write()
199 .unwrap()
200 .push(PluginEntry { path, cache });
201 Ok(())
202 }
203
204 pub fn no_record<T>(&self, f: impl FnOnce() -> T) -> T { self.0.no_record(f) }
205
206 #[inline]
208 pub fn load_rec_dir<A: DirLoadable + Asset>(
209 &self,
210 id: &str,
211 ) -> Result<&assets_manager::Handle<assets_manager::RecursiveDirectory<A>>, assets_manager::Error>
212 {
213 self.0.load_rec_dir(id)
214 }
215
216 #[inline]
217 pub fn load<A: Asset>(
218 &self,
219 id: &str,
220 ) -> Result<&assets_manager::Handle<A>, assets_manager::Error> {
221 self.0.load(id)
222 }
223
224 #[inline]
225 pub fn get_or_insert<A: Storable>(&self, id: &str, a: A) -> &assets_manager::Handle<A> {
226 self.0.get_or_insert(id, a)
227 }
228
229 #[inline]
230 pub fn load_owned<A: Asset>(&self, id: &str) -> Result<A, assets_manager::Error> {
231 self.0.load_owned(id)
232 }
233}