diff options
| author | Mica White <botahamec@outlook.com> | 2026-10-26 10:51:49 -0400 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2026-10-26 10:51:49 -0400 |
| commit | 0c128e472b1b49e32c313874eaeccc8c5f1b1202 (patch) | |
| tree | 2ce06455440e85ec3387f7348b7bd69bf2119634 /src/file.rs | |
| parent | c687e2aba30ca231b03288108b632f1bad464028 (diff) | |
Diffstat (limited to 'src/file.rs')
| -rw-r--r-- | src/file.rs | 255 |
1 files changed, 94 insertions, 161 deletions
diff --git a/src/file.rs b/src/file.rs index 4aedd48..b8f6610 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,178 +1,96 @@ -use std::{ffi::OsStr, path::Path}; +use std::{ + ffi::OsStr, + path::Path, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + mpsc::{Receiver, Sender}, + }, + time::{Duration, Instant, SystemTime}, +}; use rustc_hash::FxHashMap; -use serde::{Deserialize, Serialize}; -use smol::{Task, unblock}; +use smol::{Task, block_on, unblock}; -use crate::{ - scene::{ResourceRequirements, Scene, scene_id_by_name}, - script::{ScriptDeclaration, WasmRunner, lua::LuaModule, wasm::WasmModule}, -}; +use crate::script::{WasmRunner, wasm::WasmModule}; -type FileTask = Task<std::io::Result<FileData>>; +type FileTask = Task<std::io::Result<WasmModule>>; pub struct FileManager { + watcher: Notifier, + updated_files: Receiver<Box<Path>>, active_tasks: FxHashMap<Box<Path>, FileTask>, - cache: FxHashMap<Box<Path>, FileData>, + cache: FxHashMap<Box<Path>, Arc<std::io::Result<WasmModule>>>, } -#[derive(Debug, Clone)] -pub struct GameManifest { - pub name: Box<str>, - pub first_scene: usize, - pub scenes: Box<[Scene]>, - pub scripts: Box<[ScriptDeclaration]>, +struct Notifier { + paths: Arc<spin::Mutex<Vec<Box<Path>>>>, + cancel_token: Arc<AtomicBool>, } -#[derive(Debug, Serialize, Deserialize)] -pub struct TomlGameManifest { - name: Box<str>, - first_scene: Box<str>, - #[serde(default)] - scene: Box<[TomlScene]>, - #[serde(default)] - script: Box<[TomlScript]>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TomlScene { - name: Box<str>, - #[serde(default)] - preload_scenes: Box<[Box<str>]>, - #[serde(default)] - scripts: Box<[Box<str>]>, - #[serde(default)] - preload_scripts: Box<[Box<str>]>, -} - -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "kebab-case")] -enum TomlScript { - Wasm { name: Box<str>, path: Box<Path> }, - Lua { name: Box<str>, path: Box<Path> }, -} - -pub enum FileData { - GameManifest(GameManifest), - WasmModule(Box<WasmModule>), - LuaModule(Box<LuaModule>), -} - -pub fn normalize_game_manifest(manifest: TomlGameManifest) -> std::io::Result<GameManifest> { - let mut scripts = Vec::with_capacity(manifest.script.len()); - let script_map: FxHashMap<Box<str>, usize> = manifest - .script - .into_iter() - .map(|script| match script { - TomlScript::Wasm { name, path } => { - scripts.push(ScriptDeclaration::Wasm { - name: name.clone(), - path, - }); - (name, scripts.len() - 1) - } - TomlScript::Lua { name, path } => { - scripts.push(ScriptDeclaration::Lua { - name: name.clone(), - path, - }); - (name, scripts.len() - 1) - } - }) - .collect(); - let scenes: Box<[Scene]> = manifest - .scene - .into_iter() - .map(|scene| Scene { - name: scene.name, - preload_scenes: Box::from([]), - scripts: ResourceRequirements { - required: scene - .scripts - .iter() - .map(|s| *script_map.get(s).unwrap()) - .collect(), - preload: scene - .preload_scripts - .iter() - .map(|s| *script_map.get(s).unwrap()) - .collect(), - }, - }) - .collect(); - let first_scene = scene_id_by_name(&scenes, &manifest.first_scene).ok_or( - std::io::Error::other("Invalid first scene: scene not found"), - )?; - Ok(GameManifest { - name: manifest.name, - first_scene, - scenes, - scripts: scripts.into_boxed_slice(), - }) -} - -fn compile_data(path: &Path, data: &[u8], compiler: &WasmRunner) -> std::io::Result<FileData> { - if path == "tortuise.toml" { - return Ok(FileData::GameManifest(normalize_game_manifest( - toml::from_slice(data).map_err(std::io::Error::other)?, - )?)); - } +fn compile_data(path: &Path, data: &[u8], compiler: &WasmRunner) -> std::io::Result<WasmModule> { let extension = path .extension() .and_then(OsStr::to_str) .map(|ext| ext.to_lowercase()); if extension.as_deref() == Some("wasm") { - return Ok(FileData::WasmModule(Box::new( - compiler.compile(data).map_err(std::io::Error::other)?, - ))); - } - if extension.as_deref() == Some("lua") { - return Ok(FileData::LuaModule(Box::new( - LuaModule::new(data).map_err(std::io::Error::other)?, - ))); + return compiler.compile(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"); - } - } +impl Notifier { + pub fn new(channel: Sender<Box<Path>>, interval: Duration) -> Self { + let paths = Arc::new(spin::Mutex::new(Vec::new())); + let paths_2 = paths.clone(); + let cancel_token = Arc::new(AtomicBool::new(false)); + let cancel_token_2 = cancel_token.clone(); + std::thread::spawn(move || { + loop { + let last_check = SystemTime::now(); - pub fn unwrap_wasm_module(&self) -> &WasmModule { - if let FileData::WasmModule(module) = self { - module - } else { - panic!("Not a WASM module") - } - } + std::thread::sleep(interval); + if cancel_token_2.load(Ordering::Acquire) { + break; + } + let paths = paths_2.lock().clone(); + for path in paths { + if let Ok(metadata) = std::fs::metadata(&path) + && let Ok(mtime) = metadata.modified() + && mtime > last_check + && let Err(e) = channel.send(path) + { + log::error!("{e}"); + } + } + } + }); - pub fn unwrap_lua_module(&self) -> &LuaModule { - if let FileData::LuaModule(module) = self { - module - } else { - panic!("Not a Lua module") + Self { + paths, + cancel_token, } } - pub fn unwrap_wasm_module_mut(&mut self) -> &mut WasmModule { - if let FileData::WasmModule(module) = self { - module - } else { - panic!("Not a WASM module") + pub fn watch(&mut self, path: Box<Path>) { + let mut paths = self.paths.lock(); + if paths.contains(&path) { + return; } + + paths.push(path); } } impl FileManager { pub fn new() -> Self { + let (sender, updated_files) = std::sync::mpsc::channel(); + let watcher = Notifier::new(sender, Duration::from_secs(10)); Self { active_tasks: FxHashMap::default(), cache: FxHashMap::default(), + watcher, + updated_files, } } @@ -180,28 +98,30 @@ impl FileManager { &mut self, path: Box<Path>, wasm_compiler: &WasmRunner, - ) -> std::io::Result<&FileData> { - if !self.cache.contains_key(&path) { - let data = compile_data( - path.as_ref(), - &std::fs::read(path.as_ref()).map(Vec::into_boxed_slice)?, - wasm_compiler, - )?; - self.cache.entry(path.clone()).insert_entry(data); + ) -> Arc<std::io::Result<WasmModule>> { + if let Some(task) = self.active_tasks.get(&path) + && task.is_finished() + { + log::info!("loaded {}", path.to_string_lossy()); + let task = self.active_tasks.remove(&path).unwrap(); + let data = block_on(task); + self.cache.insert(path.clone(), Arc::new(data)); + } else if !self.cache.contains_key(&path) { + let read_time = Instant::now(); + let data = std::fs::read(path.as_ref()).map(Vec::into_boxed_slice); + log::info!("Load time: {} ms", read_time.elapsed().as_millis()); + let compile_time = Instant::now(); + let data = data.and_then(|data| compile_data(path.as_ref(), &data, wasm_compiler)); + log::info!("Compile time: {} ms", compile_time.elapsed().as_millis()); + self.cache.entry(path.clone()).insert_entry(Arc::new(data)); } - Ok(self.cache.get(&path).unwrap()) - } - pub fn get_cached_file(&self, path: &Path) -> Option<&FileData> { - self.cache.get(path) + self.watcher.watch(path.clone()); + self.cache.get(&path).unwrap().clone() } - pub fn get_cached_file_mut(&mut self, path: &Path) -> Option<&mut FileData> { - self.cache.get_mut(path) - } - - pub fn start_loading_file(&mut self, path: impl AsRef<Path>, wasm_compiler: &WasmRunner) { - let boxed_path = Box::from(path.as_ref()); + pub fn start_loading_file(&mut self, path: Box<Path>, wasm_compiler: &WasmRunner) { + let boxed_path = path.clone(); let compiler = wasm_compiler.clone(); let task = unblock(move || { compile_data( @@ -210,8 +130,15 @@ impl FileManager { &compiler, ) }); - let boxed_path = Box::from(path.as_ref()); - self.active_tasks.insert(boxed_path, task); + self.watcher.watch(path.clone()); + self.active_tasks.insert(path, task); + } + + pub fn reload_files(&mut self, wasm_compiler: &WasmRunner) { + while let Ok(path) = self.updated_files.try_recv() { + log::info!("reloading {}", path.to_string_lossy()); + self.start_loading_file(path, wasm_compiler); + } } pub fn loaded_files(&self) -> Box<[Box<Path>]> { @@ -241,3 +168,9 @@ impl Default for FileManager { Self::new() } } + +impl Drop for Notifier { + fn drop(&mut self) { + self.cancel_token.store(true, Ordering::Release); + } +} |
