summaryrefslogtreecommitdiff
path: root/src/file.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/file.rs')
-rw-r--r--src/file.rs255
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);
+ }
+}