tirbofish/dropbear · commit
726ca434d30ebb0bbbdf623d605d87988532e7e7
preparing for wasm scripting. this is so tedious :(
Signature present but could not be verified.
Unverified
@@ -50,7 +50,7 @@ wgpu = "26" winit = { version = "0.30", features = [] } zip = "4.6" walkdir = "2.3" -wasmtime = "36.0" +wasmer = { version = "6.0" } [workspace.dependencies.image] version = "0.25" @@ -32,7 +32,6 @@ hecs.workspace = true once_cell.workspace = true parking_lot.workspace = true lazy_static.workspace = true -tokio.workspace = true [target.'cfg(not(target_os = "android"))'.dependencies] rfd.workspace = true @@ -17,9 +17,9 @@ use std::io::Write; use chrono::Local; use egui::TextureId; use egui_wgpu_backend::ScreenDescriptor; +use futures::executor::block_on; use gilrs::{Gilrs, GilrsBuilder}; use spin_sleep::SpinSleeper; -use tokio::task::LocalSet; use std::{ fmt::{self, Display, Formatter}, sync::Arc, @@ -404,20 +404,10 @@ impl App { /// - setup: A closure that can initialise the first scenes, such as a menu or the game itself. /// It takes an input of a scene manager and an input manager, and expects you to return back the changed /// managers. - pub async fn run<F>(config: WindowConfiguration, app_name: &str, setup: F) -> anyhow::Result<()> + pub fn run<F>(config: WindowConfiguration, app_name: &str, setup: F) -> anyhow::Result<()> where F: FnOnce(scene::Manager, input::Manager) -> (scene::Manager, input::Manager), { - // if cfg!(debug_assertions) { - // log::info!("Running in dev mode"); - // let app_target = app_name.replace('-', "_"); - // let log_config = format!("dropbear_engine=trace,{}=debug,warn", app_target); - // unsafe { std::env::set_var("RUST_LOG", log_config) }; - // } - // - // #[cfg(not(target_os = "android"))] - // let _ = env_logger::try_init(); - let log_dir = app_dirs2::app_root(AppDataType::UserData, &config.app_info) .expect("Failed to get app data directory") .join("logs"); @@ -542,7 +532,7 @@ impl ApplicationHandler for App { let window = Arc::new(event_loop.create_window(window_attributes).unwrap()); - self.state = Some(tokio::runtime::Handle::current().block_on(State::new(window)).unwrap()); + self.state = Some(block_on(State::new(window)).unwrap()); if let Some(state) = &mut self.state { let size = state.window.inner_size(); @@ -579,10 +569,7 @@ impl ApplicationHandler for App { self.input_manager.update(&mut self.gilrs); - let ls = LocalSet::new(); - let render_result = tokio::runtime::Handle::current().block_on(ls.run_until(async { - state.render(&mut self.scene_manager, self.delta_time, event_loop).await - })); + let render_result = block_on(state.render(&mut self.scene_manager, self.delta_time, event_loop)); if let Err(e) = render_result { log::error!("Render failed: {:?}", e); @@ -8,7 +8,6 @@ use russimp_ng::{ use wgpu::{BufferAddress, VertexAttribute, VertexBufferLayout, util::DeviceExt}; use lazy_static::lazy_static; use parking_lot::Mutex; -use tokio::task; use crate::graphics::{Graphics, NO_MODEL, Texture}; use crate::utils::ResourceReference; @@ -43,20 +42,8 @@ pub struct Mesh { pub material: usize, } -struct ProcessedMesh { - name: String, - vertices: Vec<ModelVertex>, - indices: Vec<u32>, - material_index: usize, -} - -struct ProcessedMaterial { - name: String, - texture_bytes: Option<Vec<u8>>, -} - impl Model { - pub async fn load_from_memory_async( + pub fn load_from_memory( graphics: &Graphics<'_>, buffer: Vec<u8>, label: Option<&str>, @@ -69,97 +56,9 @@ impl Model { return Ok(cached_model.clone()); } - log::debug!("Loading from memory asynchronously"); + log::debug!("Loading from memory"); let res_ref = ResourceReference::from_bytes(buffer.clone()); - let label_owned = label.clone().unwrap_or_default().to_string(); - - let (processed_materials, processed_meshes) = task::spawn_blocking(move || { - Self::process_scene_from_buffer(buffer, Some(label_owned.as_str())) - }).await??; - - let model = Self::create_gpu_model( - graphics, - processed_materials, - processed_meshes, - cache_key.clone(), - res_ref, - ).await?; - - MEMORY_MODEL_CACHE.lock().insert(cache_key, model.clone()); - log::debug!("Model cached from memory: {:?}", label); - Ok(model) - } - - pub async fn load_async( - graphics: &Graphics<'_>, - path: &PathBuf, - label: Option<&str>, - ) -> anyhow::Result<Model> { - let file_name = path.file_name(); - log::debug!("Loading model asynchronously [{:?}]", file_name); - - let path_str = path.to_string_lossy().to_string(); - - if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) { - log::debug!("Model loaded from cache: {:?}", path_str); - return Ok(cached_model.clone()); - } - - let path_clone = path.clone(); - - let (processed_materials, processed_meshes) = task::spawn_blocking(move || { - Self::process_scene_from_file(&path_clone) - }).await??; - - let model_label = if let Some(l) = label { - l.to_string() - } else { - file_name.unwrap().to_str().unwrap().split('.').next().unwrap().to_string() - }; - - let resource_ref = ResourceReference::from_path(path)?; - - let model = Self::create_gpu_model( - graphics, - processed_materials, - processed_meshes, - model_label, - resource_ref, - ).await?; - - MODEL_CACHE.lock().insert(path_str, model.clone()); - log::debug!("Model cached and loaded: {:?}", file_name); - Ok(model) - } - - pub fn load_from_memory( - graphics: &Graphics<'_>, - buffer: Vec<u8>, - label: Option<&str>, - ) -> anyhow::Result<Model> { - let rt = tokio::runtime::Handle::try_current() - .or_else(|_| { - tokio::runtime::Runtime::new().map(|rt| rt.handle().clone()) - })?; - - rt.block_on(Self::load_from_memory_async(graphics, buffer, label)) - } - - pub fn load( - graphics: &Graphics<'_>, - path: &PathBuf, - label: Option<&str>, - ) -> anyhow::Result<Model> { - let rt = tokio::runtime::Handle::current(); - - rt.block_on(Self::load_async(graphics, path, label)) - } - - fn process_scene_from_buffer( - buffer: Vec<u8>, - label: Option<&str>, - ) -> anyhow::Result<(Vec<ProcessedMaterial>, Vec<ProcessedMesh>)> { let scene = match Scene::from_buffer( buffer.as_slice(), vec![ @@ -184,12 +83,137 @@ impl Model { }, }; - Self::process_scene(scene) + let mut materials = Vec::new(); + for m in &scene.materials { + let mut name = String::new(); + let diffuse_bytes_opt = m + .textures + .iter() + .find(|(t_type, _)| **t_type == TextureType::Diffuse) + .and_then(|(_, tex)| { + name = tex.borrow().filename.clone(); + match &tex.borrow().data { + DataContent::Bytes(b) => Some(b.clone()), + DataContent::Texel(_) => { + log::warn!("Skipping texel-based texture for material '{}'", &name); + None + } + } + }); + + let diffuse_texture = if let Some(bytes) = diffuse_bytes_opt { + Texture::new(graphics, &bytes) + } else { + if !name.is_empty() { + log::warn!( + "Error loading material {}, using default missing texture", + name + ); + } else { + log::warn!("Error loading material, using default missing texture"); + } + Texture::new(graphics, GREY_TEXTURE_BYTES) + }; + + let bind_group = diffuse_texture.bind_group().to_owned(); + materials.push(Material { + name: name, + diffuse_texture, + bind_group, + }); + } + + let mut meshes = Vec::new(); + for mesh in &scene.meshes { + let vertices: Vec<ModelVertex> = mesh + .vertices + .iter() + .enumerate() + .map(|(i, v)| { + let normal = mesh.normals.get(i).copied().unwrap_or(Vector3D { + x: 0.0, + y: 1.0, + z: 0.0, + }); + let tex_coords = mesh + .texture_coords + .get(0) + .and_then(|coords| coords.as_ref().and_then(|vec| vec.get(i))) + .map(|tc| [tc.x, tc.y]) + .unwrap_or([0.0, 0.0]); + ModelVertex { + position: [v.x, v.y, v.z], + tex_coords, + normal: [normal.x, normal.y, normal.z], + } + }) + .collect(); + + let indices: Vec<u32> = mesh + .faces + .iter() + .flat_map(|f| f.0.iter().copied()) + .collect(); + + let vertex_buffer = + graphics + .state + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Vertex Buffer", label)), + contents: bytemuck::cast_slice(&vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + let index_buffer = + graphics + .state + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some(&format!("{:?} Index Buffer", label)), + contents: bytemuck::cast_slice(&indices), + usage: wgpu::BufferUsages::INDEX, + }); + + meshes.push(Mesh { + name: mesh.name.clone(), + vertex_buffer, + index_buffer, + num_elements: indices.len() as u32, + material: mesh.material_index as usize, + }); + } + log::debug!("Successfully loaded model [{:?}]", label); + let model = Model { + meshes, + materials, + label: if let Some(l) = label { + l.to_string() + } else { + String::from("No named model") + }, + path: res_ref, + }; + + MEMORY_MODEL_CACHE.lock().insert(cache_key, model.clone()); + log::debug!("Model cached from memory: {:?}", label); + Ok(model) } - fn process_scene_from_file( + pub fn load( + graphics: &Graphics<'_>, path: &PathBuf, - ) -> anyhow::Result<(Vec<ProcessedMaterial>, Vec<ProcessedMesh>)> { + label: Option<&str>, + ) -> anyhow::Result<Model> { + let file_name = path.file_name(); + log::debug!("Loading model [{:?}]", file_name); + + let path_str = path.to_string_lossy().to_string(); + + if let Some(cached_model) = MODEL_CACHE.lock().get(&path_str) { + log::debug!("Model loaded from cache: {:?}", path_str); + return Ok(cached_model.clone()); + } + let scene = match Scene::from_file( path.to_str().unwrap(), vec![ @@ -213,155 +237,120 @@ impl Model { }, }; - Self::process_scene(scene) - } - - fn process_scene(scene: Scene) -> anyhow::Result<(Vec<ProcessedMaterial>, Vec<ProcessedMesh>)> { - let processed_materials: Vec<ProcessedMaterial> = scene - .materials - .into_iter() - .map(|m| { - let mut name = String::new(); - let texture_bytes = m - .textures - .iter() - .find(|(t_type, _)| **t_type == TextureType::Diffuse) - .and_then(|(_, tex)| { - name = tex.borrow().filename.clone(); - match &tex.borrow().data { - DataContent::Bytes(b) => Some(b.clone()), - DataContent::Texel(_) => { - log::warn!("Skipping texel-based texture for material '{}'", &name); - None - } + let mut materials = Vec::new(); + for m in &scene.materials { + let mut name = String::new(); + let diffuse_bytes_opt = m + .textures + .iter() + .find(|(t_type, _)| **t_type == TextureType::Diffuse) + .and_then(|(_, tex)| { + name = tex.borrow().filename.clone(); + match &tex.borrow().data { + DataContent::Bytes(b) => Some(b.clone()), + DataContent::Texel(_) => { + log::warn!("Skipping texel-based texture for material '{}'", &name); + None } - }); - - ProcessedMaterial { - name, - texture_bytes, - } - }) - .collect(); - - let processed_meshes: Vec<ProcessedMesh> = scene - .meshes - .into_iter() - .map(|mesh| { - let vertices: Vec<ModelVertex> = mesh - .vertices - .iter() - .enumerate() - .map(|(i, v)| { - let normal = mesh.normals.get(i).copied().unwrap_or(Vector3D { - x: 0.0, - y: 1.0, - z: 0.0, - }); - let tex_coords = mesh - .texture_coords - .get(0) - .and_then(|coords| coords.as_ref().and_then(|vec| vec.get(i))) - .map(|tc| [tc.x, tc.y]) - .unwrap_or([0.0, 0.0]); - ModelVertex { - position: [v.x, v.y, v.z], - tex_coords, - normal: [normal.x, normal.y, normal.z], - } - }) - .collect(); - - let indices: Vec<u32> = mesh - .faces - .iter() - .flat_map(|f| f.0.iter().copied()) - .collect(); - - ProcessedMesh { - name: mesh.name, - vertices, - indices, - material_index: mesh.material_index as usize, + } + }); + + let diffuse_texture = if let Some(bytes) = diffuse_bytes_opt { + Texture::new(graphics, &bytes) + } else { + if !name.is_empty() { + log::warn!( + "Error loading material {}, using default missing texture", + name + ); + } else { + log::warn!("Error loading material, using default missing texture"); } - }) - .collect(); - - Ok((processed_materials, processed_meshes)) - } + Texture::new(graphics, GREY_TEXTURE_BYTES) + }; + + let bind_group = diffuse_texture.bind_group().to_owned(); + materials.push(Material { + name: name, + diffuse_texture, + bind_group, + }); + } - async fn create_gpu_model( - graphics: &Graphics<'_>, - processed_materials: Vec<ProcessedMaterial>, - processed_meshes: Vec<ProcessedMesh>, - label: String, - path: ResourceReference, - ) -> anyhow::Result<Model> { - // Create materials with textures - let materials: Vec<Material> = processed_materials - .into_iter() - .map(|pm| { - let diffuse_texture = if let Some(bytes) = pm.texture_bytes { - Texture::new(graphics, &bytes) - } else { - if !pm.name.is_empty() { - log::warn!( - "Error loading material {}, using default missing texture", - pm.name - ); - } else { - log::warn!("Error loading material, using default missing texture"); + let mut meshes = Vec::new(); + for mesh in &scene.meshes { + let vertices: Vec<ModelVertex> = mesh + .vertices + .iter() + .enumerate() + .map(|(i, v)| { + let normal = mesh.normals.get(i).copied().unwrap_or(Vector3D { + x: 0.0, + y: 1.0, + z: 0.0, + }); + let tex_coords = mesh + .texture_coords + .get(0) + .and_then(|coords| coords.as_ref().and_then(|vec| vec.get(i))) + .map(|tc| [tc.x, tc.y]) + .unwrap_or([0.0, 0.0]); + ModelVertex { + position: [v.x, v.y, v.z], + tex_coords, + normal: [normal.x, normal.y, normal.z], } - Texture::new(graphics, GREY_TEXTURE_BYTES) - }; - - let bind_group = diffuse_texture.bind_group().to_owned(); - Material { - name: pm.name, - diffuse_texture, - bind_group, - } - }) - .collect(); + }) + .collect(); - let meshes: Vec<Mesh> = processed_meshes - .into_iter() - .map(|pm| { - let vertex_buffer = graphics + let indices: Vec<u32> = mesh + .faces + .iter() + .flat_map(|f| f.0.iter().copied()) + .collect(); + + let vertex_buffer = + graphics .state .device .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Vertex Buffer", label)), - contents: bytemuck::cast_slice(&pm.vertices), + label: Some(&format!("{:?} Vertex Buffer", file_name)), + contents: bytemuck::cast_slice(&vertices), usage: wgpu::BufferUsages::VERTEX, }); - - let index_buffer = graphics + let index_buffer = + graphics .state .device .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some(&format!("{} Index Buffer", label)), - contents: bytemuck::cast_slice(&pm.indices), + label: Some(&format!("{:?} Index Buffer", file_name)), + contents: bytemuck::cast_slice(&indices), usage: wgpu::BufferUsages::INDEX, }); - Mesh { - name: pm.name, - vertex_buffer, - index_buffer, - num_elements: pm.indices.len() as u32, - material: pm.material_index, - } - }) - .collect(); - - log::debug!("Successfully loaded model [{}]", label); - Ok(Model { + meshes.push(Mesh { + name: mesh.name.clone(), + vertex_buffer, + index_buffer, + num_elements: indices.len() as u32, + material: mesh.material_index as usize, + }); + } + log::debug!("Successfully loaded model [{:?}]", file_name); + let model = Model { meshes, materials, - label, - path, - }) + label: if let Some(l) = label { + l.to_string() + } else { + String::from(file_name.unwrap().to_str().unwrap().split(".").into_iter().next().unwrap()) + }, + path: ResourceReference::from_path(path).unwrap(), + }; + + MODEL_CACHE.lock().insert(path_str, model.clone()); + log::debug!("Model cached and loaded: {:?}", file_name); + Ok(model) } } @@ -26,9 +26,7 @@ serde.workspace = true winit.workspace = true zip.workspace = true rustyscript.workspace = true -tokio.workspace = true - -wasmtime.workspace = true +wasmer.workspace = true [features] editor = [] @@ -0,0 +1,41 @@ +use std::{collections::{HashMap, HashSet}, time::{Duration, Instant}}; + +use winit::{event::MouseButton, keyboard::KeyCode}; + +#[derive(Clone)] +pub struct InputState { + #[allow(dead_code)] + pub last_key_press_times: HashMap<KeyCode, Instant>, + #[allow(dead_code)] + pub double_press_threshold: Duration, + pub mouse_pos: (f64, f64), + pub mouse_button: HashSet<MouseButton>, + pub pressed_keys: HashSet<KeyCode>, + pub mouse_delta: Option<(f64, f64)>, + pub is_cursor_locked: bool, +} + + +impl Default for InputState { + fn default() -> Self { + Self::new() + } +} + +impl InputState { + pub fn new() -> Self { + Self { + mouse_pos: Default::default(), + mouse_button: Default::default(), + pressed_keys: HashSet::new(), + last_key_press_times: HashMap::new(), + double_press_threshold: Duration::from_millis(300), + mouse_delta: None, + is_cursor_locked: false, + } + } + + pub fn lock_cursor(&mut self, toggle: bool) { + self.is_cursor_locked = toggle; + } +} @@ -1,3 +1,4 @@ +pub mod input; pub mod logging; pub mod camera; pub mod states; @@ -0,0 +1,226 @@ +use dropbear_engine::camera::Camera; +use dropbear_engine::entity::{AdoptedEntity, Transform}; +use dropbear_engine::lighting::{Light, LightComponent}; +use glam::DVec3; +use hecs::World; +use rustyscript::{serde_json, Module, ModuleHandle, Runtime, RuntimeOptions}; +use std::path::PathBuf; +use std::{collections::HashMap, fs}; +use crate::camera::CameraComponent; +use crate::input::InputState; +use crate::states::{EntityNode, ModelProperties, ScriptComponent, PROJECT, SOURCE}; + +/// A trait that describes a module that can be registered. +pub trait ScriptableModule { + /// Registers the functions for the dropbear typescript API + fn register(runtime: &mut Runtime) -> anyhow::Result<()>; + // /// Gathers the information into a serializable format that can be sent over to the + // /// dropbear typescript API + // fn gather(world: &World, entity_id: hecs::Entity); + // /// Fetches the mutated information from the dropbear typescript module and applys it to the world + // fn release(world: &mut World, entity_id: hecs::Entity, data: &serde_json::Value) -> anyhow::Result<()>; +} + +pub const TEMPLATE_SCRIPT: &'static str = include_str!("../../resources/template.ts"); + +pub enum ScriptAction { + AttachScript { + script_path: PathBuf, + script_name: String, + }, + CreateAndAttachScript { + script_path: PathBuf, + script_name: String, + }, + RemoveScript, + EditScript, +} + +pub struct ScriptManager { + pub runtime: Runtime, + compiled_scripts: HashMap<String, ModuleHandle>, + entity_script_data: HashMap<hecs::Entity, serde_json::Value>, +} + +impl ScriptManager { + pub fn new() -> anyhow::Result<Self> { + todo!(); + } + + pub fn load_script_from_source(&mut self, script_name: &String, script_content: &String) -> anyhow::Result<String> { + todo!(); + } + + pub fn load_script(&mut self, script_path: &PathBuf) -> anyhow::Result<String> { + todo!(); + } + + pub fn init_entity_script( + &mut self, + entity_id: hecs::Entity, + script_name: &str, + world: &mut World, + input_state: &InputState, + ) -> anyhow::Result<()> { + log_once::debug_once!("init_entity_script: {} for {:?}", script_name, entity_id); + + if let Some(module) = self.compiled_scripts.get(script_name).cloned() { + todo!(); + Ok(()) + } else { + Err(anyhow::anyhow!("Script '{}' not found", script_name)) + } + } + + pub fn update_entity_script( + &mut self, + entity_id: hecs::Entity, + script_name: &str, + world: &mut World, + input_state: &InputState, + dt: f32, + ) -> anyhow::Result<()> { + log_once::debug_once!("Update entity script name: {}", script_name); + + if let Some(module) = self.compiled_scripts.get(script_name).cloned() { + todo!(); + } else { + log_once::error_once!("Unable to fetch compiled scripts for entity {:?}. Script Name: {}", entity_id, script_name); + } + Ok(()) + } + + pub fn remove_entity_script(&mut self, entity_id: hecs::Entity) { + self.entity_script_data.remove(&entity_id); + } +} + +pub fn move_script_to_src(script_path: &PathBuf) -> anyhow::Result<PathBuf> { + let project_path = { + let project = PROJECT.read(); + project.project_path.clone() + }; + + let src_path = { + let source_config = SOURCE.read(); + source_config.path.clone() + }; + + let scripts_dir = src_path; + fs::create_dir_all(&scripts_dir)?; + + let filename = script_path + .file_name() + .ok_or_else(|| anyhow::anyhow!("Invalid script path: no filename"))?; + + let dest_path = scripts_dir.join(filename); + + if dest_path.exists() { + log::info!( + "Script file already exists at {:?}, returning existing path", + dest_path + ); + return Ok(dest_path); + } + + const MAX_RETRIES: usize = 5; + const RETRY_DELAY_MS: u64 = 60; + + let mut last_err: Option<std::io::Error> = None; + for attempt in 0..=MAX_RETRIES { + match fs::copy(script_path, &dest_path) { + Ok(_) => { + log::info!("Copied script from {:?} to {:?}", script_path, dest_path); + last_err = None; + break; + } + Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { + log::warn!( + "Script file already exists at {:?}, continuing anyway", + dest_path + ); + last_err = None; + break; + } + Err(e) => { + if e.raw_os_error() == Some(32) && attempt < MAX_RETRIES { + log::warn!( + "Sharing violation copying script (attempt {}/{}). Retrying in {}ms: {}", + attempt + 1, + MAX_RETRIES, + RETRY_DELAY_MS, + e + ); + std::thread::sleep(std::time::Duration::from_millis(RETRY_DELAY_MS)); + last_err = Some(e); + continue; + } else { + return Err(e.into()); + } + } + } + } + if let Some(e) = last_err { + return Err(e.into()); + } + + { + let source_config = SOURCE.read(); + source_config.write_to(&project_path)?; + } + + log::info!("Moved script from {:?} to {:?}", script_path, dest_path); + Ok(dest_path) +} + +pub fn convert_entity_to_group( + world: &World, + entity_id: hecs::Entity, +) -> anyhow::Result<EntityNode> { + if let Ok(mut query) = world.query_one::<(&AdoptedEntity, &Transform)>(entity_id) { + if let Some((adopted, _transform)) = query.get() { + let entity_name = adopted.model().label.clone(); + + let script_node = if let Ok(script) = world.get::<&ScriptComponent>(entity_id) { + Some(EntityNode::Script { + name: script.name.clone(), + path: script.path.clone(), + }) + } else { + None + }; + + let entity_node = EntityNode::Entity { + id: entity_id, + name: entity_name.clone(), + }; + + if let Some(script_node) = script_node { + Ok(EntityNode::Group { + name: entity_name, + children: vec![entity_node, script_node], + collapsed: false, + }) + } else { + Ok(entity_node) + } + } else { + Err(anyhow::anyhow!("Failed to get entity components")) + } + } else { + Err(anyhow::anyhow!("Failed to query entity {:?}", entity_id)) + } +} + +pub fn attach_script_to_entity( + world: &mut World, + entity_id: hecs::Entity, + script_component: ScriptComponent, +) -> anyhow::Result<()> { + if let Err(e) = world.insert_one(entity_id, script_component) { + return Err(anyhow::anyhow!("Failed to attach script to entity: {}", e)); + } + + log::info!("Successfully attached script to entity {:?}", entity_id); + Ok(()) +} @@ -1,386 +0,0 @@ -use glam::DVec3; -use rustyscript::{serde_json, Runtime}; -use serde::{Deserialize, Serialize}; - -use crate::{camera::CameraType, scripting::ScriptableModule}; - -impl ScriptableModule for SerializableCamera { - fn register(runtime: &mut Runtime) -> anyhow::Result<()> { - runtime.register_function("moveForward", Self::move_forward)?; - runtime.register_function("moveBackward", Self::move_backward)?; - runtime.register_function("moveLeft", Self::move_left)?; - runtime.register_function("moveRight", Self::move_right)?; - runtime.register_function("moveUp", Self::move_up)?; - runtime.register_function("moveDown", Self::move_down)?; - - // Mouse look - runtime.register_function("trackMouseDelta", Self::track_mouse_delta)?; - - // Camera switching and management - runtime.register_function("switchToCameraByLabel", Self::switch_to_camera_by_label)?; - runtime.register_function("getActiveCameraLabel", Self::get_active_camera_label)?; - runtime.register_function("getAllCameraLabels", Self::get_all_camera_labels)?; - runtime.register_function("getCamerasByType", Self::get_cameras_by_type)?; - - // Multi-camera manipulation - runtime.register_function("manipulateCameraByLabel", Self::manipulate_camera_by_label)?; - runtime.register_function("getCameraByLabel", Self::get_camera_by_label)?; - runtime.register_function("setCameraByLabel", Self::set_camera_by_label)?; - - Ok(()) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct SerializableCamera { - pub label: String, - pub eye: DVec3, - pub target: DVec3, - pub up: DVec3, - pub aspect: f64, - pub fov: f64, - pub near: f64, - pub far: f64, - pub yaw: f64, - pub pitch: f64, - - pub speed: f64, - pub sensitivity: f64, - - pub camera_type: CameraType, -} - -impl SerializableCamera { - /// Moves the camera forward. - /// - /// # Parameters - /// * args[0] - Self as a Camera. The value of the speed can be adjusted - pub fn move_forward(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("moveForward requires 1 arguments".to_string())); - } - - let mut camera: SerializableCamera = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?; - - let forward = (camera.target - camera.eye).normalize(); - camera.eye += forward * camera.speed; - camera.target += forward * camera.speed; - - serde_json::to_value(camera) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e))) - } - - /// Moves the camera back. - /// - /// # Parameters - /// * args[0] - Self as a Camera. The value of the speed can be adjusted - pub fn move_backward(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("moveBackward requires 1 arguments".to_string())); - } - - let mut camera: SerializableCamera = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?; - - let forward = (camera.target - camera.eye).normalize(); - camera.eye -= forward * camera.speed; - camera.target -= forward * camera.speed; - - serde_json::to_value(camera) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e))) - } - - /// Moves the camera left. - /// - /// # Parameters - /// * args[0] - Self as a Camera. The value of the speed can be adjusted - pub fn move_left(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("moveLeft requires 1 arguments".to_string())); - } - - let mut camera: SerializableCamera = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?; - - let forward = (camera.target - camera.eye).normalize(); - let right = camera.up.cross(forward).normalize(); - camera.eye -= right * camera.speed; - camera.target -= right * camera.speed; - - serde_json::to_value(camera) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e))) - } - - /// Moves the camera right. - /// - /// # Parameters - /// * args[0] - Self as a Camera. The value of the speed can be adjusted - pub fn move_right(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("moveRight requires 1 arguments".to_string())); - } - - let mut camera: SerializableCamera = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?; - - let forward = (camera.target - camera.eye).normalize(); - let right = camera.up.cross(forward).normalize(); - camera.eye += right * camera.speed; - camera.target += right * camera.speed; - - serde_json::to_value(camera) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e))) - } - - /// Moves the camera up. - /// - /// # Parameters - /// * args[0] - Self as a Camera. The value of the speed can be adjusted - pub fn move_up(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("moveUp requires 1 arguments".to_string())); - } - - let mut camera: SerializableCamera = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?; - - let up = camera.up.normalize(); - camera.eye += up * camera.speed; - camera.target += up * camera.speed; - - serde_json::to_value(camera) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e))) - } - - /// Moves the camera down. - /// - /// # Parameters - /// * args[0] - Self as a Camera. The value of the speed can be adjusted - pub fn move_down(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("moveDown requires 1 arguments".to_string())); - } - - let mut camera: SerializableCamera = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?; - - let up = camera.up.normalize(); - camera.eye -= up * camera.speed; - camera.target -= up * camera.speed; - - serde_json::to_value(camera) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e))) - } - - /// Sets the tracking of the camera to the mouse delta - /// - /// # Parameters - /// * args[0] - Self as a Camera - /// * args[1] - The dx value of the mouse position as a number - /// * args[2] - The dy value of the mouse position as a number - pub fn track_mouse_delta(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("trackMouseDelta requires 3 arguments (camera, dx, dy)".to_string())); - } - - let mut camera: SerializableCamera = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera: {}", e)))?; - - let dx = args[1].as_f64().ok_or_else(|| { - rustyscript::Error::Runtime("dx must be a number".to_string()) - })?; - - let dy = args[2].as_f64().ok_or_else(|| { - rustyscript::Error::Runtime("dy must be a number".to_string()) - })?; - - camera.yaw += dx * camera.sensitivity; - camera.pitch += dy * camera.sensitivity; - - camera.pitch = camera.pitch.clamp(-89.0_f64.to_radians(), 89.0_f64.to_radians()); - - let direction = DVec3::new( - camera.yaw.cos() * camera.pitch.cos(), - camera.pitch.sin(), - camera.yaw.sin() * camera.pitch.cos(), - ); - camera.target = camera.eye + direction; - - serde_json::to_value(camera) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Camera: {}", e))) - } - - pub fn switch_to_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("switchToCameraByLabel requires 1 argument (label)".to_string())); - } - - let label = args[0].as_str().ok_or_else(|| { - rustyscript::Error::Runtime("Label must be a string".to_string()) - })?; - - let action = CameraAction { - action: "switch_camera".to_string(), - label: Some(label.to_string()), - camera_type: None, - camera_data: None, - property: None, - value: None, - }; - - serde_json::to_value(action) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e))) - } - - /// Get the label of the currently active camera - pub fn get_active_camera_label(_args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - let action = CameraAction { - action: "get_active_camera".to_string(), - label: None, - camera_type: None, - camera_data: None, - property: None, - value: None, - }; - - serde_json::to_value(action) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e))) - } - - /// Get all camera labels in the world - pub fn get_all_camera_labels(_args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - let action = CameraAction { - action: "get_all_cameras".to_string(), - label: None, - camera_type: None, - camera_data: None, - property: None, - value: None, - }; - - serde_json::to_value(action) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e))) - } - - /// Get cameras by type - pub fn get_cameras_by_type(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("getCamerasByType requires 1 argument (camera_type)".to_string())); - } - - let camera_type = args[0].as_str().ok_or_else(|| { - rustyscript::Error::Runtime("Camera type must be a string".to_string()) - })?; - - // Validate camera type - match camera_type { - "Normal" | "Debug" | "Player" => { - let action = CameraAction { - action: "get_cameras_by_type".to_string(), - label: None, - camera_type: Some(camera_type.to_string()), - camera_data: None, - property: None, - value: None, - }; - - serde_json::to_value(action) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e))) - } - _ => Err(rustyscript::Error::Runtime("Invalid camera type. Use 'Normal', 'Debug', or 'Player'".to_string())) - } - } - - /// Manipulate a specific camera by label without switching to it - pub fn manipulate_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("manipulateCameraByLabel requires 3 arguments (label, property, value)".to_string())); - } - - let label = args[0].as_str().ok_or_else(|| { - rustyscript::Error::Runtime("Label must be a string".to_string()) - })?; - - let property = args[1].as_str().ok_or_else(|| { - rustyscript::Error::Runtime("Property must be a string".to_string()) - })?; - - // Validate property - match property { - "position" | "target" | "speed" | "sensitivity" | "fov" | "yaw" | "pitch" => { - let action = CameraAction { - action: "manipulate_camera".to_string(), - label: Some(label.to_string()), - camera_type: None, - camera_data: None, - property: Some(property.to_string()), - value: Some(args[2].clone()), - }; - - serde_json::to_value(action) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e))) - } - _ => Err(rustyscript::Error::Runtime("Invalid property. Use 'position', 'target', 'speed', 'sensitivity', 'fov', 'yaw', or 'pitch'".to_string())) - } - } - - /// Get a camera's data by label - pub fn get_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("getCameraByLabel requires 1 argument (label)".to_string())); - } - - let label = args[0].as_str().ok_or_else(|| { - rustyscript::Error::Runtime("Label must be a string".to_string()) - })?; - - let action = CameraAction { - action: "get_camera_by_label".to_string(), - label: Some(label.to_string()), - camera_type: None, - camera_data: None, - property: None, - value: None, - }; - - serde_json::to_value(action) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e))) - } - - /// Set a camera's complete data by label - pub fn set_camera_by_label(args: &[serde_json::Value]) -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("setCameraByLabel requires 2 arguments (label, camera_data)".to_string())); - } - - let label = args[0].as_str().ok_or_else(|| { - rustyscript::Error::Runtime("Label must be a string".to_string()) - })?; - - let camera_data: SerializableCamera = serde_json::from_value(args[1].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid camera data: {}", e)))?; - - let action = CameraAction { - action: "set_camera_by_label".to_string(), - label: Some(label.to_string()), - camera_type: None, - camera_data: Some(camera_data), - property: None, - value: None, - }; - - serde_json::to_value(action) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize camera action: {}", e))) - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CameraAction { - pub action: String, - pub label: Option<String>, - pub camera_type: Option<String>, - pub camera_data: Option<SerializableCamera>, - pub property: Option<String>, - pub value: Option<serde_json::Value>, -} @@ -1,332 +0,0 @@ -use crate::{scripting::ScriptableModule, states::{ModelProperties, PropertyValue}}; -use rustyscript::{serde_json, Runtime}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Serialize, Deserialize)] -pub struct SerializableModelProperties { - pub properties: std::collections::HashMap<String, serde_json::Value>, -} - -impl From<&ModelProperties> for SerializableModelProperties { - fn from(props: &ModelProperties) -> Self { - let mut properties = std::collections::HashMap::new(); - - for (key, value) in props.custom_properties.iter() { - let json_value = match value { - PropertyValue::String(s) => serde_json::Value::String(s.clone()), - PropertyValue::Int(i) => serde_json::Value::Number(serde_json::Number::from(*i)), - PropertyValue::Float(f) => serde_json::Value::Number( - serde_json::Number::from_f64(*f as f64).unwrap_or(serde_json::Number::from(0)) - ), - PropertyValue::Bool(b) => serde_json::Value::Bool(*b), - PropertyValue::Vec3(v) => serde_json::Value::Array(vec![ - serde_json::Value::Number(serde_json::Number::from_f64(v[0] as f64).unwrap()), - serde_json::Value::Number(serde_json::Number::from_f64(v[1] as f64).unwrap()), - serde_json::Value::Number(serde_json::Number::from_f64(v[2] as f64).unwrap()), - ]), - }; - properties.insert(key.clone(), json_value); - } - - Self { properties } - } -} - -impl SerializableModelProperties { - pub fn to_model_properties(&self) -> ModelProperties { - let mut props = ModelProperties::default(); - - for (key, value) in &self.properties { - let property_value = match value { - serde_json::Value::String(s) => PropertyValue::String(s.clone()), - serde_json::Value::Number(n) => { - if let Some(i) = n.as_i64() { - PropertyValue::Int(i) - } else if let Some(f) = n.as_f64() { - PropertyValue::Float(f) - } else { - continue; - } - }, - serde_json::Value::Bool(b) => PropertyValue::Bool(*b), - serde_json::Value::Array(arr) => { - if arr.len() == 3 { - if let (Some(x), Some(y), Some(z)) = ( - arr[0].as_f64(), - arr[1].as_f64(), - arr[2].as_f64(), - ) { - PropertyValue::Vec3([x as f32, y as f32, z as f32]) - } else { - continue; - } - } else { - continue; - } - }, - _ => continue, - }; - props.set_property(key.clone(), property_value); - } - - props - } -} - -impl ScriptableModule for ModelProperties { - fn register(runtime: &mut Runtime) -> anyhow::Result<()> { - runtime.register_function("getProperty", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("getProperty requires 2 arguments (properties, key)".to_string())); - } - - let props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = props.properties.get(key) - .cloned() - .unwrap_or(serde_json::Value::Null); - - Ok(value) - })?; - - // Set property (string) - runtime.register_function("setPropertyString", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("setPropertyString requires 3 arguments (properties, key, value)".to_string())); - } - - let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = args[2].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Value must be a string".to_string()))?; - - props.properties.insert(key.to_string(), serde_json::Value::String(value.to_string())); - - serde_json::to_value(props) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e))) - })?; - - // Set property (int) - runtime.register_function("setPropertyInt", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("setPropertyInt requires 3 arguments (properties, key, value)".to_string())); - } - - let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = args[2].as_i64() - .ok_or_else(|| rustyscript::Error::Runtime("Value must be an integer".to_string()))?; - - props.properties.insert(key.to_string(), serde_json::Value::Number(serde_json::Number::from(value))); - - serde_json::to_value(props) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e))) - })?; - - // Set property (float) - runtime.register_function("setPropertyFloat", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("setPropertyFloat requires 3 arguments (properties, key, value)".to_string())); - } - - let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = args[2].as_f64() - .ok_or_else(|| rustyscript::Error::Runtime("Value must be a number".to_string()))?; - - props.properties.insert(key.to_string(), serde_json::Value::Number( - serde_json::Number::from_f64(value).unwrap_or(serde_json::Number::from(0)) - )); - - serde_json::to_value(props) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e))) - })?; - - // Set property (bool) - runtime.register_function("setPropertyBool", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("setPropertyBool requires 3 arguments (properties, key, value)".to_string())); - } - - let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = args[2].as_bool() - .ok_or_else(|| rustyscript::Error::Runtime("Value must be a boolean".to_string()))?; - - props.properties.insert(key.to_string(), serde_json::Value::Bool(value)); - - serde_json::to_value(props) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e))) - })?; - - // Set property (Vec3) - runtime.register_function("setPropertyVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("setPropertyVec3 requires 3 arguments (properties, key, value)".to_string())); - } - - let mut props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = args[2].as_array() - .ok_or_else(|| rustyscript::Error::Runtime("Value must be an array".to_string()))?; - - if value.len() != 3 { - return Err(rustyscript::Error::Runtime("Vec3 array must have exactly 3 elements".to_string())); - } - - let x = value[0].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?; - let y = value[1].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?; - let z = value[2].as_f64().ok_or_else(|| rustyscript::Error::Runtime("Vec3 elements must be numbers".to_string()))?; - - let vec3_array = serde_json::Value::Array(vec![ - serde_json::Value::Number(serde_json::Number::from_f64(x).unwrap()), - serde_json::Value::Number(serde_json::Number::from_f64(y).unwrap()), - serde_json::Value::Number(serde_json::Number::from_f64(z).unwrap()), - ]); - - props.properties.insert(key.to_string(), vec3_array); - - serde_json::to_value(props) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize properties: {}", e))) - })?; - - // Typed getters - runtime.register_function("getString", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("getString requires 2 arguments (properties, key)".to_string())); - } - - let props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = props.properties.get(key) - .and_then(|v| v.as_str()) - .unwrap_or("") - .to_string(); - - Ok(serde_json::Value::String(value)) - })?; - - runtime.register_function("getInt", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("getInt requires 2 arguments (properties, key)".to_string())); - } - - let props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = props.properties.get(key) - .and_then(|v| v.as_i64()) - .unwrap_or(0); - - Ok(serde_json::Value::Number(serde_json::Number::from(value))) - })?; - - runtime.register_function("getFloat", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("getFloat requires 2 arguments (properties, key)".to_string())); - } - - let props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = props.properties.get(key) - .and_then(|v| v.as_f64()) - .unwrap_or(0.0); - - Ok(serde_json::Value::Number(serde_json::Number::from_f64(value).unwrap())) - })?; - - runtime.register_function("getBool", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("getBool requires 2 arguments (properties, key)".to_string())); - } - - let props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = props.properties.get(key) - .and_then(|v| v.as_bool()) - .unwrap_or(false); - - Ok(serde_json::Value::Bool(value)) - })?; - - runtime.register_function("getVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("getVec3 requires 2 arguments (properties, key)".to_string())); - } - - let props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let value = props.properties.get(key) - .and_then(|v| v.as_array()) - .cloned() - .unwrap_or_else(|| vec![ - serde_json::Value::Number(serde_json::Number::from(0)), - serde_json::Value::Number(serde_json::Number::from(0)), - serde_json::Value::Number(serde_json::Number::from(0)), - ]); - - Ok(serde_json::Value::Array(value)) - })?; - - runtime.register_function("hasProperty", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("hasProperty requires 2 arguments (properties, key)".to_string())); - } - - let props: SerializableModelProperties = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid properties: {}", e)))?; - - let key = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key must be a string".to_string()))?; - - let has_property = props.properties.contains_key(key); - Ok(serde_json::Value::Bool(has_property)) - })?; - - log::info!("[Script] Initialised entity custom properties module"); - Ok(()) - } -} @@ -1,288 +0,0 @@ -use std::{collections::{HashMap, HashSet}, time::{Duration, Instant}}; - -use rustyscript::{serde_json, Runtime}; -use serde::{Deserialize, Serialize}; -use winit::{event::MouseButton, keyboard::KeyCode}; - -use crate::scripting::ScriptableModule; - -#[derive(Clone)] -pub struct InputState { - #[allow(dead_code)] - pub last_key_press_times: HashMap<KeyCode, Instant>, - #[allow(dead_code)] - pub double_press_threshold: Duration, - pub mouse_pos: (f64, f64), - pub mouse_button: HashSet<MouseButton>, - pub pressed_keys: HashSet<KeyCode>, - pub mouse_delta: Option<(f64, f64)>, - pub is_cursor_locked: bool, -} - -#[derive(Clone, Serialize, Deserialize)] -pub struct SerializableInputState { - pub mouse_pos: (f64, f64), - pub pressed_keys: Vec<String>, - pub mouse_delta: Option<(f64, f64)>, - pub is_cursor_locked: bool, -} - -impl From<&InputState> for SerializableInputState { - fn from(input_state: &InputState) -> Self { - Self { - mouse_pos: input_state.mouse_pos, - pressed_keys: input_state.pressed_keys.iter() - .filter_map(|key| keycode_to_string(*key)) - .collect(), - mouse_delta: input_state.mouse_delta, - is_cursor_locked: input_state.is_cursor_locked, - } - } -} - -impl Default for InputState { - fn default() -> Self { - Self::new() - } -} - -impl ScriptableModule for InputState { - fn register(runtime: &mut Runtime) -> anyhow::Result<()> { - runtime.register_function("isKeyPressed", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("isKeyPressed requires 2 arguments (inputState, keyCode)".to_string())); - } - - let input_state: SerializableInputState = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?; - - let key_code_str = args[1].as_str() - .ok_or_else(|| rustyscript::Error::Runtime("Key code must be a string".to_string()))?; - - let is_pressed = input_state.pressed_keys.contains(&key_code_str.to_string()); - Ok(serde_json::Value::Bool(is_pressed)) - })?; - - runtime.register_function("getMouseX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("getMouseX requires 1 argument (inputState)".to_string())); - } - - let input_state: SerializableInputState = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?; - - Ok(serde_json::Value::Number(serde_json::Number::from_f64(input_state.mouse_pos.0).unwrap())) - })?; - - runtime.register_function("getMouseY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("getMouseY requires 1 argument (inputState)".to_string())); - } - - let input_state: SerializableInputState = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?; - - Ok(serde_json::Value::Number(serde_json::Number::from_f64(input_state.mouse_pos.1).unwrap())) - })?; - - runtime.register_function("getMouseDeltaX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("getMouseDeltaX requires 1 argument (inputState)".to_string())); - } - - let input_state: SerializableInputState = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?; - - let delta_x = input_state.mouse_delta.map(|d| d.0).unwrap_or(0.0); - Ok(serde_json::Value::Number(serde_json::Number::from_f64(delta_x).unwrap())) - })?; - - runtime.register_function("getMouseDeltaY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("getMouseDeltaY requires 1 argument (inputState)".to_string())); - } - - let input_state: SerializableInputState = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid input state: {}", e)))?; - - let delta_y = input_state.mouse_delta.map(|d| d.1).unwrap_or(0.0); - Ok(serde_json::Value::Number(serde_json::Number::from_f64(delta_y).unwrap())) - })?; - - runtime.register_function("lockCursor", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("lockCursor requires 1 argument (locked)".to_string())); - } - - let _locked = args[0].as_bool() - .ok_or_else(|| rustyscript::Error::Runtime("Locked parameter must be a boolean".to_string()))?; - - // This function would need to communicate back to the engine to actually lock the cursor - // For now, just return success - Ok(serde_json::Value::Bool(true)) - })?; - - log::info!("[Script] Initialised input module"); - Ok(()) - } -} - -impl InputState { - pub fn new() -> Self { - Self { - mouse_pos: Default::default(), - mouse_button: Default::default(), - pressed_keys: HashSet::new(), - last_key_press_times: HashMap::new(), - double_press_threshold: Duration::from_millis(300), - mouse_delta: None, - is_cursor_locked: false, - } - } - - pub fn lock_cursor(&mut self, toggle: bool) { - self.is_cursor_locked = toggle; - } -} - -/// Helper function to convert string to KeyCode -#[allow(dead_code)] -fn string_to_keycode(key_str: &str) -> Option<KeyCode> { - match key_str { - "KeyA" => Some(KeyCode::KeyA), - "KeyB" => Some(KeyCode::KeyB), - "KeyC" => Some(KeyCode::KeyC), - "KeyD" => Some(KeyCode::KeyD), - "KeyE" => Some(KeyCode::KeyE), - "KeyF" => Some(KeyCode::KeyF), - "KeyG" => Some(KeyCode::KeyG), - "KeyH" => Some(KeyCode::KeyH), - "KeyI" => Some(KeyCode::KeyI), - "KeyJ" => Some(KeyCode::KeyJ), - "KeyK" => Some(KeyCode::KeyK), - "KeyL" => Some(KeyCode::KeyL), - "KeyM" => Some(KeyCode::KeyM), - "KeyN" => Some(KeyCode::KeyN), - "KeyO" => Some(KeyCode::KeyO), - "KeyP" => Some(KeyCode::KeyP), - "KeyQ" => Some(KeyCode::KeyQ), - "KeyR" => Some(KeyCode::KeyR), - "KeyS" => Some(KeyCode::KeyS), - "KeyT" => Some(KeyCode::KeyT), - "KeyU" => Some(KeyCode::KeyU), - "KeyV" => Some(KeyCode::KeyV), - "KeyW" => Some(KeyCode::KeyW), - "KeyX" => Some(KeyCode::KeyX), - "KeyY" => Some(KeyCode::KeyY), - "KeyZ" => Some(KeyCode::KeyZ), - "Space" => Some(KeyCode::Space), - "ShiftLeft" => Some(KeyCode::ShiftLeft), - "ShiftRight" => Some(KeyCode::ShiftRight), - "ControlLeft" => Some(KeyCode::ControlLeft), - "ControlRight" => Some(KeyCode::ControlRight), - "AltLeft" => Some(KeyCode::AltLeft), - "AltRight" => Some(KeyCode::AltRight), - "Escape" => Some(KeyCode::Escape), - "Enter" => Some(KeyCode::Enter), - "Tab" => Some(KeyCode::Tab), - "ArrowUp" => Some(KeyCode::ArrowUp), - "ArrowDown" => Some(KeyCode::ArrowDown), - "ArrowLeft" => Some(KeyCode::ArrowLeft), - "ArrowRight" => Some(KeyCode::ArrowRight), - "Digit0" => Some(KeyCode::Digit0), - "Digit1" => Some(KeyCode::Digit1), - "Digit2" => Some(KeyCode::Digit2), - "Digit3" => Some(KeyCode::Digit3), - "Digit4" => Some(KeyCode::Digit4), - "Digit5" => Some(KeyCode::Digit5), - "Digit6" => Some(KeyCode::Digit6), - "Digit7" => Some(KeyCode::Digit7), - "Digit8" => Some(KeyCode::Digit8), - "Digit9" => Some(KeyCode::Digit9), - "F1" => Some(KeyCode::F1), - "F2" => Some(KeyCode::F2), - "F3" => Some(KeyCode::F3), - "F4" => Some(KeyCode::F4), - "F5" => Some(KeyCode::F5), - "F6" => Some(KeyCode::F6), - "F7" => Some(KeyCode::F7), - "F8" => Some(KeyCode::F8), - "F9" => Some(KeyCode::F9), - "F10" => Some(KeyCode::F10), - "F11" => Some(KeyCode::F11), - "F12" => Some(KeyCode::F12), - _ => None, - } -} - -/// Helper function to convert KeyCode to string -fn keycode_to_string(key_code: KeyCode) -> Option<String> { - match key_code { - KeyCode::KeyA => Some("KeyA".to_string()), - KeyCode::KeyB => Some("KeyB".to_string()), - KeyCode::KeyC => Some("KeyC".to_string()), - KeyCode::KeyD => Some("KeyD".to_string()), - KeyCode::KeyE => Some("KeyE".to_string()), - KeyCode::KeyF => Some("KeyF".to_string()), - KeyCode::KeyG => Some("KeyG".to_string()), - KeyCode::KeyH => Some("KeyH".to_string()), - KeyCode::KeyI => Some("KeyI".to_string()), - KeyCode::KeyJ => Some("KeyJ".to_string()), - KeyCode::KeyK => Some("KeyK".to_string()), - KeyCode::KeyL => Some("KeyL".to_string()), - KeyCode::KeyM => Some("KeyM".to_string()), - KeyCode::KeyN => Some("KeyN".to_string()), - KeyCode::KeyO => Some("KeyO".to_string()), - KeyCode::KeyP => Some("KeyP".to_string()), - KeyCode::KeyQ => Some("KeyQ".to_string()), - KeyCode::KeyR => Some("KeyR".to_string()), - KeyCode::KeyS => Some("KeyS".to_string()), - KeyCode::KeyT => Some("KeyT".to_string()), - KeyCode::KeyU => Some("KeyU".to_string()), - KeyCode::KeyV => Some("KeyV".to_string()), - KeyCode::KeyW => Some("KeyW".to_string()), - KeyCode::KeyX => Some("KeyX".to_string()), - KeyCode::KeyY => Some("KeyY".to_string()), - KeyCode::KeyZ => Some("KeyZ".to_string()), - KeyCode::Space => Some("Space".to_string()), - KeyCode::ShiftLeft => Some("ShiftLeft".to_string()), - KeyCode::ShiftRight => Some("ShiftRight".to_string()), - KeyCode::ControlLeft => Some("ControlLeft".to_string()), - KeyCode::ControlRight => Some("ControlRight".to_string()), - KeyCode::AltLeft => Some("AltLeft".to_string()), - KeyCode::AltRight => Some("AltRight".to_string()), - KeyCode::Escape => Some("Escape".to_string()), - KeyCode::Enter => Some("Enter".to_string()), - KeyCode::Tab => Some("Tab".to_string()), - KeyCode::ArrowUp => Some("ArrowUp".to_string()), - KeyCode::ArrowDown => Some("ArrowDown".to_string()), - KeyCode::ArrowLeft => Some("ArrowLeft".to_string()), - KeyCode::ArrowRight => Some("ArrowRight".to_string()), - KeyCode::Digit0 => Some("Digit0".to_string()), - KeyCode::Digit1 => Some("Digit1".to_string()), - KeyCode::Digit2 => Some("Digit2".to_string()), - KeyCode::Digit3 => Some("Digit3".to_string()), - KeyCode::Digit4 => Some("Digit4".to_string()), - KeyCode::Digit5 => Some("Digit5".to_string()), - KeyCode::Digit6 => Some("Digit6".to_string()), - KeyCode::Digit7 => Some("Digit7".to_string()), - KeyCode::Digit8 => Some("Digit8".to_string()), - KeyCode::Digit9 => Some("Digit9".to_string()), - KeyCode::F1 => Some("F1".to_string()), - KeyCode::F2 => Some("F2".to_string()), - KeyCode::F3 => Some("F3".to_string()), - KeyCode::F4 => Some("F4".to_string()), - KeyCode::F5 => Some("F5".to_string()), - KeyCode::F6 => Some("F6".to_string()), - KeyCode::F7 => Some("F7".to_string()), - KeyCode::F8 => Some("F8".to_string()), - KeyCode::F9 => Some("F9".to_string()), - KeyCode::F10 => Some("F10".to_string()), - KeyCode::F11 => Some("F11".to_string()), - KeyCode::F12 => Some("F12".to_string()), - _ => None, - } -} - -#[derive(Clone, Copy)] -pub struct Key(pub KeyCode); @@ -1,11 +0,0 @@ -use rustyscript::Runtime; - -use crate::scripting::ScriptableModule; - -pub struct Lighting; - -impl ScriptableModule for Lighting { - fn register(_runtime: &mut Runtime) -> anyhow::Result<()> { - Ok(()) - } -} @@ -1,166 +0,0 @@ -use dropbear_engine::entity::Transform; -use glam::{DQuat, DVec3}; -use rustyscript::{serde_json, Runtime}; - -use crate::scripting::ScriptableModule; - -pub struct Math; - -impl ScriptableModule for Math { - fn register(runtime: &mut Runtime) -> anyhow::Result<()> { - runtime.register_function("createTransform", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - let transform = Transform::new(); - serde_json::to_value(transform) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e))) - })?; - - runtime.register_function("transformTranslate", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("transformTranslate requires 2 arguments".to_string())); - } - - let mut transform: Transform = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?; - - let translation = if let Some(array) = args[1].as_array() { - if array.len() != 3 { - return Err(rustyscript::Error::Runtime("Translation array must have 3 elements".to_string())); - } - DVec3::new( - array[0].as_f64().unwrap_or(0.0), - array[1].as_f64().unwrap_or(0.0), - array[2].as_f64().unwrap_or(0.0), - ) - } else { - return Err(rustyscript::Error::Runtime("Translation must be an array".to_string())); - }; - - transform.position += translation; - serde_json::to_value(transform) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e))) - })?; - - runtime.register_function("transformRotateX", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("transformRotateX requires 2 arguments".to_string())); - } - - let mut transform: Transform = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?; - - let angle = args[1].as_f64().unwrap_or(0.0); - let rotation = DQuat::from_rotation_x(angle); - transform.rotation = rotation * transform.rotation; - - serde_json::to_value(transform) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e))) - })?; - - runtime.register_function("transformRotateY", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("transformRotateY requires 2 arguments".to_string())); - } - - let mut transform: Transform = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?; - - let angle = args[1].as_f64().unwrap_or(0.0); - let rotation = DQuat::from_rotation_y(angle); - transform.rotation = rotation * transform.rotation; - - serde_json::to_value(transform) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e))) - })?; - - runtime.register_function("transformRotateZ", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("transformRotateZ requires 2 arguments".to_string())); - } - - let mut transform: Transform = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?; - - let angle = args[1].as_f64().unwrap_or(0.0); - let rotation = DQuat::from_rotation_z(angle); - transform.rotation = rotation * transform.rotation; - - serde_json::to_value(transform) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e))) - })?; - - runtime.register_function("transformScale", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 2 { - return Err(rustyscript::Error::Runtime("transformScale requires 2 arguments".to_string())); - } - - let mut transform: Transform = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?; - - let scale = if let Some(num) = args[1].as_f64() { - DVec3::splat(num) - } else if let Some(array) = args[1].as_array() { - if array.len() != 3 { - return Err(rustyscript::Error::Runtime("Scale array must have 3 elements".to_string())); - } - DVec3::new( - array[0].as_f64().unwrap_or(1.0), - array[1].as_f64().unwrap_or(1.0), - array[2].as_f64().unwrap_or(1.0), - ) - } else { - return Err(rustyscript::Error::Runtime("Scale must be a number or array".to_string())); - }; - - transform.scale *= scale; - serde_json::to_value(transform) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Transform: {}", e))) - })?; - - // this shouldn't be here as there is no need for a matrix... - runtime.register_function("transformMatrix", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 1 { - return Err(rustyscript::Error::Runtime("transformMatrix requires 1 argument".to_string())); - } - - let transform: Transform = serde_json::from_value(args[0].clone()) - .map_err(|e| rustyscript::Error::Runtime(format!("Invalid transform: {}", e)))?; - - let matrix = transform.matrix(); - serde_json::to_value(matrix) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize matrix: {}", e))) - })?; - - runtime.register_function("createVec3", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - let x = args.get(0).and_then(|v| v.as_f64()).unwrap_or(0.0); - let y = args.get(1).and_then(|v| v.as_f64()).unwrap_or(0.0); - let z = args.get(2).and_then(|v| v.as_f64()).unwrap_or(0.0); - - let vec3 = DVec3::new(x, y, z); - serde_json::to_value(vec3) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Vec3: {}", e))) - })?; - - runtime.register_function("createQuatIdentity", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - let quat = DQuat::IDENTITY; - serde_json::to_value(quat) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Quaternion: {}", e))) - })?; - - runtime.register_function("createQuatFromEuler", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - if args.len() != 3 { - return Err(rustyscript::Error::Runtime("createQuatFromEuler requires 3 arguments".to_string())); - } - - let x = args[0].as_f64().unwrap_or(0.0); - let y = args[1].as_f64().unwrap_or(0.0); - let z = args[2].as_f64().unwrap_or(0.0); - - let quat = DQuat::from_euler(glam::EulerRot::XYZ, x, y, z); - serde_json::to_value(quat) - .map_err(|e| rustyscript::Error::Runtime(format!("Failed to serialize Quaternion: {}", e))) - })?; - - log::info!("[Script] Initialised math module"); - Ok(()) - } -} @@ -1,834 +0,0 @@ -pub mod camera; -pub mod entity; -pub mod math; -pub mod input; -pub mod lighting; - -use dropbear_engine::camera::Camera; -use dropbear_engine::entity::{AdoptedEntity, Transform}; -use dropbear_engine::lighting::{Light, LightComponent}; -use glam::DVec3; -use hecs::World; -use rustyscript::{serde_json, Module, ModuleHandle, Runtime, RuntimeOptions}; -use std::path::PathBuf; -use std::{collections::HashMap, fs}; -use crate::camera::CameraComponent; -use crate::states::{EntityNode, ModelProperties, ScriptComponent, PROJECT, SOURCE}; - -/// A trait that describes a module that can be registered. -pub trait ScriptableModule { - /// Registers the functions for the dropbear typescript API - fn register(runtime: &mut Runtime) -> anyhow::Result<()>; - // /// Gathers the information into a serializable format that can be sent over to the - // /// dropbear typescript API - // fn gather(world: &World, entity_id: hecs::Entity); - // /// Fetches the mutated information from the dropbear typescript module and applys it to the world - // fn release(world: &mut World, entity_id: hecs::Entity, data: &serde_json::Value) -> anyhow::Result<()>; -} - -pub const TEMPLATE_SCRIPT: &'static str = include_str!("../../../resources/template.ts"); - -pub enum ScriptAction { - AttachScript { - script_path: PathBuf, - script_name: String, - }, - CreateAndAttachScript { - script_path: PathBuf, - script_name: String, - }, - RemoveScript, - EditScript, -} - -pub struct ScriptManager { - pub runtime: Runtime, - compiled_scripts: HashMap<String, ModuleHandle>, - entity_script_data: HashMap<hecs::Entity, serde_json::Value>, -} - -impl ScriptManager { - pub async fn new() -> anyhow::Result<Self> { - // let mut runtime = Runtime::new(RuntimeOptions::default())?; - let mut runtime = Runtime::with_tokio_runtime_handle(RuntimeOptions::default(), tokio::runtime::Handle::try_current()?)?; - let dropbear_content = include_str!("../../../resources/dropbear.ts"); - let dropbear_module = Module::new("./dropbear.ts", dropbear_content); - runtime.load_module(&dropbear_module)?; - log::debug!("Loaded dropbear module"); - - let mut result = Self { - runtime, - compiled_scripts: HashMap::new(), - entity_script_data: HashMap::new(), - }; - - result = result - .register_module::<input::InputState>()? - .register_module::<math::Math>()? - .register_module::<ModelProperties>()? - .register_module::<camera::SerializableCamera>()? - .register_module::<lighting::Lighting>()?; - - // Register utility functions - result.runtime.register_function("log", |args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - let msg = args.get(0) - .and_then(|v| v.as_str()) - .unwrap_or("undefined"); - println!("[Script] {}", msg); - Ok(serde_json::Value::Null) - })?; - - result.runtime.register_function("time", |_args: &[serde_json::Value]| -> Result<serde_json::Value, rustyscript::Error> { - let time = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_secs_f64(); - Ok(serde_json::Value::Number(serde_json::Number::from_f64(time).unwrap())) - })?; - log::debug!("Initialised ScriptManager"); - Ok(result) - } - - /// Allows for the registering of other modules to use with the dropbear typescript - /// API - /// - /// # Parameters - /// * A turbofish to a struct that uses the ScriptableModule trait - /// - /// # Examples - /// ```rust - /// use eucalyptus_core::scripting::{camera, input, lighting, ScriptManager}; - /// let script_manager = ScriptManager::new()? - /// .register_module::<input::InputState>()? - /// .register_module::<camera::SerializableCamera>()? - /// .register_module::<lighting::Lighting>()?; - /// ``` - pub fn register_module<T: ScriptableModule>(mut self) -> anyhow::Result<Self> { - T::register(&mut self.runtime)?; - Ok(self) - } - - pub fn init_entity_script( - &mut self, - entity_id: hecs::Entity, - script_name: &str, - world: &mut World, - input_state: &input::InputState, - ) -> anyhow::Result<()> { - log_once::debug_once!("init_entity_script: {} for {:?}", script_name, entity_id); - - if let Some(module) = self.compiled_scripts.get(script_name).cloned() { - // construct RawSceneData-like payload - let mut scene_map = serde_json::Map::new(); - - // entities array - let mut entities_arr: Vec<serde_json::Value> = Vec::new(); - for (id, (adopted, transform)) in world.query::<(&AdoptedEntity, &Transform)>().iter() { - // try to get ModelProperties for this entity - let props_value = if let Ok(mut pq) = world.query_one::<&ModelProperties>(id) { - if let Some(p) = pq.get() { - serde_json::to_value(p)? - } else { - serde_json::Value::Object(serde_json::Map::new()) - } - } else { - serde_json::Value::Object(serde_json::Map::new()) - }; - - let mut ent_obj = serde_json::Map::new(); - ent_obj.insert("label".to_string(), serde_json::Value::String(adopted.label().clone())); - - // properties object expected shape: { custom_properties: Record<string, any> } or full ModelProperties - ent_obj.insert("properties".to_string(), props_value); - - ent_obj.insert("transform".to_string(), serde_json::to_value(transform)?); - - entities_arr.push(serde_json::Value::Object(ent_obj)); - } - scene_map.insert("entities".to_string(), serde_json::Value::Array(entities_arr)); - - // cameras snapshot (keep minimal fields) - let mut cameras_arr: Vec<serde_json::Value> = Vec::new(); - for (_id, (cam, _comp)) in world.query::<(&Camera, &CameraComponent)>().iter() { - cameras_arr.push(serde_json::json!({ - "label": cam.label, - "data": { - "eye": cam.eye.to_array(), - "target": cam.target.to_array(), - "up": cam.up.to_array(), - "aspect": cam.aspect, - "fov": cam.fov_y, - "near": cam.znear, - "far": cam.zfar, - "yaw": cam.yaw, - "pitch": cam.pitch, - "speed": cam.speed, - "sensitivity": cam.sensitivity - } - })); - } - scene_map.insert("cameras".to_string(), serde_json::Value::Array(cameras_arr)); - - // lights snapshot (label only) - let mut lights_arr: Vec<serde_json::Value> = Vec::new(); - for (_id, (_transform, _light_comp, light)) in world.query::<(&Transform, &LightComponent, &Light)>().iter() { - lights_arr.push(serde_json::json!({ - "label": light.label(), - })); - } - scene_map.insert("lights".to_string(), serde_json::Value::Array(lights_arr)); - - // current entity label (if available) - if let Ok(mut q) = world.query_one::<&AdoptedEntity>(entity_id) { - if let Some(adopted) = q.get() { - scene_map.insert("current_entity".to_string(), serde_json::Value::String(adopted.label().clone())); - } - } - - // input state - let serializable_input = input::SerializableInputState::from(input_state); - let mut payload = serde_json::Map::new(); - payload.insert("scene".to_string(), serde_json::Value::Object(scene_map)); - payload.insert("input".to_string(), serde_json::to_value(&serializable_input)?); - - let script_data_value = serde_json::Value::Object(payload); - - // call onLoad (module scripts call dropbear.start(s) and return dropbear.end()) - match self.runtime.call_function::<serde_json::Value>(Some(&module), "onLoad", &vec![script_data_value.clone()]) { - Ok(result) => { - log::debug!("Called onLoad for entity {:?}", entity_id); - // apply whatever the script returned (expect Partial RawSceneData shape) - let _ = self.apply_script_data_to_world(entity_id, &result, world); - // store initial data (script-managed) - self.entity_script_data.insert(entity_id, result); - } - Err(e) => { - log::warn!("onLoad missing/failed for entity {:?}: {}", entity_id, e); - // store the payload we sent so future updates have context - self.entity_script_data.insert(entity_id, script_data_value); - } - } - - Ok(()) - } else { - Err(anyhow::anyhow!("Script '{}' not found", script_name)) - } - } - - pub fn update_entity_script( - &mut self, - entity_id: hecs::Entity, - script_name: &str, - world: &mut World, - input_state: &input::InputState, - dt: f32, - ) -> anyhow::Result<()> { - log_once::debug_once!("Update entity script name: {}", script_name); - - if let Some(module) = self.compiled_scripts.get(script_name).cloned() { - // Build the same RawSceneData-like payload as init - let mut scene_map = serde_json::Map::new(); - - let mut entities_arr: Vec<serde_json::Value> = Vec::new(); - for (id, (adopted, transform)) in world.query::<(&AdoptedEntity, &Transform)>().iter() { - let props_value = if let Ok(mut pq) = world.query_one::<&ModelProperties>(id) { - if let Some(p) = pq.get() { - serde_json::to_value(p)? - } else { - serde_json::Value::Object(serde_json::Map::new()) - } - } else { - serde_json::Value::Object(serde_json::Map::new()) - }; - - let mut ent_obj = serde_json::Map::new(); - ent_obj.insert("label".to_string(), serde_json::Value::String(adopted.label().clone())); - ent_obj.insert("properties".to_string(), props_value); - ent_obj.insert("transform".to_string(), serde_json::to_value(transform)?); - entities_arr.push(serde_json::Value::Object(ent_obj)); - } - scene_map.insert("entities".to_string(), serde_json::Value::Array(entities_arr)); - - // cameras - let mut cameras_arr: Vec<serde_json::Value> = Vec::new(); - for (_id, (cam, _comp)) in world.query::<(&Camera, &CameraComponent)>().iter() { - cameras_arr.push(serde_json::json!({ - "label": cam.label, - "data": { - "eye": cam.eye.to_array(), - "target": cam.target.to_array(), - "up": cam.up.to_array(), - "aspect": cam.aspect, - "fov": cam.fov_y, - "near": cam.znear, - "far": cam.zfar, - "yaw": cam.yaw, - "pitch": cam.pitch, - "speed": cam.speed, - "sensitivity": cam.sensitivity - } - })); - } - scene_map.insert("cameras".to_string(), serde_json::Value::Array(cameras_arr)); - - // lights - let mut lights_arr: Vec<serde_json::Value> = Vec::new(); - for (_id, (_transform, _light_comp, light)) in world.query::<(&Transform, &LightComponent, &Light)>().iter() { - lights_arr.push(serde_json::json!({ - "label": light.label(), - })); - } - scene_map.insert("lights".to_string(), serde_json::Value::Array(lights_arr)); - - // current entity label - if let Ok(mut q) = world.query_one::<&AdoptedEntity>(entity_id) { - if let Some(adopted) = q.get() { - scene_map.insert("current_entity".to_string(), serde_json::Value::String(adopted.label().clone())); - } - } - - let serializable_input = input::SerializableInputState::from(input_state); - let mut payload = serde_json::Map::new(); - payload.insert("scene".to_string(), serde_json::Value::Object(scene_map)); - payload.insert("input".to_string(), serde_json::to_value(&serializable_input)?); - - let script_data_value = serde_json::Value::Object(payload); - let dt_value = serde_json::Value::Number(serde_json::Number::from_f64(dt as f64).unwrap()); - - // call onUpdate(s, dt) - match self.runtime.call_function::<serde_json::Value>(Some(&module), "onUpdate", &vec![script_data_value.clone(), dt_value]) { - Ok(result) => { - log::trace!("Called update for entity {:?}", entity_id); - // delegate applying of returned data to the helper - let _ = self.apply_script_data_to_world(entity_id, &result, world); - // store latest returned data - self.entity_script_data.insert(entity_id, result); - } - Err(e) => { - log_once::error_once!("Script execution error for entity {:?}: {}", entity_id, e); - } - } - } else { - log_once::error_once!("Unable to fetch compiled scripts for entity {:?}. Script Name: {}", entity_id, script_name); - } - Ok(()) - } - - fn apply_script_data_to_world( - &self, - entity_id: hecs::Entity, - script_data: &serde_json::Value, - world: &mut World, - ) -> anyhow::Result<()> { - if let Some(obj) = script_data.as_object() { - if let Some(entities_val) = obj.get("entities").and_then(|v| v.as_array()) { - for ent in entities_val { - if let Some(ent_obj) = ent.as_object() { - if let Some(label) = ent_obj.get("label").and_then(|l| l.as_str()) { - if let Some(target_entity) = world - .query_mut::<(&AdoptedEntity,)>() - .into_iter() - .find_map(|(e, (adopted,))| { - if adopted.label() == label { - Some(e) - } else { - None - } - }) { - if let Some(transform_val) = ent_obj.get("transform") { - if let Ok(new_t) = serde_json::from_value::<Transform>(transform_val.clone()) { - if let Ok(mut tq) = world.query_one::<&mut Transform>(target_entity) { - if let Some(tref) = tq.get() { - *tref = new_t; - log::trace!("apply: updated transform for {:?}", target_entity); - } - } - } else { - log::trace!("apply: failed to deserialize transform for label '{}'", label); - } - } - if let Some(props_val) = ent_obj.get("properties") { - if let Ok(new_props) = serde_json::from_value::<ModelProperties>(props_val.clone()) { - if let Ok(mut pq) = world.query_one::<&mut ModelProperties>(target_entity) { - if let Some(p) = pq.get() { - *p = new_props; - log::trace!("apply: updated properties for {:?}", target_entity); - } - } else { - if let Err(e) = world.insert_one(target_entity, new_props) { - log::warn!("apply: failed to insert properties for {:?}: {}", target_entity, e); - } - } - } else { - log::trace!("apply: failed to deserialize properties for label '{}'", label); - } - } - } else { - log::trace!("apply: no entity in world matching label '{}'", label); - } - } - } - } - } else { - // No entities array: treat the object as direct payload for the single entity - // apply transform if present - if let Some(transform_value) = obj.get("transform") { - if let Ok(updated_transform) = serde_json::from_value::<Transform>(transform_value.clone()) { - if let Ok(mut transform_query) = world.query_one::<&mut Transform>(entity_id) { - if let Some(transform) = transform_query.get() { - *transform = updated_transform; - log::trace!("apply: Updated transform for entity {:?}", entity_id); - } - } - } - } - // apply properties if present (support "properties" or legacy "entity") - if let Some(props_value) = obj.get("properties").or_else(|| obj.get("entity")) { - if let Ok(updated_properties) = serde_json::from_value::<ModelProperties>(props_value.clone()) { - if let Ok(mut properties_query) = world.query_one::<&mut ModelProperties>(entity_id) { - if let Some(properties) = properties_query.get() { - *properties = updated_properties; - log::trace!("apply: Updated properties for entity {:?}", entity_id); - } - } else { - if let Err(e) = world.insert_one(entity_id, updated_properties) { - log::warn!("apply: Failed to insert updated properties for entity {:?}: {}", entity_id, e); - } - } - } - } - - // cameras: if script returned camera array with full data, apply by label - if let Some(cameras_val) = obj.get("cameras").and_then(|v| v.as_array()) { - for cam in cameras_val { - if let Some(cam_obj) = cam.as_object() { - if let Some(label) = cam_obj.get("label").and_then(|l| l.as_str()) { - if let Some(data_obj) = cam_obj.get("data").and_then(|d| d.as_object()) { - // find camera entity by label - if let Some(cam_entity) = world - .query::<&Camera>() - .iter() - .find_map(|(e, camera)| if camera.label == label { Some(e) } else { None }) { - if let Ok(mut cam_q) = world.query_one::<&mut Camera>(cam_entity) { - if let Some(cam_struct) = cam_q.get() { - // update common camera fields if present - if let Some(eye_val) = data_obj.get("eye") { - if let Ok(arr) = serde_json::from_value::<[f64; 3]>(eye_val.clone()) { - cam_struct.eye = DVec3::from_array(arr); - } - } - if let Some(target_val) = data_obj.get("target") { - if let Ok(arr) = serde_json::from_value::<[f64; 3]>(target_val.clone()) { - cam_struct.target = DVec3::from_array(arr); - } - } - if let Some(up_val) = data_obj.get("up") { - if let Ok(arr) = serde_json::from_value::<[f64; 3]>(up_val.clone()) { - cam_struct.up = DVec3::from_array(arr); - } - } - if let Some(aspect) = data_obj.get("aspect").and_then(|v| v.as_f64()) { - cam_struct.aspect = aspect; - } - if let Some(fov) = data_obj.get("fov").and_then(|v| v.as_f64()) { - cam_struct.fov_y = fov; - } - if let Some(near) = data_obj.get("near").and_then(|v| v.as_f64()) { - cam_struct.znear = near; - } - if let Some(far) = data_obj.get("far").and_then(|v| v.as_f64()) { - cam_struct.zfar = far; - } - if let Some(yaw) = data_obj.get("yaw").and_then(|v| v.as_f64()) { - cam_struct.yaw = yaw; - } - if let Some(pitch) = data_obj.get("pitch").and_then(|v| v.as_f64()) { - cam_struct.pitch = pitch; - } - if let Some(speed) = data_obj.get("speed").and_then(|v| v.as_f64()) { - cam_struct.speed = speed; - } - if let Some(sensitivity) = data_obj.get("sensitivity").and_then(|v| v.as_f64()) { - cam_struct.sensitivity = sensitivity; - } - log::trace!("apply: Updated camera '{}'", label); - } - } - } - } - } - } - } - } - } - } else { - log::trace!("apply: script_data not an object for entity {:?}", entity_id); - } - - Ok(()) - } - - pub fn load_script_from_source(&mut self, script_name: &String, script_content: &String) -> anyhow::Result<String> { - let module = Module::new(&script_name, script_content); - - match self.runtime.load_module(&module) { - Ok(module) => { - self.compiled_scripts.insert(script_name.clone(), module); - log::info!("Loaded script: {}", script_name); - Ok(script_name.to_string()) - } - Err(e) => { - log::error!("Compiling module for script [{}] returned an error: {}", script_name, e); - Err(e.into()) - } - } - } - - pub fn load_script(&mut self, script_path: &PathBuf) -> anyhow::Result<String> { - log::debug!("Reading script content"); - let script_content = fs::read_to_string(script_path)?; - log::debug!("Fetching script name"); - let script_name = script_path - .file_name() - .unwrap_or_default() - .to_string_lossy() - .to_string(); - log::debug!("Script name: {}", script_name); - - log::debug!("Creating module for typescript runtime"); - let module = Module::new(&script_name, &script_content); - - log::debug!("Loading module"); - match self.runtime.load_module(&module) { - Ok(module) => { - self.compiled_scripts.insert(script_name.clone(), module); - log::info!("Loaded script: {}", script_name); - Ok(script_name) - } - Err(e) => { - log::error!("Compiling module for script path [{}] returned an error: {}", script_path.display(), e); - Err(e.into()) - } - } - } - - pub fn remove_entity_script(&mut self, entity_id: hecs::Entity) { - self.entity_script_data.remove(&entity_id); - } - - pub fn handle_camera_actions(&self, world: &mut hecs::World, active_camera: &mut Option<hecs::Entity>, actions: Vec<serde_json::Value>) -> anyhow::Result<()> { - use crate::camera::{CameraComponent, CameraType}; - use dropbear_engine::camera::Camera; - - for action_value in actions { - if let Ok(action) = serde_json::from_value::<crate::scripting::camera::CameraAction>(action_value) { - match action.action.as_str() { - "switch_camera" => { - if let Some(label) = action.label { - let camera_entity = world - .query::<&Camera>() - .iter() - .find(|(_, camera)| camera.label == label) - .map(|(entity, _)| entity); - - if let Some(entity) = camera_entity { - *active_camera = Some(entity); - log::info!("Switched to camera with label: {}", label); - } else { - log::warn!("Camera with label '{}' not found", label); - } - } - } - "get_active_camera" => { - if let Some(active_entity) = active_camera { - if let Ok(camera) = world.get::<&Camera>(*active_entity) { - log::info!("Active camera label: {}", camera.label); - } - } - } - "get_all_cameras" => { - let labels: Vec<String> = world - .query::<&Camera>() - .iter() - .map(|(_, camera)| camera.label.clone()) - .collect(); - log::info!("All camera labels: {:?}", labels); - } - "get_cameras_by_type" => { - if let Some(type_str) = action.camera_type { - let target_type = match type_str.as_str() { - "Normal" => CameraType::Normal, - "Debug" => CameraType::Debug, - "Player" => CameraType::Player, - _ => continue, - }; - - let labels: Vec<String> = world - .query::<(&Camera, &CameraComponent)>() - .iter() - .filter_map(|(_, (camera, component))| { - if component.camera_type == target_type { - Some(camera.label.clone()) - } else { - None - } - }) - .collect(); - log::info!("Cameras of type {:?}: {:?}", target_type, labels); - } - } - "manipulate_camera" => { - if let (Some(label), Some(property), Some(value)) = (action.label, action.property, action.value) { - let camera_entity = world - .query::<&Camera>() - .iter() - .find(|(_, camera)| camera.label == label) - .map(|(entity, _)| entity); - - if let Some(entity) = camera_entity { - match property.as_str() { - "position" => { - if let Ok(pos_array) = serde_json::from_value::<[f64; 3]>(value) { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.eye = DVec3::from_array(pos_array); - log::info!("Updated camera '{}' position to {:?}", label, pos_array); - } - } - } - "target" => { - if let Ok(target_array) = serde_json::from_value::<[f64; 3]>(value) { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.target = DVec3::from_array(target_array); - log::info!("Updated camera '{}' target to {:?}", label, target_array); - } - } - } - "speed" => { - if let Some(speed) = value.as_f64() { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.speed = speed; - } - if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) { - component.speed = speed; - } - log::info!("Updated camera '{}' speed to {}", label, speed); - } - } - "sensitivity" => { - if let Some(sensitivity) = value.as_f64() { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.sensitivity = sensitivity; - } - if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) { - component.sensitivity = sensitivity; - } - log::info!("Updated camera '{}' sensitivity to {}", label, sensitivity); - } - } - "fov" => { - if let Some(fov) = value.as_f64() { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.fov_y = fov; - } - if let Ok(mut component) = world.get::<&mut CameraComponent>(entity) { - component.fov_y = fov; - } - log::info!("Updated camera '{}' FOV to {}", label, fov); - } - } - "yaw" => { - if let Some(yaw) = value.as_f64() { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.yaw = yaw; - } - log::info!("Updated camera '{}' yaw to {}", label, yaw); - } - } - "pitch" => { - if let Some(pitch) = value.as_f64() { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.pitch = pitch; - } - log::info!("Updated camera '{}' pitch to {}", label, pitch); - } - } - _ => { - log::warn!("Unknown camera property: {}", property); - } - } - } else { - log::warn!("Camera with label '{}' not found for manipulation", label); - } - } - } - "get_camera_by_label" => { - if let Some(label) = action.label { - log::info!("Getting camera data for label: {}", label); - } - } - "set_camera_by_label" => { - if let (Some(label), Some(camera_data)) = (action.label, action.camera_data) { - // Find and update the entire camera - let camera_entity = world - .query::<&Camera>() - .iter() - .find(|(_, camera)| camera.label == label) - .map(|(entity, _)| entity); - - if let Some(entity) = camera_entity { - if let Ok(mut camera) = world.get::<&mut Camera>(entity) { - camera.eye = camera_data.eye; - camera.target = camera_data.target; - camera.up = camera_data.up; - camera.aspect = camera_data.aspect; - camera.fov_y = camera_data.fov; - camera.znear = camera_data.near; - camera.zfar = camera_data.far; - camera.yaw = camera_data.yaw; - camera.pitch = camera_data.pitch; - camera.speed = camera_data.speed; - camera.sensitivity = camera_data.sensitivity; - log::info!("Updated complete camera data for label: {}", label); - } - } - } - } - _ => { - log::warn!("Unknown camera action: {}", action.action); - } - } - } - } - Ok(()) - } -} - -pub fn move_script_to_src(script_path: &PathBuf) -> anyhow::Result<PathBuf> { - let project_path = { - let project = PROJECT.read(); - project.project_path.clone() - }; - - let src_path = { - let source_config = SOURCE.read(); - source_config.path.clone() - }; - - let scripts_dir = src_path; - fs::create_dir_all(&scripts_dir)?; - - let filename = script_path - .file_name() - .ok_or_else(|| anyhow::anyhow!("Invalid script path: no filename"))?; - - let dest_path = scripts_dir.join(filename); - - if dest_path.exists() { - log::info!( - "Script file already exists at {:?}, returning existing path", - dest_path - ); - return Ok(dest_path); - } - - const MAX_RETRIES: usize = 5; - const RETRY_DELAY_MS: u64 = 60; - - let mut last_err: Option<std::io::Error> = None; - for attempt in 0..=MAX_RETRIES { - match fs::copy(script_path, &dest_path) { - Ok(_) => { - log::info!("Copied script from {:?} to {:?}", script_path, dest_path); - last_err = None; - break; - } - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => { - log::warn!( - "Script file already exists at {:?}, continuing anyway", - dest_path - ); - last_err = None; - break; - } - Err(e) => { - if e.raw_os_error() == Some(32) && attempt < MAX_RETRIES { - log::warn!( - "Sharing violation copying script (attempt {}/{}). Retrying in {}ms: {}", - attempt + 1, - MAX_RETRIES, - RETRY_DELAY_MS, - e - ); - std::thread::sleep(std::time::Duration::from_millis(RETRY_DELAY_MS)); - last_err = Some(e); - continue; - } else { - return Err(e.into()); - } - } - } - } - if let Some(e) = last_err { - return Err(e.into()); - } - - { - let source_config = SOURCE.read(); - source_config.write_to(&project_path)?; - } - - log::info!("Moved script from {:?} to {:?}", script_path, dest_path); - Ok(dest_path) -} - -pub fn convert_entity_to_group( - world: &World, - entity_id: hecs::Entity, -) -> anyhow::Result<EntityNode> { - if let Ok(mut query) = world.query_one::<(&AdoptedEntity, &Transform)>(entity_id) { - if let Some((adopted, _transform)) = query.get() { - let entity_name = adopted.model().label.clone(); - - let script_node = if let Ok(script) = world.get::<&ScriptComponent>(entity_id) { - Some(EntityNode::Script { - name: script.name.clone(), - path: script.path.clone(), - }) - } else { - None - }; - - let entity_node = EntityNode::Entity { - id: entity_id, - name: entity_name.clone(), - }; - - if let Some(script_node) = script_node { - Ok(EntityNode::Group { - name: entity_name, - children: vec![entity_node, script_node], - collapsed: false, - }) - } else { - Ok(entity_node) - } - } else { - Err(anyhow::anyhow!("Failed to get entity components")) - } - } else { - Err(anyhow::anyhow!("Failed to query entity {:?}", entity_id)) - } -} - -pub fn attach_script_to_entity( - world: &mut World, - entity_id: hecs::Entity, - script_component: ScriptComponent, -) -> anyhow::Result<()> { - if let Err(e) = world.insert_one(entity_id, script_component) { - return Err(anyhow::anyhow!("Failed to attach script to entity: {}", e)); - } - - log::info!("Successfully attached script to entity {:?}", entity_id); - Ok(()) -} @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true license-file.workspace = true repository.workspace = true +default-run = "eucalyptus-editor" readme = "README.md" [dependencies] @@ -33,7 +34,6 @@ winit.workspace = true clap.workspace = true walkdir.workspace = true zip.workspace = true -tokio.workspace = true [target.'cfg(not(target_os = "android"))'.dependencies] rfd.workspace = true @@ -2,13 +2,13 @@ use std::time::Instant; use egui::{CollapsingHeader, ComboBox, Ui}; +use eucalyptus_core::scripting::{ScriptAction, TEMPLATE_SCRIPT}; use glam::Vec3; use hecs::Entity; use dropbear_engine::attenuation::ATTENUATION_PRESETS; use dropbear_engine::entity::{AdoptedEntity, Transform}; use dropbear_engine::lighting::{Light, LightComponent, LightType}; use crate::editor::{EntityType, Signal, StaticallyKept, UndoableAction}; -use eucalyptus_core::scripting::{ScriptAction, TEMPLATE_SCRIPT}; use eucalyptus_core::states::ScriptComponent; use eucalyptus_core::warn; @@ -25,7 +25,7 @@ use wgpu::{Color, Extent3d, RenderPipeline}; use winit::{keyboard::KeyCode, window::Window}; use eucalyptus_core::camera::{CameraAction, CameraComponent, CameraFollowTarget, CameraType, DebugCamera}; use eucalyptus_core::{fatal, info, states, success, warn}; -use eucalyptus_core::scripting::input::InputState; +use eucalyptus_core::input::InputState; use eucalyptus_core::scripting::{ScriptAction, ScriptManager}; use eucalyptus_core::states::{CameraConfig, EditorTab, EntityNode, LightConfig, ModelProperties, SceneEntity, ScriptComponent, PROJECT, SCENES}; use eucalyptus_core::utils::ViewportMode; @@ -73,7 +73,7 @@ pub struct Editor { } impl Editor { - pub async fn new() -> Self { + pub fn new() -> Self { let tabs = vec![EditorTab::Viewport]; let mut dock_state = DockState::new(tabs); @@ -125,7 +125,7 @@ impl Editor { viewport_mode: ViewportMode::None, signal: Signal::None, undo_stack: Vec::new(), - script_manager: ScriptManager::new().await.unwrap(), + script_manager: ScriptManager::new().unwrap(), editor_state: EditorState::Editing, gizmo_mode: EnumSet::empty(), play_mode_backup: None, @@ -16,8 +16,7 @@ pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo { author: "4tkbytes", }; -#[tokio::main] -async fn main() -> anyhow::Result<()> { +fn main() -> anyhow::Result<()> { #[cfg(target_os = "android")] compile_error!("The `editor` feature is not supported on Android. If you are attempting\ to use the Eucalyptus editor on Android, please don't. Instead, use the `data-only` feature\ @@ -114,11 +113,9 @@ async fn main() -> anyhow::Result<()> { }; let main_menu = Rc::new(RefCell::new(crate::menu::MainMenu::new())); - let editor = Rc::new(RefCell::new(crate::editor::Editor::new().await)); + let editor = Rc::new(RefCell::new(crate::editor::Editor::new())); let _app = dropbear_engine::run_app!(config, |mut scene_manager, mut input_manager| { - - scene::add_scene_with_input( &mut scene_manager, &mut input_manager, @@ -136,7 +133,6 @@ async fn main() -> anyhow::Result<()> { (scene_manager, input_manager) }) - .await .unwrap(); } _ => unreachable!(),