diff --git a/.vscode/launch.json b/.vscode/launch.json index cdff0ce..ad599e0 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -40,6 +40,28 @@ }, "args": [], "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'lyra-ecs'", + "cargo": { + "args": [ + "test", + "--no-run", + "--lib", + "--package=lyra-ecs", + "world::tests::view_change_tracking", + "--", + "--exact --nocapture" + ], + "filter": { + "name": "lyra-ecs", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}/lyra-ecs" } ] } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5e4598f..e3db98e 100755 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,7 +261,7 @@ checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -399,7 +399,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -631,7 +631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c94d80dc0f05250a9082bb9455bbf3d6c6c51db388b060df914aebcfb4a9b9f1" dependencies = [ "edict-proc-lib", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -643,7 +643,7 @@ dependencies = [ "proc-easy", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -931,7 +931,7 @@ dependencies = [ "inflections", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -1283,6 +1283,25 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lyra-ecs" +version = "0.1.0" +dependencies = [ + "anyhow", + "lyra-ecs-derive", + "rand 0.8.5", + "thiserror", +] + +[[package]] +name = "lyra-ecs-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.42", +] + [[package]] name = "lyra-engine" version = "0.0.1" @@ -1294,17 +1313,17 @@ dependencies = [ "atomicell", "bytemuck", "cfg-if", - "edict", "gilrs-core", "glam", "image", "instant", "itertools", + "lyra-ecs", "lyra-resource", "petgraph", "quote", "stopwatch", - "syn 2.0.26", + "syn 2.0.42", "tobj", "tracing", "tracing-appender", @@ -1685,7 +1704,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -1834,7 +1853,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -1898,7 +1917,7 @@ checksum = "ea59c637cd0e6b71ae18e589854e9de9b7cb17fefdbf2047e42bd38e24285b19" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -1913,9 +1932,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.64" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" dependencies = [ "unicode-ident", ] @@ -1937,9 +1956,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -2137,7 +2156,7 @@ checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -2291,9 +2310,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.26" +version = "2.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970" +checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" dependencies = [ "proc-macro2", "quote", @@ -2315,30 +2334,30 @@ version = "0.1.0" dependencies = [ "anyhow", "async-std", - "edict", "fps_counter", + "lyra-ecs", "lyra-engine", "tracing", ] [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -2478,7 +2497,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", ] [[package]] @@ -2613,7 +2632,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", "wasm-bindgen-shared", ] @@ -2647,7 +2666,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.26", + "syn 2.0.42", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/Cargo.toml b/Cargo.toml index fb786f7..d12e414 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,13 @@ edition = "2021" [workspace] members = [ "lyra-resource", + "lyra-ecs", "examples/testbed" ] [dependencies] lyra-resource = { path = "lyra-resource", version = "0.0.1" } +lyra-ecs = { path = "lyra-ecs" } winit = "0.28.1" tracing = "0.1.37" @@ -31,7 +33,7 @@ glam = { version = "0.24.0", features = ["bytemuck", "debug-glam-assert"] } gilrs-core = "0.5.6" syn = "2.0.26" quote = "1.0.29" -edict = "0.5.0" +#edict = "0.5.0" atomicell = "0.1.9" aligned-vec = "0.5.0" tracing-appender = "0.2.2" diff --git a/examples/testbed/Cargo.toml b/examples/testbed/Cargo.toml index 6ec2aa1..c840c2c 100644 --- a/examples/testbed/Cargo.toml +++ b/examples/testbed/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" [dependencies] lyra-engine = { path = "../../", version = "0.0.1" } +lyra-ecs = { path = "../../lyra-ecs"} anyhow = "1.0.75" async-std = "1.12.0" tracing = "0.1.37" -fps_counter = "2.0.0" -edict = "0.5.0" \ No newline at end of file +fps_counter = "2.0.0" \ No newline at end of file diff --git a/examples/testbed/src/free_fly_camera.rs b/examples/testbed/src/free_fly_camera.rs index 57b833a..e3de9d1 100644 --- a/examples/testbed/src/free_fly_camera.rs +++ b/examples/testbed/src/free_fly_camera.rs @@ -1,6 +1,6 @@ use std::ops::Deref; -use edict::{Component, World}; +use lyra_ecs::{Component, world::World}; use lyra_engine::{ ecs::{components::{camera::CameraComponent, DeltaTime}, EventQueue, SimpleSystem}, game::Game, @@ -48,14 +48,14 @@ impl SimpleSystem for FreeFlyCameraPlugin { fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { let mut camera_rot = Vec3::default(); - let delta_time = **world.get_resource::().unwrap(); + let delta_time = **world.get_resource::(); let events = world - .get_resource_mut::() + .try_get_resource_mut::() .and_then(|q| q.read_events::()); let keys = world - .get_resource::>() + .try_get_resource::>() .map(|r| r.deref().clone()); if let Some(keys) = keys.as_ref() { @@ -84,9 +84,8 @@ impl SimpleSystem for FreeFlyCameraPlugin { } } - for (cam, fly) in world - .query_mut::<(&mut CameraComponent, &mut FreeFlyCamera)>() - .iter_mut() + for (mut cam, mut fly) in world + .view_iter::<(&mut CameraComponent, &mut FreeFlyCamera)>() { let forward = cam.transform.forward(); let left = cam.transform.left(); diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index fec11c3..f4c90e0 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,4 +1,4 @@ -use lyra_engine::{math::{self, Vec3}, ecs::{World, components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Component, Criteria, CriteriaSchedule, BatchedSystem}, math::Transform, input::{KeyCode, InputButtons, MouseMotion, ActionHandler, Layout, Action, ActionKind, LayoutId, ActionMapping, Binding, ActionSource, ActionMappingId, InputActionPlugin, ActionState}, game::Game, plugin::Plugin, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct}; +use lyra_engine::{math::{self, Vec3}, ecs::{components::{transform::TransformComponent, camera::CameraComponent, model::ModelComponent, DeltaTime}, EventQueue, SimpleSystem, Criteria, CriteriaSchedule, BatchedSystem, lyra_ecs::{Component, world::World}}, math::Transform, input::{KeyCode, InputButtons, MouseMotion, ActionHandler, Layout, Action, ActionKind, LayoutId, ActionMapping, Binding, ActionSource, ActionMappingId, InputActionPlugin, ActionState}, game::Game, plugin::Plugin, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct}; use lyra_engine::assets::{ResourceManager, Model}; mod free_fly_camera; @@ -39,9 +39,9 @@ impl FixedTimestep { } impl Criteria for FixedTimestep { - fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule { + fn can_run(&mut self, world: &mut World, check_count: u32) -> CriteriaSchedule { if check_count == 0 { - let delta_time = world.get_resource::().unwrap(); + let delta_time = world.get_resource::(); self.accumulator += **delta_time; } @@ -64,12 +64,12 @@ struct CubeFlag; async fn main() { let setup_sys = |world: &mut World| -> anyhow::Result<()> { { - let mut window_options = world.get_resource_mut::>().unwrap(); + let mut window_options = world.get_resource_mut::>(); window_options.cursor_grab = CursorGrabMode::Confined; window_options.cursor_visible = false; } - let mut resman = world.get_resource_mut::().unwrap(); + let mut resman = world.get_resource_mut::(); //let diffuse_texture = resman.request::("assets/happy-tree.png").unwrap(); let antique_camera_model = resman.request::("assets/AntiqueCamera.glb").unwrap(); //let cube_model = resman.request::("assets/cube-texture-bin.glb").unwrap(); @@ -183,7 +183,7 @@ async fn main() { }; let fps_system = |world: &mut World| -> anyhow::Result<()> { - let mut counter = world.get_resource_mut::().unwrap(); + let mut counter = world.get_resource_mut::(); let fps = counter.tick(); @@ -193,32 +193,32 @@ async fn main() { }; let fps_plugin = move |game: &mut Game| { let world = game.world(); - world.insert_resource(fps_counter::FPSCounter::new()); + world.add_resource(fps_counter::FPSCounter::new()); }; let spin_system = |world: &mut World| -> anyhow::Result<()> { const SPEED: f32 = 4.0; - let delta_time = **world.get_resource::().unwrap(); + let delta_time = **world.get_resource::(); - for (transform, _) in world.query_mut::<(&mut TransformComponent, &CubeFlag)>().iter_mut() { + for (mut transform, _) in world.view_iter::<(&mut TransformComponent, &CubeFlag)>() { let t = &mut transform.transform; t.rotate_y(math::Angle::Degrees(SPEED * delta_time)); } - /* for (transform, s) in world.query_mut::<(&mut TransformComponent, &mut SpotLight)>().iter_mut() { + for (mut transform, _s) in world.view_iter::<(&mut TransformComponent, &mut SpotLight)>() { let t = &mut transform.transform; t.rotate_x(math::Angle::Degrees(SPEED * delta_time)); - } */ + } Ok(()) }; let jiggle_plugin = move |game: &mut Game| { - game.world().insert_resource(TpsAccumulator(0.0)); + game.world().add_resource(TpsAccumulator(0.0)); let mut sys = BatchedSystem::new(); sys.with_criteria(FixedTimestep::new(45)); - //sys.with_system(spin_system); + sys.with_system(spin_system); //sys.with_system(fps_system); game.with_system("fixed", sys, &[]); @@ -243,7 +243,7 @@ async fn main() { ); let test_system = |world: &mut World| -> anyhow::Result<()> { - let handler = world.get_resource::().unwrap(); + let handler = world.get_resource::(); if let Some(alpha) = handler.get_pressed_modifier("forward_backward") { debug!("'forward_backward': {alpha}"); @@ -256,7 +256,7 @@ async fn main() { Ok(()) }; - game.world().insert_resource(action_handler); + game.world().add_resource(action_handler); game.with_plugin(InputActionPlugin); //game.with_system("test_actions", test_system, &[]); }; diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index d676dd4..27db880 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,6 +1,6 @@ use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; -use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component_info::ComponentInfo, DynTypeId}; +use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick}; #[derive(Clone)] pub struct ComponentColumn { @@ -8,6 +8,8 @@ pub struct ComponentColumn { pub len: usize, pub capacity: usize, pub info: ComponentInfo, + /// The last tick that this component was modified at. + pub entity_ticks: Vec, } impl Drop for ComponentColumn { @@ -58,6 +60,7 @@ impl ComponentColumn { capacity, info, len: 0, + entity_ticks: Vec::new(), } } @@ -66,7 +69,7 @@ impl ComponentColumn { /// # Safety /// /// This column must have space to fit the component, if it does not have room it will panic. - pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull) { + pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull, tick: Tick) { assert!(entity_index < self.capacity); let mut data = self.data.borrow_mut(); @@ -77,7 +80,15 @@ impl ComponentColumn { unsafe { let layout = self.info.layout.into_layout_unchecked(); - std::alloc::dealloc(comp_src.as_ptr(), layout); + //std::alloc::dealloc(comp_src.as_ptr(), layout); + } + + // check if a component spot is being set twice and that the entity's tick is + // already stored + if self.entity_ticks.len() > entity_index { + self.entity_ticks[entity_index].tick_to(&tick); + } else { + self.entity_ticks.push(tick); } } @@ -101,7 +112,9 @@ impl ComponentColumn { /// # Safety /// /// This column must have the entity. - pub unsafe fn get_mut(&mut self, entity_index: usize) -> &mut T { + pub unsafe fn get_mut(&mut self, entity_index: usize, tick: &Tick) -> &mut T { + self.entity_ticks[entity_index].tick_to(&tick); + let mut data = self.data.borrow_mut(); let data = data.deref_mut(); @@ -116,6 +129,8 @@ impl ComponentColumn { /// Parameters: /// * `new_capacity` - The new capacity of components that can fit in this column. /// + /// Note: This does not modify the Tick of this column, since no components were actually modified. + /// /// # Safety /// /// Will panic if `new_capacity` is less than the current capacity of the column. @@ -123,7 +138,6 @@ impl ComponentColumn { assert!(new_capacity > self.capacity); let mut data = self.data.borrow_mut(); - //let data = data.deref_mut(); let mut new_ptr = Self::alloc(self.info.layout.into_layout_unchecked(), new_capacity); @@ -148,7 +162,9 @@ impl ComponentColumn { } /// Removes a component from the column, freeing it, and returning the old index of the entity that took its place in the column. - pub unsafe fn remove_component(&mut self, entity_index: usize) -> Option { + pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option { + //self.entity_ticks[entity_index].tick_to(&tick); + let mut data = self.data.borrow_mut(); let data = data.deref_mut(); @@ -163,6 +179,11 @@ impl ComponentColumn { ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), self.info.layout.size); mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr + + // make sure to keep entity indexes correct in the ticks list as well + self.entity_ticks.swap(moved_index, entity_index); + self.entity_ticks.pop(); + Some(moved_index) } else { None }; @@ -231,7 +252,7 @@ impl Archetype { /// # Safety: /// /// Archetype must contain all of the components - pub(crate) fn add_entity(&mut self, entity: Entity, bundle: B) -> ArchetypeEntityId + pub(crate) fn add_entity(&mut self, entity: Entity, bundle: B, tick: &Tick) -> ArchetypeEntityId where B: Bundle { @@ -246,7 +267,7 @@ impl Archetype { bundle.take(|data, type_id, _size| { let col = self.get_column_mut(type_id).unwrap(); - unsafe { col.set_at(entity_index, data); } + unsafe { col.set_at(entity_index, data, tick.clone()); } col.len += 1; }); @@ -254,20 +275,21 @@ impl Archetype { } /// Removes an entity from the Archetype and frees its components. Returns the entity record that took its place in the component column. - pub(crate) fn remove_entity(&mut self, entity: Entity) -> Option<(Entity, ArchetypeEntityId)> { + pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> { let entity_index = *self.entities.get(&entity) .expect("The entity is not in this Archetype!"); let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None; for c in self.columns.iter_mut() { - let moved_entity = unsafe { c.remove_component(entity_index.0 as usize) }; + let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) }; // Make sure that the moved entity is the same as what was moved in other columns. // If this is the first move, find the EntityId that points to the column index. // If there wasn't a moved entity, make sure no other columns moved something. if let Some(res) = moved_entity { + if let Some((_, aid)) = removed_entity { - assert!(res as u64 == aid.0); // make sure we removed the same entity + assert!(res as u64 == aid.0); // make sure all columns removed the same entity } else { let replaced_entity = self.entities.iter().find(|(_, a)| a.0 == res as u64) .map(|(e, _a)| *e).expect("Failure to find entity for moved component!"); @@ -330,6 +352,9 @@ impl Archetype { self.columns.iter().find(|c| c.info.type_id == type_id) } + /// Returns a mutable borrow to a component column for `type_id`. + /// + /// Note: This does not modify the tick for the column! pub fn get_column_mut(&mut self, type_id: DynTypeId) -> Option<&mut ComponentColumn> { self.columns.iter_mut().find(|c| c.info.type_id == type_id) } @@ -358,7 +383,7 @@ impl Archetype { /// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`]. /// It was done this way because I had some borrow check issues when writing [`World::insert`] /// related to borrowing mutably from self more than once. - pub fn move_into(&self, into_arch: &mut Archetype, entity: Entity, new_components: B) + /* pub fn move_into(&self, into_arch: &mut Archetype, entity: Entity, new_components: B) where B: Bundle { @@ -387,7 +412,7 @@ impl Archetype { }); //self.remove_entity(entity); - } + } */ pub fn entities(&self) -> &HashMap { &self.entities @@ -400,7 +425,7 @@ mod tests { use rand::Rng; - use crate::{ArchetypeId, tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::Bundle, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle}; + use crate::{ArchetypeId, tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::Bundle, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle, Tick}; use super::Archetype; @@ -413,7 +438,7 @@ mod tests { }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info()); - let entity_arch_id = a.add_entity(entity, bundle); + let entity_arch_id = a.add_entity(entity, bundle, &Tick::default()); let col = a.columns.get(0).unwrap(); let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) }; @@ -429,7 +454,7 @@ mod tests { }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), bundle.info()); - let entity_arch_id = a.add_entity(entity, bundle); + let entity_arch_id = a.add_entity(entity, bundle, &Tick::default()); let col = a.columns.get(0).unwrap(); let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) }; @@ -454,8 +479,8 @@ mod tests { }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info()); - let earch1 = a.add_entity(e1, b1); - let earch2 = a.add_entity(e2, b2); + let earch1 = a.add_entity(e1, b1, &Tick::default()); + let earch2 = a.add_entity(e2, b2, &Tick::default()); let col = a.columns.get(0).unwrap(); let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) }; @@ -478,8 +503,8 @@ mod tests { }; let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), b1.info()); - let earch1 = a.add_entity(e1, b1); - let earch2 = a.add_entity(e2, b2); + let earch1 = a.add_entity(e1, b1, &Tick::default()); + let earch2 = a.add_entity(e2, b2, &Tick::default()); let col = a.columns.get(0).unwrap(); let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) }; @@ -525,7 +550,8 @@ mod tests { id: EntityId(i as u64), generation: 0 }, - c + c, + &Tick::default() ); } println!("Inserted {} entities", bundle_count); @@ -553,6 +579,7 @@ mod tests { generation: 0 }, (bundles[i],), + &Tick::default() ); } @@ -560,8 +587,9 @@ mod tests { let moved_entity = a.remove_entity( Entity { id: EntityId(1u64), - generation: 0 - } + generation: 0, + }, + &Tick::default() ).expect("No entity was moved"); // The last entity in the column should have been moved @@ -592,7 +620,9 @@ mod tests { Entity { id: EntityId(0), generation: 0 - }, dynamic_bundle + }, + dynamic_bundle, + &Tick::default() ); let col = a.columns.iter().next().unwrap(); @@ -600,47 +630,4 @@ mod tests { assert_eq!(unsafe { *ptr.cast::().as_ref() }, comp); assert_eq!(col.info, info); } - - #[test] - fn move_archetypes() { - let bundles = vec![Vec2::rand(), ]; - - let mut info = (bundles[0],).info(); - let mut a = Archetype::from_bundle_info(ArchetypeId(0), info.clone()); - - let entity = Entity { - id: EntityId(0), - generation: 0 - }; - - a.add_entity(entity, (bundles[0],)); - - info.extend((Vec3::new(0.0, 0.0, 0.0),).info().into_iter()); - let mut new_a = Archetype::from_bundle_info(ArchetypeId(1), info); - - let inserting_vec3 = Vec3::rand(); - let new_bundle = (inserting_vec3,); - a.move_into(&mut new_a, entity, new_bundle); - - let new_index = new_a.entities.get(&entity).unwrap(); - - // Check Vec3 - { - let new_col = new_a.get_column(DynTypeId::of::()).unwrap(); - let from_col = unsafe { new_col.get::(new_index.0 as _) }; - println!("expected {:?}, got {:?}", inserting_vec3, *from_col); - assert_eq!(*from_col, inserting_vec3); - } - - // Check Vec2 - { - let new_col = new_a.get_column(DynTypeId::of::()).unwrap(); - let from_col = unsafe { new_col.get::(new_index.0 as _) }; - println!("expected {:?}, got {:?}", bundles[0], *from_col); - assert_eq!(*from_col, bundles[0]); - } - - assert!(a.entities.contains_key(&entity)); - assert!(a.remove_entity(entity).is_none()); // No entity would take its place - } } \ No newline at end of file diff --git a/lyra-ecs/src/lib.rs b/lyra-ecs/src/lib.rs index 43c1f6c..b1a36f5 100644 --- a/lyra-ecs/src/lib.rs +++ b/lyra-ecs/src/lib.rs @@ -27,6 +27,9 @@ pub use resource::*; mod system; pub use system::*; +mod tick; +pub use tick::*; + pub use lyra_ecs_derive::*; #[cfg(test)] diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index 1ee7af4..568499f 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -1,6 +1,6 @@ use std::{marker::PhantomData, any::TypeId, ptr::NonNull, cell::{Ref, RefCell, RefMut}}; -use crate::{world::World, ComponentColumn, DynTypeId}; +use crate::{world::World, ComponentColumn, DynTypeId, Tick}; use super::{Fetch, Query, AsQuery}; @@ -78,7 +78,7 @@ where archetype.columns.iter().any(|c| c.info.type_id == self.type_id) } - unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id) .expect("You ignored 'can_visit_archetype'!"); @@ -100,8 +100,9 @@ impl AsQuery for &T { /// A fetcher for mutably borrowing components from archetypes. pub struct FetchBorrowMut<'a, T> { - col: &'a ComponentColumn, + col: NonNull, size: usize, + tick: Tick, _phantom: PhantomData<&'a T> } @@ -116,7 +117,10 @@ where } unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { - let ptr = self.col.borrow_mut_ptr(); + let col = unsafe { self.col.as_mut() }; + col.entity_ticks[entity.0 as usize] = self.tick; + let ptr = col.borrow_mut_ptr(); + RefMut::map(ptr, |ptr| { let ptr = NonNull::new_unchecked(ptr.as_ptr() .add(entity.0 as usize * self.size)) @@ -164,6 +168,8 @@ where type Fetch<'a> = FetchBorrowMut<'a, T>; + const MUTATES: bool = true; + fn new() -> Self { QueryBorrowMut::::new() } @@ -172,13 +178,19 @@ where archetype.columns.iter().any(|c| c.info.type_id == self.type_id) } - unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id) .expect("You ignored 'can_visit_archetype'!"); + let layout_size = col.info.layout.size; + let mut col = NonNull::from(col); + + // TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated. + // the fetcher needs to tick the entities tick in the archetype FetchBorrowMut { - col, - size: col.info.layout.size, + col: NonNull::from(col), + size: layout_size, + tick, _phantom: PhantomData, } } @@ -194,9 +206,9 @@ impl AsQuery for &mut T { #[cfg(test)] mod tests { - use std::{any::TypeId, mem::size_of, marker::PhantomData}; + use std::{any::TypeId, mem::size_of, marker::PhantomData, ptr::NonNull}; - use crate::{world::{World, Entity, EntityId}, archetype::{Archetype, ArchetypeId}, query::View, tests::Vec2, bundle::Bundle, Fetch, DynTypeId}; + use crate::{world::{World, Entity, EntityId}, archetype::{Archetype, ArchetypeId}, query::View, tests::Vec2, bundle::Bundle, Fetch, DynTypeId, Tick}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut}; @@ -270,13 +282,14 @@ mod tests { a.add_entity(Entity { id: EntityId(i), generation: 0, - }, (Vec2::rand(),)); + }, (Vec2::rand(),), &Tick::default()); } let col = a.columns.iter().find(|c| c.info.type_id == DynTypeId::of::()).unwrap(); let mut bmut = FetchBorrowMut:: { - col, + col: NonNull::from(col), + tick: Tick::default(), size: size_of::(), _phantom: PhantomData, }; diff --git a/lyra-ecs/src/query/entities.rs b/lyra-ecs/src/query/entities.rs index b13502f..1e2c94f 100644 --- a/lyra-ecs/src/query/entities.rs +++ b/lyra-ecs/src/query/entities.rs @@ -1,4 +1,4 @@ -use crate::{world::{Entity, World}, archetype::{Archetype, ArchetypeId}}; +use crate::{world::{Entity, World}, archetype::{Archetype, ArchetypeId}, AsQuery}; use super::{Fetch, Query}; @@ -38,10 +38,14 @@ impl Query for Entities { true } - unsafe fn fetch<'a>(&self, _world: &'a World, arch_id: ArchetypeId, archetype: &'a Archetype) -> Self::Fetch<'a> { - let _ = arch_id; // ignore unused warnings + unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> { + let _ = tick; // ignore unused warnings EntitiesFetch { entities: archetype.entities.keys().cloned().collect::>(), } } +} + +impl AsQuery for Entities { + type Query = Self; } \ No newline at end of file diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index 074a873..c295865 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -1,4 +1,4 @@ -use crate::{archetype::{Archetype, ArchetypeId}, world::{ArchetypeEntityId, World}}; +use crate::{archetype::{Archetype, ArchetypeId}, world::{ArchetypeEntityId, World}, Tick}; pub mod view; pub use view::*; @@ -19,6 +19,10 @@ pub mod resource; #[allow(unused_imports)] pub use resource::*; +pub mod tick; +#[allow(unused_imports)] +pub use tick::*; + pub mod dynamic; /// A [`Fetch`]er implementation gets data out of an archetype. @@ -52,12 +56,15 @@ pub trait Query: Copy { /// [`View`] uses this to determine if it should continue to iterate this Query. const ALWAYS_FETCHES: bool = false; + /// A constant that signifies if this Query will mutate data. + const MUTATES: bool = false; + fn new() -> Self; /// Returns true if the archetype should be visited or skipped. fn can_visit_archetype(&self, archetype: &Archetype) -> bool; - unsafe fn fetch<'a>(&self, world: &'a World, arch_id: ArchetypeId, archetype: &'a Archetype) -> Self::Fetch<'a>; + unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a Archetype, tick: Tick) -> Self::Fetch<'a>; /// Attempt to fetch only from the world. /// diff --git a/lyra-ecs/src/query/resource.rs b/lyra-ecs/src/query/resource.rs index 514763b..ed038b8 100644 --- a/lyra-ecs/src/query/resource.rs +++ b/lyra-ecs/src/query/resource.rs @@ -61,7 +61,7 @@ impl Query for QueryResource { true } - unsafe fn fetch<'a>(&self, world: &'a World, _arch_id: crate::archetype::ArchetypeId, _archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, world: &'a World, _archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { self.fetch_world(world).unwrap() } @@ -135,7 +135,7 @@ impl Query for QueryResourceMut { true } - unsafe fn fetch<'a>(&self, world: &'a World, _arch_id: crate::archetype::ArchetypeId, _archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, world: &'a World, _archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { self.fetch_world(world).unwrap() } diff --git a/lyra-ecs/src/query/tick.rs b/lyra-ecs/src/query/tick.rs new file mode 100644 index 0000000..685c993 --- /dev/null +++ b/lyra-ecs/src/query/tick.rs @@ -0,0 +1,105 @@ +use std::marker::PhantomData; + +use crate::{ComponentColumn, Fetch, Tick, DynTypeId, Query, world::World, AsQuery}; + +#[derive(Clone, Copy, Debug, Default)] +pub struct TickOf { + tick: Tick, + _phantom: PhantomData, +} + +impl std::ops::Deref for TickOf { + type Target = Tick; + + fn deref(&self) -> &Self::Target { + &self.tick + } +} + +/// Fetcher for borrowing components from archetypes. +pub struct FetchTickOf<'a, T> { + col: &'a ComponentColumn, + _phantom: PhantomData<&'a T> +} + +impl<'a, T> Fetch<'a> for FetchTickOf<'a, T> +where + T: 'a, +{ + type Item = Tick; + + fn dangling() -> Self { + unreachable!() + } + + unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { + self.col.entity_ticks[entity.0 as usize] + } +} + +/// A Query for borrowing components from archetypes. +/// +/// Since [`AsQuery`] is implemented for `&T`, you can use this query like this: +/// ```nobuild +/// for ts in world.view::<&T>() { +/// println!("Got a &T!"); +/// } +/// ``` +pub struct QueryTickOf { + type_id: DynTypeId, + _phantom: PhantomData +} + +// manually implemented to avoid a Copy bound on T +impl Copy for QueryTickOf {} + +// manually implemented to avoid a Clone bound on T +impl Clone for QueryTickOf { + fn clone(&self) -> Self { + *self + } +} + +impl QueryTickOf { + pub fn new() -> Self { + Self { + type_id: DynTypeId::of::(), + _phantom: PhantomData, + } + } +} + +impl Query for QueryTickOf +where + T: 'static +{ + type Item<'a> = Tick; + + type Fetch<'a> = FetchTickOf<'a, T>; + + fn new() -> Self { + QueryTickOf::::new() + } + + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { + archetype.columns.iter().any(|c| c.info.type_id == self.type_id) + } + + unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> { + let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id) + .expect("You ignored 'can_visit_archetype'!"); + + FetchTickOf { + col, + _phantom: PhantomData, + } + } +} + +impl AsQuery for QueryTickOf { + type Query = Self; +} + +impl AsQuery for TickOf { + type Query = QueryTickOf; +} \ No newline at end of file diff --git a/lyra-ecs/src/query/tuple.rs b/lyra-ecs/src/query/tuple.rs index 95966a0..cb5807c 100644 --- a/lyra-ecs/src/query/tuple.rs +++ b/lyra-ecs/src/query/tuple.rs @@ -46,9 +46,9 @@ where q1.can_visit_archetype(archetype) && q2.can_visit_archetype(archetype) } - unsafe fn fetch<'a>(&self, world: &'a World, arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let (q1, q2) = self; - ( q1.fetch(world, arch_id, archetype), q2.fetch(world, arch_id, archetype) ) + ( q1.fetch(world, archetype, tick), q2.fetch(world, archetype, tick) ) } } @@ -101,9 +101,9 @@ macro_rules! impl_bundle_tuple { bools.iter().all(|b| *b) } - unsafe fn fetch<'a>(&self, world: &'a World, arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let ( $($name,)+ ) = self; - ( $($name.fetch(world, arch_id, archetype),)+ ) + ( $($name.fetch(world, archetype, tick),)+ ) } } diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index c319d08..bae2d3d 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use crate::{archetype::{Archetype, ArchetypeId}, world::{ArchetypeEntityId, World}, EntityId}; +use crate::{archetype::{Archetype, ArchetypeId}, world::{ArchetypeEntityId, World}, EntityId, TickTracker, Tick}; use super::{Query, Fetch}; @@ -32,8 +32,11 @@ where type IntoIter = ViewIter<'a, Q>; fn into_iter(self) -> Self::IntoIter { + let tick = self.world.tick_tracker().tick_when(Q::MUTATES); + ViewIter { world: self.world, + tick, query: self.query, fetcher: None, archetypes: self.archetypes, @@ -45,6 +48,7 @@ where pub struct ViewIter<'a, Q: Query> { world: &'a World, + tick: Tick, query: Q, fetcher: Option>, archetypes: Vec<&'a Archetype>, @@ -100,7 +104,7 @@ where continue; } - self.fetcher = unsafe { Some(self.query.fetch(self.world, ArchetypeId(arch_id as u64), arch)) }; + self.fetcher = unsafe { Some(self.query.fetch(self.world, arch, self.tick)) }; self.component_indices = 0..arch.entities.len() as u64; } } @@ -109,14 +113,18 @@ where pub struct ViewOne<'a, Q: Query> { world: &'a World, + tick: Tick, entity: EntityId, query: Q, } impl<'a, Q: Query> ViewOne<'a, Q> { pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self { + let tick = world.tick_tracker().tick_when(Q::MUTATES); + Self { world, + tick, entity, query } @@ -128,7 +136,7 @@ impl<'a, Q: Query> ViewOne<'a, Q> { .expect("An invalid record was specified for an entity"); if self.query.can_visit_archetype(arch) { - let mut fetch = unsafe { self.query.fetch(self.world, arch.id, arch) }; + let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) }; if fetch.can_visit_item(record.index) { return Some(unsafe { fetch.get_item(record.index) }); } diff --git a/lyra-ecs/src/tick.rs b/lyra-ecs/src/tick.rs new file mode 100644 index 0000000..02dd4c7 --- /dev/null +++ b/lyra-ecs/src/tick.rs @@ -0,0 +1,88 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +/// TickTracker is used for tracking changes of [`Component`]s and entities. +/// +/// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well. +/// Note that [`Tick::Clone`] only clones the inner value of atomic, and not the atomic itself. +#[derive(Debug, Default)] +pub struct TickTracker { + tick: AtomicU64, +} + +impl Clone for TickTracker { + fn clone(&self) -> Self { + Self { + tick: AtomicU64::from(self.tick.load(Ordering::Acquire)), + } + } +} + +impl From for TickTracker { + fn from(value: u64) -> Self { + Self { + tick: AtomicU64::from(value), + } + } +} + +impl TickTracker { + /// Create a new tracker starting at zero ticks. + pub fn new() -> Self { + Self::default() + } + + /// Increment the tick counter, and return the new Tick. + pub fn tick(&self) -> Tick { + let old = self.tick.fetch_add(1, Ordering::Relaxed); + Tick(old + 1) + } + + /// If `when` is true, increment the tick counter. + pub fn tick_when(&self, when: bool) -> Tick { + if when { + self.tick() + } else { + self.current() + } + } + + /// Sync this TickTracker with the provided `Tick`. + /// + /// This will only update the tracker if `Tick` is larger than self. + pub fn tick_to(&self, other: &Tick) -> Tick { + self.tick.fetch_max(other.0, Ordering::AcqRel); + Tick(self.tick.load(Ordering::Acquire)) + } + + /// Sync this TickTracker with the other tracker. + /// + /// This will only update the tracker if other is larger than self. + pub fn sync_tracker(&self, other: &TickTracker) -> Tick { + let other = other.tick.load(Ordering::Acquire); + self.tick_to(&Tick(other)) + } + + /// Gets the current Tick that the tracker is at. + pub fn current(&self) -> Tick { + Tick(self.tick.load(Ordering::Acquire)) + } +} + +/// A specific Tick that was resulted from ticking a [`TickTracker`]. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default, PartialOrd, Ord)] +pub struct Tick(u64); + +impl std::ops::Deref for Tick { + type Target = u64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Tick { + /// Increment this Tick up to `other` + pub fn tick_to(&mut self, other: &Tick) { + self.0 = self.0.max(other.0); + } +} \ No newline at end of file diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index f8d472f..7795b30 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,6 +1,6 @@ use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}, ptr::NonNull}; -use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View}, resource::ResourceData, Query, AsQuery, dynamic::{DynamicViewIter, QueryDynamicType, DynamicView}, ViewOne, ComponentInfo, DynTypeId}; +use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, component::Component, query::{ViewIter, View}, resource::ResourceData, Query, AsQuery, dynamic::{DynamicViewIter, QueryDynamicType, DynamicView}, ViewOne, ComponentInfo, DynTypeId, TickTracker, Tick}; #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct EntityId(pub u64); @@ -29,6 +29,7 @@ pub struct World { dead_entities: VecDeque, next_entity_id: EntityId, resources: HashMap, + tracker: TickTracker, } impl Default for World { @@ -40,6 +41,7 @@ impl Default for World { dead_entities: VecDeque::new(), next_entity_id: EntityId(0), resources: HashMap::new(), + tracker: TickTracker::new(), } } } @@ -49,9 +51,13 @@ impl World { Self::default() } + /// Gets a new Entity, will recycle dead entities and increment their generation. fn get_new_entity(&mut self) -> Entity { match self.dead_entities.pop_front() { - Some(e) => e, + Some(mut e) => { + e.generation += 1; + e + }, None => { let new_id = self.next_entity_id; self.next_entity_id.0 += 1; @@ -64,6 +70,7 @@ impl World { } } + /// Spawns a new entity and inserts the component `bundle` into it. pub fn spawn(&mut self, bundle: B) -> Entity where B: Bundle @@ -71,13 +78,15 @@ impl World { let bundle_types = bundle.type_ids(); let new_entity = self.get_new_entity(); + let tick = self.tick(); + // try to find an archetype let archetype = self.archetypes .values_mut() .find(|a| a.is_archetype_for(&bundle_types)); if let Some(archetype) = archetype { - let arche_idx = archetype.add_entity(new_entity, bundle); + let arche_idx = archetype.add_entity(new_entity, bundle, &tick); // Create entity record and store it let record = Record { @@ -92,7 +101,7 @@ impl World { // create archetype let new_arch_id = self.next_archetype_id.increment(); let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info()); - let entity_arch_id = archetype.add_entity(new_entity, bundle); + let entity_arch_id = archetype.add_entity(new_entity, bundle, &tick); // store archetype self.archetypes.insert(new_arch_id, archetype); @@ -112,22 +121,36 @@ impl World { /// Despawn an entity from the World pub fn despawn(&mut self, entity: Entity) { + // Tick the tracker if the entity is spawned. This is done here instead of the `if let` + // below due to the borrow checker complaining about multiple mutable borrows to self. + let tick = if self.entity_index.contains_key(&entity.id) { + Some(self.tick()) + } else { None }; + if let Some(record) = self.entity_index.get_mut(&entity.id) { + let tick = tick.unwrap(); let arch = self.archetypes.get_mut(&record.id).unwrap(); - if let Some((moved, new_index)) = arch.remove_entity(entity) { + if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) { // replace the archetype index of the moved index with its new index. self.entity_index.get_mut(&moved.id).unwrap().index = new_index; } } } + /// Insert a bundle into an existing entity. If the components are already existing on the + /// entity, they will be updated, else the entity will be moved to a different Archetype + /// that can store the entity. That may involve creating a new Archetype. pub fn insert(&mut self, entity: Entity, bundle: B) where B: Bundle { - // TODO: If the archetype has a single entity, add a component column for the new component instead of - // moving the entity to a brand new archetype. + // TODO: If the archetype has a single entity, add a component column for the new + // component instead of moving the entity to a brand new archetype. + // TODO: If the entity already has the components in `bundle`, update the values of the + // components with the bundle. + + let tick = self.tick(); let record = *self.entity_index.get(&entity.id).unwrap(); let current_arch = self.archetypes.get(&record.id).unwrap(); @@ -140,7 +163,7 @@ impl World { col_infos.extend(bundle.info().into_iter()); let col_ptrs: Vec<(NonNull, ComponentInfo)> = current_arch.columns.iter().map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) }).collect(); - + if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) { let res_index = arch.reserve_one(entity); @@ -149,13 +172,13 @@ impl World { let ptr = NonNull::new_unchecked(col_ptr.as_ptr() .add(res_index.0 as usize * col_info.layout.size)); let col = arch.get_column_mut(col_type).unwrap(); - col.set_at(res_index.0 as _, ptr); + col.set_at(res_index.0 as _, ptr, tick); } } bundle.take(|data, type_id, _size| { let col = arch.get_column_mut(type_id).unwrap(); - unsafe { col.set_at(res_index.0 as _, data); } + unsafe { col.set_at(res_index.0 as _, data, tick); } col.len += 1; }); @@ -169,7 +192,7 @@ impl World { } else { let new_arch_id = self.next_archetype_id.increment(); let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos); - let entity_arch_id = archetype.add_entity(entity, bundle); + let entity_arch_id = archetype.add_entity(entity, bundle, &tick); self.archetypes.insert(new_arch_id, archetype); @@ -183,7 +206,7 @@ impl World { } let current_arch = self.archetypes.get_mut(&record.id).unwrap(); - current_arch.remove_entity(entity); + current_arch.remove_entity(entity, &tick); } /// View into the world for a set of entities that satisfy the queries. @@ -253,11 +276,31 @@ impl World { .map(|r| r.try_get_mut()) .flatten() } + + /// Increments the TickTracker which is used for tracking changes to components. + /// + /// Most users wont need to call this manually, its done for you through queries and views. + pub fn tick(&self) -> Tick { + self.tracker.tick() + } + + /// Gets the current tick that the world is at. + /// + /// See [`TickTracker`] + pub fn current_tick(&self) -> Tick { + self.tracker.current() + } + + pub fn tick_tracker(&self) -> &TickTracker { + &self.tracker + } } #[cfg(test)] mod tests { - use crate::tests::{Vec2, Vec3}; + use std::ops::Deref; + + use crate::{tests::{Vec2, Vec3}, TickOf}; use super::World; @@ -390,4 +433,32 @@ mod tests { let view = world.view_one::<&Vec2>(e); assert_eq!(*view.get().unwrap(), v); } + + #[test] + fn view_change_tracking() { + let mut world = World::new(); + + /* let v = Vec2::rand(); + world.spawn((v,)); + let v = Vec2::rand(); + world.spawn((v,)); */ + + println!("spawning"); + world.spawn((Vec2::new(10.0, 10.0),)); + world.spawn((Vec2::new(5.0, 5.0),)); + println!("spawned"); + + for mut v in world.view_iter::<&mut Vec2>() { + v.y += 50.0; + println!("Moved v to {:?}", v); + } + + let world_tick = world.current_tick(); + println!("The world tick is {}", *world_tick); + for (v, tick) in world.view_iter::<(&Vec2, TickOf)>() { + println!("Is at {:?}, it was changed at {}", v, *tick); + assert!(v.y > 50.0); + assert!(tick >= world_tick); + } + } } \ No newline at end of file diff --git a/src/ecs/components/camera.rs b/src/ecs/components/camera.rs index 66db2f3..36137a5 100755 --- a/src/ecs/components/camera.rs +++ b/src/ecs/components/camera.rs @@ -1,4 +1,4 @@ -use edict::Component; +use lyra_ecs::Component; use crate::{math::{Angle, Transform}, render::camera::CameraProjectionMode}; diff --git a/src/ecs/components/delta_time.rs b/src/ecs/components/delta_time.rs index a1f13c2..9bf32b8 100644 --- a/src/ecs/components/delta_time.rs +++ b/src/ecs/components/delta_time.rs @@ -1,7 +1,7 @@ use std::borrow::BorrowMut; -use edict::{Component, World}; use instant::Instant; +use lyra_ecs::{Component, world::World}; use crate::plugin::Plugin; @@ -24,12 +24,10 @@ impl std::ops::DerefMut for DeltaTime { fn delta_time_system(world: &mut World) -> anyhow::Result<()> { let now = Instant::now(); - let mut delta = world.get_resource_mut::().unwrap(); + let mut delta = world.get_resource_mut::(); delta.0 = delta.1.unwrap_or(now).elapsed().as_secs_f32(); delta.1 = Some(now); - //println!("delta: {}", delta.0); - Ok(()) } @@ -37,7 +35,7 @@ pub struct DeltaTimePlugin; impl Plugin for DeltaTimePlugin { fn setup(&self, game: &mut crate::game::Game) { - game.world().insert_resource(DeltaTime(0.0, None)); + game.world().add_resource(DeltaTime(0.0, None)); game.with_system("delta_time", delta_time_system, &[]); } } \ No newline at end of file diff --git a/src/ecs/components/free_fly_camera.rs b/src/ecs/components/free_fly_camera.rs index 9b24679..abfc6f4 100644 --- a/src/ecs/components/free_fly_camera.rs +++ b/src/ecs/components/free_fly_camera.rs @@ -1,4 +1,4 @@ -use edict::Component; +use lyra_ecs::Component; use crate::{math::{Angle, Transform}, render::camera::CameraProjectionMode}; diff --git a/src/ecs/components/mesh.rs b/src/ecs/components/mesh.rs index 6612ea6..7de1ef5 100755 --- a/src/ecs/components/mesh.rs +++ b/src/ecs/components/mesh.rs @@ -1,5 +1,4 @@ -use edict::Component; - +use lyra_ecs::Component; use lyra_resource::Mesh; #[derive(Clone, Component)] diff --git a/src/ecs/components/model.rs b/src/ecs/components/model.rs index b50b3de..8e62541 100644 --- a/src/ecs/components/model.rs +++ b/src/ecs/components/model.rs @@ -1,8 +1,9 @@ +use lyra_ecs::Component; use lyra_resource::ResHandle; use crate::assets::Model; -#[derive(Clone, edict::Component)] +#[derive(Clone, Component)] pub struct ModelComponent(pub ResHandle); impl From> for ModelComponent { diff --git a/src/ecs/components/transform.rs b/src/ecs/components/transform.rs index 0027492..516f6f8 100755 --- a/src/ecs/components/transform.rs +++ b/src/ecs/components/transform.rs @@ -1,4 +1,4 @@ -use edict::Component; +use lyra_ecs::Component; use crate::math::Transform; diff --git a/src/ecs/events.rs b/src/ecs/events.rs index 00d4bbe..aee0bb0 100644 --- a/src/ecs/events.rs +++ b/src/ecs/events.rs @@ -78,6 +78,6 @@ pub struct EventsPlugin; impl Plugin for EventsPlugin { fn setup(&self, game: &mut crate::game::Game) { - game.world().insert_resource(EventQueue::new()); + game.world().add_resource(EventQueue::new()); } } \ No newline at end of file diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index 569acd0..53a566d 100755 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -1,4 +1,4 @@ -pub use edict::*; +pub use lyra_ecs; pub mod components; diff --git a/src/ecs/system/batched.rs b/src/ecs/system/batched.rs index ca67b9f..7c05e18 100644 --- a/src/ecs/system/batched.rs +++ b/src/ecs/system/batched.rs @@ -1,5 +1,6 @@ +use lyra_ecs::world::World; + use super::{SimpleSystem, Criteria}; -use edict::World; /// A system that executes a batch of systems in order that they were given. /// You can optionally add criteria that must pass before the systems are diff --git a/src/ecs/system/criteria.rs b/src/ecs/system/criteria.rs index dec6e58..295385d 100644 --- a/src/ecs/system/criteria.rs +++ b/src/ecs/system/criteria.rs @@ -1,3 +1,5 @@ +use lyra_ecs::world::World; + pub enum CriteriaSchedule { Yes, No, @@ -11,13 +13,13 @@ pub trait Criteria { /// Parameters: /// * `world` - The ecs world. /// * `check_count` - The amount of times the Criteria has been checked this tick. - fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule; + fn can_run(&mut self, world: &mut World, check_count: u32) -> CriteriaSchedule; } impl Criteria for F - where F: FnMut(&mut edict::World, u32) -> CriteriaSchedule + where F: FnMut(&mut World, u32) -> CriteriaSchedule { - fn can_run(&mut self, world: &mut edict::World, check_count: u32) -> CriteriaSchedule { + fn can_run(&mut self, world: &mut World, check_count: u32) -> CriteriaSchedule { self(world, check_count) } } \ No newline at end of file diff --git a/src/ecs/system/mod.rs b/src/ecs/system/mod.rs index 21a4dfd..6be2304 100644 --- a/src/ecs/system/mod.rs +++ b/src/ecs/system/mod.rs @@ -3,9 +3,9 @@ pub use batched::*; pub mod criteria; pub use criteria::*; +use lyra_ecs::world::World; use std::collections::HashMap; -use edict::World; use petgraph::{stable_graph::{StableDiGraph, NodeIndex}, visit::Topo}; use tracing::warn; @@ -20,7 +20,7 @@ pub trait SimpleSystem { } impl SimpleSystem for S - where S: FnMut(&mut edict::World) -> anyhow::Result<()> + where S: FnMut(&mut World) -> anyhow::Result<()> { fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { self(world) diff --git a/src/game.rs b/src/game.rs index 05d55f2..d79bd74 100755 --- a/src/game.rs +++ b/src/game.rs @@ -2,6 +2,7 @@ use std::{sync::Arc, collections::VecDeque}; use async_std::task::block_on; +use lyra_ecs::world::World; use tracing::{info, error, Level, debug}; use tracing_appender::non_blocking; use tracing_subscriber::{ @@ -15,7 +16,7 @@ use winit::{window::{WindowBuilder, Window}, event::{Event, WindowEvent, Keyboar use crate::{render::{renderer::{Renderer, BasicRenderer}, window::WindowOptions}, input::InputEvent, ecs::{SimpleSystem, SystemDispatcher, EventQueue, Events}, plugin::Plugin, change_tracker::Ct}; pub struct Controls<'a> { - pub world: &'a mut edict::World, + pub world: &'a mut World, } #[derive(Clone, Default)] @@ -34,14 +35,14 @@ struct GameLoop { window: Arc, renderer: Box, - world: edict::World, + world: World, /// higher priority systems engine_sys_dispatcher: SystemDispatcher, user_sys_dispatcher: SystemDispatcher, } impl GameLoop { - pub async fn new(window: Arc, world: edict::World, user_systems: SystemDispatcher) -> GameLoop { + pub async fn new(window: Arc, world: World, user_systems: SystemDispatcher) -> GameLoop { Self { window: Arc::clone(&window), renderer: Box::new(BasicRenderer::create_with_window(window).await), @@ -58,7 +59,7 @@ impl GameLoop { pub async fn on_init(&mut self) { // Create the EventQueue resource in the world - self.world.insert_resource(self.window.clone()); + self.world.add_resource(self.window.clone()); } pub fn run_sync(&mut self, event: Event<()>, control_flow: &mut ControlFlow) { @@ -113,11 +114,11 @@ impl GameLoop { /* let trigger = matches!(self.world.get_resource::(), Some(window_state) if window_state.is_focused && window_state.is_cursor_inside_window); */ - let trigger = matches!(self.world.get_resource::>(), Some(window) + let trigger = matches!(self.world.try_get_resource::>(), Some(window) if window.focused && window.cursor_inside_window); if trigger { - let mut event_queue = self.world.get_resource_mut::().unwrap(); + let mut event_queue = self.world.try_get_resource_mut::().unwrap(); let input_event = InputEvent::MouseMotion { device_id, delta, }; event_queue.trigger_event(input_event); @@ -134,7 +135,7 @@ impl GameLoop { // Its possible to receive multiple input events before the update event for // the InputSystem is called, so we must use a queue for the events. { - if let Some(mut event_queue) = self.world.get_resource_mut::() { + if let Some(mut event_queue) = self.world.try_get_resource_mut::() { event_queue.trigger_event(input_event.clone()); }; } @@ -159,7 +160,7 @@ impl GameLoop { }, WindowEvent::Focused(is_focused) => { - let state = self.world.with_resource(WindowState::new); + let mut state = self.world.get_resource_or_else(WindowState::new); state.is_focused = *is_focused; }, @@ -178,7 +179,7 @@ impl GameLoop { } */ self.renderer.as_mut().prepare(&mut self.world); - if let Some(mut event_queue) = self.world.get_resource_mut::() { + if let Some(mut event_queue) = self.world.try_get_resource_mut::() { event_queue.update_events(); } @@ -202,7 +203,7 @@ impl GameLoop { } pub struct Game { - world: Option, + world: Option, plugins: VecDeque>, system_dispatcher: Option, startup_systems: VecDeque>, @@ -211,7 +212,7 @@ pub struct Game { impl Default for Game { fn default() -> Self { Self { - world: Some(edict::World::new()), + world: Some(World::new()), plugins: VecDeque::new(), system_dispatcher: Some(SystemDispatcher::new()), startup_systems: VecDeque::new(), @@ -225,7 +226,7 @@ impl Game { } /// Get the world of this game - pub fn world(&mut self) -> &mut edict::World { + pub fn world(&mut self) -> &mut World { // world is always `Some`, so unwrapping is safe self.world.as_mut().unwrap() } @@ -265,7 +266,7 @@ impl Game { /// Override the default (empty) world /// /// This isn't recommended, you should create a startup system and add it to `with_startup_system` - pub fn with_world(&mut self, world: edict::World) -> &mut Self { + pub fn with_world(&mut self, world: World) -> &mut Self { self.world = Some(world); self diff --git a/src/input/action.rs b/src/input/action.rs index 187b50d..482d998 100644 --- a/src/input/action.rs +++ b/src/input/action.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, cell::RefCell, borrow::BorrowMut, ops::Deref}; -use edict::{action, World}; +use lyra_ecs::world::World; use crate::{castable_any::CastableAny, plugin::Plugin}; @@ -369,11 +369,11 @@ impl ActionHandler { } fn actions_system(world: &mut World) -> anyhow::Result<()> { - let keys = world.get_resource::>() + let keys = world.try_get_resource::>() .map(|r| r.deref().clone()); if let Some(keys) = keys { - let mut handler = world.get_resource_mut::() + let mut handler = world.try_get_resource_mut::() .expect("No Input Action handler was created in the world!"); //handler diff --git a/src/input/system.rs b/src/input/system.rs index bd7f535..40a2987 100755 --- a/src/input/system.rs +++ b/src/input/system.rs @@ -1,4 +1,5 @@ use glam::Vec2; +use lyra_ecs::world::World; use winit::event::MouseScrollDelta; use crate::{ecs::{SimpleSystem, EventQueue}, plugin::Plugin,}; @@ -11,8 +12,8 @@ pub type KeyCode = winit::event::VirtualKeyCode; pub struct InputSystem; impl InputSystem { - pub fn process_event(&mut self, world: &mut edict::World, event: &InputEvent) -> bool { - let event_queue = world.get_resource_mut::(); + pub fn process_event(&mut self, world: &mut World, event: &InputEvent) -> bool { + let event_queue = world.try_get_resource_mut::(); if event_queue.is_none() { return false; } @@ -22,7 +23,7 @@ impl InputSystem { InputEvent::KeyboardInput { input, .. } => { if let Some(code) = input.virtual_keycode { drop(event_queue); - let e = world.with_resource(InputButtons::::new); + let mut e = world.get_resource_or_else(InputButtons::::new); //let mut e = with_resource_mut(world, || InputButtons::::new()); e.add_input_from_winit(code, input.state); } @@ -72,7 +73,7 @@ impl InputSystem { event_queue.trigger_event(button_event); drop(event_queue); - let e = world.with_resource(InputButtons::::new); + let mut e = world.get_resource_or_else(InputButtons::::new); e.add_input_from_winit(button_event, *state); }, InputEvent::Touch(t) => { @@ -85,7 +86,7 @@ impl InputSystem { finger_id: t.id, }; - let touches = world.with_resource(Touches::new); + let mut touches = world.get_resource_or_else(Touches::new); touches.touches.push(touch); }, _ => {}, @@ -96,8 +97,8 @@ impl InputSystem { } impl SimpleSystem for InputSystem { - fn execute_mut(&mut self, world: &mut edict::World) -> anyhow::Result<()> { - let queue = world.get_resource_mut::() + fn execute_mut(&mut self, world: &mut World) -> anyhow::Result<()> { + let queue = world.try_get_resource_mut::() .and_then(|q| q.read_events::()); if queue.is_none() { diff --git a/src/plugin.rs b/src/plugin.rs index 1b78b32..00033bb 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -98,7 +98,7 @@ pub struct ResourceManagerPlugin; impl Plugin for ResourceManagerPlugin { fn setup(&self, game: &mut Game) { - game.world().insert_resource(ResourceManager::new()); + game.world().add_resource(ResourceManager::new()); } } diff --git a/src/render/light/directional.rs b/src/render/light/directional.rs index 3dadbdb..d3360af 100644 --- a/src/render/light/directional.rs +++ b/src/render/light/directional.rs @@ -1,4 +1,6 @@ -#[derive(Default, Debug, Clone, edict::Component)] +use lyra_ecs::Component; + +#[derive(Default, Debug, Clone, Component)] pub struct DirectionalLight { //pub direction: glam::Quat, pub color: glam::Vec3, diff --git a/src/render/light/mod.rs b/src/render/light/mod.rs index 10b640d..de2c923 100644 --- a/src/render/light/mod.rs +++ b/src/render/light/mod.rs @@ -2,13 +2,13 @@ pub mod point; pub mod directional; pub mod spotlight; +use lyra_ecs::{Entity, Tick, world::World, Entities, TickOf}; pub use point::*; pub use directional::*; pub use spotlight::*; use std::{collections::{VecDeque, HashMap}, num::{NonZeroU64, NonZeroU32}, marker::PhantomData}; -use edict::query::EpochOf; pub use point::*; use tracing::{debug, Instrument}; use wgpu::util::DeviceExt; @@ -33,7 +33,7 @@ pub struct LightBuffer { /// to recreate the array and remove the gaps. pub buffer_count: usize, /// The buffer index for a specific entity/caster. - used_indexes: HashMap, + used_indexes: HashMap, /// Indexes that were being used but are no longer needed. dead_indexes: VecDeque, } @@ -49,12 +49,12 @@ impl LightBuffer { } } - pub fn has_light(&self, entity: edict::EntityId) -> bool { + pub fn has_light(&self, entity: Entity) -> bool { self.used_indexes.contains_key(&entity) } /// Update an existing light in the light buffer. - pub fn update_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: edict::EntityId, light: U) { + pub fn update_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { let buffer_idx = *self.used_indexes.get(&entity) .expect("Entity for Light is not in buffer!"); @@ -62,7 +62,7 @@ impl LightBuffer { } /// Add a new light to the light buffer. - pub fn add_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: edict::EntityId, light: U) { + pub fn add_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { let buffer_idx = match self.dead_indexes.pop_front() { Some(i) => i, None => { @@ -82,7 +82,7 @@ impl LightBuffer { } /// Update, or add a new caster, to the light buffer. - pub fn update_or_add(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: edict::EntityId, light: U) { + pub fn update_or_add(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity, light: U) { if self.used_indexes.contains_key(&entity) { self.update_light(lights_buffer, entity, light); } else { @@ -91,7 +91,7 @@ impl LightBuffer { } /// Remove a caster from the buffer, returns true if it was removed. - pub fn remove_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: edict::EntityId) -> bool { + pub fn remove_light(&mut self, lights_buffer: &mut [U; MAX_LIGHT_COUNT], entity: Entity) -> bool { if let Some(removed_idx) = self.used_indexes.remove(&entity) { self.dead_indexes.push_back(removed_idx); //self.current_count -= 1; @@ -170,31 +170,31 @@ impl LightUniformBuffers { } } - pub fn update_lights(&mut self, queue: &wgpu::Queue, world_epoch: edict::epoch::EpochId, world: &edict::World) { + pub fn update_lights(&mut self, queue: &wgpu::Queue, world_tick: Tick, world: &World) { for (entity, point_light, transform, light_epoch, transform_epoch) - in world.query::<(edict::Entities, &PointLight, &TransformComponent, EpochOf, EpochOf)>().iter() { + in world.view_iter::<(Entities, &PointLight, &TransformComponent, TickOf, TickOf)>() { - if !self.point_lights.has_light(entity) || light_epoch == world_epoch || transform_epoch == world_epoch { - let uniform = PointLightUniform::from_bundle(point_light, &transform.transform); + if !self.point_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick { + let uniform = PointLightUniform::from_bundle(&point_light, &transform.transform); self.point_lights.update_or_add(&mut self.lights_uniform.point_lights, entity, uniform); debug!("Updated point light"); } } for (entity, spot_light, transform, light_epoch, transform_epoch) - in world.query::<(edict::Entities, &SpotLight, &TransformComponent, EpochOf, EpochOf)>().iter() { + in world.view_iter::<(Entities, &SpotLight, &TransformComponent, TickOf, TickOf)>() { - if !self.spot_lights.has_light(entity) || light_epoch == world_epoch || transform_epoch == world_epoch { - let uniform = SpotLightUniform::from_bundle(spot_light, &transform.transform); + if !self.spot_lights.has_light(entity) || light_epoch == world_tick || transform_epoch == world_tick { + let uniform = SpotLightUniform::from_bundle(&spot_light, &transform.transform); self.spot_lights.update_or_add(&mut self.lights_uniform.spot_lights, entity, uniform); //debug!("Updated spot light"); } } if let Some((dir_light, transform)) = - world.query::<(&DirectionalLight, &TransformComponent)>().iter().next() { + world.view_iter::<(&DirectionalLight, &TransformComponent)>().next() { - let uniform = DirectionalLightUniform::from_bundle(dir_light, &transform.transform); + let uniform = DirectionalLightUniform::from_bundle(&dir_light, &transform.transform); self.lights_uniform.directional_light = uniform; } diff --git a/src/render/light/point.rs b/src/render/light/point.rs index be9d8f4..64f7688 100644 --- a/src/render/light/point.rs +++ b/src/render/light/point.rs @@ -1,4 +1,6 @@ -#[derive(Default, Debug, Clone, edict::Component)] +use lyra_ecs::Component; + +#[derive(Default, Debug, Clone, Component)] pub struct PointLight { pub color: glam::Vec3, pub intensity: f32, diff --git a/src/render/light/spotlight.rs b/src/render/light/spotlight.rs index af3ea65..c0eeed8 100644 --- a/src/render/light/spotlight.rs +++ b/src/render/light/spotlight.rs @@ -1,6 +1,8 @@ +use lyra_ecs::Component; + use crate::math; -#[derive(Debug, Clone, edict::Component)] +#[derive(Debug, Clone, Component)] pub struct SpotLight { pub color: glam::Vec3, pub cutoff: math::Angle, diff --git a/src/render/render_job.rs b/src/render/render_job.rs index 91424aa..5b220e7 100755 --- a/src/render/render_job.rs +++ b/src/render/render_job.rs @@ -1,18 +1,19 @@ -use edict::EntityId; + +use lyra_ecs::Entity; use crate::math::Transform; use super::material::Material; pub struct RenderJob { - pub entity: EntityId, + pub entity: Entity, pub shader_id: u64, pub mesh_buffer_id: uuid::Uuid, pub transform: Transform, } impl RenderJob { - pub fn new(entity: EntityId, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform) -> Self { + pub fn new(entity: Entity, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform) -> Self { Self { entity, shader_id, diff --git a/src/render/renderer.rs b/src/render/renderer.rs index 48faeea..f48fa9e 100755 --- a/src/render/renderer.rs +++ b/src/render/renderer.rs @@ -4,11 +4,11 @@ use std::num::NonZeroU64; use std::sync::Arc; use std::borrow::Cow; -use edict::query::EpochOf; -use edict::{EntityId, Entities}; use glam::Vec3; use instant::Instant; use itertools::izip; +use lyra_ecs::{Entity, Entities, TickOf}; +use lyra_ecs::world::World; use tracing::{debug, warn}; use wgpu::{BindGroup, BindGroupLayout, Limits}; use wgpu::util::DeviceExt; @@ -35,7 +35,7 @@ use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, r use lyra_resource::Mesh; pub trait Renderer { - fn prepare(&mut self, main_world: &mut edict::World); + fn prepare(&mut self, main_world: &mut World); fn render(&mut self) -> Result<(), wgpu::SurfaceError>; fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize); @@ -79,8 +79,8 @@ pub struct BasicRenderer { pub render_jobs: VecDeque, mesh_buffers: HashMap, // TODO: clean up left over buffers from deleted entities/components - entity_meshes: HashMap, - entity_last_transforms: HashMap, + entity_meshes: HashMap, + entity_last_transforms: HashMap, transform_buffers: TransformBuffers, @@ -269,7 +269,7 @@ impl BasicRenderer { s } - fn update_mesh_buffers(&mut self, _entity: EntityId, mesh: &Mesh) { + fn update_mesh_buffers(&mut self, _entity: Entity, mesh: &Mesh) { if let Some(buffers) = self.mesh_buffers.get_mut(&mesh.uuid) { // check if the buffer sizes dont match. If they dont, completely remake the buffers let vertices = mesh.position().unwrap(); @@ -372,7 +372,7 @@ impl BasicRenderer { } /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. - fn process_mesh(&mut self, entity: EntityId, transform: Transform, mesh: &Mesh) -> bool { + fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh) -> bool { if self.transform_buffers.should_expand() { self.transform_buffers.expand_buffers(&self.device); } @@ -393,14 +393,14 @@ impl BasicRenderer { } impl Renderer for BasicRenderer { - fn prepare(&mut self, main_world: &mut edict::World) { - let last_epoch = main_world.epoch(); + fn prepare(&mut self, main_world: &mut World) { + let last_epoch = main_world.current_tick(); let mut alive_entities = HashSet::new(); let now_inst = Instant::now(); - for (entity, model, model_epoch, transform, transform_epoch) in main_world.query::<(Entities, &ModelComponent, EpochOf, &TransformComponent, EpochOf)>().iter() { + for (entity, model, model_epoch, transform, transform_epoch) in main_world.view_iter::<(Entities, &ModelComponent, TickOf, &TransformComponent, TickOf)>() { alive_entities.insert(entity); let cached = match self.entity_last_transforms.get_mut(&entity) { @@ -464,8 +464,8 @@ impl Renderer for BasicRenderer { self.mesh_buffers.retain(|u, _| !removed_entities.contains(u)); } - if let Some(camera) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() { - let view_proj = self.inuse_camera.update_view_projection(camera); + if let Some(camera) = main_world.view_iter::<&mut CameraComponent>().next() { + let view_proj = self.inuse_camera.update_view_projection(&camera); let pos = camera.transform.translation; let uniform = CameraUniform { view_proj: *view_proj, diff --git a/src/render/transform_buffer_storage.rs b/src/render/transform_buffer_storage.rs index ea13abc..74f4694 100644 --- a/src/render/transform_buffer_storage.rs +++ b/src/render/transform_buffer_storage.rs @@ -1,6 +1,6 @@ use std::{collections::{VecDeque, HashMap}, num::NonZeroU64}; -use edict::EntityId; +use lyra_ecs::Entity; use tracing::debug; use wgpu::Limits; @@ -23,8 +23,8 @@ pub(crate) struct TransformBufferEntry { pub(crate) struct TransformBuffers { pub bindgroup_layout: wgpu::BindGroupLayout, /// A vector storing the EntityId and - pub just_updated: HashMap, - pub not_updated: HashMap, + pub just_updated: HashMap, + pub not_updated: HashMap, pub dead_indices: VecDeque, pub next_indices: TransformBufferIndices, /// (transform count, buffer, bindgroup) @@ -80,7 +80,7 @@ impl TransformBuffers { } /// Update an entity's buffer with the new transform. Will panic if the entity isn't stored - pub fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { + pub fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { let indices = self.not_updated.remove(&entity) .or_else(|| self.just_updated.remove(&entity)) .expect("Use 'insert_entity' for new entities"); @@ -96,7 +96,7 @@ impl TransformBuffers { } /// Insert a new entity into the buffer, returns where it was stored. - pub fn insert_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { + pub fn insert_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices { let indices = match self.dead_indices.pop_front() { Some(indices) => indices, None => { @@ -119,7 +119,7 @@ impl TransformBuffers { } /// Update or insert an entities transform - pub fn update_or_insert(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform_fn: TFn) -> TransformBufferIndices + pub fn update_or_insert(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform_fn: TFn) -> TransformBufferIndices where TFn: Fn() -> (glam::Mat4, glam::Mat3) { let (tran, norm) = transform_fn(); @@ -131,7 +131,7 @@ impl TransformBuffers { } /// Returns true if the entity's transform is stored (does not mean its up-to-date). - pub fn contains(&self, entity: EntityId) -> bool { + pub fn contains(&self, entity: Entity) -> bool { self.not_updated.contains_key(&entity) || self.just_updated.contains_key(&entity) } @@ -165,7 +165,7 @@ impl TransformBuffers { .map(|entry| &entry.bindgroup) } - pub fn entity_bind_group(&self, entity: EntityId) -> Option<&wgpu::BindGroup> { + pub fn entity_bind_group(&self, entity: Entity) -> Option<&wgpu::BindGroup> { self.entity_indices(entity).and_then(|i| self.bind_group(*i)) } @@ -233,7 +233,7 @@ impl TransformBuffers { self.buffer_bindgroups.push(entry); } - pub fn entity_indices(&self, entity: EntityId) -> Option<&TransformBufferIndices> { + pub fn entity_indices(&self, entity: Entity) -> Option<&TransformBufferIndices> { self.just_updated.get(&entity).or_else(|| self.not_updated.get(&entity)) } } \ No newline at end of file diff --git a/src/render/window.rs b/src/render/window.rs index 3a42f98..a7442e9 100644 --- a/src/render/window.rs +++ b/src/render/window.rs @@ -1,6 +1,7 @@ use std::{sync::Arc, collections::VecDeque}; use glam::{Vec2, IVec2}; +use lyra_ecs::world::World; use tracing::{warn, error}; use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError}; @@ -274,14 +275,14 @@ fn center_mouse(window: &Window, options: &WindowOptions) { } } -fn window_updater_system(world: &mut edict::World) -> anyhow::Result<()> { - if let (Some(window), Some(opts)) = (world.get_resource::>(), world.get_resource::>()) { +fn window_updater_system(world: &mut World) -> anyhow::Result<()> { + if let (Some(window), Some(opts)) = (world.try_get_resource::>(), world.try_get_resource::>()) { // if the options changed, update the window if opts.peek_changed() { drop(opts); // drop the Ref, we're about to get a RefMut // now we can get it mutable, this will trigger the ChangeTracker, so it will be reset at the end of this scope. - let mut opts = world.get_resource_mut::>().unwrap(); + let mut opts = world.get_resource_mut::>(); if opts.focused { window.focus_window(); @@ -337,9 +338,9 @@ fn window_updater_system(world: &mut edict::World) -> anyhow::Result<()> { center_mouse(&window, &opts); } else { drop(opts); // drop the Ref, we're about to get a RefMut - let mut opts = world.get_resource_mut::>().unwrap(); + let mut opts = world.get_resource_mut::>(); - if let Some(event_queue) = world.get_resource_mut::() { + if let Some(event_queue) = world.try_get_resource_mut::() { if let Some(mut events) = event_queue.read_events::() { while let Some(ev) = events.pop_front() { match ev { @@ -372,7 +373,7 @@ impl Plugin for WindowPlugin { fn setup(&self, game: &mut crate::game::Game) { let window_options = WindowOptions::default(); - game.world().insert_resource(Ct::new(window_options)); + game.world().add_resource(Ct::new(window_options)); game.with_system("window_updater", window_updater_system, &[]); } }