From 5a0e06f94d654c117fa754422b1b288d9b558aef Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 30 Nov 2024 12:33:23 -0500 Subject: [PATCH] Fix Changed by rewriting ecs `Filter`s, position `TileMapPos` entities with ChildOf relations --- crates/lyra-ecs/src/archetype.rs | 10 +- crates/lyra-ecs/src/bundle.rs | 4 +- crates/lyra-ecs/src/command.rs | 8 +- crates/lyra-ecs/src/query/filter/changed.rs | 17 ++- crates/lyra-ecs/src/query/filter/has.rs | 8 +- crates/lyra-ecs/src/query/filter/mod.rs | 68 ++++++++++- crates/lyra-ecs/src/query/filter/not.rs | 8 +- crates/lyra-ecs/src/query/mod.rs | 25 +--- crates/lyra-ecs/src/query/tuple.rs | 29 ++--- crates/lyra-ecs/src/query/view.rs | 36 ++++-- crates/lyra-ecs/src/world.rs | 20 ++-- .../src/render/graph/passes/transform.rs | 24 ++-- crates/lyra-game/src/sprite/tilemap.rs | 108 ++++++++++++------ crates/lyra-game/src/winit/plugin.rs | 14 +-- crates/lyra-gltf/src/loader.rs | 7 +- crates/lyra-reflect/src/component.rs | 8 +- crates/lyra-scene/src/lib.rs | 8 +- crates/lyra-scene/src/node.rs | 4 +- crates/lyra-scene/src/world_transform.rs | 12 +- examples/2d/src/main.rs | 62 +++------- 20 files changed, 279 insertions(+), 201 deletions(-) diff --git a/crates/lyra-ecs/src/archetype.rs b/crates/lyra-ecs/src/archetype.rs index ab1fbff..f0a2aeb 100644 --- a/crates/lyra-ecs/src/archetype.rs +++ b/crates/lyra-ecs/src/archetype.rs @@ -56,12 +56,15 @@ impl ComponentColumn { pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self { let data = ComponentColumn::alloc(info.layout(), capacity); + let mut ticks = Vec::with_capacity(capacity); + ticks.resize(capacity, Tick::from(0)); + Self { data: RefCell::new(data), capacity, info, len: 0, - entity_ticks: Vec::new(), + entity_ticks: ticks, } } @@ -99,9 +102,7 @@ impl ComponentColumn { let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size)); ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size); - // check if a component spot is being set twice and that the entity's tick is - // already stored - self.entity_ticks.push(tick); + self.entity_ticks[entity_index] = tick; self.len += 1; } @@ -175,6 +176,7 @@ impl ComponentColumn { *data = new_ptr; } + self.entity_ticks.resize(new_capacity, Tick::from(0)); self.capacity = new_capacity; } diff --git a/crates/lyra-ecs/src/bundle.rs b/crates/lyra-ecs/src/bundle.rs index 7f86a24..eda955f 100644 --- a/crates/lyra-ecs/src/bundle.rs +++ b/crates/lyra-ecs/src/bundle.rs @@ -272,11 +272,11 @@ mod tests { let mut world = World::new(); let e = world.spawn(b); - let pos = world.view_one::<&Vec2>(e).get() + let pos = world.view_one::<&Vec2>(e) .expect("failed to find spawned Vec2 from Bundle on Entity"); assert!(pos.x == b_pos.x && pos.y == b_pos.y, "Spawned Vec2 values were not correct, got: {:?}, expected: {:?}", *pos, b_pos); - let flag = world.view_one::<&SomeFlag>(e).get() + let flag = world.view_one::<&SomeFlag>(e) .expect("failed to find spawned SomeFlag from Bundle on Entity"); assert_eq!(*flag, b_flag); } diff --git a/crates/lyra-ecs/src/command.rs b/crates/lyra-ecs/src/command.rs index ac30f75..b24180e 100644 --- a/crates/lyra-ecs/src/command.rs +++ b/crates/lyra-ecs/src/command.rs @@ -1,6 +1,6 @@ use std::{any::Any, cell::RefMut, mem::{self, MaybeUninit}, ptr::{self, NonNull}}; -use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World}; +use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, Relation, World}; /// A Command be used to delay mutation of the world until after this system is ran. pub trait Command: Any { @@ -175,6 +175,12 @@ impl<'a, 'b> Commands<'a, 'b> { }); } + pub fn add_relation(&mut self, origin: Entity, relation: R, target: Entity){ + self.add(move | world: &mut World| { + world.add_relation(origin, relation, target); + }); + } + /// Execute all commands in the queue, in order of insertion pub fn execute(&mut self, world: &mut World) { self.queue.execute(Some(world)); diff --git a/crates/lyra-ecs/src/query/filter/changed.rs b/crates/lyra-ecs/src/query/filter/changed.rs index b4d8e4b..83cbf7f 100644 --- a/crates/lyra-ecs/src/query/filter/changed.rs +++ b/crates/lyra-ecs/src/query/filter/changed.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::{query::{AsFilter, AsQuery, Fetch, Filter, Query}, Component, ComponentColumn, DynTypeId, Tick, World}; +use crate::{query::{AsQuery, Fetch, Query}, Component, ComponentColumn, DynTypeId, Tick, World}; pub struct ChangedFetcher<'a, T> { col: &'a ComponentColumn, @@ -20,7 +20,10 @@ where unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { let tick = self.col.entity_ticks[entity.0 as usize]; - *tick >= (*self.tick) - 1 + if *tick > 50 { + //debug!("tick: {}, world tick: {}", *tick, *self.tick); + } + *tick >= *self.tick } } @@ -77,10 +80,10 @@ where archetype.has_column(self.type_id) } - unsafe fn fetch<'a>(&self, w: &'a World, a: &'a crate::archetype::Archetype, _: crate::Tick) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, _: &'a World, a: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { ChangedFetcher { col: a.get_column(self.type_id).unwrap(), - tick: w.current_tick(), + tick, _phantom: PhantomData::<&T>, } } @@ -88,10 +91,4 @@ where impl AsQuery for Changed { type Query = Self; -} - -impl Filter for Changed { } - -impl AsFilter for Changed { - type Filter = Self; } \ No newline at end of file diff --git a/crates/lyra-ecs/src/query/filter/has.rs b/crates/lyra-ecs/src/query/filter/has.rs index e715ea5..16eafcf 100644 --- a/crates/lyra-ecs/src/query/filter/has.rs +++ b/crates/lyra-ecs/src/query/filter/has.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::{query::{AsFilter, AsQuery, Filter, Query}, Archetype, Component, DynTypeId, World}; +use crate::{query::{AsQuery, Query}, Archetype, Component, DynTypeId, World}; use super::StaticFetcher; @@ -45,8 +45,8 @@ impl AsQuery for Has { type Query = Self; } -impl Filter for Has { } +//impl Filter for Has { } -impl AsFilter for Has { +/* impl AsFilter for Has { type Filter = Self; -} \ No newline at end of file +} */ \ No newline at end of file diff --git a/crates/lyra-ecs/src/query/filter/mod.rs b/crates/lyra-ecs/src/query/filter/mod.rs index a3fe19c..8031f56 100644 --- a/crates/lyra-ecs/src/query/filter/mod.rs +++ b/crates/lyra-ecs/src/query/filter/mod.rs @@ -10,7 +10,73 @@ pub use not::*; mod changed; pub use changed::*; -use super::Fetch; +use crate::{Archetype, ArchetypeEntityId, Tick, World}; + +use super::{Fetch, Query}; + +pub trait FilterFetch<'a> { + /// Returns true if the entity should be visited or skipped. + fn can_visit(&mut self, entity: ArchetypeEntityId) -> bool; +} + +pub trait Filter: Copy { + /// The fetcher used for the filter + type Fetch<'a>: FilterFetch<'a>; + + 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, archetype: &'a Archetype, tick: Tick) -> Self::Fetch<'a>; + + /// Returns a fetcher that doesn't fetch from an archetype. + unsafe fn fetch_world<'a>(&self, world: &'a World) -> Option> { + let _ = world; + None + } +} + +/// A trait for getting the filter of a type. +pub trait AsFilter { + /// The query for this type + type Filter: Filter; +} + +impl Filter for Q +where + Q: for <'a> Query = bool>, +{ + type Fetch<'a> = Q::Fetch<'a>; + + fn new() -> Self { + Query::new() + } + + fn can_visit_archetype(&self, archetype: &Archetype) -> bool { + Query::can_visit_archetype(self, archetype) + } + + unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a Archetype, tick: Tick) -> Self::Fetch<'a> { + Query::fetch(self, world, archetype, tick) + } +} + +impl<'a, F> FilterFetch<'a> for F +where + F: Fetch<'a, Item = bool> +{ + fn can_visit(&mut self, entity: ArchetypeEntityId) -> bool { + Fetch::can_visit_item(self, entity) && unsafe { Fetch::get_item(self, entity) } + } +} + +impl AsFilter for Q +where + Q: for <'a> Query = bool> +{ + type Filter = Q; +} /// A fetcher that just returns a provided value pub struct StaticFetcher { diff --git a/crates/lyra-ecs/src/query/filter/not.rs b/crates/lyra-ecs/src/query/filter/not.rs index 6c87d51..33f9052 100644 --- a/crates/lyra-ecs/src/query/filter/not.rs +++ b/crates/lyra-ecs/src/query/filter/not.rs @@ -1,4 +1,4 @@ -use crate::{query::{AsFilter, AsQuery, Filter, Query}, Archetype, World}; +use crate::{query::{AsQuery, Query}, Archetype, World}; use super::StaticFetcher; @@ -43,10 +43,4 @@ impl Query for Not { impl AsQuery for Not { type Query = Self; -} - -impl Filter for Not { } - -impl AsFilter for Not { - type Filter = Self; } \ No newline at end of file diff --git a/crates/lyra-ecs/src/query/mod.rs b/crates/lyra-ecs/src/query/mod.rs index 604d0cf..edefcde 100644 --- a/crates/lyra-ecs/src/query/mod.rs +++ b/crates/lyra-ecs/src/query/mod.rs @@ -38,6 +38,7 @@ pub use world_tick::*; pub mod dynamic; pub mod filter; +pub use filter::{Filter, AsFilter}; /// A [`Fetch`]er implementation gets data out of an archetype. pub trait Fetch<'a> { @@ -93,30 +94,24 @@ pub trait AsQuery { type Query: Query; } -/// A trait for getting the filter of a type. -pub trait AsFilter { - /// The query for this type - type Filter: Filter; -} - pub trait IntoQuery { fn into_query(self) -> Self; } impl<'a> Fetch<'a> for () { - type Item = (); + type Item = bool; fn dangling() -> Self { unreachable!() } unsafe fn get_item(&mut self, _: ArchetypeEntityId) -> Self::Item { - () + true } } impl Query for () { - type Item<'a> = (); + type Item<'a> = bool; type Fetch<'a> = (); @@ -135,22 +130,10 @@ impl Query for () { } } -impl Filter for () { - type Item<'a> = bool; -} - impl AsQuery for () { type Query = (); } -pub trait Filter: Query { - type Item<'a> = bool; -} - -impl AsFilter for () { - type Filter = (); -} - #[cfg(test)] mod tests { use crate::{World, archetype::Archetype, tests::Vec2}; diff --git a/crates/lyra-ecs/src/query/tuple.rs b/crates/lyra-ecs/src/query/tuple.rs index 818b4ca..cb03e2a 100644 --- a/crates/lyra-ecs/src/query/tuple.rs +++ b/crates/lyra-ecs/src/query/tuple.rs @@ -1,6 +1,6 @@ use crate::World; -use super::{Query, Fetch, AsQuery, Filter, AsFilter}; +use super::{Query, Fetch, AsQuery, Filter, AsFilter, filter::FilterFetch, ArchetypeEntityId}; /* impl<'a, F1> Fetch<'a> for (F1,) where @@ -139,10 +139,7 @@ macro_rules! impl_bundle_tuple { fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { let ( $($name,)+ ) = self; - - // this is the only way I could figure out how to do an 'and' - let bools = vec![$($name.can_visit_archetype(archetype),)+]; - bools.iter().all(|b| *b) + $($name.can_visit_archetype(archetype)) &&+ } unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { @@ -156,26 +153,30 @@ macro_rules! impl_bundle_tuple { } #[allow(non_snake_case)] - impl<$($name: Filter),+> Filter for ($($name,)+) { - //type Item<'a> = ($(<$name as Query>::Item<'a>,)+); - //type Fetch<'a> = ($(<$name as Query>::Fetch<'a>,)+); + impl<'a, $($name: FilterFetch<'a>),+> FilterFetch<'a> for ($($name,)+) { + fn can_visit(&mut self, entity: ArchetypeEntityId) -> bool { + let ( $($name,)+ ) = self; + $($name.can_visit(entity)) &&+ + } + } - /* fn new() -> Self { + #[allow(non_snake_case)] + impl<$($name: Filter),+> Filter for ($($name,)+) { + type Fetch<'a> = ($(<$name as Filter>::Fetch<'a>,)+); + + fn new() -> Self { ( $($name::new(),)+ ) } fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { let ( $($name,)+ ) = self; - - // this is the only way I could figure out how to do an 'and' - let bools = vec![$($name.can_visit_archetype(archetype),)+]; - bools.iter().all(|b| *b) + $($name.can_visit_archetype(archetype)) &&+ } 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, archetype, tick),)+ ) - } */ + } } impl<$($name: AsFilter),+> AsFilter for ($($name,)+) { diff --git a/crates/lyra-ecs/src/query/view.rs b/crates/lyra-ecs/src/query/view.rs index c61091f..92d4aa6 100644 --- a/crates/lyra-ecs/src/query/view.rs +++ b/crates/lyra-ecs/src/query/view.rs @@ -1,8 +1,8 @@ -use std::ops::Range; +use std::{ops::Range, ptr::NonNull}; -use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick}; +use crate::{archetype::Archetype, system::FnArgFetcher, world::{ArchetypeEntityId, World}, Entity, Tick}; -use super::{AsFilter, AsQuery, Fetch, Filter, Query}; +use super::{filter::{AsFilter, Filter, FilterFetch}, AsQuery, Fetch, Query}; pub type View<'a, Q, F = ()> = ViewState<'a, ::Query, ::Query>; @@ -109,7 +109,7 @@ where let filter_fetcher = self.filter_fetcher.as_mut().unwrap(); let entity_index = ArchetypeEntityId(entity_index); - if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) { + if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit(entity_index) { let i = unsafe { fetcher.get_item(entity_index) }; return Some(i); } @@ -138,27 +138,27 @@ where } } -pub struct ViewOne<'a, Q: Query> { +pub type ViewOne<'a, Q> = ViewOneState<'a, ::Query>; + +pub struct ViewOneState<'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 { +impl<'a, Q: Query> ViewOneState<'a, Q> { + pub fn new(world: &'a World, query: Q) -> Self { //let tick = world.tick_tracker().tick_when(Q::MUTATES); Self { world, tick: world.current_tick(), - entity, query } } - pub fn get(self) -> Option> { - if let Some(record) = self.world.entities.arch_index.get(&self.entity) { + pub fn get(&self, entity: Entity) -> Option> { + if let Some(record) = self.world.entities.arch_index.get(&entity.id()) { let arch = self.world.archetypes.get(&record.id) .expect("An invalid record was specified for an entity"); @@ -172,4 +172,18 @@ impl<'a, Q: Query> ViewOne<'a, Q> { None } +} + +impl FnArgFetcher for ViewOneState<'_, Q> { + type State = (); + type Arg<'a, 'state> = ViewOneState<'a, Q>; + + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { + let world = world.as_ref(); + ViewOneState::::new(world, Q::new()) + } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } \ No newline at end of file diff --git a/crates/lyra-ecs/src/world.rs b/crates/lyra-ecs/src/world.rs index 443b39b..f5fb26a 100644 --- a/crates/lyra-ecs/src/world.rs +++ b/crates/lyra-ecs/src/world.rs @@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ops::Deref, ptr::NonNull}; use atomic_refcell::{AtomicRef, AtomicRefMut}; -use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, Res, ResMut, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker, TrackedResource}; +use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, Filter, AsFilter, AsQuery, Query, Res, ResMut, ViewIter, ViewOneState, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker, TrackedResource}; /// The id of the entity for the Archetype. /// @@ -397,8 +397,8 @@ impl World { DynamicView::new(self) } - pub fn view_one(&self, entity: Entity) -> ViewOne { - ViewOne::new(self, entity.id, T::Query::new()) + pub fn view_one(&self, entity: Entity) -> Option<::Item<'_>> { + ViewOneState::::new(self, T::Query::new()).get(entity) } /// Add a resource to the world. @@ -688,7 +688,7 @@ mod tests { world.insert(e, (Vec3::rand(),)); - assert!(world.view_one::<&Vec3>(e).get().is_some()) + assert!(world.view_one::<&Vec3>(e).is_some()) } #[test] @@ -698,7 +698,7 @@ mod tests { world.insert(e, (Vec3::rand(),)); - assert!(world.view_one::<&Vec3>(e).get().is_some()) + assert!(world.view_one::<&Vec3>(e).is_some()) } #[test] @@ -712,7 +712,7 @@ mod tests { let e3 = world.spawn(v2s[2]); println!("Spawned entities"); - let ev2 = world.view_one::<&Vec2>(e2).get() + let ev2 = world.view_one::<&Vec2>(e2) .expect("Failed to find Vec2 and Vec3 on inserted entity!"); assert_eq!(*ev2, v2s[1]); drop(ev2); @@ -722,7 +722,7 @@ mod tests { world.insert(e, (v3,)); println!("inserted entity"); - let (ev2, ev3) = world.view_one::<(&Vec2, &Vec3)>(e).get() + let (ev2, ev3) = world.view_one::<(&Vec2, &Vec3)>(e) .expect("Failed to find Vec2 and Vec3 on inserted entity!"); assert_eq!(*ev2, v2); assert_eq!(*ev3, v3); @@ -747,7 +747,7 @@ mod tests { let e = world.spawn((v,)); let view = world.view_one::<&Vec2>(e); - assert_eq!(*view.get().unwrap(), v); + assert_eq!(*view.unwrap(), v); } #[test] @@ -782,11 +782,11 @@ mod tests { world.insert(first, Vec2::new(50.0, 50.0)); - let pos = world.view_one::<&mut Vec2>(first).get().unwrap(); + let pos = world.view_one::<&mut Vec2>(first).unwrap(); assert_eq!(*pos, Vec2::new(50.0, 50.0)); drop(pos); - let pos = world.view_one::<&mut Vec2>(second).get().unwrap(); + let pos = world.view_one::<&mut Vec2>(second).unwrap(); assert_eq!(*pos, Vec2::new(5.0, 5.0)); } diff --git a/crates/lyra-game/src/render/graph/passes/transform.rs b/crates/lyra-game/src/render/graph/passes/transform.rs index 0797533..283077c 100644 --- a/crates/lyra-game/src/render/graph/passes/transform.rs +++ b/crates/lyra-game/src/render/graph/passes/transform.rs @@ -43,8 +43,6 @@ impl TransformsNode { fn process_component_queue(world: &mut lyra_ecs::World, component_queue: Vec<(Entity, Option, Option)>) { for (en, interp, index) in component_queue { - println!("writing index {:?} for entity {}", index, en.id().0); - match (interp, index) { (None, None) => unreachable!(), (None, Some(index)) => world.insert(en, index), @@ -94,7 +92,7 @@ fn update_transforms( // Interpolate the transform for this entity using a component. // If the entity does not have the component then it will be queued to be added // to it after all the entities are prepared for rendering. - let transform = match interp_tran { + /* let transform = match interp_tran { Some(mut interp_transform) => { // found in https://youtu.be/YJB1QnEmlTs?t=472 interp_transform.alpha = 1.0 - interp_transform.alpha.powf(*delta_time); @@ -112,6 +110,21 @@ fn update_transforms( component_queue.push((entity, Some(interp), None)); transform } + }; */ + + let transform = match interp_tran { + Some(mut interp_transform) => { + interp_transform.last_transform = transform; + transform + } + None => { + let interp = InterpTransform { + last_transform: transform, + alpha: 1.0, + }; + component_queue.push((entity, Some(interp), None)); + transform + } }; // Get the TransformIndex from the entity, or reserve a new one if the entity doesn't have @@ -120,11 +133,6 @@ fn update_transforms( Some(i) => *i, None => { let i = buffers.reserve_transform(&device); - debug!( - "Reserved transform index {:?} for entity {}", - i, - entity.id().0 - ); component_queue.push((entity, None, Some(i))); i diff --git a/crates/lyra-game/src/sprite/tilemap.rs b/crates/lyra-game/src/sprite/tilemap.rs index 8ce9f63..c6cffcb 100644 --- a/crates/lyra-game/src/sprite/tilemap.rs +++ b/crates/lyra-game/src/sprite/tilemap.rs @@ -1,24 +1,25 @@ -use std::collections::VecDeque; - use glam::{UVec2, UVec3, Vec2, Vec3}; use lyra_ecs::{ - query::{Entities, View}, - relation::ChildOf, - Commands, Component, Entity, World, + query::{ + filter::{Changed, Has, Not}, + Entities, View, ViewOne, + }, + relation::{ChildOf, RelationOriginComponent}, + Commands, Component, Entity, }; use lyra_math::Transform; use lyra_reflect::Reflect; use lyra_resource::ResHandle; use lyra_scene::WorldTransform; -use tracing::{debug, error}; +use tracing::error; use crate::{game::GameStages, plugin::Plugin}; use super::{AtlasSprite, TextureAtlas}; -/// A position relative to a tile on a tilemap +/// A position on a tile map #[derive(Clone, Copy, Component, Reflect)] -pub struct RelativeToTile { +pub struct TileMapPos { #[reflect(skip)] // TODO: impl reflect for Entity pub tilemap_entity: Entity, /// The position of the tile to spawn at. @@ -107,9 +108,9 @@ impl TileMap { /// A system to update the tilemap when tiles are inserted/removed. pub fn system_tilemap_update( mut commands: Commands, - view: View<(&mut TileMap, &Transform)>, + view: View<(Entities, &mut TileMap)>, ) -> anyhow::Result<()> { - for (mut map, pos) in view.into_iter() { + for (map_en, mut map) in view.into_iter() { let tile_size = map.tile_size; let atlas_handle = map.atlas.clone(); let atlas = match atlas_handle.data_ref() { @@ -128,15 +129,32 @@ pub fn system_tilemap_update( }; let grid = tile.tile.position * tile_size; - let sprite_pos = *pos - + Transform::from_xyz( - grid.x as _, - grid.y as _, - tile.tile.z_level as _, - ); + let sprite_pos = Transform::from_xyz( + grid.x as _, + // expand the grid downwards so 0,0 is top left of the tilemap + -(grid.y as f32), + tile.tile.z_level as _, + ); - tile.entity = Some(commands.spawn((sprite, sprite_pos, WorldTransform::default()))); - debug!("Spawned tile at ({}, {})", grid.x, grid.y); + let tile_en = commands.spawn(( + sprite, + TileMapPos { + tilemap_entity: map_en, + position: tile.tile.position, + z_level: tile.tile.z_level, + }, + tile.tile, + sprite_pos, + WorldTransform::default(), + )); + commands.add_relation(tile_en, ChildOf, map_en); + tile.entity = Some(tile_en); + //debug!("Spawned tile at ({}, {})", grid.x, grid.y); + + /* let tile_en = commands.spawn((sprite, sprite_pos, WorldTransform::default())); + commands.add_relation(tile_en, ChildOf, map_en); + tile.entity = Some(tile_en); + debug!("Spawned tile at ({}, {})", grid.x, grid.y); */ } else { error!( "Invalid atlas index '{}' for tile at pos '{:?}'", @@ -152,17 +170,22 @@ pub fn system_tilemap_update( } fn system_relative_tile_position_update( - world: &mut World, - view: View<(Entities, &RelativeToTile)>, + mut commands: Commands, + view: View< + ( + Entities, + &TileMapPos, + Option<&mut Transform>, + Option<&WorldTransform>, + ), + (Changed, Not>), + >, + tile_map_view: ViewOne<&TileMap>, + child_of_rel_view: ViewOne<&RelationOriginComponent>, ) -> anyhow::Result<()> { - let mut to_relate = VecDeque::new(); - - for (en, rel) in view.into_iter() { - match world - .view_one::<(&TileMap, &Transform)>(rel.tilemap_entity) - .get() - { - Some((map, pos)) => { + for (en, rel, pos, wpos) in view.into_iter() { + match tile_map_view.get(rel.tilemap_entity) { + Some(map) => { let layer = map.layers.last().unwrap(); if let Some(tile_en) = layer @@ -171,7 +194,27 @@ fn system_relative_tile_position_update( .find(|t| t.tile.position == rel.position) .and_then(|t| t.entity) { - to_relate.push_back((en, tile_en, Transform::from_translation(Vec3::new(0.0, 0.0, rel.z_level as _)))); + if child_of_rel_view + .get(en) + .map(|rel| rel.target() != tile_en) + .unwrap_or(true) + { + commands.add_relation(en, ChildOf, tile_en); + } + + if let Some(mut pos) = pos { + *pos = Transform::from_translation(Vec3::new(0.0, 0.0, rel.z_level as _)); + + if wpos.is_none() { + commands.insert(en, WorldTransform::from(*pos)); + } + } else { + let pos = + Transform::from_translation(Vec3::new(0.0, 0.0, rel.z_level as _)); + commands.insert(en, (pos, WorldTransform::from(pos))); + } + } else { + error!("Unknown tile in map at {:?}", rel.position); } } None => { @@ -183,13 +226,6 @@ fn system_relative_tile_position_update( } } - while let Some((from, to, pos)) = to_relate.pop_front() { - if world.view_one::<&WorldTransform>(from).get().is_none() { - world.add_relation(from, ChildOf, to); - world.insert(from, (pos, WorldTransform::default())); - } - } - Ok(()) } diff --git a/crates/lyra-game/src/winit/plugin.rs b/crates/lyra-game/src/winit/plugin.rs index ced2134..91d61b7 100644 --- a/crates/lyra-game/src/winit/plugin.rs +++ b/crates/lyra-game/src/winit/plugin.rs @@ -226,7 +226,7 @@ impl ApplicationHandler for WinitRunner { .expect("missing window entity"); // update the window and its cache so the sync system doesn't try to update the window - let (mut en_window, mut en_last_win) = self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*en).get().unwrap(); + let (mut en_window, mut en_last_win) = self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*en).unwrap(); let pos = Some(DVec2::new(position.x, position.y)); en_window.set_physical_cursor_position(pos); en_last_win.set_physical_cursor_position(pos); @@ -242,7 +242,7 @@ impl ApplicationHandler for WinitRunner { .expect("world missing WinitWindows resource") .window_to_entity .get(&window_id) - .and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get()) + .and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e)) .unwrap(); // update the window and its cache so the sync system doesn't try to update the window @@ -259,7 +259,7 @@ impl ApplicationHandler for WinitRunner { .expect("world missing WinitWindows resource") .window_to_entity .get(&window_id) - .and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get()) + .and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e)) .unwrap(); window.set_physical_cursor_position(None); last_window.set_physical_cursor_position(None); @@ -284,7 +284,7 @@ impl ApplicationHandler for WinitRunner { .expect("world missing WinitWindows resource") .window_to_entity .get(&window_id) - .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get()) + .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e)) .unwrap(); window_opts.focused = focused; }, @@ -299,7 +299,7 @@ impl ApplicationHandler for WinitRunner { .expect("world missing WinitWindows resource") .window_to_entity .get(&window_id) - .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get()) + .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e)) .unwrap(); window_opts.scale_factor = scale_factor; }, @@ -311,7 +311,7 @@ impl ApplicationHandler for WinitRunner { .expect("world missing WinitWindows resource") .window_to_entity .get(&window_id) - .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get()) + .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e)) .unwrap(); window_opts.theme = Some(theme); }, @@ -323,7 +323,7 @@ impl ApplicationHandler for WinitRunner { .expect("world missing WinitWindows resource") .window_to_entity .get(&window_id) - .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get()) + .and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e)) .unwrap(); window_opts.occluded = occ; }, diff --git a/crates/lyra-gltf/src/loader.rs b/crates/lyra-gltf/src/loader.rs index d6d4fee..66de803 100644 --- a/crates/lyra-gltf/src/loader.rs +++ b/crates/lyra-gltf/src/loader.rs @@ -224,7 +224,8 @@ impl ResourceLoader for GltfLoader { } for en in graph.world().view_iter::() { - graph.world().view_one::<(&WorldTransform, &Transform)>(en).get().expect("Scene node is missing world and local transform bundle!"); + graph.world().view_one::<(&WorldTransform, &Transform)>(en) + .expect("Scene node is missing world and local transform bundle!"); } let graph = ResHandle::new_ready(Some(path.as_str()), graph); @@ -300,14 +301,14 @@ mod tests { let mut node = None; //scene.world().view::(|_, no, tran| { - tran.get().expect("scene node is missing a WorldTransform"); + tran.expect("scene node is missing a WorldTransform"); node = Some(no.clone()); }); let world = scene.world(); let node = node.unwrap(); - let data = world.view_one::<(&ResHandle, &Transform)>(node.entity()).get(); + let data = world.view_one::<(&ResHandle, &Transform)>(node.entity()); debug_assert!(data.is_some(), "The mesh was not loaded"); // transform will always be there let data = data.unwrap(); diff --git a/crates/lyra-reflect/src/component.rs b/crates/lyra-reflect/src/component.rs index f16d323..5291a01 100644 --- a/crates/lyra-reflect/src/component.rs +++ b/crates/lyra-reflect/src/component.rs @@ -73,17 +73,17 @@ impl FromType for ReflectedComponent { }, fn_reflect: |world: &World, entity: Entity| { world.view_one::<&C>(entity) - .get().map(|c| c as Ref) + .map(|c| c as Ref) }, fn_reflect_mut: |world: &mut World, entity: Entity| { world.view_one::<&mut C>(entity) - .get().map(|c| c as RefMut) + .map(|c| c as RefMut) }, fn_reflect_tick: |world: &World, entity: Entity| { - world.view_one::>(entity).get() + world.view_one::>(entity) }, fn_reflect_is_changed: |world: &World, entity: Entity| { - world.view_one::>(entity).get() + world.view_one::>(entity) } } } diff --git a/crates/lyra-scene/src/lib.rs b/crates/lyra-scene/src/lib.rs index 95227df..361c197 100644 --- a/crates/lyra-scene/src/lib.rs +++ b/crates/lyra-scene/src/lib.rs @@ -6,7 +6,7 @@ pub use node::*; mod world_transform; pub use world_transform::*; -use lyra_ecs::{query::{Entities, ViewOne}, relation::ChildOf, Bundle, Component, World}; +use lyra_ecs::{query::{Entities, Query}, relation::ChildOf, Bundle, Component, World}; use lyra_math::Transform; // So we can use lyra_ecs::Component derive macro @@ -95,7 +95,7 @@ impl SceneGraph { /// The traversal does not include the root scene node. pub fn traverse_down(&self, mut callback: F) where - F: FnMut(&World, &SceneNode, ViewOne), + F: FnMut(&World, &SceneNode, Option<::Item<'_>>), Q: lyra_ecs::query::AsQuery, { self.traverse_down_from::(self.root_node.clone(), &mut callback); @@ -105,7 +105,7 @@ impl SceneGraph { /// SceneNode and its world transform. fn traverse_down_from(&self, start: SceneNode, callback: &mut F) where - F: FnMut(&World, &SceneNode, ViewOne), + F: FnMut(&World, &SceneNode, Option<::Item<'_>>), Q: lyra_ecs::query::AsQuery, { let v = self.world @@ -211,7 +211,7 @@ pub mod tests { let mut idx = 0; scene.traverse_down::<_, &WorldTransform>(|_, _, v| { - let pos = v.get().unwrap(); + let pos = v.unwrap(); if idx == 0 { assert_eq!(**pos, Transform::from_translation(v2s[idx])); } else if idx == 1 { diff --git a/crates/lyra-scene/src/node.rs b/crates/lyra-scene/src/node.rs index c5e5f5e..267e7fb 100644 --- a/crates/lyra-scene/src/node.rs +++ b/crates/lyra-scene/src/node.rs @@ -25,7 +25,7 @@ impl SceneNode { if let Some(parent) = self.parent { let v = graph.world.view_one::<&RelationOriginComponent>(parent); - let p_parent = if let Some(pp) = v.get() { + let p_parent = if let Some(pp) = v { Some(pp.target()) } else { None }; @@ -81,7 +81,7 @@ mod tests { let view = scene.world.filtered_view::<(Entities, &mut WorldTransform, &Transform, Option<&ResHandle>), Not>>>(); crate::system_update_world_transforms(&scene.world, view).unwrap(); - let tran = scene.world.view_one::<&WorldTransform>(b.entity).get().unwrap(); + let tran = scene.world.view_one::<&WorldTransform>(b.entity).unwrap(); assert_eq!(**tran, Transform::from_translation(Vec3::new(60.0, 60.0, 60.0))); } } \ No newline at end of file diff --git a/crates/lyra-scene/src/world_transform.rs b/crates/lyra-scene/src/world_transform.rs index fa3b650..2f5fa1e 100644 --- a/crates/lyra-scene/src/world_transform.rs +++ b/crates/lyra-scene/src/world_transform.rs @@ -104,7 +104,7 @@ mod tests { let view = world.filtered_view::<(Entities, &mut WorldTransform, &Transform, Option<&ResHandle>), Not>>>(); system_update_world_transforms(&world, view).unwrap(); - let g = world.view_one::<&WorldTransform>(child).get().unwrap(); + let g = world.view_one::<&WorldTransform>(child).unwrap(); assert_eq!(**g, Transform::from_xyz(25.0, 25.0, 25.0)); } @@ -131,7 +131,7 @@ mod tests { let mut base_offset = 25.0; for child in children.into_iter() { - let g = world.view_one::<&WorldTransform>(child).get().unwrap(); + let g = world.view_one::<&WorldTransform>(child).unwrap(); println!("Child {:?} at {:?}", child, g.translation); assert_eq!(**g, Transform::from_xyz(base_offset, base_offset, base_offset)); @@ -162,7 +162,7 @@ mod tests { let view = world.filtered_view::<(Entities, &mut WorldTransform, &Transform, Option<&ResHandle>), Not>>>(); system_update_world_transforms(&world, view).unwrap(); - let g = world.view_one::<&WorldTransform>(five_child).get().unwrap(); + let g = world.view_one::<&WorldTransform>(five_child).unwrap(); assert_eq!(**g, Transform::from_xyz(611.0, 248.0, 899.0)); } @@ -189,13 +189,13 @@ mod tests { let view = world.filtered_view::<(Entities, &mut WorldTransform, &Transform, Option<&ResHandle>), Not>>>(); system_update_world_transforms(&world, view).unwrap(); - let g = world.view_one::<&WorldTransform>(five_child).get().unwrap(); + let g = world.view_one::<&WorldTransform>(five_child).unwrap(); assert_eq!(**g, Transform::from_xyz(536.0, 102.0, 817.0)); - let g = world.view_one::<&WorldTransform>(thir_chi).get().unwrap(); + let g = world.view_one::<&WorldTransform>(thir_chi).unwrap(); assert_eq!(**g, Transform::from_xyz(76.0, 110.0, 42.0)); - let g = world.view_one::<&WorldTransform>(four_child).get().unwrap(); + let g = world.view_one::<&WorldTransform>(four_child).unwrap(); assert_eq!(**g, Transform::from_xyz(100.0, 171.0, 107.0)); } } \ No newline at end of file diff --git a/examples/2d/src/main.rs b/examples/2d/src/main.rs index b641771..dca6d65 100644 --- a/examples/2d/src/main.rs +++ b/examples/2d/src/main.rs @@ -1,9 +1,11 @@ use lyra_engine::{ assets::{Image, ResourceManager}, ecs::{ - query::{Res, ResMut, View}, Commands, Component, Entity, World + query::{Res, View, WorldTick}, + relation::{ChildOf, RelationOriginComponent}, + Commands, Component, Entity, World, }, - game::App, + game::{App, GameStages}, gltf::Gltf, input::{ Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, @@ -18,8 +20,8 @@ use lyra_engine::{ ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, }, sprite::{ - self, AtlasAnimations, AtlasSprite, Pivot, RelativeToTile, Sprite, SpriteAnimation, - TextureAtlas, TileMap, TileMapPlugin, + self, AtlasAnimations, AtlasSprite, Pivot, Sprite, SpriteAnimation, TextureAtlas, Tile, + TileMap, TileMapPlugin, TileMapPos, }, DeltaTime, }; @@ -27,7 +29,6 @@ use rand::{ distributions::{Distribution, WeightedIndex}, Rng, }; -use tracing::debug; #[async_std::main] async fn main() { @@ -105,41 +106,21 @@ async fn main() { .with_plugin(setup_scene_plugin) .with_plugin(action_handler_plugin) .with_plugin(TileMapPlugin) - //.with_plugin(camera_debug_plugin) .with_plugin(TopDown2dCameraPlugin) - .with_system( + .add_system_to_stage( + GameStages::Last, "update_world_transforms", system_update_world_transforms, &[], - ).with_system( - "spawn_egg", - system_spawn_egg, - &[], - ).with_system( - "egg_location", - system_egg_location, - &[], - ); + ) + .with_system("spawn_egg", system_spawn_egg, &[]); a.run(); } fn setup_scene_plugin(app: &mut App) { - /* app.with_system( - "sprite_atlas_animation", - sprite::system_sprite_atlas_animation, - &[], - ); */ - let world = &mut app.world; let resman = world.get_resource_mut::().unwrap(); - let cube_gltf = resman - .request::("../assets/cube-texture-embedded.gltf") - .unwrap(); - - cube_gltf.wait_recurse_dependencies_load().unwrap(); - let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0]; - let grass_tileset = resman .request::("../assets/sprout_lands/Tilesets/Grass.png") .unwrap(); @@ -209,7 +190,7 @@ fn setup_scene_plugin(app: &mut App) { }, Transform::from_xyz( (map_size.x * tile_size.x) as f32 * 0.5, - (map_size.y * tile_size.y) as f32 * 0.5, + (map_size.y * tile_size.y) as f32 * -0.5, 0.0, ), TopDown2dCamera { @@ -223,25 +204,13 @@ fn setup_scene_plugin(app: &mut App) { struct GroundTileMap(Entity); -#[derive(Component)] -struct EggEntity; - -fn system_egg_location(view: View<(&WorldTransform, &EggEntity)>) -> anyhow::Result<()> { - for (pos, _) in view.into_iter() { - println!("Found egg at world pos {:?}", **pos); - } - - Ok(()) -} - fn system_spawn_egg( mut commands: Commands, inputs: Res, tile_map: Res, resman: Res, + wtick: WorldTick, ) -> anyhow::Result<()> { - let debug_state = inputs.get_action_state("Debug").unwrap(); - if inputs.was_action_just_pressed("Debug").unwrap() { let egg = resman .request::("../assets/sprout_lands/Objects/Egg_item.png") @@ -250,12 +219,14 @@ fn system_spawn_egg( let x = rand::thread_rng().gen_range(0..32); let y = rand::thread_rng().gen_range(0..16); - let rtt = RelativeToTile { + let rtt = TileMapPos { tilemap_entity: tile_map.0, position: UVec2::new(x, y), - z_level: -9, + z_level: 2, }; + //println!("Spawned egg at ({}, {}) on tick {}", x, y, **wtick); + commands.spawn(( Sprite { texture: egg, @@ -263,7 +234,6 @@ fn system_spawn_egg( pivot: Pivot::TopLeft, }, rtt, - EggEntity )); }