diff --git a/crates/lyra-ecs/lyra-ecs-derive/src/lib.rs b/crates/lyra-ecs/lyra-ecs-derive/src/lib.rs index 46707fb..b3131b1 100644 --- a/crates/lyra-ecs/lyra-ecs-derive/src/lib.rs +++ b/crates/lyra-ecs/lyra-ecs-derive/src/lib.rs @@ -1,5 +1,5 @@ use quote::quote; -use syn::{parse_macro_input, DeriveInput}; +use syn::{parse_macro_input, spanned::Spanned, DeriveInput}; #[proc_macro_derive(Component)] pub fn derive_component(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -16,4 +16,54 @@ pub fn derive_component(input: proc_macro::TokenStream) -> proc_macro::TokenStre } } }) +} + +#[proc_macro_derive(Bundle)] +pub fn derive_bundle(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let type_ident = &input.ident; + + let s = match &input.data { + syn::Data::Struct(s) => { + s + }, + _ => { + return syn::Error::new(input.span(), "Bundle derive macro only supports Structs") + .into_compile_error().into(); + } + }; + + let field_names: Vec<_> = s.fields.iter().map(|f| f.ident.as_ref().unwrap()).collect(); + + proc_macro::TokenStream::from(quote! { + impl #impl_generics lyra_engine::ecs::Bundle for #type_ident #ty_generics #where_clause { + fn type_ids(&self) -> Vec { + let mut v = vec![]; + #( + v.extend(self.#field_names.type_ids().into_iter()); + )* + v + } + + fn info(&self) -> Vec { + let mut v = vec![]; + #( + v.extend(self.#field_names.info().into_iter()); + )* + v + } + + fn take(self, f: &mut impl FnMut(std::ptr::NonNull, lyra_engine::ecs::DynTypeId, lyra_engine::ecs::ComponentInfo)) { + #( + self.#field_names.take(f); + )* + } + + fn is_dynamic(&self) -> bool { + false + } + } + }) } \ No newline at end of file diff --git a/crates/lyra-ecs/src/archetype.rs b/crates/lyra-ecs/src/archetype.rs index aebfa93..ab1fbff 100644 --- a/crates/lyra-ecs/src/archetype.rs +++ b/crates/lyra-ecs/src/archetype.rs @@ -348,7 +348,7 @@ impl Archetype { self.entity_ids.insert(entity, entity_index); self.entities.push(entity); - bundle.take(|data, type_id, info| { + bundle.take(&mut |data, type_id, info| { self.put_component_at( tick, data, @@ -621,7 +621,7 @@ impl Archetype { } for (eid, bundle) in new_columns.into_iter().enumerate() { - bundle.take(|ptr, tyid, _size| unsafe { + bundle.take(&mut |ptr, tyid, _size| unsafe { let col = self.get_column_mut(tyid).unwrap(); col.insert_entity(eid, ptr, tick.clone()); }); diff --git a/crates/lyra-ecs/src/bundle.rs b/crates/lyra-ecs/src/bundle.rs index 729e3d7..7f86a24 100644 --- a/crates/lyra-ecs/src/bundle.rs +++ b/crates/lyra-ecs/src/bundle.rs @@ -11,7 +11,7 @@ pub trait Bundle { /// Take the bundle by calling the closure with pointers to each component, its type and size. /// The closure is expected to take ownership of the pointer. - fn take(self, f: impl FnMut(NonNull, DynTypeId, ComponentInfo)); + fn take(self, f: &mut impl FnMut(NonNull, DynTypeId, ComponentInfo)); /// Returns a boolean indicating if this Bundle is dynamic. See [`DynamicBundle`] fn is_dynamic(&self) -> bool; @@ -26,7 +26,7 @@ impl Bundle for () { vec![ComponentInfo::new::<()>()] } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { + fn take(self, f: &mut impl FnMut(NonNull, DynTypeId, ComponentInfo)) { f(NonNull::from(&self).cast(), DynTypeId::of::<()>(), ComponentInfo::new::<()>()); } @@ -44,7 +44,7 @@ impl Bundle for C { vec![ComponentInfo::new::()] } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { + fn take(self, f: &mut impl FnMut(NonNull, DynTypeId, ComponentInfo)) { f(NonNull::from(&self).cast(), DynTypeId::of::(), ComponentInfo::new::()); // this must be done to avoid calling drop on heap memory that the component @@ -59,29 +59,41 @@ impl Bundle for C { macro_rules! impl_bundle_tuple { ( $($name: ident),+ ) => ( + // these names wont follow rust convention, but its a macro so deal with it #[allow(non_snake_case)] - impl<$($name: Component),+> Bundle for ($($name,)+) { + impl<$($name: Bundle),+> Bundle for ($($name,)+) { + #[inline(always)] fn type_ids(&self) -> Vec { - // these names wont follow rust convention, but its a macro so deal with it - vec![$(DynTypeId::of::<$name>()),+] + let ($($name,)+) = self; + + let mut v = vec![]; + $( + v.extend($name.type_ids().into_iter()); + )+ + v } + #[inline(always)] fn info(&self) -> Vec { - vec![$(ComponentInfo::new::<$name>()),+] + let ($($name,)+) = self; + + let mut v = vec![]; + $( + v.extend($name.info().into_iter()); + )+ + v } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { - // these names wont follow rust convention, but its a macro so deal with it + #[inline(always)] + fn take(self, f: &mut impl FnMut(NonNull, DynTypeId, ComponentInfo)) { let ($($name,)+) = self; $( - f(NonNull::from(&$name).cast(), DynTypeId::of::<$name>(), ComponentInfo::new::<$name>()); - // this must be done to avoid calling drop on heap memory that the component - // may manage. So something like a Vec, or HashMap, etc. - std::mem::forget($name); + $name.take(f); )+ } + #[inline(always)] fn is_dynamic(&self) -> bool { false } @@ -166,7 +178,7 @@ impl DynamicBundle { where B: Bundle { - bundle.take(|ptr, _, info| { + bundle.take(&mut |ptr, _, info| { // unfortunately the components in the bundle must be copied since there is no guarantee that // `bundle` lasts for as long as the `DynamicBundle`. If the data wasn't copied, the pointers // could be invalid later. @@ -192,7 +204,7 @@ impl Bundle for DynamicBundle { self.bundle.iter().map(|b| b.1).collect() } - fn take(self, mut f: impl FnMut(NonNull, DynTypeId, ComponentInfo)) { + fn take(self, f: &mut impl FnMut(NonNull, DynTypeId, ComponentInfo)) { for (data, info) in self.bundle.into_iter() { f(data, info.type_id(), info); } @@ -201,4 +213,71 @@ impl Bundle for DynamicBundle { fn is_dynamic(&self) -> bool { true } +} + +#[cfg(test)] +mod tests { + use lyra_ecs_derive::{Bundle, Component}; + use crate::{lyra_engine, ComponentInfo, World}; + + use super::Bundle; + + #[allow(dead_code)] + #[derive(Component, PartialEq, Clone, Copy, Debug)] + struct Vec2 { + x: f32, + y: f32, + } + + #[allow(dead_code)] + #[derive(Component, Debug, PartialEq, Eq, Clone, Copy)] + enum SomeFlag { + SomethingA, + SomethingB, + } + + #[derive(Bundle)] + struct CompBundle { + pos: Vec2, + flag: SomeFlag + } + + #[test] + fn check_bundle_order() { + let b = CompBundle { + pos: Vec2 { + x: 10.0, y: 10.0, + }, + flag: SomeFlag::SomethingA, + }; + + let info = b.info(); + let mut info = info.into_iter(); + + assert_eq!(info.next().unwrap(), ComponentInfo::new::()); + assert_eq!(info.next().unwrap(), ComponentInfo::new::()); + } + + #[test] + fn check_bundle_spawn() { + let b_pos = Vec2 { + x: 10.0, y: 10.0, + }; + let b_flag = SomeFlag::SomethingA; + let b = CompBundle { + pos: b_pos, + flag: b_flag, + }; + + let mut world = World::new(); + let e = world.spawn(b); + + let pos = world.view_one::<&Vec2>(e).get() + .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() + .expect("failed to find spawned SomeFlag from Bundle on Entity"); + assert_eq!(*flag, b_flag); + } } \ 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 18f129d..a3fe19c 100644 --- a/crates/lyra-ecs/src/query/filter/mod.rs +++ b/crates/lyra-ecs/src/query/filter/mod.rs @@ -1,6 +1,4 @@ mod has; -use std::marker::PhantomData; - pub use has::*; mod or; diff --git a/crates/lyra-ecs/src/query/resource.rs b/crates/lyra-ecs/src/query/resource.rs index f8ad30a..1082ccb 100644 --- a/crates/lyra-ecs/src/query/resource.rs +++ b/crates/lyra-ecs/src/query/resource.rs @@ -112,7 +112,7 @@ impl<'a, T: ResourceObject> Res<'a, T> { /// Returns a boolean indicating if the resource changed. pub fn changed(&self) -> bool { - *self.inner.tick >= *self.world_tick - 1 + self.inner.changed(self.world_tick) } /// The tick that this resource was last modified at @@ -236,7 +236,7 @@ impl<'a, T: ResourceObject> ResMut<'a, T> { } pub fn changed(&self) -> bool { - *self.inner.tick - 1 >= *self.world_tick - 1 + self.inner.changed(self.world_tick) } /// The tick that this resource was last modified at diff --git a/crates/lyra-ecs/src/resource.rs b/crates/lyra-ecs/src/resource.rs index 0e4f7b4..9c9311c 100644 --- a/crates/lyra-ecs/src/resource.rs +++ b/crates/lyra-ecs/src/resource.rs @@ -2,7 +2,7 @@ use std::{any::{Any, TypeId}, sync::Arc}; use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use crate::{Tick, TickTracker}; +use crate::Tick; /// Shorthand for `Send + Sync + 'static`, so it never needs to be implemented manually. pub trait ResourceObject: Send + Sync + Any { @@ -25,6 +25,14 @@ pub struct TrackedResource { pub res: T, } +impl TrackedResource { + pub fn changed(&self, tick: Tick) -> bool { + let tick = tick.checked_sub(1).unwrap_or(0); + //println!("self: {}, world: {}", *self.tick, tick); + *self.tick >= tick + } +} + /// A type erased storage for a Resource. #[derive(Clone)] pub struct ResourceData { @@ -89,6 +97,6 @@ impl ResourceData { } pub fn changed(&self, tick: Tick) -> bool { - *self.data.borrow().tick >= *tick - 1 + self.data.borrow().changed(tick) } } \ No newline at end of file diff --git a/crates/lyra-ecs/src/world.rs b/crates/lyra-ecs/src/world.rs index 6d986c0..443b39b 100644 --- a/crates/lyra-ecs/src/world.rs +++ b/crates/lyra-ecs/src/world.rs @@ -180,7 +180,7 @@ impl World { let entry_idx = *current_arch.entity_indexes() .get(&entity).unwrap(); - bundle.take(|ptr, id, _info| { + bundle.take(&mut |ptr, id, _info| { let col = current_arch.get_column_mut(id).unwrap(); unsafe { col.set_at(entry_idx.0 as _, ptr, tick) }; }); @@ -459,6 +459,8 @@ impl World { } /// Gets a resource from the World. + /// + /// Returns `None` if the resource wasn't found, or is already borrowed. pub fn get_resource(&self) -> Option> { self.get_tracked_resource::().map(|r| Res { inner: r, @@ -476,20 +478,24 @@ impl World { /// Gets a reference to a change tracked resource. /// + /// Returns `None` if the resource wasn't found, or is already borrowed. + /// /// You will have to manually downcast the inner resource. Most people don't need this, see /// [`World::get_resource`]. pub fn get_tracked_resource(&self) -> Option>> { self.resources.get(&TypeId::of::()) - .map(|r| r.data.borrow()) + .and_then(|r| r.data.try_borrow().ok()) } /// Gets a mutable borrow to a change tracked resource. /// + /// Returns `None` if the resource wasn't found, or is already borrowed. + /// /// You will have to manually downcast the inner resource. Most people don't need this, see /// [`World::get_resource_mut`]. pub fn get_tracked_resource_mut(&self) -> Option>> { self.resources.get(&TypeId::of::()) - .map(|r| r.data.borrow_mut()) + .and_then(|r| r.data.try_borrow_mut().ok()) } /// Returns a boolean indicating if the resource changed. @@ -791,14 +797,18 @@ mod tests { world.add_resource(SimpleCounter(50)); assert!(world.has_resource_changed::()); + world.tick(); world.spawn(Vec2::new(50.0, 50.0)); + world.tick(); assert!(!world.has_resource_changed::()); + world.tick(); let mut counter = world.get_resource_mut::() .expect("Counter resource is missing"); counter.0 += 100; + drop(counter); assert!(world.has_resource_changed::()); }