tirbofish/dropbear · diff
wip: got most of the runtime working, camera doesnt seem to update to my positions at all for some reason :(
Signature present but could not be verified.
Unverified
@@ -15,7 +15,7 @@ members = [ "eucalyptus-core", "eucalyptus-editor", "magna-carta", - "redback-runtime" +# "redback-runtime" ] [workspace.dependencies] @@ -30,12 +30,10 @@ use futures::executor::block_on; use gilrs::{Gilrs, GilrsBuilder}; use log::LevelFilter; use parking_lot::{Mutex, RwLock}; -use serde::{Serialize}; use spin_sleep::SpinSleeper; use std::fs::OpenOptions; -use std::io::Write; use std::sync::OnceLock; -use std::{fs, sync::Arc, time::{Duration, Instant, SystemTime, UNIX_EPOCH}}; +use std::{fs, sync::Arc, time::{Duration, Instant}}; use std::collections::HashMap; use std::rc::Rc; use wgpu::{ @@ -629,6 +627,7 @@ impl DropbearWindowBuilder { /// A struct storing the information about the application/game that is using the engine. pub struct App { + #[allow(dead_code)] app_data: AppInfo, /// The input manager, manages any inputs and their actions input_manager: input::Manager, @@ -867,7 +866,7 @@ impl ApplicationHandler for App { scene::SceneCommand::CloseWindow(target_window_id) => { log::info!("Scene requested closing window: {:?}", target_window_id); if Some(target_window_id) == self.root_window_id { - log::warn!("Cannot close root window via CloseWindow command, use Quit instead"); + event_loop.exit(); } else { self.windows.remove(&target_window_id); } @@ -40,7 +40,6 @@ use std::any::Any; use std::cell::RefCell; use std::collections::VecDeque; use std::future::Future; -use std::marker::PhantomData; use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; @@ -100,7 +100,7 @@ impl ProjectConfig { log::debug!("Updating with new content"); config.load_config_to_memory()?; config.write_to_all()?; - log::debug!("Successfully updated!"); + log::debug!("Project config successfully updated!"); Ok(config) } @@ -243,7 +243,7 @@ impl ProjectConfig { if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("eucs") { match SceneConfig::read_from(&path) { Ok(scene) => { - log::debug!("Loaded scene: {}", scene.scene_name); + log::debug!("Loaded scene config: {}", scene.scene_name); scene_configs.push(scene); } Err(e) => { @@ -24,7 +24,6 @@ use std::fs; use std::path::{Path, PathBuf}; use std::sync::Arc; use crossbeam_channel::Sender; -use tokio::sync::mpsc::UnboundedSender; #[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct SceneEntity { @@ -608,6 +607,7 @@ impl SceneConfig { .iter() .find_map(|(entity, (_, component))| { if component.starting_camera { + log::debug!("Found starting camera: {:?}", entity); Some(entity) } else { None @@ -627,6 +627,7 @@ impl SceneConfig { .iter() .find_map(|(entity, (_, component))| { if matches!(component.camera_type, CameraType::Debug) { + log::debug!("Found debug camera: {:?}", entity); Some(entity) } else { None @@ -646,6 +647,7 @@ impl SceneConfig { .iter() .filter_map(|(entity, label)| { if label.as_str() == "Viewport Camera" { + log::debug!("Found 'Viewport Camera' entity: {:?}", entity); Some(entity) } else { None @@ -10,7 +10,6 @@ pub mod utils; pub static JVM_ARGS: OnceLock<String> = OnceLock::new(); use std::sync::OnceLock; -use crate::input::InputState; use crate::ptr::{CommandBufferPtr, InputStatePtr, WorldPtr}; use crate::scripting::jni::JavaContext; use crate::scripting::native::NativeLibrary; @@ -26,7 +25,7 @@ use tokio::process::Command; use magna_carta::Target; /// The target of the script. This can be either a JVM or a native library. -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub enum ScriptTarget { #[default] /// The default target. Using this will always return an error. @@ -13,11 +13,19 @@ use jni::sys::jlong; use jni::{InitArgsBuilder, JNIVersion, JavaVM}; use sha2::{Digest, Sha256}; use std::fs; +use std::net::TcpListener; use std::path::PathBuf; use crate::scripting::JVM_ARGS; const LIBRARY_PATH: &[u8] = include_bytes!("../../../build/libs/dropbear-1.0-SNAPSHOT-all.jar"); +fn is_port_available(port: u16) -> bool { + match TcpListener::bind(("127.0.0.1", port)) { + Ok(_) => true, + Err(_) => false, + } +} + /// Provides a context for any eucalyptus-core JNI calls and JVM hosting. pub struct JavaContext { pub(crate) jvm: JavaVM, @@ -116,6 +124,8 @@ impl JavaContext { let mut args_log = Vec::new(); + + if let Some(args) = external_vm_args { args_log.push(args.clone()); jvm_args = jvm_args.option(args); @@ -126,7 +136,12 @@ impl JavaContext { #[cfg(feature = "jvm_debug")] { - let debug_arg = "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:6741"; + let port = 6741; + let debug_arg = if is_port_available(port) { + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:6741" + } else { + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:6742" + }; args_log.push(debug_arg.to_string()); jvm_args = jvm_args.option(debug_arg); } @@ -140,7 +155,7 @@ impl JavaContext { .parent() .ok_or_else(|| anyhow::anyhow!("Unable to locate parent"))?; - println!("Libs folder at {}", path.display()); + log::debug!("Libs folder at {}", path.display()); if !path.exists() { log::warn!( "Libs folder ({}) does not exist; native libraries may fail to load", @@ -89,13 +89,22 @@ pub unsafe extern "C" fn dropbear_is_mouse_button_pressed( let input = unsafe { &*input_state_ptr }; - match keycode_from_ordinal(button_code) { + let button = match button_code { + 0 => Some(winit::event::MouseButton::Left), + 1 => Some(winit::event::MouseButton::Right), + 2 => Some(winit::event::MouseButton::Middle), + 3 => Some(winit::event::MouseButton::Back), + 4 => Some(winit::event::MouseButton::Forward), + _ => None, + }; + + match button { None => { eprintln!("[dropbear_is_mouse_button_pressed] [WARN] Invalid button code"); unsafe { *out_pressed = 0 }; } - Some(key) => { - if input.pressed_keys.contains(&key) { + Some(btn) => { + if input.mouse_button.contains(&btn) { unsafe { *out_pressed = 1 }; } else { unsafe { *out_pressed = 0 }; @@ -119,7 +128,7 @@ pub unsafe extern "C" fn dropbear_get_mouse_delta( let input = unsafe { &mut *(input_state_ptr as InputStatePtr) }; - if let Some(pos) = input.mouse_delta.take() { + if let Some(pos) = input.mouse_delta { unsafe { *out_delta_x = pos.0 as f32; *out_delta_y = pos.1 as f32; @@ -1,59 +0,0 @@ -use dropbear_engine::graphics::RenderContext; -use eucalyptus_core::command::{CommandBufferPoller, COMMAND_BUFFER, CommandBuffer, WindowCommand}; -use winit::window::CursorGrabMode; - -use crate::editor::Editor; - -impl CommandBufferPoller for Editor { - fn poll(&mut self, graphics: &RenderContext) { - while let Ok(cmd) = COMMAND_BUFFER.1.try_recv() { - log::trace!("Received GRAPHICS_COMMAND update: {:?}", cmd); - match cmd { - CommandBuffer::WindowCommand(w_cmd) => match w_cmd { - WindowCommand::WindowGrab(lock) => { - if lock { - if let Err(e) = graphics.shared.window - .set_cursor_grab(CursorGrabMode::Confined) - .or_else(|_| graphics.shared.window.set_cursor_grab(CursorGrabMode::Locked)) - { - log_once::warn_once!("Failed to grab cursor: {:?}", e); - } else { - log_once::info_once!("Grabbed cursor"); - } - } else if let Err(e) = graphics.shared.window.set_cursor_grab(CursorGrabMode::None) { - log_once::warn_once!("Failed to release cursor: {:?}", e); - } else { - log_once::info_once!("Released cursor"); - } - } - WindowCommand::HideCursor(should_hide) => { - if should_hide { - graphics.shared.window.set_cursor_visible(false); - } else { - graphics.shared.window.set_cursor_visible(true); - } - } - }, - CommandBuffer::Quit => { - log::info!("Quit command received in editor, stopping play mode"); - self.signal = crate::editor::Signal::StopPlaying; - }, - CommandBuffer::SwitchScene(scene_name) => { - if matches!(self.editor_state, crate::editor::EditorState::Playing) { - log::info!( - "SwitchScene requested during play mode; switching play-world scene to '{}'", - scene_name - ); - self.pending_play_scene_load = Some(scene_name); - } else if let Err(err) = self.queue_scene_load_by_name(&scene_name) { - log::error!( - "Failed to queue scene load for '{}': {}", - scene_name, - err - ); - } - } - } - } - } -} @@ -644,57 +644,31 @@ impl<'a> TabViewer for EditorTabViewer<'a> { let local_scene_settings = cfg.root_node_selected; if let Some(entity) = self.selected_entity { - let is_playing = matches!(*self.editor_mode, EditorState::Playing); - - let author_entity = *entity; - let selected_label = self - .world - .get::<&Label>(author_entity) - .ok() - .map(|l| l.as_str().to_string()); - - let mut inspect_play_world = false; - let mut play_entity: Option<Entity> = None; - - if is_playing && !self.editor.is_null() { - let editor = unsafe { &mut *self.editor }; - if let (Some(label), Some(play_world)) = - (selected_label.as_deref(), editor.play_world.as_deref()) - { - play_entity = play_world - .query::<&Label>() - .iter() - .find_map(|(e, l)| (l.as_str() == label).then_some(e)); - inspect_play_world = play_entity.is_some(); - } - } - - if inspect_play_world { - ui.label("Play Mode: showing live values (read-only)"); - ui.separator(); - } - let mut local_set_initial_camera = false; - let mut inspect_entity = author_entity; - let world_ptr: *mut World = if inspect_play_world { - let editor = unsafe { &mut *self.editor }; - let play_world = editor - .play_world - .as_deref_mut() - .expect("inspect_play_world implies play_world exists"); - inspect_entity = play_entity.expect("inspect_play_world implies play_entity exists"); - play_world as *mut World - } else { - self.world as *mut World - }; + let mut inspect_entity = *entity; + let world = &self.world; + + if !cfg.root_node_selected { + if let Ok(mut q) = world.query_one::<(&mut Label,)>(inspect_entity) { + if let Some((label, )) = q.get() { + label.inspect( + &mut inspect_entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut String::new(), + ); - let world = unsafe { &mut *world_ptr }; + ui.label(format!("Entity ID: {}", inspect_entity.id())); - ui.add_enabled_ui(!inspect_play_world, |ui| { - if !cfg.root_node_selected { - if let Ok(mut q) = world.query_one::<(&mut Label,)>(inspect_entity) { - if let Some((label, )) = q.get() { - label.inspect( + ui.separator(); + + if let Ok(mut q) = world.query_one::<&mut MeshRenderer>(inspect_entity) + && let Some(e) = q.get() + { + // entity + e.inspect( &mut inspect_entity, &mut cfg, ui, @@ -702,16 +676,46 @@ impl<'a> TabViewer for EditorTabViewer<'a> { self.signal, &mut String::new(), ); + } - ui.label(format!("Entity ID: {}", inspect_entity.id())); + if let Ok(mut q) = world.query_one::<&mut EntityTransform>(inspect_entity) + && let Some(t) = q.get() + { + CollapsingHeader::new("Transform").default_open(true).show(ui, |ui| { + t.inspect( + &mut inspect_entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut String::new(), + ); + }); + ui.separator(); + } + if let Ok(mut q) = world.query_one::<&mut CustomProperties>(inspect_entity) + && let Some(props) = q.get() + { + CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| { + props.inspect( + &mut inspect_entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + label.as_mut_string(), + ); + }); ui.separator(); + } - if let Ok(mut q) = world.query_one::<&mut MeshRenderer>(inspect_entity) - && let Some(e) = q.get() - { - // entity - e.inspect( + if let Ok(mut q) = world + .query_one::<(&mut Camera, &mut CameraComponent)>(inspect_entity) + && let Some((camera, camera_component)) = q.get() + { + CollapsingHeader::new("Camera").default_open(true).show(ui, |ui| { + camera.inspect( &mut inspect_entity, &mut cfg, ui, @@ -719,161 +723,113 @@ impl<'a> TabViewer for EditorTabViewer<'a> { self.signal, &mut String::new(), ); - } - if let Ok(mut q) = world.query_one::<&mut EntityTransform>(inspect_entity) - && let Some(t) = q.get() - { - CollapsingHeader::new("Transform").default_open(true).show(ui, |ui| { - t.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); - }); ui.separator(); - } - if let Ok(mut q) = world.query_one::<&mut CustomProperties>(inspect_entity) - && let Some(props) = q.get() - { - CollapsingHeader::new("Custom Properties").default_open(true).show(ui, |ui| { - props.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - }); - ui.separator(); - } - - if let Ok(mut q) = world - .query_one::<(&mut Camera, &mut CameraComponent)>(inspect_entity) - && let Some((camera, camera_component)) = q.get() - { - CollapsingHeader::new("Camera").default_open(true).show(ui, |ui| { - camera.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut String::new(), - ); + camera_component.inspect( + &mut inspect_entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut camera.label.clone(), + ); - ui.separator(); + ui.separator(); - camera_component.inspect( - &mut inspect_entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - &mut camera.label.clone(), - ); + // camera controller + let mut active_camera = self.active_camera.lock(); - ui.separator(); + if active_camera.equivalent(&Some(*entity)) { + ui.label("Status: Currently viewing through camera"); + } else { + ui.label("Status: Not viewing through this camera"); + } - // camera controller - let mut active_camera = self.active_camera.lock(); + if ui.button("Set as active camera").clicked() { + *active_camera = Some(*entity); + log::info!( + "Currently viewing from camera angle '{}'", + camera.label + ); + } - if active_camera.equivalent(&Some(*entity)) { - ui.label("Status: Currently viewing through camera"); - } else { - ui.label("Status: Not viewing through this camera"); + if camera_component.starting_camera { + if ui.button("Stop making camera initial").clicked() { + log::debug!("'Stop making camera initial' button clicked"); + camera_component.starting_camera = false; + success!("Removed {} from starting camera", camera.label); } - - if ui.button("Set as active camera").clicked() { - *active_camera = Some(*entity); - log::info!( - "Currently viewing from camera angle '{}'", - camera.label + } else if ui.button("Set as initial camera").clicked() { + log::debug!("'Set as initial camera' button clicked"); + if matches!(camera_component.camera_type, CameraType::Debug) { + warn!( + "Cannot set any cameras of type 'Debug' to initial camera" ); + } else { + local_set_initial_camera = true } + } + }); + ui.separator(); + } - if camera_component.starting_camera { - if ui.button("Stop making camera initial").clicked() { - log::debug!("'Stop making camera initial' button clicked"); - camera_component.starting_camera = false; - success!("Removed {} from starting camera", camera.label); - } - } else if ui.button("Set as initial camera").clicked() { - log::debug!("'Set as initial camera' button clicked"); - if matches!(camera_component.camera_type, CameraType::Debug) { - warn!( - "Cannot set any cameras of type 'Debug' to initial camera" - ); - } else { - local_set_initial_camera = true - } - } - }); - ui.separator(); - } + if let Ok(mut q) = world.query_one::<(&mut Light, &mut LightComponent, &mut Transform)>(inspect_entity) + && let Some((light, comp, transform)) = q.get() + { + light.inspect( + entity, + &mut cfg, + ui, + self.undo_stack, + self.signal, + &mut String::new(), + ); - if let Ok(mut q) = world.query_one::<(&mut Light, &mut LightComponent, &mut Transform)>(inspect_entity) - && let Some((light, comp, transform)) = q.get() - { - light.inspect( + *transform = light.transform; + *comp = light.light_component.clone(); + ui.separator(); + } + + if let Ok(mut q) = self.world.query_one::<&mut Script>(*entity) + && let Some(script) = q.get() + { + CollapsingHeader::new("Script").default_open(true).show(ui, |ui| { + script.inspect( entity, &mut cfg, ui, self.undo_stack, self.signal, - &mut String::new(), + label.as_mut_string(), ); + }); + ui.separator(); + } - *transform = light.transform; - *comp = light.light_component.clone(); - ui.separator(); - } - - if let Ok(mut q) = self.world.query_one::<&mut Script>(*entity) - && let Some(script) = q.get() - { - CollapsingHeader::new("Script").default_open(true).show(ui, |ui| { - script.inspect( - entity, - &mut cfg, - ui, - self.undo_stack, - self.signal, - label.as_mut_string(), - ); - }); - ui.separator(); - } - - if let Some(t) = cfg.label_last_edit - && t.elapsed() >= Duration::from_millis(500) + if let Some(t) = cfg.label_last_edit + && t.elapsed() >= Duration::from_millis(500) + { + if let Some(ent) = cfg.old_label_entity.take() + && let Some(orig) = cfg.label_original.take() { - if let Some(ent) = cfg.old_label_entity.take() - && let Some(orig) = cfg.label_original.take() - { - UndoableAction::push_to_undo( - self.undo_stack, - UndoableAction::Label(ent, orig), - ); - log::debug!( - "Pushed label change to undo stack after 500ms debounce period" - ); - } - cfg.label_last_edit = None; + UndoableAction::push_to_undo( + self.undo_stack, + UndoableAction::Label(ent, orig), + ); + log::debug!( + "Pushed label change to undo stack after 500ms debounce period" + ); } + cfg.label_last_edit = None; } - } else { - log_once::debug_once!("Unable to query entity inside resource inspector"); } + } else { + log_once::debug_once!("Unable to query entity inside resource inspector"); } - }); + } - if local_set_initial_camera && !inspect_play_world { + if local_set_initial_camera { for (id, comp) in self.world.query::<&mut CameraComponent>().iter() { comp.starting_camera = false; self.undo_stack @@ -3,7 +3,7 @@ pub mod console_error; pub mod dock; pub mod input; pub mod scene; -mod settings; +pub mod settings; pub(crate) use crate::editor::dock::*; @@ -37,8 +37,7 @@ use eucalyptus_core::{ camera::{CameraComponent, CameraType, DebugCamera}, fatal, info, input::InputState, - ptr::{CommandBufferPtr, InputStatePtr, WorldPtr}, - scripting::{BuildStatus, ScriptManager, ScriptTarget}, + scripting::{BuildStatus, ScriptManager}, states, states::{ Camera3D, EditorTab, Light, PROJECT, SCENES, Script, WorldLoadingStatus, @@ -46,12 +45,10 @@ use eucalyptus_core::{ success, utils::ViewportMode, warn, - command::COMMAND_BUFFER, }; use hecs::{Entity, World}; use parking_lot::Mutex; use rfd::FileDialog; -use std::path::Path; use std::{ collections::HashMap, fs, @@ -59,7 +56,6 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tokio::sync::oneshot; use transform_gizmo_egui::{EnumSet, Gizmo, GizmoMode, GizmoOrientation}; use wgpu::{Color, Extent3d, RenderPipeline}; @@ -101,19 +97,10 @@ pub struct Editor { pub gizmo_mode: EnumSet<GizmoMode>, pub gizmo_orientation: GizmoOrientation, + #[allow(unused)] // unused to allow for JVM to startup pub(crate) script_manager: ScriptManager, pub play_mode_backup: Option<PlayModeBackup>, - /// Separate ECS world used only for play mode simulation/rendering. - /// The editor UI (entity list / inspector) remains bound to `world`. - pub(crate) play_world: Option<Box<World>>, - pub(crate) play_world_receiver: Option<oneshot::Receiver<Result<hecs::World, String>>>, - pub(crate) play_world_load_handle: Option<FutureHandle>, - pub(crate) pending_play_scene_load: Option<String>, - pub(crate) play_active_camera: Arc<Mutex<Option<Entity>>>, - pub(crate) play_script_jar: Option<PathBuf>, - pub(crate) play_scripts_loaded: bool, - /// State of the input pub(crate) input_state: Box<InputState>, @@ -159,6 +146,11 @@ pub struct Editor { // component registry component_registry: Arc<ComponentRegistry>, + + // play mode process tracking + pub(crate) play_mode_process: Option<std::process::Child>, + pub(crate) play_mode_pid: Option<u32>, + pub(crate) play_mode_exit_rx: Option<std::sync::mpsc::Receiver<()>>, } impl Editor { @@ -264,13 +256,6 @@ impl Editor { gizmo_mode: EnumSet::empty(), gizmo_orientation: GizmoOrientation::Global, play_mode_backup: None, - play_world: None, - play_world_receiver: None, - play_world_load_handle: None, - pending_play_scene_load: None, - play_active_camera: Arc::new(Mutex::new(None)), - play_script_jar: None, - play_scripts_loaded: false, input_state: Box::new(InputState::new()), light_manager: LightManager::new(), active_camera: Arc::new(Mutex::new(None)), @@ -299,121 +284,12 @@ impl Editor { show_about: false, nerd_stats: NerdStats::default(), component_registry, + play_mode_process: None, + play_mode_pid: None, + play_mode_exit_rx: None, }) } - fn ensure_play_world(&mut self) { - if self.play_world.is_none() { - self.play_world = Some(Box::new(World::new())); - } - } - - fn start_async_play_scene_load_by_name( - &mut self, - scene_name: &str, - graphics_shared: Arc<SharedGraphicsContext>, - ) -> anyhow::Result<()> { - if scene_name.trim().is_empty() { - return Err(anyhow::anyhow!("Scene name cannot be empty")); - } - - // Check if a scene load is already in progress - if self.play_world_receiver.is_some() { - log::warn!( - "Scene load already in progress, ignoring request to load '{}'", - scene_name - ); - return Ok(()); - } - - self.ensure_play_world(); - self.play_scripts_loaded = false; - - if let Some(handle) = self.play_world_load_handle.take() { - graphics_shared.future_queue.cancel(&handle); - } - - let scene = states::load_scene(scene_name)?; - - let (world_sender, world_receiver) = oneshot::channel(); - self.play_world_receiver = Some(world_receiver); - - let graphics_shared_for_task = graphics_shared.clone(); - let play_active_camera = self.play_active_camera.clone(); - let scene_name_owned = scene.scene_name.clone(); - let component_registry_clone = self.component_registry.clone(); - - let handle = graphics_shared.future_queue.push(async move { - let mut temp_world = World::new(); - - let load_result = scene - .load_into_world( - &mut temp_world, - graphics_shared_for_task.clone(), - Some(component_registry_clone.as_ref()), - None, - true, - ) - .await; - - let result = match load_result { - Ok(active_entity) => { - let mut camera_lock = play_active_camera.lock(); - *camera_lock = Some(active_entity); - Ok(temp_world) - } - Err(err) => { - let error_msg = format!("Failed to load play scene '{}': {}", scene_name_owned, err); - log::error!("{}", error_msg); - Err(error_msg) - } - }; - - if let Err(_) = world_sender.send(result) { - log::error!( - "Failed to deliver play world result for scene '{}' (receiver dropped)", - scene_name_owned - ); - } - }); - - self.play_world_load_handle = Some(handle); - Ok(()) - } - - fn reload_play_scripts(&mut self) -> anyhow::Result<()> { - let Some(jar_path) = self.play_script_jar.clone() else { - return Ok(()); - }; - - let Some(play_world) = self.play_world.as_mut() else { - return Ok(()); - }; - - let mut etag: HashMap<String, Vec<Entity>> = HashMap::new(); - for (entity_id, script) in play_world.query::<&Script>().iter() { - for tag in &script.tags { - etag.entry(tag.clone()).or_default().push(entity_id); - } - } - - self.script_manager.init_script( - None, - etag, - ScriptTarget::JVM { - library_path: jar_path, - }, - )?; - - let world_ptr = play_world.as_mut() as WorldPtr; - let input_ptr = self.input_state.as_mut() as InputStatePtr; - let graphics_ptr = COMMAND_BUFFER.0.as_ref() as CommandBufferPtr; - - self.script_manager.load_script(world_ptr, input_ptr, graphics_ptr)?; - self.play_scripts_loaded = true; - Ok(()) - } - fn double_key_pressed(&mut self, key: KeyCode) -> bool { let now = Instant::now(); @@ -1502,52 +1378,69 @@ impl Editor { self.is_world_loaded.mark_rendering_loaded(); } - pub fn load_play_mode( - &mut self, - path: impl AsRef<Path>, - graphics_shared: Arc<SharedGraphicsContext>, - ) -> anyhow::Result<()> { - let path = path.as_ref(); - let has_player_camera_target = self - .world - .query::<(&Camera, &CameraComponent)>() - .iter() - .any(|(_, (_, comp))| comp.starting_camera); + /// Initialises another eucalyptus-editor play mode app as a separate process and monitors it in a separate thread. + pub fn load_play_mode(&mut self) -> anyhow::Result<()> { + use std::process::{Command, Stdio}; + use std::sync::mpsc::channel; + use std::thread; - if has_player_camera_target { - self.save_current_scene()?; - success!("Saved scene"); + let current_exe = std::env::current_exe() + .map_err(|e| anyhow::anyhow!("Failed to get current executable path: {}", e))?; - if let Err(e) = self.create_backup() { - self.signal = Signal::None; - fatal!("Failed to create play mode backup: {}", e); - } + let project_dir = { + let cfg = PROJECT.read(); + cfg.project_path.clone() + }; - self.editor_state = EditorState::Playing; + let current_scene = self.current_scene_name.clone().ok_or_else(|| { + anyhow::anyhow!("No current scene loaded; cannot launch play mode") + })?; - self.play_script_jar = Some(path.to_path_buf()); - self.ensure_play_world(); + log::info!("Launching play mode: {} play --project {:?}", + current_exe.display(), project_dir); - let scene_name = self - .current_scene_name - .clone() - .or_else(|| PROJECT.read().last_opened_scene.clone()) - .ok_or_else(|| anyhow::anyhow!("No active scene available for play mode"))?; + let mut child = Command::new(¤t_exe) + .arg("play") + .arg(&project_dir) + .arg(¤t_scene) + .stdin(Stdio::null()) + .stdout(Stdio::inherit()) + .stderr(Stdio::inherit()) + .spawn() + .map_err(|e| anyhow::anyhow!("Failed to spawn play mode process: {}", e))?; - self.start_async_play_scene_load_by_name(&scene_name, graphics_shared)?; - info!( - "Entering play mode (loading play-world scene '{}')", - scene_name - ); + let pid = child.id(); + log::info!("Play mode process started with PID: {}", pid); + success!("Play mode launched (PID: {})", pid); - self.signal = Signal::None; - Ok(()) - } else { - self.signal = Signal::None; - self.editor_state = EditorState::Editing; - fatal!("Unable to build: No initial camera set"); - Err(anyhow::anyhow!("Unable to build: No initial camera set")) - } + let (tx, rx) = channel(); + + thread::spawn(move || { + log::debug!("Watch thread started for play mode process {}", pid); + + match child.wait() { + Ok(status) => { + log::info!("Play mode process {} exited with status: {}", pid, status); + + if let Err(e) = tx.send(()) { + log::error!("Failed to send play mode exit notification: {}", e); + } + } + Err(e) => { + log::error!("Error waiting for play mode process {}: {}", pid, e); + let _ = tx.send(()); + } + } + + log::debug!("Watch thread for play mode process {} terminated", pid); + }); + + self.play_mode_process = None; + self.play_mode_pid = Some(pid); + self.play_mode_exit_rx = Some(rx); + self.editor_state = EditorState::Playing; + + Ok(()) } } @@ -14,10 +14,8 @@ use dropbear_engine::{ use eucalyptus_core::hierarchy::EntityTransformExt; use eucalyptus_core::logging; use eucalyptus_core::states::{CustomProperties, Label, WorldLoadingStatus}; -use eucalyptus_core::command::{CommandBufferPoller}; use log; use parking_lot::Mutex; -use tokio::sync::mpsc::unbounded_channel; use wgpu::Color; use wgpu::util::DeviceExt; use winit::{event_loop::ActiveEventLoop, keyboard::KeyCode}; @@ -38,6 +36,8 @@ impl Scene for Editor { } }; + log::debug!("Current scene name = {:?}", self.current_scene_name); + let (tx, rx) = unbounded::<WorldLoadingStatus>(); let (tx2, rx2) = oneshot::channel::<World>(); self.progress_tx = Some(rx); @@ -80,47 +80,20 @@ impl Scene for Editor { } fn update(&mut self, dt: f32, graphics: &mut RenderContext) { + if let Some(rx) = &self.play_mode_exit_rx { + if rx.try_recv().is_ok() { + log::info!("Play mode process has exited, returning to editing mode"); + success!("Exited play mode"); + self.editor_state = EditorState::Editing; + self.play_mode_exit_rx = None; + self.play_mode_pid = None; + } + } + if let Some(request) = self.pending_scene_load.take() { self.start_async_scene_load(request.scene, graphics); } - if matches!(self.editor_state, EditorState::Playing) { - if let Some(scene_name) = self.pending_play_scene_load.take() { - if let Err(e) = self - .start_async_play_scene_load_by_name(&scene_name, graphics.shared.clone()) - { - fatal!("Failed to start play-world scene load: {}", e); - self.signal = Signal::StopPlaying; - } - } - - if let Some(mut receiver) = self.play_world_receiver.take() { - if let Ok(result) = receiver.try_recv() { - match result { - Ok(loaded_world) => { - self.ensure_play_world(); - if let Some(play_world) = self.play_world.as_mut() { - **play_world = loaded_world; - } - - if let Err(e) = self.reload_play_scripts() { - fatal!("Failed to initialise play-mode scripts: {}", e); - self.signal = Signal::StopPlaying; - } else { - success!("You are in play mode now! Press Escape (or F12) to exit"); - log::info!("Play-mode scripts initialised"); - } - } - Err(error_msg) => { - fatal!("Scene loading failed: {}", error_msg); - self.signal = Signal::StopPlaying; - } - } - } else { - self.play_world_receiver = Some(receiver); - } - } - } if let Some(mut receiver) = self.world_receiver.take() { self.show_project_loading_window(&graphics.shared.get_egui_context()); @@ -188,8 +161,6 @@ impl Scene for Editor { graphics.shared.window.set_title(&title); } - self.poll(graphics); - { // basic futurequeue spawn queue management. let mut completed = Vec::new(); @@ -231,18 +202,6 @@ impl Scene for Editor { if self.input_state.pressed_keys.contains(&KeyCode::Escape) { self.signal = Signal::StopPlaying; } - - if self.play_scripts_loaded { - if let Some(play_world) = self.play_world.as_mut() { - let world_ptr = play_world.as_mut() as *mut World; - - if let Err(e) = self.script_manager.update_script(&self.world, dt) - { - fatal!("Failed to update script: {}", e); - self.signal = Signal::StopPlaying; - } - } - } } if self.is_viewport_focused @@ -273,53 +232,19 @@ impl Scene for Editor { let _ = self.run_signal(graphics.shared.clone()); - if matches!(self.editor_state, EditorState::Playing) { - if let Some(play_world) = self.play_world.as_mut() { - let resolve_play_entity = |author_entity: Entity, author_world: &World, play_world: &World| { - let label = author_world - .get::<&Label>(author_entity) - .ok() - .map(|l| l.as_str().to_string())?; - play_world - .query::<&Label>() - .iter() - .find_map(|(e, l)| (l.as_str() == label).then_some(e)) - }; - if let Some(prev) = self.previously_selected_entity { - if let Some(prev_play) = resolve_play_entity(prev, self.world.as_ref(), play_world) { - if let Ok(mut q) = play_world.query_one::<&mut MeshRenderer>(prev_play) - && let Some(entity) = q.get() - { - entity.is_selected = false; - } - } - } - - if let Some(sel) = self.selected_entity { - if let Some(sel_play) = resolve_play_entity(sel, self.world.as_ref(), play_world) { - if let Ok(mut q) = play_world.query_one::<&mut MeshRenderer>(sel_play) - && let Some(entity) = q.get() - { - entity.is_selected = true; - } - } - } - } - } else { - if let Some(e) = self.previously_selected_entity - && let Ok(mut q) = self.world.query_one::<&mut MeshRenderer>(e) - && let Some(entity) = q.get() - { - entity.is_selected = false - } + if let Some(e) = self.previously_selected_entity + && let Ok(mut q) = self.world.query_one::<&mut MeshRenderer>(e) + && let Some(entity) = q.get() + { + entity.is_selected = false + } - if let Some(e) = self.selected_entity - && let Ok(mut q) = self.world.query_one::<&mut MeshRenderer>(e) - && let Some(entity) = q.get() - { - entity.is_selected = true - } + if let Some(e) = self.selected_entity + && let Ok(mut q) = self.world.query_one::<&mut MeshRenderer>(e) + && let Some(entity) = q.get() + { + entity.is_selected = true } let current_size = graphics.shared.viewport_texture.size; @@ -328,16 +253,11 @@ impl Scene for Editor { let new_aspect = current_size.width as f64 / current_size.height as f64; { - let (active_camera, world_for_aspect): (Option<Entity>, Option<&mut World>) = if matches!( - self.editor_state, - EditorState::Playing - ) { - (*self.play_active_camera.lock(), self.play_world.as_deref_mut()) - } else { - (*self.active_camera.lock(), Some(self.world.as_mut())) - }; + // Play mode runs in separate process, always use editor world + let active_camera = *self.active_camera.lock(); + let world = self.world.as_mut(); - if let (Some(active_camera), Some(world)) = (active_camera, world_for_aspect) + if let Some(active_camera) = active_camera && let Ok(mut query) = world.query_one::<&mut Camera>(active_camera) && let Some(camera) = query.get() { @@ -346,13 +266,7 @@ impl Scene for Editor { } { - let sim_world: &mut World = if matches!(self.editor_state, EditorState::Playing) { - self.play_world - .as_deref_mut() - .unwrap_or_else(|| self.world.as_mut()) - } else { - self.world.as_mut() - }; + let sim_world = self.world.as_mut(); for (_entity_id, (camera, component)) in sim_world .query::<(&mut Camera, &mut CameraComponent)>() @@ -364,13 +278,7 @@ impl Scene for Editor { } { - let sim_world: &mut World = if matches!(self.editor_state, EditorState::Playing) { - self.play_world - .as_deref_mut() - .unwrap_or_else(|| self.world.as_mut()) - } else { - self.world.as_mut() - }; + let sim_world = self.world.as_mut(); { let query = sim_world.query_mut::<(&mut MeshRenderer, &Transform)>(); @@ -403,11 +311,7 @@ impl Scene for Editor { } { - let sim_world: &World = if matches!(self.editor_state, EditorState::Playing) { - self.play_world.as_deref().unwrap_or_else(|| self.world.as_ref()) - } else { - self.world.as_ref() - }; + let sim_world = self.world.as_ref(); self.light_manager .update(graphics.shared.clone(), sim_world); @@ -417,6 +321,7 @@ impl Scene for Editor { self.input_state.window = self.window.clone(); self.previously_selected_entity = self.selected_entity; + self.input_state.mouse_delta = None; } fn render(&mut self, graphics: &mut RenderContext) { @@ -440,18 +345,8 @@ impl Scene for Editor { logging::render(&graphics.shared.get_egui_context()); if let Some(pipeline) = &self.render_pipeline { log_once::debug_once!("Found render pipeline"); - let (active_camera, world): (Option<Entity>, &World) = if matches!( - self.editor_state, - EditorState::Playing - ) { - let world = self - .play_world - .as_deref() - .unwrap_or_else(|| self.world.as_ref()); - (*self.play_active_camera.lock(), world) - } else { - (*self.active_camera.lock(), self.world.as_ref()) - }; + let (active_camera, world): (Option<Entity>, &World) = + (*self.active_camera.lock(), self.world.as_ref()); if let Some(active_camera) = active_camera { let cam = { @@ -9,5 +9,4 @@ pub mod signal; pub mod spawn; pub mod stats; pub mod utils; -pub mod command; pub mod runtime; @@ -204,7 +204,20 @@ async fn main() -> anyhow::Result<()> { path = find_eucp_in_dir(path.as_path())?; } - let config = ProjectConfig::read_from(path)?; + let config = ProjectConfig::read_from(path.clone())?; + + { + let mut project = eucalyptus_core::states::PROJECT.write(); + *project = config.clone(); + } + + let scene_to_load = initial_scene + .as_ref() + .or(config.runtime_settings.initial_scene.as_ref()) + .ok_or_else(|| anyhow::anyhow!("No initial scene specified and no default scene in project config"))?; + + eucalyptus_core::states::load_scene_into_memory(scene_to_load)?; + log::info!("Loaded initial scene '{}' for play mode", scene_to_load); let future_queue = Arc::new(FutureQueue::new()); @@ -1,6 +1,5 @@ use dropbear_engine::graphics::RenderContext; -use eucalyptus_core::command::{CommandBufferPoller, COMMAND_BUFFER, CommandBuffer, WindowCommand, get_config}; -use hecs::World; +use eucalyptus_core::command::{CommandBufferPoller, COMMAND_BUFFER, CommandBuffer, WindowCommand}; use winit::window::CursorGrabMode; use crate::runtime::PlayMode; @@ -18,9 +18,19 @@ impl Keyboard for PlayMode { impl Mouse for PlayMode { fn mouse_move(&mut self, position: PhysicalPosition<f64>, delta: Option<(f64, f64)>) { - self.input_state.last_mouse_pos = Some(<(f64, f64)>::from(position)); + let delta = if delta.is_none() { + if let Some(last_pos) = self.input_state.last_mouse_pos { + Some((position.x - last_pos.0, position.y - last_pos.1)) + } else { + None + } + } else { + delta + }; + self.input_state.mouse_delta = delta; self.input_state.mouse_pos = (position.x, position.y); + self.input_state.last_mouse_pos = Some(<(f64, f64)>::from(position)); } fn mouse_down(&mut self, button: MouseButton) { @@ -2,7 +2,6 @@ use std::sync::Arc; use crossbeam_channel::{unbounded, Receiver}; -use egui::layers::GraphicLayers; use futures::executor; use hecs::{Entity, World}; use wgpu::RenderPipeline; @@ -15,10 +14,8 @@ use dropbear_engine::shader::Shader; use eucalyptus_core::camera::CameraComponent; use eucalyptus_core::input::InputState; use eucalyptus_core::scripting::ScriptManager; -use eucalyptus_core::states::{WorldLoadingStatus, PROJECT, SCENES}; +use eucalyptus_core::states::{WorldLoadingStatus, SCENES}; use eucalyptus_core::traits::registry::ComponentRegistry; -use crate::editor::IsWorldLoadedYet; -use crate::graphics::OutlineShader; use crate::runtime::scene::IsSceneLoaded; mod scene; @@ -68,13 +65,13 @@ impl PlayMode { scripts_ready: false, display_settings: DisplaySettings { window_mode: WindowMode::Windowed, - render_resolution: (1280, 720), - display_resolution: (1280, 720), maintain_aspect_ratio: true, vsync: true, }, }; + log::debug!("Created new play mode instance"); + Ok(result) } @@ -130,6 +127,7 @@ impl PlayMode { /// Requests an asynchronous scene load, returning immediately and loading the scene in the background. pub fn request_async_scene_load(&mut self, graphics: &mut RenderContext, requested_scene: IsSceneLoaded) { + log::debug!("Requested async scene load: {}", requested_scene.requested_scene); let scene_name = requested_scene.requested_scene.clone(); self.scene_progress = Some(requested_scene); @@ -168,6 +166,8 @@ impl PlayMode { } }); + log::debug!("Created future handle for scene loading: {:?}", handle); + self.scene_loading_handle = Some(handle); if let Some(ref mut progress) = self.scene_progress { progress.scene_handle_requested = true; @@ -177,7 +177,7 @@ impl PlayMode { /// Requests an immediate scene load, blocking the current thread until the scene is fully loaded. pub fn request_immediate_scene_load(&mut self, graphics: &mut RenderContext, requested_scene: IsSceneLoaded) { let scene_name = requested_scene.requested_scene.clone(); - log::info!("Immediate scene load requested: {}", scene_name); + log::debug!("Immediate scene load requested: {}", scene_name); self.world = Box::new(World::new()); self.active_camera = None; @@ -230,12 +230,13 @@ impl PlayMode { progress.camera_received = true; self.scene_progress = Some(progress); - log::info!("Scene '{}' loaded immediately", scene_name); + log::debug!("Scene '{}' loaded immediately", scene_name); } /// Switches to a new scene, clearing the current world and preparing to load the new scene. pub fn switch_to(&mut self, scene_progress: IsSceneLoaded, _future_queue: Arc<FutureQueue>) { - log::info!("Switching to scene: {}", scene_progress.requested_scene); + // todo: fix this + log::debug!("Switching to new scene requested: {}", scene_progress.requested_scene); self.world = Box::new(World::new()); self.active_camera = None; @@ -253,8 +254,6 @@ impl PlayMode { pub struct DisplaySettings { pub window_mode: WindowMode, - pub render_resolution: (u32, u32), - pub display_resolution: (u32, u32), pub maintain_aspect_ratio: bool, pub vsync: bool, } @@ -1,8 +1,7 @@ use std::collections::HashMap; use std::path::PathBuf; -use crossbeam_channel::unbounded; use egui::{CentralPanel, MenuBar, TopBottomPanel}; -use hecs::{Entity, World}; +use hecs::{Entity}; use wgpu::Color; use wgpu::util::DeviceExt; use winit::event_loop::ActiveEventLoop; @@ -13,11 +12,9 @@ use dropbear_engine::lighting::{Light, LightComponent}; use dropbear_engine::model::{DrawLight, DrawModel, ModelId, MODEL_CACHE}; use dropbear_engine::scene::{Scene, SceneCommand}; use eucalyptus_core::command::{COMMAND_BUFFER, CommandBufferPoller}; -use eucalyptus_core::input::InputState; -use eucalyptus_core::logging; +use eucalyptus_core::ptr::{CommandBufferPtr, InputStatePtr, WorldPtr}; use eucalyptus_core::scripting::ScriptTarget; -use eucalyptus_core::states::{Script, WorldLoadingStatus, PROJECT, SCENES}; -use crate::editor::Signal; +use eucalyptus_core::states::{Script, PROJECT}; use crate::runtime::{PlayMode, WindowMode}; impl Scene for PlayMode { @@ -27,9 +24,11 @@ impl Scene for PlayMode { s.clone() } else { let proj = PROJECT.read(); - proj.runtime_settings.initial_scene.clone().unwrap() + proj.runtime_settings.initial_scene.clone().expect("No initial scene set in project settings") }; + log::debug!("Loading initial scene: {}", initial_scene); + let first_time = IsSceneLoaded::new_first_time(initial_scene); self.request_async_scene_load(graphics, first_time); @@ -58,7 +57,6 @@ impl Scene for PlayMode { } } else { self.world_receiver = Some(receiver); - return; } } @@ -74,7 +72,7 @@ impl Scene for PlayMode { self.scene_loading_handle = Some(handle) } } - + if let Some(ref progress) = self.scene_progress { if progress.is_everything_loaded() { if progress.is_first_scene() && !self.scripts_ready { @@ -90,21 +88,21 @@ impl Scene for PlayMode { fn find_jvm_library_path() -> PathBuf { let proj = PROJECT.read(); let project_path = if !proj.project_path.is_dir() { - proj.project_path.parent().unwrap().to_path_buf() + proj.project_path.parent().expect("Unable to locate parent of project").to_path_buf() } else { proj.project_path.clone() }.join("build/libs"); let mut latest_jar: Option<(PathBuf, std::time::SystemTime)> = None; - for entry in std::fs::read_dir(&project_path).unwrap() { - let entry = entry.unwrap(); + for entry in std::fs::read_dir(&project_path).expect("Unable to read directory") { + let entry = entry.expect("Unable to get directory entry"); let path = entry.path(); if let Some(filename) = path.file_name().and_then(|n| n.to_str()) { if filename.ends_with("-all.jar") { - let metadata = entry.metadata().unwrap(); - let modified = metadata.modified().unwrap(); + let metadata = entry.metadata().expect("Unable to get file metadata"); + let modified = metadata.modified().expect("Unable to get file modified time"); match latest_jar { None => latest_jar = Some((path.clone(), modified)), @@ -122,6 +120,7 @@ impl Scene for PlayMode { let target = ScriptTarget::JVM { library_path: find_jvm_library_path() }; + log::debug!("Awaiting script initialisation with target: {:?}", target); if let Err(e) = self.script_manager.init_script( None, entity_tag_map.clone(), @@ -129,9 +128,9 @@ impl Scene for PlayMode { ) { log::error!("Failed to initialise scripts: {}", e); } else { - let world_ptr = self.world.as_mut() as *mut World; - let input_ptr = &mut self.input_state as *mut InputState; - let graphics_ptr = &COMMAND_BUFFER.0 as *const _ as *const _; + let world_ptr = self.world.as_mut() as WorldPtr; + let input_ptr = &mut self.input_state as InputStatePtr; + let graphics_ptr = COMMAND_BUFFER.0.as_ref() as CommandBufferPtr; if let Err(e) = self.script_manager.load_script(world_ptr, input_ptr, graphics_ptr) { log::error!("Failed to load scripts: {}", e); @@ -143,8 +142,8 @@ impl Scene for PlayMode { } if self.scripts_ready { - if let Err(e) = unsafe { self.script_manager.update_script(&self.world, dt) } { - log::error!("Script update error: {}", e); + if let Err(e) = self.script_manager.update_script(&self.world, dt) { + panic!("Script update error: {}", e); } } } @@ -153,26 +152,6 @@ impl Scene for PlayMode { TopBottomPanel::top("menu_bar").show(&graphics.shared.get_egui_context(), |ui| { MenuBar::new().ui(ui, |ui| { ui.menu_button("Window", |ui| { - ui.menu_button("Resolution", |ui| { - let resolutions = [ - (1280, 720, "1280×720 (HD)"), - (1600, 900, "1600×900"), - (1920, 1080, "1920×1080 (Full HD)"), - (2560, 1440, "2560×1440 (QHD)"), - (3840, 2160, "3840×2160 (4K)"), - ]; - - for (width, height, label) in resolutions { - let is_current = self.display_settings.render_resolution == (width, height); - if ui.selectable_label(is_current, label).clicked() { - self.display_settings.render_resolution = (width, height); - ui.close(); - } - } - }); - - ui.separator(); - ui.menu_button("Window Mode", |ui| { let is_windowed = matches!(self.display_settings.window_mode, WindowMode::Windowed); if ui.selectable_label(is_windowed, "Windowed").clicked() { @@ -201,13 +180,8 @@ impl Scene for PlayMode { ui.separator(); - if ui.checkbox(&mut self.display_settings.maintain_aspect_ratio, "Maintain aspect ratio").clicked() { - self.display_settings.maintain_aspect_ratio = !self.display_settings.maintain_aspect_ratio; - } - - if ui.checkbox(&mut self.display_settings.vsync, "VSync").clicked() { - self.display_settings.vsync = !self.display_settings.vsync; - } + ui.checkbox(&mut self.display_settings.maintain_aspect_ratio, "Maintain aspect ratio"); + ui.checkbox(&mut self.display_settings.vsync, "VSync").clicked(); }); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { @@ -241,170 +215,160 @@ impl Scene for PlayMode { } let texture_id = *graphics.shared.texture_id; - - let (render_width, render_height) = self.display_settings.render_resolution; - let render_aspect = render_width as f32 / render_height as f32; let available_size = ui.available_size(); let available_rect = ui.available_rect_before_wrap(); - let (display_width, display_height) = if self.display_settings.maintain_aspect_ratio { - if available_size.x / available_size.y > render_aspect { - let height = available_size.y; - let width = height * render_aspect; - (width, height) - } else { - let width = available_size.x; - let height = width / render_aspect; - (width, height) - } - } else { - (available_size.x, available_size.y) - }; + if let Some(active_camera) = self.active_camera { + if let Ok(cam) = self.world.query_one_mut::<&Camera>(active_camera) { + let (display_width, display_height) = if self.display_settings.maintain_aspect_ratio { + let width = available_size.x; + let height = width / cam.aspect as f32; + (width, height) + } else { + (available_size.x, available_size.y) + }; - let center_x = available_rect.center().x; - let center_y = available_rect.center().y; + let center_x = available_rect.center().x; + let center_y = available_rect.center().y; - let image_rect = egui::Rect::from_center_size( - egui::pos2(center_x, center_y), - egui::vec2(display_width, display_height), - ); + let image_rect = egui::Rect::from_center_size( + egui::pos2(center_x, center_y), + egui::vec2(display_width, display_height), + ); - ui.allocate_exact_size(available_size, egui::Sense::hover()); + ui.allocate_exact_size(available_size, egui::Sense::hover()); - ui.scope_builder(egui::UiBuilder::new().max_rect(image_rect), |ui| { - ui.add_sized( - [display_width, display_height], - egui::Image::new((texture_id, [display_width, display_height].into())) - .fit_to_exact_size([display_width, display_height].into()), - ) - }); + ui.scope_builder(egui::UiBuilder::new().max_rect(image_rect), |ui| { + ui.add_sized( + [display_width, display_height], + egui::Image::new((texture_id, [display_width, display_height].into())) + .fit_to_exact_size([display_width, display_height].into()), + ) + }); + } + } }); + + self.input_state.mouse_delta = None; } fn render(&mut self, graphics: &mut RenderContext) { - // cornflower blue - let color = Color { + let Some(active_camera) = self.active_camera else { + return; + }; + log_once::debug_once!("Active camera found: {:?}", active_camera); + + let q = if let Ok(mut query) = self.world.query_one::<&Camera>(active_camera) { + query.get().cloned() + } else { + None + }; + + let Some(camera) = q else { + return; + }; + log_once::debug_once!("Camera ready"); + log_once::debug_once!("Camera currently being viewed: {}", camera.label); + + // camera.debug_camera_state(); + // println!("{:#?}", self.project_config); + + let Some(pipeline) = &self.render_pipeline else { + log_once::warn_once!("Render pipeline not ready"); + return; + }; + log_once::debug_once!("Pipeline ready"); + + let clear_color = Color { r: 100.0 / 255.0, g: 149.0 / 255.0, b: 237.0 / 255.0, a: 1.0, }; - if let Some(pipeline) = &self.render_pipeline { - log_once::debug_once!("Found render pipeline"); + let lights = { + let mut lights = Vec::new(); + let mut query = self.world.query::<(&Light, &LightComponent)>(); + for (_, (light, comp)) in query.iter() { + lights.push((light.clone(), comp.clone())); + } + lights + }; - if let Some(active_camera) = self.active_camera { - let cam = { - if let Ok(mut query) = self.world.query_one::<&Camera>(active_camera) { - query.get().cloned() - } else { - None - } - }; - - if let Some(camera) = cam { - let lights = { - let mut lights = Vec::new(); - let mut light_query = self.world.query::<(&Light, &LightComponent)>(); - for (_, (light, comp)) in light_query.iter() { - lights.push((light.clone(), comp.clone())); - } - lights - }; + let renderers = { + let mut renderers = Vec::new(); + let mut query = self.world.query::<&MeshRenderer>(); + for (_, renderer) in query.iter() { + renderers.push(renderer.clone()); + } + renderers + }; - let entities = { - let mut entities = Vec::new(); - let mut entity_query = self.world.query::<&MeshRenderer>(); - for (_, renderer) in entity_query.iter() { - entities.push(renderer.clone()); - } - entities - }; + { + let mut query = self.world.query::<(&mut LightComponent, &dropbear_engine::entity::Transform, &mut Light)>(); + for (_, (light_component, transform, light)) in query.iter() { + light.update(light_component, transform); + } + } - { - // light cube rendering - let mut render_pass = graphics.clear_colour(color); - if let Some(light_pipeline) = &self.light_manager.pipeline { - render_pass.set_pipeline(light_pipeline); - for (light, _component) in &lights { - render_pass.set_vertex_buffer( - 1, - light.instance_buffer.as_ref().unwrap().slice(..), - ); - if _component.visible { - render_pass.draw_light_model( - &light.cube_model, - camera.bind_group(), - light.bind_group(), - ); - } - } + self.light_manager.update(graphics.shared.clone(), &self.world); + + { + let mut render_pass = graphics.clear_colour(clear_color); + if let Some(light_pipeline) = &self.light_manager.pipeline { + render_pass.set_pipeline(light_pipeline); + for (light, component) in &lights { + if let Some(buffer) = &light.instance_buffer { + render_pass.set_vertex_buffer(1, buffer.slice(..)); + if component.visible { + render_pass.draw_light_model( + &light.cube_model, + camera.bind_group(), + light.bind_group(), + ); } } + } + } + } - let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new(); - for renderer in &entities { - let model_ptr = renderer.model_id(); - let instance_raw = renderer.instance.to_raw(); - model_batches - .entry(model_ptr) - .or_default() - .push(instance_raw); - } + let mut model_batches: HashMap<ModelId, Vec<InstanceRaw>> = HashMap::new(); + for renderer in &renderers { + model_batches + .entry(renderer.model_id()) + .or_default() + .push(renderer.instance.to_raw()); + } - for (model_ptr, instances) in model_batches { - { - let model_opt = { - let cache = MODEL_CACHE.lock(); - cache.values().find(|m| m.id == model_ptr).cloned() - }; - - if let Some(model) = model_opt { - let instance_buffer = graphics.shared.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Batched Instance Buffer"), - contents: bytemuck::cast_slice(&instances), - usage: wgpu::BufferUsages::VERTEX, - }, - ); - - { - // normal model rendering - let mut render_pass = graphics.continue_pass(); - render_pass.set_pipeline(pipeline); - - render_pass.set_vertex_buffer(1, instance_buffer.slice(..)); - render_pass.draw_model_instanced( - &model, - 0..instances.len() as u32, - camera.bind_group(), - self.light_manager.bind_group(), - ); - } + for (model_id, instances) in model_batches { + let model_opt = { + let cache = MODEL_CACHE.lock(); + cache.values().find(|model| model.id == model_id).cloned() + }; - log_once::debug_once!("Rendered {:?}", model_ptr); - } else { - log_once::error_once!("No such MODEL as {:?}", model_ptr); - } - } - } - } else { - log_once::error_once!("Camera returned None"); - } - } else { - log_once::error_once!("No active camera found"); - } - } else { - if let Some(p) = &self.scene_progress { - if p.is_everything_loaded() { - log_once::warn_once!("No render pipeline exists"); - } else { - log_once::debug_once!("No render pipeline exists, but world not loaded yet"); - } - } else { - log_once::debug_once!("No render pipeline exists"); - } + let Some(model) = model_opt else { + log_once::error_once!("Missing model {:?} in cache", model_id); + continue; + }; + + let instance_buffer = graphics.shared.device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Runtime Instance Buffer"), + contents: bytemuck::cast_slice(&instances), + usage: wgpu::BufferUsages::VERTEX, + }, + ); + + let mut render_pass = graphics.continue_pass(); + render_pass.set_pipeline(pipeline); + render_pass.set_vertex_buffer(1, instance_buffer.slice(..)); + render_pass.draw_model_instanced( + &model, + 0..instances.len() as u32, + camera.bind_group(), + self.light_manager.bind_group(), + ); } } @@ -219,7 +219,7 @@ impl SignalController for Editor { self.show_build_window = false; - self.load_play_mode(jar_path, graphics.clone())?; + self.load_play_mode()?; return Ok(()); } @@ -357,7 +357,7 @@ impl SignalController for Editor { success!("Build completed successfully!"); self.show_build_window = false; - self.load_play_mode(path, graphics.clone())?; + self.load_play_mode()?; } Err(e) => { let error_msg = format!("Build process error: {}", e); @@ -424,24 +424,16 @@ impl SignalController for Editor { Ok(()) } Signal::StopPlaying => { - if let Err(e) = self.restore() { - warn!("Failed to restore from play mode backup: {}", e); - log::warn!("Failed to restore scene state: {}", e); - } - - // Drop play-mode-only state; authoring world remains intact. - self.play_world = None; - self.play_world_receiver = None; - self.play_world_load_handle = None; - self.pending_play_scene_load = None; - self.play_active_camera.lock().take(); - self.play_script_jar = None; - self.play_scripts_loaded = false; self.editor_state = EditorState::Editing; - - self.switch_to_debug_camera(); - + + if let Some(pid) = self.play_mode_pid { + log::info!("Play mode process {} should exit soon", pid); + } + + self.play_mode_pid = None; + self.play_mode_exit_rx = None; + success!("Exited play mode"); log::info!("Back to the editor you go..."); @@ -88,7 +88,7 @@ impl RuntimeScene { current_scene: None, pending_scene_switch: None, component_registry, - script_manager: ScriptManager::new(window_config.jvm_args)?, + script_manager: ScriptManager::new()?, script_target, scripts_ready: false, scene_command: Default::default(),