use std::collections::{HashMap, HashSet};
use freecs::Entity;
use rhai::{AST, Engine, Scope};
use winit::keyboard::KeyCode;
struct CachedScript {
ast: AST,
source_hash: u64,
}
pub struct ScriptRuntime {
pub engine: Engine,
compiled_scripts: HashMap<String, CachedScript>,
pub entity_scopes: HashMap<Entity, Scope<'static>>,
pub previous_keys: HashSet<KeyCode>,
pub accumulated_time: f64,
pub game_state: HashMap<String, f64>,
#[cfg(not(target_arch = "wasm32"))]
pub file_modification_times: HashMap<String, std::time::SystemTime>,
}
impl Default for ScriptRuntime {
fn default() -> Self {
Self::new()
}
}
impl ScriptRuntime {
pub fn new() -> Self {
let engine = Engine::new();
Self {
engine,
compiled_scripts: HashMap::new(),
entity_scopes: HashMap::new(),
previous_keys: HashSet::new(),
accumulated_time: 0.0,
game_state: HashMap::new(),
#[cfg(not(target_arch = "wasm32"))]
file_modification_times: HashMap::new(),
}
}
pub fn get_or_create_scope(&mut self, entity: Entity) -> &mut Scope<'static> {
self.entity_scopes.entry(entity).or_default()
}
pub fn remove_entity_scope(&mut self, entity: Entity) {
self.entity_scopes.remove(&entity);
}
pub fn compile_script(&mut self, key: &str, source: &str) -> Result<&AST, String> {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
source.hash(&mut hasher);
let source_hash = hasher.finish();
let needs_compile = match self.compiled_scripts.get(key) {
Some(cached) => cached.source_hash != source_hash,
None => true,
};
if needs_compile {
let ast = self
.engine
.compile(source)
.map_err(|e| format!("Compilation error: {}", e))?;
self.compiled_scripts
.insert(key.to_string(), CachedScript { ast, source_hash });
}
Ok(&self.compiled_scripts.get(key).unwrap().ast)
}
pub fn invalidate_script(&mut self, key: &str) {
self.compiled_scripts.remove(key);
}
pub fn reset_time(&mut self) {
self.accumulated_time = 0.0;
}
pub fn reset_game_state(&mut self) {
self.game_state.clear();
}
pub fn get_state(&self, key: &str) -> f64 {
self.game_state.get(key).copied().unwrap_or(0.0)
}
pub fn set_state(&mut self, key: String, value: f64) {
self.game_state.insert(key, value);
}
#[cfg(not(target_arch = "wasm32"))]
pub fn check_hot_reload(&mut self) -> Vec<String> {
let mut reloaded = Vec::new();
let paths: Vec<String> = self.file_modification_times.keys().cloned().collect();
for path in paths {
if let Ok(metadata) = std::fs::metadata(&path)
&& let Ok(modified) = metadata.modified()
&& let Some(cached_time) = self.file_modification_times.get(&path)
&& modified > *cached_time
{
self.compiled_scripts.remove(&path);
self.file_modification_times.insert(path.clone(), modified);
reloaded.push(path);
}
}
reloaded
}
#[cfg(not(target_arch = "wasm32"))]
pub fn track_file(&mut self, path: &str) {
if let Ok(metadata) = std::fs::metadata(path)
&& let Ok(modified) = metadata.modified()
{
self.file_modification_times
.insert(path.to_string(), modified);
}
}
}