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>; pub struct FileManager { pub wasm_compiler: Arc, active_tasks: FxHashMap, FileTask>, cache: FxHashMap, FileData>, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct GameManifest { pub name: Box, pub first_scene: usize, pub scenes: Box<[Scene]>, } #[derive(Debug, Serialize, Deserialize)] struct TomlGameManifest { name: Box, first_scene: Box, scene: Box<[Scene]>, } pub enum FileData { GameManifest(GameManifest), WasmModule(wasmi::Module), } fn normalize_game_manifest(manifest: TomlGameManifest) -> std::io::Result { 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 { 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) -> 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) -> Option> { 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) { 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) -> 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) { self.active_tasks.remove(path.as_ref()); self.cache.remove(path.as_ref()); } } impl Default for FileManager { fn default() -> Self { Self::new() } }