diff --git a/crates/comet_app/Cargo.toml b/crates/comet_app/Cargo.toml index 446a9d8..db42dd4 100644 --- a/crates/comet_app/Cargo.toml +++ b/crates/comet_app/Cargo.toml @@ -21,6 +21,7 @@ bytemuck = "1.18.0" chrono = "0.4.0" winit_input_helper = "0.16.0" spin_sleep = "1.2.1" +crossbeam-channel = "0.5.14" [dependencies.image] version = "0.24" diff --git a/crates/comet_app/src/app.rs b/crates/comet_app/src/app.rs index d0dfd07..e9745cb 100644 --- a/crates/comet_app/src/app.rs +++ b/crates/comet_app/src/app.rs @@ -1,8 +1,10 @@ use std::any::{type_name, Any}; use std::sync::{Arc, Mutex, RwLock}; +use std::sync::atomic::AtomicBool; use std::thread; use std::time::{Duration, Instant}; -use comet_ecs::{Component, ComponentSet, Render, Transform2D, Transform3D, World}; +use crossbeam_channel::bounded; +use comet_ecs::{Component, ComponentSet, Entity, Render, Transform2D, Transform3D, World}; use comet_resources::{ResourceManager, Vertex}; use comet_renderer::renderer2d::Renderer2D; @@ -17,6 +19,7 @@ use comet_ecs::math::Point3; use comet_log::*; use winit::dpi::{LogicalSize, PhysicalSize}; use winit::event_loop::ControlFlow; +use winit::window::Fullscreen; use winit_input_helper::WinitInputHelper; use comet_input::input_handler::InputHandler; use comet_input::keyboard::Key; @@ -28,8 +31,15 @@ pub enum ApplicationType { App3D } -pub struct App<'a> { - title: &'a str, +pub enum AppMessage { + Resize(PhysicalSize), + Input(WinitInputHelper), + UpdateCompleted(f32), + Quit +} + +pub struct App { + title: String, icon: Option, size: Option>, clear_color: Option, @@ -42,10 +52,10 @@ pub struct App<'a> { should_quit: bool } -impl<'a> App<'a> { +impl App { pub fn new() -> Self { Self { - title: "Untitled", + title: "Untitled".to_string(), icon: None, size: None, clear_color: None, @@ -59,13 +69,13 @@ impl<'a> App<'a> { } } - pub fn with_title(mut self, title: &'a str) -> Self { - self.title = title; + pub fn with_title(mut self, title: impl Into) -> Self { + self.title = title.into(); self } - pub fn with_icon(mut self, path: &'a str) -> Self { - self.icon = Some(Self::load_icon(std::path::Path::new(path)).unwrap()); + pub fn with_icon(mut self, path: impl AsRef) -> Self { + self.icon = Self::load_icon(path.as_ref()); self } @@ -99,7 +109,13 @@ impl<'a> App<'a> { } fn load_icon(path: &std::path::Path) -> Option { - let image = image::open(path).expect("Failed to open icon image"); + let image = match image::open(path) { + Ok(image) => image, + Err(e) => { + error!("Failed loading icon {}", path.display()); + return None; + } + }; let rgba_image = image.to_rgba8(); let (width, height) = rgba_image.dimensions(); Some(Icon::from_rgba(rgba_image.into_raw(), width, height).unwrap()) @@ -113,14 +129,6 @@ impl<'a> App<'a> { self.game_state.as_mut()?.downcast_mut::() } - pub fn world(&self) -> &World { - &self.world - } - - pub fn world_mut(&mut self) -> &mut World { - &mut self.world - } - pub fn input_manager(&self) -> &WinitInputHelper { &self.input_manager } @@ -137,6 +145,46 @@ impl<'a> App<'a> { self.input_manager.key_released(key) } + pub fn new_entity(&mut self) -> u32 { + self.world.new_entity() + } + + pub fn delete_entity(&mut self, entity_id: usize) { + self.world.delete_entity(entity_id) + } + + pub fn get_entity(&self, entity_id: usize) -> Option<&Entity> { + self.world.get_entity(entity_id) + } + + pub fn get_entity_mut(&mut self, entity_id: usize) -> Option<&mut Entity> { + self.world.get_entity_mut(entity_id) + } + + pub fn register_component(&mut self) { + self.world.register_component::() + } + + pub fn deregister_component(&mut self) { + self.world.deregister_component::() + } + + pub fn add_component(&mut self, entity_id: usize, component: C) { + self.world.add_component(entity_id, component) + } + + pub fn remove_component(&mut self, entity_id: usize) { + self.world.remove_component::(entity_id) + } + + pub fn get_component(&self, entity_id: usize) -> Option<&C> { + self.world.get_component::(entity_id) + } + + pub fn get_component_mut(&mut self, entity_id: usize) -> Option<&mut C> { + self.world.get_component_mut::(entity_id) + } + pub fn quit(&mut self) { self.should_quit = true; } @@ -154,7 +202,7 @@ impl<'a> App<'a> { self.update_timer = 1.0/update_rate as f32; } - fn create_window(app_title: &str, app_icon: &Option, window_size: &Option>, event_loop: &EventLoop<()>) -> Window { + fn create_window(app_title: String, app_icon: &Option, window_size: &Option>, event_loop: &EventLoop<()>) -> Window { let winit_window = winit::window::WindowBuilder::new() .with_title(app_title); @@ -173,19 +221,103 @@ impl<'a> App<'a> { winit_window.build(event_loop).unwrap() } - /*pub fn run(mut self, setup: fn(&mut App, &mut R), update: fn(&mut App, &mut R, f32)) { + /*pub fn run(mut self, setup: fn(&mut App, &mut R), update: fn(&mut App, &mut R, f32)) { info!("Starting up {}!", self.title); pollster::block_on(async { let event_loop = EventLoop::new().unwrap(); - let window = Arc::new(Self::create_window(self.title, &self.icon, &self.size ,&event_loop)); - let mut renderer = R::new(window.clone(), self.clear_color.clone()).await; + let window = Arc::new(Self::create_window(self.title.clone(), &self.icon, &self.size, &event_loop)); + let mut renderer = Arc::new(RwLock::new(R::new(window.clone(), self.clear_color.clone()).await)); info!("Renderer created! ({})", type_name::()); + window.set_maximized(true); - setup(&mut self, &mut renderer); + let app = Arc::new(RwLock::new(self.clone())); + + // Run setup with locked app + { + let mut app_lock = app.write().unwrap(); + let mut renderer_lock = renderer.write().unwrap(); + setup(&mut *app_lock, &mut *renderer_lock); + } + + let (game_tx, game_rx) = bounded::(10); + let (render_tx, render_rx) = bounded::(10); + + // Spawn game logic thread + let game_thread_app = Arc::clone(&app); + let game_thread_renderer = Arc::clone(&renderer); + + thread::spawn(move || { + let mut time_stack = 0.0; + let mut last_update = Instant::now(); + + while !game_thread_app.read().unwrap().should_quit { + let now = Instant::now(); + let delta = now.duration_since(last_update).as_secs_f32(); + + // Get a single write lock and use it for the entire update + let mut app_lock = game_thread_app.write().unwrap(); + app_lock.delta_time = delta; + + time_stack += delta; + let update_timer = app_lock.update_timer; // Store the timer value + + while time_stack > update_timer { + let mut renderer_lock = game_thread_renderer.write().unwrap(); + update(&mut *app_lock, &mut *renderer_lock, delta); + drop(renderer_lock); + time_stack -= update_timer; + render_tx.send(AppMessage::UpdateCompleted(delta)).unwrap(); + } + + // Lock is automatically released here + drop(app_lock); + + last_update = now; + thread::sleep(Duration::from_millis(1)); + } + }); + + // Main thread handles events and rendering + info!("Starting event loop!"); + event_loop.run(move |event, elwt| { + // Get a single write lock for the entire input handling + let mut app_lock = app.write().unwrap(); + if app_lock.should_quit { + elwt.exit(); + } + app_lock.input_manager.update(&event); + drop(app_lock); // Explicitly drop the lock before event handling + + match event { + Event::WindowEvent { ref event, .. } => { + match event { + WindowEvent::CloseRequested => { + app.write().unwrap().quit(); + elwt.exit(); + } + WindowEvent::Resized(size) => { + let mut renderer_lock = renderer.write().unwrap(); + renderer_lock.resize(*size); + } + WindowEvent::RedrawRequested => { + while let Ok(AppMessage::UpdateCompleted(_)) = render_rx.try_recv() { + let mut renderer_lock = renderer.write().unwrap(); + match renderer_lock.render() { + Ok(_) => window.request_redraw(), + Err(e) => error!("Error rendering: {}", e) + } + } + } + _ => {} + } + } + _ => {} + } + }).unwrap(); }); - info!("Shutting down {}!", self.title); + info!("Shutting down {}!", self.title.clone()); }*/ pub fn run(mut self, setup: fn(&mut App, &mut R), update: fn(&mut App, &mut R, f32)) { @@ -193,10 +325,10 @@ impl<'a> App<'a> { pollster::block_on(async { let event_loop = EventLoop::new().unwrap(); - let window = Arc::new(Self::create_window(self.title, &self.icon, &self.size ,&event_loop)); + let window = Arc::new(Self::create_window(self.title.clone(), &self.icon, &self.size ,&event_loop)); let mut renderer = R::new(window.clone(), self.clear_color.clone()).await; info!("Renderer created! ({})", type_name::()); - window.set_maximized(true); + //window.set_maximized(true); info!("Setting up!"); setup(&mut self, &mut renderer); diff --git a/crates/comet_ecs/src/component.rs b/crates/comet_ecs/src/component.rs index 097dbf6..9fb7e77 100644 --- a/crates/comet_ecs/src/component.rs +++ b/crates/comet_ecs/src/component.rs @@ -8,6 +8,7 @@ use crate::math::{ Vec3 }; use component_derive::Component; +use crate::Entity; // ################################################## // # BASIC # @@ -48,6 +49,17 @@ pub struct Render2D { scale: Vec2 } +#[derive(Component)] +pub struct Camera2D { + left: f32, + right: f32, + bottom: f32, + top: f32, + near: f32, + far: f32, + zoom: f32 +} + // ################################################## // # BUNDLES # // ################################################## @@ -91,6 +103,10 @@ pub trait Render { fn set_texture(&mut self, texture: &'static str); } +pub trait Camera { + fn get_visible_entities(&self) -> Vec; +} + // ################################################## // # IMPLS # // ################################################## @@ -170,11 +186,9 @@ impl Rectangle2D { pub fn position(&self) -> Position2D { self.position } - pub fn set_position(&mut self, position: Position2D) { self.position = position; } - pub fn size(&self) -> Vec2 { self.size } @@ -261,4 +275,33 @@ impl Transform3D { pub fn rotation_mut(&mut self) -> &mut Rotation3D { &mut self.rotation } +} + +impl Camera2D { + pub fn new(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32, zoom: f32) -> Self { + Self { + left, + right, + bottom, + top, + near, + far, + zoom + } + } + + fn in_view_frustum(&self, camera_pos: Position2D, entity: Position2D) -> bool { + let left = camera_pos.x() - self.zoom; + let right = camera_pos.x() + self.zoom; + let bottom = camera_pos.y() - self.zoom; + let top = camera_pos.y() + self.zoom; + + entity.x() < right && entity.x() > left && entity.y() < top && entity.y() > bottom + } +} + +impl Camera for Camera2D { + fn get_visible_entities(&self) -> Vec { + unimplemented!() + } } \ No newline at end of file diff --git a/crates/comet_ecs/src/storage.rs b/crates/comet_ecs/src/storage.rs index 6cd707c..e056480 100644 --- a/crates/comet_ecs/src/storage.rs +++ b/crates/comet_ecs/src/storage.rs @@ -193,6 +193,9 @@ impl Drop for BlobVec { } } +unsafe impl Send for BlobVec {} +unsafe impl Sync for BlobVec {} + fn array_layout(layout: &Layout, n: usize) -> Option { let (array_layout, offset) = repeat_layout(layout, n)?; debug_assert_eq!(layout.size(), offset); @@ -538,7 +541,7 @@ impl Hash for ComponentSet { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Archetypes { archetypes: HashMap> } diff --git a/crates/comet_ecs/src/world.rs b/crates/comet_ecs/src/world.rs index 744ae87..aa777cc 100644 --- a/crates/comet_ecs/src/world.rs +++ b/crates/comet_ecs/src/world.rs @@ -13,6 +13,7 @@ use crate::{ }; use comet_log::*; +#[derive(Clone)] pub struct World { id_queue: IdQueue, next_id: u32, @@ -23,17 +24,16 @@ pub struct World { impl World { pub fn new() -> Self { - let mut component_storage = ComponentStorage::new(); - Self { id_queue: IdQueue::new(), next_id: 0, entities: Vec::new(), - components: component_storage, + components: ComponentStorage::new(), archetypes: Archetypes::new() } } + /// Returns the number of how many entities exist in the current World. pub fn active_entities(&self) -> u32 { self.entities.len() as u32 - self.id_queue.size() } @@ -48,48 +48,42 @@ impl World { } } - pub fn id_queue(&self) -> &IdQueue { - &self.id_queue - } - + /// Retuns the `Vec` of `Option` which contains all the entities in the current World. pub fn entities(&self) -> &Vec> { &self.entities } - pub fn components(&self) -> &ComponentStorage { - &self.components - } - - pub fn components_mut(&mut self) -> &mut ComponentStorage { - &mut self.components - } - + /// Creates a new entity and returns its ID. pub fn new_entity(&mut self) -> u32 { let id = self.next_id; if (self.next_id as usize) >= self.entities.len() { self.entities.push(Some(Entity::new(self.next_id))); self.get_next_id(); + info!("Created entity! ID: {}", id); return id; } self.entities[self.next_id as usize] = Some(Entity::new(self.next_id)); self.get_next_id(); + info!("Created entity! ID: {}", id); id } - pub fn get_entity(&self, entity_id: usize) -> &Entity { - assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); - self.entities.get(entity_id).unwrap().as_ref().unwrap() + /// Gets an immutable reference to an entity by its ID. + pub fn get_entity(&self, entity_id: usize) -> Option<&Entity> { + //assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + self.entities.get(entity_id).unwrap().as_ref() } - pub fn get_entity_mut(&mut self, entity_id: usize) -> &mut Entity { - assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); - self.entities.get_mut(entity_id).unwrap().as_mut().unwrap() + /// Gets a mutable reference to an entity by its ID. + pub fn get_entity_mut(&mut self, entity_id: usize) -> Option<&mut Entity> { + //assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + self.entities.get_mut(entity_id).unwrap().as_mut() //self.entities.get_mut(id).unwrap() } + /// Deletes an entity by its ID. pub fn delete_entity(&mut self, entity_id: usize) { self.entities[entity_id] = None; - //self.get_entity(id); for (key, value) in self.components.iter_mut() { value.remove::(entity_id); } @@ -154,55 +148,59 @@ impl World { ComponentSet::from_ids(type_ids) } - pub fn register_component(&mut self) { - self.components.register_component::(self.entities.len()); - self.create_archetype(ComponentSet::from_ids(vec![T::type_id()])); - info!("Registered component: {}", T::type_name()); + /// Registers a new component in the world. + pub fn register_component(&mut self) { + self.components.register_component::(self.entities.len()); + self.create_archetype(ComponentSet::from_ids(vec![C::type_id()])); + info!("Registered component: {}", C::type_name()); } - pub fn deregister_component(&mut self) { - self.components.deregister_component::(); - info!("Deregistered component: {}", T::type_name()); + /// Deregisters a component from the world. + pub fn deregister_component(&mut self) { + self.components.deregister_component::(); + info!("Deregistered component: {}", C::type_name()); } - pub fn add_component(&mut self, entity_id: usize, component: T) { + /// Adds a component to an entity by its ID and an instance of the component. + pub fn add_component(&mut self, entity_id: usize, component: C) { assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); self.components.set_component(entity_id, component); - let component_index = self.components.keys.iter_mut().position(|x| *x == T::type_id()).unwrap(); + let component_index = self.components.keys.iter_mut().position(|x| *x == C::type_id()).unwrap(); - self.get_entity_mut(entity_id).add_component(component_index); + self.get_entity_mut(entity_id).unwrap().add_component(component_index); if !self.archetypes.contains_archetype(&self.get_component_set(entity_id)) { self.create_archetype(self.get_component_set(entity_id)); } - self.add_entity_to_archetype(entity_id as u32, ComponentSet::from_ids(vec![T::type_id()])); - if self.get_component_set(entity_id) != ComponentSet::from_ids(vec![T::type_id()]) { + self.add_entity_to_archetype(entity_id as u32, ComponentSet::from_ids(vec![C::type_id()])); + if self.get_component_set(entity_id) != ComponentSet::from_ids(vec![C::type_id()]) { self.add_entity_to_archetype(entity_id as u32, self.get_component_set(entity_id)); } - info!("Added component {} to entity {}", T::type_name(), entity_id); + info!("Added component {} to entity {}", C::type_name(), entity_id); } - pub fn remove_component(&mut self, entity_id: usize) { - self.components.remove_component::(entity_id); + /// Removes a component from an entity by its ID. + pub fn remove_component(&mut self, entity_id: usize) { + self.components.remove_component::(entity_id); self.remove_entity_from_archetype_subsets(entity_id as u32, self.get_component_set(entity_id)); - info!("Removed component {} from entity {}", T::type_name(), entity_id); + info!("Removed component {} from entity {}", C::type_name(), entity_id); } - pub fn get_component(&self, entity_id: usize) -> &T { + /// Returns a reference to a component of an entity by its ID. + pub fn get_component(&self, entity_id: usize) -> Option<&C> { + //assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); + self.components.get_component::(entity_id) + } + + pub fn get_component_mut(&mut self, entity_id: usize) -> Option<&mut C> { assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); - //assert_ne!(self.components.get_component::(entity_id), None, "There is no component {} bound to the entity {} in the world!", T::type_name(), entity_id); - self.components.get_component::(entity_id).unwrap() - } - - pub fn get_component_mut(&mut self, entity_id: usize) -> &mut T { - assert_ne!(self.entities.get(entity_id), None, "There is no entity with this ID ({}) in the world!", entity_id); - assert!(self.components.get_component::(entity_id) != None, "There is no component {} bound to the entity {} in the world!", T::type_name(), entity_id); - self.components.get_component_mut::(entity_id).unwrap() + assert!(self.components.get_component::(entity_id) != None, "There is no component {} bound to the entity {} in the world!", C::type_name(), entity_id); + self.components.get_component_mut::(entity_id) } + /// Returns a list of entities that have the given components. pub fn get_entities_with(&self, components: ComponentSet) -> Vec { assert!(self.archetypes.contains_archetype(&components), "The given components {:?} are not registered in the world!", components); - //debug!(format!("Querying entities with components: {:?}", components)); self.archetypes.get_archetype(&components).unwrap().clone() } } diff --git a/crates/comet_renderer/src/renderer.rs b/crates/comet_renderer/src/renderer.rs index 7bb52f4..af148b3 100644 --- a/crates/comet_renderer/src/renderer.rs +++ b/crates/comet_renderer/src/renderer.rs @@ -3,7 +3,7 @@ use winit::dpi::PhysicalSize; use winit::window::Window; use comet_colors::LinearRgba; -pub trait Renderer: Sized { +pub trait Renderer: Sized + Send + Sync { async fn new(window: Arc, clear_color: Option) -> Self; fn size(&self) -> PhysicalSize; fn resize(&mut self, new_size: winit::dpi::PhysicalSize); diff --git a/crates/comet_renderer/src/renderer2d.rs b/crates/comet_renderer/src/renderer2d.rs index f4062db..be6f0f6 100644 --- a/crates/comet_renderer/src/renderer2d.rs +++ b/crates/comet_renderer/src/renderer2d.rs @@ -740,12 +740,12 @@ impl<'a> Renderer2D<'a> { let renderer_component = world.get_component::(entity as usize); let transform_component = world.get_component::(entity as usize); - if renderer_component.is_visible() { + if renderer_component.unwrap().is_visible() { //renderer.draw_texture_at(renderer_component.get_texture(), Point3::new(transform_component.position().x(), transform_component.position().y(), 0.0)); - let mut position = transform_component.position().clone(); + let mut position = transform_component.unwrap().position().clone(); position.set_x(position.x() / self.config().width as f32); position.set_y(position.y() / self.config().height as f32); - let region = self.get_texture_region(renderer_component.get_texture().to_string()); + let region = self.get_texture_region(renderer_component.unwrap().get_texture().to_string()); let (dim_x, dim_y) = region.dimensions(); let (bound_x, bound_y) =