From 7e4168eee5e16dc29c6d8261513ee108d1d94dd0 Mon Sep 17 00:00:00 2001 From: reo Date: Tue, 1 Jul 2025 23:25:57 +0300 Subject: [PATCH] Add ECSRenderer for improved integration of rendering and ECS - Introduced ECSRenderer struct to manage the connection between the renderer and the ECS. - Implemented methods for spawning and despawning meshes within the ECS. - Updated main function to utilize ECSRenderer for rendering and object management. - Added a new Time module to handle frame timing and updates. --- src/ecs.rs | 40 ++++++++++++++++++++++++++++++++++--- src/main.rs | 57 ++++++++++++++++++++++++++++------------------------- src/time.rs | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 30 deletions(-) create mode 100644 src/time.rs diff --git a/src/ecs.rs b/src/ecs.rs index 0f47217..a67aa32 100644 --- a/src/ecs.rs +++ b/src/ecs.rs @@ -1,13 +1,48 @@ use glam::{Mat4, Quat, Vec3}; -use hecs::World; +use hecs::{Entity, World}; +use crate::{render::{GliumRenderer, Renderer}, model}; + +/// This system joins the renderer and ECS, +/// and provides tools to use them together +/// effectively. +pub struct ECSRenderer { + pub renderer: GliumRenderer, + pub world: World, +} + +impl ECSRenderer { + pub fn new(renderer: GliumRenderer, world: World) -> Self { + Self { renderer, world } + } + + pub fn spawn_mesh(&mut self, mesh: model::Mesh, transform: Transform) -> Entity { + let mesh_id = self.renderer.meshes.len(); + self.renderer.meshes.push(mesh); + self.world.spawn((transform, MeshHandle(mesh_id))) + } + + pub fn despawn_mesh(&mut self, entity: Entity) { + if let Ok(mesh_handle) = self.world.get::<&MeshHandle>(entity) { + if mesh_handle.0 < self.renderer.meshes.len() { + self.renderer.meshes.remove(mesh_handle.0); + } + } + let _ = self.world.despawn(entity); + } + + /// Render a single frame using the internal renderer & world. + pub fn render(&mut self) { + self.renderer.render(&self.world); + } +} -/// ------------ components ------------ #[derive(Copy, Clone)] pub struct Transform { pub translation: Vec3, pub rotation: Quat, pub scale: Vec3, } + impl Transform { pub fn matrix(&self) -> Mat4 { Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation) @@ -16,4 +51,3 @@ impl Transform { #[derive(Clone)] pub struct MeshHandle(pub usize); - diff --git a/src/main.rs b/src/main.rs index cebe8e7..74fda0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod camera; mod ecs; mod model; mod render; +mod time; use anyhow::Result; use camera::Camera; @@ -9,9 +10,10 @@ use ecs::{MeshHandle, Transform}; use glam::{Quat, Vec3}; use glium::backend::glutin::SimpleWindowBuilder; use render::{Renderer, GliumRenderer}; -use std::time::Instant; fn main() -> Result<()> { + const ROTATION_SPEED: f32 = 1.0; + let event_loop = glium::winit::event_loop::EventLoop::builder() .build() .expect("create event-loop"); @@ -21,27 +23,28 @@ fn main() -> Result<()> { .with_inner_size(1280, 720) .build(&event_loop); - let mut world = hecs::World::new(); + // Create ECS renderer which internally owns both the world and the renderer + let mut ecsr = { + let world = hecs::World::new(); + let renderer = GliumRenderer::new(display.clone())?; + ecs::ECSRenderer::new(renderer, world) + }; - let mesh = model::load_gltf("resources/monkey-smooth.gltf", &display)?; - // let mesh = model::cube(&display)?; - let mut renderer = GliumRenderer::new(display)?; - let mesh_id = renderer.meshes.len(); - renderer.meshes.push(mesh); + let mut time = time::Time::new(); - let object_ent = world.spawn(( - Transform { - translation: Vec3::ZERO, - rotation: Quat::IDENTITY, - scale: Vec3::ONE, - }, - MeshHandle(mesh_id), - )); + let object_ent = { + let mesh = model::load_gltf("resources/monkey-smooth.gltf", &display)?; + ecsr.spawn_mesh(mesh, Transform { + translation: Vec3::ZERO, + rotation: Quat::IDENTITY, + scale: Vec3::ONE, + }) + }; let camera_ent = { let (w, h): (u32, u32) = window.inner_size().into(); - world.spawn((Camera { + ecsr.world.spawn((Camera { eye: Vec3::new(3.0, 2.0, 3.0), center: Vec3::ZERO, up: Vec3::Y, @@ -60,27 +63,27 @@ fn main() -> Result<()> { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => el.exit(), WindowEvent::Resized(sz) => { - world.query_one_mut::<&mut crate::camera::Camera>(camera_ent).map(|mut cam| { + ecsr.world.query_one_mut::<&mut crate::camera::Camera>(camera_ent).map(|mut cam| { cam.aspect = sz.width as f32 / sz.height as f32; }); } WindowEvent::RedrawRequested => { - renderer.render(&world); + ecsr.render(); } _ => {} }, Event::AboutToWait => { - // -- update logic -- - let now = Instant::now(); - static mut LAST: Option = None; - let dt = unsafe { // FIXME - let last = LAST.replace(now).unwrap_or(now); - (now - last).as_secs_f32() - }; - world.query_one_mut::<&mut Transform>(object_ent).map(|mut object| { - object.rotation *= Quat::from_rotation_y(dt); + time.tick(); + let dt = time.delta_seconds(); + ecsr.world.query_one_mut::<&mut Transform>(object_ent).map(|mut object| { + object.rotation *= Quat::from_rotation_y(ROTATION_SPEED * dt); }); + // despawn the object after 3 seconds + if time.total_seconds() > 3.0 { + ecsr.despawn_mesh(object_ent); + } + // ask for next frame window.request_redraw(); } diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..5e8159b --- /dev/null +++ b/src/time.rs @@ -0,0 +1,34 @@ +use std::time::{Duration, Instant}; + +#[derive(Clone, Debug)] +pub struct Time { + last: Instant, + delta: Duration, + total: Duration, +} + +impl Time { + pub fn new() -> Self { + let now = Instant::now(); + Self { + last: now, + delta: Duration::ZERO, + total: Duration::ZERO, + } + } + + pub fn tick(&mut self) { + let now = Instant::now(); + self.delta = now - self.last; + self.total += self.delta; + self.last = now; + } + + pub fn delta_seconds(&self) -> f32 { + self.delta.as_secs_f32() + } + + pub fn total_seconds(&self) -> f32 { + self.total.as_secs_f32() + } +} \ No newline at end of file