good_web_game/
filesystem.rs1use std::sync::{Arc, Mutex};
4use std::{collections::HashMap, io, path};
5
6use crate::GameError::ResourceLoadError;
7use crate::{conf::Conf, Context, GameError, GameResult};
8use std::panic::panic_any;
9
10#[derive(Debug, Clone)]
11pub struct File {
12 pub bytes: io::Cursor<Vec<u8>>,
13}
14
15impl io::Read for File {
16 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
17 self.bytes.read(buf)
18 }
19}
20
21#[derive(Debug)]
23pub struct Filesystem {
24 root: Option<path::PathBuf>,
25 files: HashMap<path::PathBuf, File>,
26}
27
28impl Filesystem {
29 #[allow(clippy::redundant_closure)]
30 pub(crate) fn new(conf: &Conf) -> Filesystem {
31 let mut files = HashMap::new();
32
33 if let Some(tar_file) = conf.cache {
34 let mut archive = tar::Archive::new(tar_file);
35
36 for file in archive.entries().unwrap_or_else(|e| panic_any(e)) {
37 use std::io::Read;
38
39 let mut file = file.unwrap_or_else(|e| panic_any(e));
40 let filename =
41 std::path::PathBuf::from(file.path().unwrap_or_else(|e| panic_any(e)));
42 let mut buf = vec![];
43
44 file.read_to_end(&mut buf).unwrap_or_else(|e| panic_any(e));
45 if !buf.is_empty() {
46 files.insert(
47 filename,
48 File {
49 bytes: io::Cursor::new(buf),
50 },
51 );
52 }
53 }
54 }
55
56 let root = conf.physical_root_dir.clone();
57 Filesystem { root, files }
58 }
59
60 pub fn open<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
63 let mut path = path::PathBuf::from(path.as_ref());
64
65 if let Ok(stripped) = path.strip_prefix("/") {
68 path = path::PathBuf::from(stripped);
69 }
70
71 if self.files.contains_key(&path) {
73 Ok(self.files[&path].clone())
74 } else {
75 let file = self.load_file(&path)?;
77 Ok(file)
78 }
79 }
80
81 #[cfg(not(target_os = "wasm32"))]
82 fn load_file<P: AsRef<path::Path>>(&self, path: P) -> GameResult<File> {
85 fn load_file_inner(path: &str) -> GameResult<Vec<u8>> {
86 let contents = Arc::new(Mutex::new(None));
87
88 {
89 let contents = contents.clone();
90 let err_path = path.to_string();
91
92 miniquad::fs::load_file(path, move |bytes| {
93 *contents.lock().unwrap() = Some(bytes.map_err(|kind| {
94 GameError::ResourceLoadError(format!(
95 "Couldn't load file {}: {}",
96 err_path, kind
97 ))
98 }));
99 });
100 }
101
102 loop {
109 let mut contents_guard = contents.lock().unwrap();
110 if let Some(contents) = contents_guard.take() {
111 return contents;
112 }
113 drop(contents_guard);
114 std::thread::yield_now();
115 }
116 }
117
118 #[cfg(target_os = "ios")]
119 let _ = std::env::set_current_dir(std::env::current_exe().unwrap().parent().unwrap());
120
121 let path = path
122 .as_ref()
123 .as_os_str()
124 .to_os_string()
125 .into_string()
126 .map_err(|os_string| {
127 ResourceLoadError(format!("utf-8-invalid path: {:?}", os_string))
128 })?;
129
130 #[cfg(not(target_os = "android"))]
131 let path = if let Some(ref root) = self.root {
132 format!(
133 "{}/{}",
134 root.as_os_str()
135 .to_os_string()
136 .into_string()
137 .map_err(|os_string| ResourceLoadError(format!(
138 "utf-8-invalid root: {:?}",
139 os_string
140 )))?,
141 path
142 )
143 } else {
144 path
145 };
146
147 let buf = load_file_inner(&path)?;
148 let bytes = io::Cursor::new(buf);
149 Ok(File { bytes })
150 }
151
152 #[cfg(target_os = "wasm32")]
153 fn load_file<P: AsRef<path::Path>>(&self, path: P) -> GameResult<File> {
156 Err(GameError::ResourceLoadError(format!(
157 "Couldn't load file {}",
158 path.as_display()
159 )))
160 }
161}
162
163pub fn open<P: AsRef<path::Path>>(ctx: &mut Context, path: P) -> GameResult<File> {
170 ctx.filesystem.open(path)
171}
172
173pub fn load_file_async<P: AsRef<path::Path>>(path: P) -> Arc<Mutex<Option<GameResult<File>>>> {
179 let contents = Arc::new(Mutex::new(None));
181 let path = path
182 .as_ref()
183 .as_os_str()
184 .to_os_string()
185 .into_string()
186 .map_err(|os_string| ResourceLoadError(format!("utf-8-invalid path: {:?}", os_string)));
187
188 if let Ok(path) = path {
189 let contents = contents.clone();
190
191 miniquad::fs::load_file(&*(path.clone()), move |response| {
192 let result = match response {
193 Ok(bytes) => Ok(File {
194 bytes: io::Cursor::new(bytes),
195 }),
196 Err(e) => Err(GameError::ResourceLoadError(format!(
197 "Couldn't load file {}: {}",
198 path, e
199 ))),
200 };
201 *contents.lock().unwrap() = Some(result);
202 });
203 }
204
205 contents
206}