diff options
Diffstat (limited to 'src/file.rs')
| -rw-r--r-- | src/file.rs | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..8e96fa9 --- /dev/null +++ b/src/file.rs @@ -0,0 +1,154 @@ +use std::{ffi::OsStr, path::Path, sync::Arc}; + +use rustc_hash::FxHashMap; +use serde::{Deserialize, Serialize}; +use smol::{Task, block_on, unblock}; + +use crate::scene::{Scene, scene_id_by_name}; + +type FileTask = Task<std::io::Result<FileData>>; + +pub struct FileManager { + pub wasm_compiler: Arc<wasmi::Engine>, + active_tasks: FxHashMap<Box<Path>, FileTask>, + cache: FxHashMap<Box<Path>, FileData>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct GameManifest { + pub name: Box<str>, + pub first_scene: usize, + pub scenes: Box<[Scene]>, +} + +#[derive(Debug, Serialize, Deserialize)] +struct TomlGameManifest { + name: Box<str>, + first_scene: Box<str>, + scene: Box<[Scene]>, +} + +pub enum FileData { + GameManifest(GameManifest), + WasmModule(wasmi::Module), +} + +fn normalize_game_manifest(manifest: TomlGameManifest) -> std::io::Result<GameManifest> { + let first_scene = scene_id_by_name(&manifest.scene, &manifest.first_scene).ok_or( + std::io::Error::other("Invalid first scene: scene not found"), + )?; + Ok(GameManifest { + name: manifest.name, + first_scene, + scenes: manifest.scene, + }) +} + +fn compile_data(path: &Path, data: &[u8], compiler: &wasmi::Engine) -> std::io::Result<FileData> { + if path == "manifest.postcard" { + return Ok(FileData::GameManifest( + postcard::from_bytes(data).map_err(std::io::Error::other)?, + )); + } else if path == "manifest.toml" { + return Ok(FileData::GameManifest(normalize_game_manifest( + toml::from_slice(data).map_err(std::io::Error::other)?, + )?)); + } + let extension = path + .extension() + .and_then(OsStr::to_str) + .map(|ext| ext.to_lowercase()); + if extension.as_deref() == Some("wasm") { + return Ok(FileData::WasmModule( + wasmi::Module::new(compiler, data).map_err(std::io::Error::other)?, + )); + } + Err(std::io::Error::other("Unrecognized file extension")) +} + +impl FileData { + pub fn unwrap_manifest(&self) -> &GameManifest { + if let FileData::GameManifest(manifest) = self { + manifest + } else { + panic!("Not a game manifest"); + } + } + + pub fn unwrap_wasm_module(&self) -> &wasmi::Module { + if let FileData::WasmModule(module) = self { + module + } else { + panic!("Not a WASM module") + } + } +} + +impl FileManager { + pub fn new() -> Self { + Self { + wasm_compiler: Arc::new(wasmi::Engine::default()), + active_tasks: FxHashMap::default(), + cache: FxHashMap::default(), + } + } + + pub fn load_file(&mut self, path: Box<Path>) -> std::io::Result<&FileData> { + let data = compile_data( + path.as_ref(), + &std::fs::read(path.as_ref()).map(Vec::into_boxed_slice)?, + &self.wasm_compiler.clone(), + )?; + self.cache.entry(path.clone()).insert_entry(data); + Ok(self.cache.get(&path).unwrap()) + } + + pub fn get_loaded_file(&mut self, path: Box<Path>) -> Option<std::io::Result<&FileData>> { + if let Some(task) = self.active_tasks.get(path.as_ref()) + && task.is_finished() + { + let data = match block_on(self.active_tasks.remove(&path).unwrap()) { + Ok(data) => data, + Err(e) => return Some(Err(e)), + }; + self.cache.entry(path.clone()).insert_entry(data); + Some(Ok(self.cache.get(&path).unwrap())) + } else if let Some(data) = self.cache.get(&path) { + Some(Ok(data)) + } else { + None + } + } + + pub fn start_loading_file(&mut self, path: impl AsRef<Path>) { + let boxed_path = Box::from(path.as_ref()); + let compiler = self.wasm_compiler.clone(); + let task = unblock(move || { + compile_data( + &boxed_path, + &std::fs::read(&boxed_path).map(Vec::into_boxed_slice)?, + &compiler, + ) + }); + let boxed_path = Box::from(path.as_ref()); + self.active_tasks.insert(boxed_path, task); + } + + pub fn is_file_loaded(&self, path: impl AsRef<Path>) -> bool { + self.active_tasks + .get(path.as_ref()) + .is_some_and(|task| task.is_finished()) + || self.cache.contains_key(path.as_ref()) + } + + pub fn unload_file(&mut self, path: impl AsRef<Path>) { + self.active_tasks.remove(path.as_ref()); + self.cache.remove(path.as_ref()); + } +} + +impl Default for FileManager { + fn default() -> Self { + Self::new() + } +} |
