diff --git a/crates/bevy_core/src/transform/hierarchy.rs b/crates/bevy_core/src/transform/hierarchy.rs index 1ffcf0deae..947b404c20 100644 --- a/crates/bevy_core/src/transform/hierarchy.rs +++ b/crates/bevy_core/src/transform/hierarchy.rs @@ -1,7 +1,7 @@ use bevy_transform::prelude::Children; use legion::{ - prelude::{Entity, World}, - systems::SubWorld, + prelude::{Entity, World, EntityStore}, + subworld::SubWorld, }; pub fn run_on_hierarchy( diff --git a/crates/bevy_legion/Cargo.toml b/crates/bevy_legion/Cargo.toml index a0e51a68bd..8b80812a4e 100644 --- a/crates/bevy_legion/Cargo.toml +++ b/crates/bevy_legion/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "legion" -version = "0.2.1" +version = "0.2.4" description = "High performance entity component system (ECS) library" authors = ["Thomas Gillen "] repository = "https://github.com/TomGillen/legion" @@ -30,8 +30,8 @@ more-system-fns = ["legion-systems/more-system-fns"] # ] [dependencies] -legion-core = { path = "legion_core", version = "0.2.1", default-features = false } -legion-systems = { path = "legion_systems", version = "0.2.1", default-features = false } +legion-core = { path = "legion_core", version = "0.2.4", default-features = false } +legion-systems = { path = "legion_systems", version = "0.2.4", default-features = false } [dev-dependencies] criterion = "0.3" diff --git a/crates/bevy_legion/benches/parallel_query.rs b/crates/bevy_legion/benches/parallel_query.rs index d477be0e58..428677b972 100644 --- a/crates/bevy_legion/benches/parallel_query.rs +++ b/crates/bevy_legion/benches/parallel_query.rs @@ -120,12 +120,12 @@ fn sequential(world: &mut World) { fn parallel(world: &mut World) { join( || unsafe { - for (mut b, a) in <(Write, Read)>::query().iter_unchecked(&world) { + for (mut b, a) in <(Write, Read)>::query().iter_unchecked(world) { b.0 = a.0; } }, || unsafe { - for (mut c, a) in <(Write, Read)>::query().iter_unchecked(&world) { + for (mut c, a) in <(Write, Read)>::query().iter_unchecked(world) { c.0 = a.0; } }, @@ -135,12 +135,12 @@ fn parallel(world: &mut World) { fn par_for_each_mut(world: &mut World) { join( || unsafe { - <(Write, Read)>::query().par_for_each_unchecked(&world, |(mut b, a)| { + <(Write, Read)>::query().par_for_each_unchecked(world, |(mut b, a)| { b.0 = a.0; }); }, || unsafe { - <(Write, Read)>::query().par_for_each_unchecked(&world, |(mut c, a)| { + <(Write, Read)>::query().par_for_each_unchecked(world, |(mut c, a)| { c.0 = a.0; }); }, diff --git a/crates/bevy_legion/example/hello_world/src/main.rs b/crates/bevy_legion/example/hello_world/src/main.rs index ab799c800f..9b53292aac 100644 --- a/crates/bevy_legion/example/hello_world/src/main.rs +++ b/crates/bevy_legion/example/hello_world/src/main.rs @@ -53,10 +53,10 @@ fn main() { .write_resource::() .read_resource::() .with_query(<(Write, Read)>::query()) - .build(|_, mut world, (res1, res2), query| { + .build(|_, world, (res1, res2), query| { res1.0 = res2.0.clone(); // Write the mutable resource from the immutable resource - for (mut pos, vel) in query.iter_mut(&mut world) { + for (mut pos, vel) in query.iter_mut(world) { pos.0 += vel.0; pos.1 += vel.1; pos.2 += vel.2; diff --git a/crates/bevy_legion/legion_core/Cargo.toml b/crates/bevy_legion/legion_core/Cargo.toml index 0dd7518a6d..81c8089b7d 100644 --- a/crates/bevy_legion/legion_core/Cargo.toml +++ b/crates/bevy_legion/legion_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "legion-core" -version = "0.2.1" +version = "0.2.4" description = "High performance entity component system (ECS) library" authors = ["Thomas Gillen "] repository = "https://github.com/TomGillen/legion" @@ -33,6 +33,7 @@ metrics = { version = "0.12", optional = true } serde = { version = "1", optional = true } fxhash = "0.2" thiserror = "1.0" +bit-set = "0.5" [dev-dependencies] tracing-subscriber = "0.2" diff --git a/crates/bevy_legion/legion_core/readme.md b/crates/bevy_legion/legion_core/readme.md new file mode 100644 index 0000000000..32e7e81c8d --- /dev/null +++ b/crates/bevy_legion/legion_core/readme.md @@ -0,0 +1,3 @@ +# Legion + +This is the core crate for the [legion](https://crates.io/crates/legion) ECS library. See the documentation for the parent crate for more information. \ No newline at end of file diff --git a/crates/bevy_legion/legion_core/src/borrow.rs b/crates/bevy_legion/legion_core/src/borrow.rs index 4e8e5fa4af..9a5892fa40 100644 --- a/crates/bevy_legion/legion_core/src/borrow.rs +++ b/crates/bevy_legion/legion_core/src/borrow.rs @@ -711,3 +711,99 @@ impl<'a, T: 'a, I: Iterator + ExactSizeIterator> ExactSizeIter for TryRefIterMut<'a, T, I> { } + +/// A set of RefMaps +#[derive(Debug)] +pub struct RefMapSet<'a, T: 'a> { + #[allow(dead_code)] + // held for drop impl + borrow: Vec>, + value: T, +} + +impl<'a, T: 'a> RefMapSet<'a, T> { + #[inline(always)] + pub fn new(borrow: Vec>, value: T) -> Self { Self { borrow, value } } + + #[inline(always)] + pub fn map_into K>(mut self, mut f: F) -> RefMapSet<'a, K> { + RefMapSet::new(self.borrow, f(&mut self.value)) + } + + /// Deconstructs this mapped borrow to its underlying borrow state and value. + /// + /// # Safety + /// + /// Ensure that you still follow all safety guidelines of this mapped ref. + #[inline(always)] + pub unsafe fn deconstruct(self) -> (Vec>, T) { (self.borrow, self.value) } +} + +impl<'a, T: 'a> Deref for RefMapSet<'a, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { &self.value } +} + +impl<'a, T: 'a> AsRef for RefMapSet<'a, T> { + #[inline(always)] + fn as_ref(&self) -> &T { &self.value } +} + +impl<'a, T: 'a> std::borrow::Borrow for RefMapSet<'a, T> { + #[inline(always)] + fn borrow(&self) -> &T { &self.value } +} + +/// A set of RefMapMuts +#[derive(Debug)] +pub struct RefMapMutSet<'a, T: 'a> { + #[allow(dead_code)] + // held for drop impl + borrow: Vec>, + value: T, +} + +impl<'a, T: 'a> RefMapMutSet<'a, T> { + #[inline(always)] + pub fn new(borrow: Vec>, value: T) -> Self { Self { borrow, value } } + + #[inline(always)] + pub fn map_into K>(mut self, mut f: F) -> RefMapMutSet<'a, K> { + RefMapMutSet { + value: f(&mut self.value), + borrow: self.borrow, + } + } + + /// Deconstructs this mapped borrow to its underlying borrow state and value. + /// + /// # Safety + /// + /// Ensure that you still follow all safety guidelines of this mutable mapped ref. + #[inline(always)] + pub unsafe fn deconstruct(self) -> (Vec>, T) { (self.borrow, self.value) } +} + +impl<'a, T: 'a> Deref for RefMapMutSet<'a, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { &self.value } +} + +impl<'a, T: 'a> DerefMut for RefMapMutSet<'a, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.value } +} + +impl<'a, T: 'a> AsRef for RefMapMutSet<'a, T> { + #[inline(always)] + fn as_ref(&self) -> &T { &self.value } +} + +impl<'a, T: 'a> std::borrow::Borrow for RefMapMutSet<'a, T> { + #[inline(always)] + fn borrow(&self) -> &T { &self.value } +} diff --git a/crates/bevy_legion/legion_core/src/entity.rs b/crates/bevy_legion/legion_core/src/entity.rs index cd14f16de3..41e16c1e18 100644 --- a/crates/bevy_legion/legion_core/src/entity.rs +++ b/crates/bevy_legion/legion_core/src/entity.rs @@ -20,7 +20,9 @@ pub struct Entity { } impl Entity { - pub fn new(index: EntityIndex, version: EntityVersion) -> Entity { Entity { index, version } } + pub fn new(index: EntityIndex, version: EntityVersion) -> Entity { + Entity { index, version } + } pub fn index(self) -> EntityIndex { self.index } } diff --git a/crates/bevy_legion/legion_core/src/lib.rs b/crates/bevy_legion/legion_core/src/lib.rs index 4ba09dce61..21e1ab1757 100644 --- a/crates/bevy_legion/legion_core/src/lib.rs +++ b/crates/bevy_legion/legion_core/src/lib.rs @@ -10,8 +10,10 @@ pub mod filter; pub mod guid_entity_allocator; pub mod index; pub mod iterator; +pub mod permission; pub mod query; pub mod storage; +pub mod subworld; pub mod world; #[cfg(feature = "serialize")] @@ -27,6 +29,7 @@ pub mod prelude { event::Event, filter::filter_fns::*, query::{IntoQuery, Query as FilteredQuery, Read, Tagged, TryRead, TryWrite, Write}, - world::{Universe, World}, + subworld::SubWorld, + world::{EntityStore, Universe, World}, }; } diff --git a/crates/bevy_legion/legion_core/src/permission.rs b/crates/bevy_legion/legion_core/src/permission.rs new file mode 100644 index 0000000000..eddf865d5a --- /dev/null +++ b/crates/bevy_legion/legion_core/src/permission.rs @@ -0,0 +1,382 @@ +use smallvec::SmallVec; +use std::fmt::{Debug, Display}; + +#[derive(Clone)] +pub struct Permissions { + items: SmallVec<[T; 4]>, + shared: usize, // index of first shared + write: usize, // index of first write exclusive +} + +impl Permissions { + pub fn new() -> Self { + Self { + items: SmallVec::default(), + shared: 0, + write: 0, + } + } + + fn find(&self, item: &T) -> Option { self.items.iter().position(|x| x == item) } + + pub fn push(&mut self, item: T) { + if let Some(index) = self.find(&item) { + if index < self.shared { + // if it is in read exclusive, move it up into shared + self.items.swap(index, self.shared - 1); + self.shared -= 1; + } else if index > self.write { + // if it is in write exclusive, move it down into shared + self.items.swap(index, self.write); + self.write += 1; + } + } else { + // add the item + self.items.push(item); + + // swap it down into shared + let index = self.items.len() - 1; + self.items.swap(index, self.write); + self.write += 1; + } + } + + pub fn push_read(&mut self, item: T) { + if let Some(index) = self.find(&item) { + // if the item had exclusive write, move it into shared + if index >= self.write { + // swap it down to the beginning of the exclusive write segment, + // then move the boundry over it + self.items.swap(index, self.write); + self.write += 1; + } + } else { + // add the item to the end of the vec + self.items.push(item); + let index = self.items.len() - 1; + + // move it down into shared + self.items.swap(index, self.write); + + // move it down into read exclusive + self.items.swap(self.write, self.shared); + + // move the boundaries + self.write += 1; + self.shared += 1; + } + } + + pub fn push_write(&mut self, item: T) { + if let Some(index) = self.find(&item) { + if index < self.shared { + // move it into shared + self.items.swap(index, self.shared - 1); + self.shared -= 1; + } + } else { + // add the item to the end of the vec + self.items.push(item); + } + } + + pub fn remove(&mut self, item: &T) { + if let Some(mut index) = self.find(item) { + if index < self.shared { + // push value up into shared + self.items.swap(index, self.shared - 1); + self.shared -= 1; + index = self.shared; + } + + if index < self.write { + // push value up into write + self.items.swap(index, self.write - 1); + self.write -= 1; + index = self.write; + } + + self.items.swap_remove(index); + } + } + + pub fn remove_read(&mut self, item: &T) { + if let Some(index) = self.find(item) { + if index < self.shared { + // move into shared + self.items.swap(index, self.shared - 1); + self.shared -= 1; + + // move into write + self.items.swap(self.shared, self.write - 1); + self.write -= 1; + + // remove + self.items.swap_remove(self.write); + } else if index < self.write { + // move into write-only + self.items.swap(index, self.write - 1); + self.write -= 1; + } + } + } + + pub fn remove_write(&mut self, item: &T) { + if let Some(index) = self.find(item) { + if index >= self.write { + // remove + self.items.swap_remove(index); + } else if index >= self.shared { + // move into read-only + self.items.swap(index, self.shared); + self.shared += 1; + } + } + } + + pub fn add(&mut self, mut other: Self) { + for read in other.items.drain(..other.shared) { + self.push_read(read); + } + + for shared in other.items.drain(..(other.write - other.shared)) { + self.push(shared); + } + + for write in other.items.drain(..) { + self.push_write(write); + } + } + + pub fn subtract(&mut self, other: &Self) { + for read in other.read_only() { + self.remove_read(read); + } + + for shared in other.readwrite() { + self.remove(shared); + } + + for write in other.write_only() { + self.remove_write(write); + } + } + + pub fn reads(&self) -> &[T] { &self.items[..self.write] } + + pub fn writes(&self) -> &[T] { &self.items[self.shared..] } + + pub fn read_only(&self) -> &[T] { &self.items[..self.shared] } + + pub fn write_only(&self) -> &[T] { &self.items[self.write..] } + + pub fn readwrite(&self) -> &[T] { &self.items[self.shared..self.write] } + + pub fn is_superset(&self, other: &Self) -> bool { + for read in other.read_only() { + // exit if reads are in exclusive write range, or are not found + if self.find(read).map(|i| i >= self.write).unwrap_or(true) { + return false; + } + } + + for shared in other.readwrite() { + // exit if shareds are in exclusive read or write range, or are not found + if self + .find(shared) + .map(|i| i < self.shared || i >= self.write) + .unwrap_or(true) + { + return false; + } + } + + for write in other.write_only() { + // exit if writes are in exclusive read range, or are not found + if self.find(write).map(|i| i < self.shared).unwrap_or(true) { + return false; + } + } + + true + } + + pub fn is_disjoint(&self, other: &Self) -> bool { + for read in other.read_only() { + // exit if reads are in read-only or shared range + if self.find(read).map(|i| i < self.write).unwrap_or(false) { + return false; + } + } + + for shared in other.readwrite() { + // exit if shareds are found + if self.find(shared).is_some() { + return false; + } + } + + for write in other.write_only() { + // exit if writes are in write-only or shared range + if self.find(write).map(|i| i >= self.shared).unwrap_or(false) { + return false; + } + } + + true + } +} + +impl Default for Permissions { + fn default() -> Self { Self::new() } +} + +impl Debug for Permissions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn list(items: &[V]) -> String { + use itertools::Itertools; + items + .iter() + .map(|x| format!("{:?}", x)) + .fold1(|x, y| format!("{}, {}", x, y)) + .unwrap_or_else(|| "".to_owned()) + } + + write!( + f, + "Permissions {{ reads: [{}], writes: [{}] }}", + list::(&self.reads()), + list::(&self.writes()) + ) + } +} + +impl Display for Permissions { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn list(items: &[V]) -> String { + use itertools::Itertools; + items + .iter() + .map(|x| format!("{}", x)) + .fold1(|x, y| format!("{}, {}", x, y)) + .unwrap_or_else(|| "".to_owned()) + } + + write!( + f, + "reads: [{}], writes: [{}]", + list::(&self.reads()), + list::(&self.writes()) + ) + } +} + +#[cfg(test)] +mod tests { + use super::Permissions; + + #[test] + fn push_read() { + let mut permissions = Permissions::new(); + permissions.push_read(1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), &[1usize]); + assert_eq!(permissions.readwrite(), empty); + assert_eq!(permissions.write_only(), empty); + } + + #[test] + fn push_write() { + let mut permissions = Permissions::new(); + permissions.push_write(1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), empty); + assert_eq!(permissions.readwrite(), empty); + assert_eq!(permissions.write_only(), &[1usize]); + } + + #[test] + fn push_both() { + let mut permissions = Permissions::new(); + permissions.push(1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), empty); + assert_eq!(permissions.readwrite(), &[1usize]); + assert_eq!(permissions.write_only(), empty); + } + + #[test] + fn promote_read_to_readwrite() { + let mut permissions = Permissions::new(); + permissions.push_read(1usize); + permissions.push_write(1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), empty); + assert_eq!(permissions.readwrite(), &[1usize]); + assert_eq!(permissions.write_only(), empty); + } + + #[test] + fn promote_write_to_readwrite() { + let mut permissions = Permissions::new(); + permissions.push_write(1usize); + permissions.push_read(1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), empty); + assert_eq!(permissions.readwrite(), &[1usize]); + assert_eq!(permissions.write_only(), empty); + } + + #[test] + fn remove_write() { + let mut permissions = Permissions::new(); + permissions.push_write(1usize); + permissions.remove_write(&1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), empty); + assert_eq!(permissions.readwrite(), empty); + assert_eq!(permissions.write_only(), empty); + } + + #[test] + fn remove_read() { + let mut permissions = Permissions::new(); + permissions.push_read(1usize); + permissions.remove_read(&1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), empty); + assert_eq!(permissions.readwrite(), empty); + assert_eq!(permissions.write_only(), empty); + } + + #[test] + fn demote_readwrite_to_read() { + let mut permissions = Permissions::new(); + permissions.push(1usize); + permissions.remove_write(&1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), &[1usize]); + assert_eq!(permissions.readwrite(), empty); + assert_eq!(permissions.write_only(), empty); + } + + #[test] + fn demote_readwrite_to_write() { + let mut permissions = Permissions::new(); + permissions.push(1usize); + permissions.remove_read(&1usize); + + let empty: &[usize] = &[]; + assert_eq!(permissions.read_only(), empty); + assert_eq!(permissions.readwrite(), empty); + assert_eq!(permissions.write_only(), &[1usize]); + } +} diff --git a/crates/bevy_legion/legion_core/src/query.rs b/crates/bevy_legion/legion_core/src/query.rs index 51e48c7218..dd6ed1fc84 100644 --- a/crates/bevy_legion/legion_core/src/query.rs +++ b/crates/bevy_legion/legion_core/src/query.rs @@ -2,6 +2,8 @@ use crate::borrow::RefIter; use crate::borrow::RefIterMut; use crate::borrow::RefMap; use crate::borrow::RefMapMut; +use crate::borrow::RefMapMutSet; +use crate::borrow::RefMapSet; use crate::borrow::TryRefIter; use crate::borrow::TryRefIterMut; use crate::entity::Entity; @@ -17,17 +19,17 @@ use crate::filter::FilterResult; use crate::filter::Passthrough; use crate::filter::TagFilter; use crate::index::ChunkIndex; -use crate::index::SetIndex; +use crate::index::{ArchetypeIndex, SetIndex}; #[cfg(feature = "par-iter")] use crate::iterator::{FissileEnumerate, FissileIterator}; use crate::storage::ArchetypeData; use crate::storage::Component; use crate::storage::ComponentStorage; use crate::storage::ComponentTypeId; -use crate::storage::Storage; use crate::storage::Tag; use crate::storage::TagTypeId; -use crate::world::World; +use crate::subworld::{ComponentAccess, StorageAccessor}; +use crate::{permission::Permissions, world::EntityStore}; use derivative::Derivative; use std::any::TypeId; use std::iter::Enumerate; @@ -60,17 +62,17 @@ pub trait View<'a>: Sized + Send + Sync + 'static { /// Validates that the view does not break any component borrowing rules. fn validate() -> bool; + /// Determines if the given component access includes all permissions required by the view. + fn validate_access(access: &ComponentAccess) -> bool; + /// Determines if the view reads the specified data type. fn reads() -> bool; /// Determines if the view writes to the specified data type. fn writes() -> bool; - /// Returns an array of the components read by this view - fn read_types() -> Vec; - - /// Returns an array of the components written by this view - fn write_types() -> Vec; + /// Returns the set of permissions required by the view. + fn requires_permissions() -> Permissions; } /// A type which can construct a default entity filter. @@ -156,9 +158,15 @@ impl<'a, T: Component> View<'a> for Read { fn writes() -> bool { false } - fn read_types() -> Vec { vec![ComponentTypeId::of::()] } + fn validate_access(access: &ComponentAccess) -> bool { + access.allows_read(ComponentTypeId::of::()) + } - fn write_types() -> Vec { Vec::with_capacity(0) } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push_read(ComponentTypeId::of::()); + permissions + } } impl ViewElement for Read { @@ -209,9 +217,15 @@ impl<'a, T: Component> View<'a> for TryRead { fn writes() -> bool { false } - fn read_types() -> Vec { vec![ComponentTypeId::of::()] } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push_read(ComponentTypeId::of::()); + permissions + } - fn write_types() -> Vec { Vec::with_capacity(0) } + fn validate_access(access: &ComponentAccess) -> bool { + access.allows_read(ComponentTypeId::of::()) + } } impl ViewElement for TryRead { @@ -268,11 +282,15 @@ impl<'a, T: Component> View<'a> for Write { #[inline] fn writes() -> bool { TypeId::of::() == TypeId::of::() } - #[inline] - fn read_types() -> Vec { vec![ComponentTypeId::of::()] } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push(ComponentTypeId::of::()); + permissions + } - #[inline] - fn write_types() -> Vec { vec![ComponentTypeId::of::()] } + fn validate_access(access: &ComponentAccess) -> bool { + access.allows_write(ComponentTypeId::of::()) + } } impl ViewElement for Write { @@ -323,11 +341,15 @@ impl<'a, T: Component> View<'a> for TryWrite { #[inline] fn writes() -> bool { TypeId::of::() == TypeId::of::() } - #[inline] - fn read_types() -> Vec { vec![ComponentTypeId::of::()] } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push(ComponentTypeId::of::()); + permissions + } - #[inline] - fn write_types() -> Vec { vec![ComponentTypeId::of::()] } + fn validate_access(access: &ComponentAccess) -> bool { + access.allows_write(ComponentTypeId::of::()) + } } impl ViewElement for TryWrite { @@ -386,11 +408,10 @@ impl<'a, T: Tag> View<'a> for Tagged { #[inline] fn writes() -> bool { false } - #[inline] - fn read_types() -> Vec { Vec::with_capacity(0) } + fn requires_permissions() -> Permissions { Permissions::new() } #[inline] - fn write_types() -> Vec { Vec::with_capacity(0) } + fn validate_access(_: &ComponentAccess) -> bool { true } } impl ViewElement for Tagged { @@ -449,6 +470,10 @@ macro_rules! impl_view_tuple { true } + fn validate_access(access: &ComponentAccess) -> bool { + $( $ty::validate_access(access) )&&* + } + fn reads() -> bool { $( $ty::reads::() )||* } @@ -457,16 +482,10 @@ macro_rules! impl_view_tuple { $( $ty::writes::() )||* } - fn read_types() -> Vec { - let mut vec = vec![]; - $( vec.extend($ty::read_types()); )* - vec - } - - fn write_types() -> Vec { - let mut vec = vec![]; - $( vec.extend($ty::write_types()); )* - vec + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + $( permissions.add($ty::requires_permissions()); )* + permissions } } }; @@ -515,7 +534,7 @@ impl<'a, V: for<'b> View<'b>> Chunk<'a, V> { /// Get an iterator of all data contained within the chunk. #[inline] - pub fn iter(&mut self) -> >::Iter { + pub fn iter_mut(&mut self) -> >::Iter { V::fetch( self.archetype, self.components, @@ -618,7 +637,7 @@ where FChunk: Filter>, { _view: PhantomData, - storage: &'data Storage, + storage: StorageAccessor<'data>, arch_filter: &'filter FArch, chunkset_filter: &'filter FChunkset, chunk_filter: &'filter FChunk, @@ -655,10 +674,20 @@ where match self.archetypes.next() { Some((arch_index, arch_data)) => { if self.arch_filter.is_match(&arch_data).is_pass() { + // validate that we are allowed to access this archetype + if !self + .storage + .can_access_archetype(ArchetypeIndex(arch_index)) + { + panic!( + "query attempted to access archetype unavailable via sub world" + ); + } // we have found another set self.set_frontier = { - let chunks = - unsafe { self.storage.archetypes().get_unchecked(arch_index) }; + let chunks = unsafe { + self.storage.inner().archetypes().get_unchecked(arch_index) + }; let data = ChunksetFilterData { archetype_data: chunks, }; @@ -748,7 +777,7 @@ where } } match self.iter.next() { - Some(mut inner) => self.frontier = Some(inner.iter()), + Some(mut inner) => self.frontier = Some(inner.iter_mut()), None => return None, } } @@ -947,17 +976,17 @@ where /// # Panics /// /// This function may panic if other code is concurrently accessing the same components. - pub unsafe fn iter_chunks_unchecked<'a, 'data>( + pub unsafe fn iter_chunks_unchecked<'a, 'data, T: EntityStore>( &'a self, - world: &'data World, + world: &'data T, ) -> ChunkViewIter<'data, 'a, V, F::ArchetypeFilter, F::ChunksetFilter, F::ChunkFilter> { self.filter.init(); let (arch_filter, chunkset_filter, chunk_filter) = self.filter.filters(); - let storage = world.storage(); + let storage = world.get_component_storage::().unwrap(); let archetypes = arch_filter .collect(ArchetypeFilterData { - component_types: storage.component_types(), - tag_types: storage.tag_types(), + component_types: storage.inner().component_types(), + tag_types: storage.inner().tag_types(), }) .enumerate(); ChunkViewIter { @@ -973,9 +1002,9 @@ where } /// Gets an iterator which iterates through all chunks that match the query. - pub fn iter_chunks<'a, 'data>( + pub fn iter_chunks<'a, 'data, T: EntityStore>( &'a self, - world: &'data World, + world: &'data T, ) -> ChunkViewIter<'data, 'a, V, F::ArchetypeFilter, F::ChunksetFilter, F::ChunkFilter> where V: ReadOnly, @@ -985,9 +1014,9 @@ where } /// Gets an iterator which iterates through all chunks that match the query. - pub fn iter_chunks_mut<'a, 'data>( + pub fn iter_chunks_mut<'a, 'data, T: EntityStore>( &'a self, - world: &'data mut World, + world: &'data mut T, ) -> ChunkViewIter<'data, 'a, V, F::ArchetypeFilter, F::ChunksetFilter, F::ChunkFilter> { // safe because the &mut World ensures exclusivity unsafe { self.iter_chunks_unchecked(world) } @@ -1005,9 +1034,9 @@ where /// # Panics /// /// This function may panic if other code is concurrently accessing the same components. - pub unsafe fn iter_entities_unchecked<'a, 'data>( + pub unsafe fn iter_entities_unchecked<'a, 'data, T: EntityStore>( &'a self, - world: &'data World, + world: &'data T, ) -> ChunkEntityIter< 'data, V, @@ -1021,9 +1050,9 @@ where } /// Gets an iterator which iterates through all entity data that matches the query, and also yields the the `Entity` IDs. - pub fn iter_entities<'a, 'data>( + pub fn iter_entities<'a, 'data, T: EntityStore>( &'a self, - world: &'data World, + world: &'data T, ) -> ChunkEntityIter< 'data, V, @@ -1037,9 +1066,9 @@ where } /// Gets an iterator which iterates through all entity data that matches the query, and also yields the the `Entity` IDs. - pub fn iter_entities_mut<'a, 'data>( + pub fn iter_entities_mut<'a, 'data, T: EntityStore>( &'a self, - world: &'data mut World, + world: &'data mut T, ) -> ChunkEntityIter< 'data, V, @@ -1061,9 +1090,9 @@ where /// # Panics /// /// This function may panic if other code is concurrently accessing the same components. - pub unsafe fn iter_unchecked<'a, 'data>( + pub unsafe fn iter_unchecked<'a, 'data, T: EntityStore>( &'a self, - world: &'data World, + world: &'data T, ) -> ChunkDataIter< 'data, V, @@ -1077,9 +1106,9 @@ where } /// Gets an iterator which iterates through all entity data that matches the query. - pub fn iter<'a, 'data>( + pub fn iter<'a, 'data, T: EntityStore>( &'a self, - world: &'data World, + world: &'data T, ) -> ChunkDataIter< 'data, V, @@ -1093,9 +1122,9 @@ where } /// Gets an iterator which iterates through all entity data that matches the query. - pub fn iter_mut<'a, 'data>( + pub fn iter_mut<'a, 'data, T: EntityStore>( &'a self, - world: &'data mut World, + world: &'data mut T, ) -> ChunkDataIter< 'data, V, @@ -1117,27 +1146,32 @@ where /// # Panics /// /// This function may panic if other code is concurrently accessing the same components. - pub unsafe fn for_each_entities_unchecked<'a, 'data, T>(&'a self, world: &'data World, mut f: T) + pub unsafe fn for_each_entities_unchecked<'a, 'data, T, W>(&'a self, world: &'data W, mut f: T) where T: Fn((Entity, <>::Iter as Iterator>::Item)), + W: EntityStore, { - self.iter_entities_unchecked(world).for_each(&mut f); + for mut chunk in self.iter_chunks_unchecked(world) { + chunk.iter_entities_mut().for_each(&mut f) + } } /// Iterates through all entity data that matches the query. - pub fn for_each_entities<'a, 'data, T>(&'a self, world: &'data World, f: T) + pub fn for_each_entities<'a, 'data, T, W>(&'a self, world: &'data W, f: T) where T: Fn((Entity, <>::Iter as Iterator>::Item)), V: ReadOnly, + W: EntityStore, { // safe because the view can only read data immutably unsafe { self.for_each_entities_unchecked(world, f) }; } /// Iterates through all entity data that matches the query. - pub fn for_each_entities_mut<'a, 'data, T>(&'a self, world: &'data mut World, f: T) + pub fn for_each_entities_mut<'a, 'data, T, W>(&'a self, world: &'data mut W, f: T) where T: Fn((Entity, <>::Iter as Iterator>::Item)), + W: EntityStore, { // safe because the &mut World ensures exclusivity unsafe { self.for_each_entities_unchecked(world, f) }; @@ -1155,32 +1189,104 @@ where /// # Panics /// /// This function may panic if other code is concurrently accessing the same components. - pub unsafe fn for_each_unchecked<'a, 'data, T>(&'a self, world: &'data World, mut f: T) + pub unsafe fn for_each_unchecked<'a, 'data, T, W>(&'a self, world: &'data W, mut f: T) where T: Fn(<>::Iter as Iterator>::Item), + W: EntityStore, { - self.iter_unchecked(world).for_each(&mut f); + for mut chunk in self.iter_chunks_unchecked(world) { + chunk.iter_mut().for_each(&mut f) + } } /// Iterates through all entity data that matches the query. - pub fn for_each<'a, 'data, T>(&'a self, world: &'data World, f: T) + pub fn for_each<'a, 'data, T, W>(&'a self, world: &'data W, f: T) where T: Fn(<>::Iter as Iterator>::Item), V: ReadOnly, + W: EntityStore, { // safe because the view can only read data immutably unsafe { self.for_each_unchecked(world, f) }; } /// Iterates through all entity data that matches the query. - pub fn for_each_mut<'a, 'data, T>(&'a self, world: &'data mut World, f: T) + pub fn for_each_mut<'a, 'data, T, W>(&'a self, world: &'data mut W, f: T) where T: Fn(<>::Iter as Iterator>::Item), + W: EntityStore, { // safe because the &mut World ensures exclusivity unsafe { self.for_each_unchecked(world, f) }; } + /// Returns a RefMapSet of all components of a given type. This simplifies getting a slice of + /// references to all components of type T that match the filter. This can be useful for passing + /// to other libraries or FFI. + pub fn components<'a, T: Component, W: EntityStore>( + &self, + world: &'a W, + ) -> RefMapSet<'a, Vec<&'a T>> { + if !V::reads::() { + panic!("data type not readable via this query"); + } + + let mut borrows = vec![]; + let mut refs = vec![]; + let storage = world.get_component_storage::>().unwrap().inner(); + + unsafe { + self.filter + .iter_archetype_indexes(storage) + .flat_map(|archetype_index| { + storage + .archetypes() + .get_unchecked(archetype_index.0) + .iter_data_slice::() + }) + .map(|x| x.deconstruct()) + .for_each(|(borrow, slice)| { + borrows.push(borrow); + refs.extend(slice); + }); + } + + RefMapSet::new(borrows, refs) + } + + /// Returns a RefMapMutSet of all components of a given type. This simplifies getting a slice of + /// mutable refs to all components of type T that match the filter. + pub fn components_mut<'a, T: Component, W: EntityStore>( + &self, + world: &'a mut W, + ) -> RefMapMutSet<'a, Vec<&'a mut T>> { + if !V::writes::() { + panic!("data type not writable via this query"); + } + + let mut borrows = vec![]; + let mut refs = vec![]; + let storage = world.get_component_storage::>().unwrap().inner(); + + unsafe { + self.filter + .iter_archetype_indexes(storage) + .flat_map(|archetype_index| { + storage + .archetypes() + .get_unchecked(archetype_index.0) + .iter_data_slice_unchecked_mut::() + }) + .map(|x| x.deconstruct()) + .for_each(|(borrow, slice)| { + borrows.push(borrow); + refs.extend(slice); + }); + } + + RefMapMutSet::new(borrows, refs) + } + #[cfg(feature = "par-iter")] /// Gets an iterator which iterates through all chunks that match the query in parallel. /// Does not perform static borrow checking. @@ -1194,21 +1300,22 @@ where /// # Panics /// /// This function may panic if other code is concurrently accessing the same components. - pub unsafe fn par_iter_chunks_unchecked<'a, 'data>( + pub unsafe fn par_iter_chunks_unchecked<'a, 'data, W>( &'a self, - world: &'data World, + world: &'data W, ) -> ChunkViewParIter<'data, 'a, V, F::ArchetypeFilter, F::ChunksetFilter, F::ChunkFilter> where >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { self.filter.init(); let (arch_filter, chunkset_filter, chunk_filter) = self.filter.filters(); - let storage = world.storage(); + let storage = world.get_component_storage::().unwrap(); let archetypes = FissileEnumerate::new(arch_filter.collect(ArchetypeFilterData { - component_types: storage.component_types(), - tag_types: storage.tag_types(), + component_types: storage.inner().component_types(), + tag_types: storage.inner().tag_types(), })); ChunkViewParIter { storage, @@ -1224,15 +1331,16 @@ where #[cfg(feature = "par-iter")] /// Gets an iterator which iterates through all chunks that match the query in parallel. - pub fn par_iter_chunks<'a, 'data>( + pub fn par_iter_chunks<'a, 'data, W>( &'a self, - world: &'data World, + world: &'data W, ) -> ChunkViewParIter<'data, 'a, V, F::ArchetypeFilter, F::ChunksetFilter, F::ChunkFilter> where >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, V: ReadOnly, + W: EntityStore, { // safe because the view can only read data immutably unsafe { self.par_iter_chunks_unchecked(world) } @@ -1240,14 +1348,15 @@ where #[cfg(feature = "par-iter")] /// Gets an iterator which iterates through all chunks that match the query in parallel. - pub fn par_iter_chunks_mut<'a, 'data>( + pub fn par_iter_chunks_mut<'a, 'data, W>( &'a self, - world: &'data mut World, + world: &'data mut W, ) -> ChunkViewParIter<'data, 'a, V, F::ArchetypeFilter, F::ChunksetFilter, F::ChunkFilter> where >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { // safe because the &mut World ensures exclusivity unsafe { self.par_iter_chunks_unchecked(world) } @@ -1266,12 +1375,13 @@ where /// /// This function may panic if other code is concurrently accessing the same components. #[cfg(feature = "par-iter")] - pub unsafe fn par_entities_for_each_unchecked<'a, T>(&'a self, world: &'a World, f: T) + pub unsafe fn par_entities_for_each_unchecked<'a, T, W>(&'a self, world: &'a W, f: T) where T: Fn((Entity, <>::Iter as Iterator>::Item)) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { self.par_for_each_chunk_unchecked(world, |mut chunk| { for data in chunk.iter_entities_mut() { @@ -1282,13 +1392,14 @@ where /// Iterates through all entity data that matches the query in parallel. #[cfg(feature = "par-iter")] - pub fn par_entities_for_each<'a, T>(&'a self, world: &'a World, f: T) + pub fn par_entities_for_each<'a, T, W>(&'a self, world: &'a W, f: T) where T: Fn((Entity, <>::Iter as Iterator>::Item)) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, V: ReadOnly, + W: EntityStore, { // safe because the view can only read data immutably unsafe { self.par_entities_for_each_unchecked(world, f) }; @@ -1296,12 +1407,13 @@ where /// Iterates through all entity data that matches the query in parallel. #[cfg(feature = "par-iter")] - pub fn par_entities_for_each_mut<'a, T>(&'a self, world: &'a mut World, f: T) + pub fn par_entities_for_each_mut<'a, T, W>(&'a self, world: &'a mut W, f: T) where T: Fn((Entity, <>::Iter as Iterator>::Item)) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { // safe because the &mut World ensures exclusivity unsafe { self.par_entities_for_each_unchecked(world, f) }; @@ -1320,15 +1432,16 @@ where /// /// This function may panic if other code is concurrently accessing the same components. #[cfg(feature = "par-iter")] - pub unsafe fn par_for_each_unchecked<'a, T>(&'a self, world: &'a World, f: T) + pub unsafe fn par_for_each_unchecked<'a, T, W>(&'a self, world: &'a W, f: T) where T: Fn(<>::Iter as Iterator>::Item) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { self.par_for_each_chunk_unchecked(world, |mut chunk| { - for data in chunk.iter() { + for data in chunk.iter_mut() { f(data); } }); @@ -1336,13 +1449,14 @@ where /// Iterates through all entity data that matches the query in parallel. #[cfg(feature = "par-iter")] - pub fn par_for_each<'a, T>(&'a self, world: &'a World, f: T) + pub fn par_for_each<'a, T, W>(&'a self, world: &'a W, f: T) where T: Fn(<>::Iter as Iterator>::Item) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, V: ReadOnly, + W: EntityStore, { // safe because the view can only read data immutably unsafe { self.par_for_each_unchecked(world, f) }; @@ -1350,12 +1464,13 @@ where /// Iterates through all entity data that matches the query in parallel. #[cfg(feature = "par-iter")] - pub fn par_for_each_mut<'a, T>(&'a self, world: &'a mut World, f: T) + pub fn par_for_each_mut<'a, T, W>(&'a self, world: &'a mut W, f: T) where T: Fn(<>::Iter as Iterator>::Item) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { // safe because the &mut World ensures exclusivity unsafe { self.par_for_each_unchecked(world, f) }; @@ -1374,12 +1489,13 @@ where /// /// This function may panic if other code is concurrently accessing the same components. #[cfg(feature = "par-iter")] - pub unsafe fn par_for_each_chunk_unchecked<'a, T>(&'a self, world: &'a World, f: T) + pub unsafe fn par_for_each_chunk_unchecked<'a, T, W>(&'a self, world: &'a W, f: T) where T: Fn(Chunk<'a, V>) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { let par_iter = self.par_iter_chunks_unchecked(world); ParallelIterator::for_each(par_iter, |chunk| { @@ -1389,13 +1505,14 @@ where /// Iterates through all chunks that match the query in parallel. #[cfg(feature = "par-iter")] - pub fn par_for_each_chunk<'a, T>(&'a self, world: &'a World, f: T) + pub fn par_for_each_chunk<'a, T, W>(&'a self, world: &'a W, f: T) where T: Fn(Chunk<'a, V>) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, V: ReadOnly, + W: EntityStore, { // safe because the view can only read data immutably unsafe { self.par_for_each_chunk_unchecked(world, f) }; @@ -1403,12 +1520,13 @@ where /// Iterates through all chunks that match the query in parallel. #[cfg(feature = "par-iter")] - pub fn par_for_each_chunk_mut<'a, T>(&'a self, world: &'a mut World, f: T) + pub fn par_for_each_chunk_mut<'a, T, W>(&'a self, world: &'a mut W, f: T) where T: Fn(Chunk<'a, V>) + Send + Sync, >>::Iter: FissileIterator, >>::Iter: FissileIterator, >>::Iter: FissileIterator, + W: EntityStore, { // safe because the &mut World ensures exclusivity unsafe { self.par_for_each_chunk_unchecked(world, f) }; @@ -1428,7 +1546,7 @@ where FChunk::Iter: FissileIterator, { _view: PhantomData, - storage: &'data Storage, + storage: StorageAccessor<'data>, arch_filter: &'filter FArch, chunkset_filter: &'filter FChunkset, chunk_filter: &'filter FChunk, @@ -1476,10 +1594,20 @@ where match self.archetypes.next() { Some((arch_index, arch_data)) => { if self.arch_filter.is_match(&arch_data).is_pass() { + // validate that we are allowed to access this archetype + if !self + .storage + .can_access_archetype(ArchetypeIndex(arch_index)) + { + panic!( + "query attempted to access archetype unavailable via sub world" + ); + } // we have found another set self.set_frontier = { - let arch = - unsafe { self.storage.archetypes().get_unchecked(arch_index) }; + let arch = unsafe { + self.storage.inner().archetypes().get_unchecked(arch_index) + }; let data = ChunksetFilterData { archetype_data: arch, }; @@ -1619,7 +1747,7 @@ where let right_split = Self { _view, - storage, + storage: storage.clone(), arch_filter, chunkset_filter, chunk_filter, diff --git a/crates/bevy_legion/legion_core/src/storage.rs b/crates/bevy_legion/legion_core/src/storage.rs index b8d81e9c31..9a728082c9 100644 --- a/crates/bevy_legion/legion_core/src/storage.rs +++ b/crates/bevy_legion/legion_core/src/storage.rs @@ -23,7 +23,7 @@ use smallvec::SmallVec; use std::any::type_name; use std::cell::UnsafeCell; use std::fmt::Debug; -use std::fmt::Formatter; +use std::fmt::{Display, Formatter}; use std::mem::size_of; use std::ops::Deref; use std::ops::DerefMut; @@ -43,50 +43,27 @@ fn next_version() -> u64 { .unwrap() } -#[cfg(not(feature = "ffi"))] -/// A type ID identifying a component type. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct ComponentTypeId(pub &'static str); -#[cfg(not(feature = "ffi"))] impl ComponentTypeId { /// Gets the component type ID that represents type `T`. pub fn of() -> Self { Self(type_name::()) } } -#[cfg(feature = "ffi")] -/// A type ID identifying a component type. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub struct ComponentTypeId(pub &'static str, pub u32); - -#[cfg(feature = "ffi")] -impl ComponentTypeId { - /// Gets the component type ID that represents type `T`. - pub fn of() -> Self { Self(type_name::(), 0) } +impl Display for ComponentTypeId { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } -#[cfg(not(feature = "ffi"))] /// A type ID identifying a tag type. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] pub struct TagTypeId(pub &'static str); -#[cfg(not(feature = "ffi"))] impl TagTypeId { /// Gets the tag type ID that represents type `T`. pub fn of() -> Self { Self(type_name::()) } } -#[cfg(feature = "ffi")] -/// A type ID identifying a tag type. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] -pub struct TagTypeId(pub &'static str, pub u32); - -#[cfg(feature = "ffi")] -impl TagTypeId { - /// Gets the tag type ID that represents type `T`. - pub fn of() -> Self { Self(type_name::(), 0) } -} - /// A `Component` is per-entity data that can be attached to a single entity. pub trait Component: Send + Sync + 'static {} @@ -1214,6 +1191,28 @@ impl ArchetypeData { ) -> &mut Chunkset { self.chunksets_mut().get_unchecked_mut(index) } + + pub(crate) fn iter_data_slice<'a, T: Component>( + &'a self, + ) -> impl Iterator> + 'a { + self.chunk_sets.iter().flat_map(move |set| { + set.chunks.iter().map(move |chunk| { + let c = chunk.components(ComponentTypeId::of::()).unwrap(); + unsafe { c.data_slice::() } + }) + }) + } + + pub(crate) unsafe fn iter_data_slice_unchecked_mut<'a, T: Component>( + &'a self, + ) -> impl Iterator> + 'a { + self.chunk_sets.iter().flat_map(move |set| { + set.chunks.iter().map(move |chunk| { + let c = chunk.components(ComponentTypeId::of::()).unwrap(); + c.data_slice_mut::() + }) + }) + } } fn align_up(addr: usize, align: usize) -> usize { (addr + (align - 1)) & align.wrapping_neg() } diff --git a/crates/bevy_legion/legion_core/src/subworld.rs b/crates/bevy_legion/legion_core/src/subworld.rs new file mode 100644 index 0000000000..d9cf93e136 --- /dev/null +++ b/crates/bevy_legion/legion_core/src/subworld.rs @@ -0,0 +1,503 @@ +use crate::{ + borrow::{Ref, RefMut}, + entity::Entity, + filter::EntityFilter, + index::ArchetypeIndex, + permission::Permissions, + query::Query, + query::View, + storage::{Component, ComponentTypeId, Storage, Tag}, + world::{EntityStore, World}, +}; +use bit_set::BitSet; +use std::{borrow::Cow, ops::Deref}; + +#[derive(Debug)] +/// Describes which archetypes are available for access. +pub enum ArchetypeAccess { + /// All archetypes. + All, + /// Some archetypes. + Some(BitSet), +} + +impl ArchetypeAccess { + pub fn is_disjoint(&self, other: &ArchetypeAccess) -> bool { + match self { + Self::All => false, + Self::Some(mine) => match other { + Self::All => false, + Self::Some(theirs) => mine.is_disjoint(theirs), + }, + } + } +} + +#[derive(Clone)] +pub enum ComponentAccess<'a> { + All, + Allow(Cow<'a, Permissions>), + Disallow(Cow<'a, Permissions>), +} + +impl<'a> ComponentAccess<'a> { + pub fn allows_read(&self, component: ComponentTypeId) -> bool { + match self { + Self::All => true, + Self::Allow(components) => components.reads().contains(&component), + Self::Disallow(components) => !components.reads().contains(&component), + } + } + + pub fn allows_write(&self, component: ComponentTypeId) -> bool { + match self { + Self::All => true, + Self::Allow(components) => components.writes().contains(&component), + Self::Disallow(components) => !components.writes().contains(&component), + } + } + + pub(crate) fn split(&mut self, access: Permissions) -> (Self, Self) { + fn append_incompatible( + denied: &mut Permissions, + to_deny: &Permissions, + ) { + // reads are now denied writes + for read in to_deny.reads() { + denied.push_write(*read); + } + + // writes are now entirely denied + for write in to_deny.writes() { + denied.push(*write); + } + } + + fn incompatible( + permissions: &Permissions, + ) -> Permissions { + let mut denied = Permissions::new(); + // if the current permission allows reads, then everything else must deny writes + for read in permissions.read_only() { + denied.push_write(*read); + } + + // if the current permission allows writes, then everything else must deny all + for write in permissions.writes() { + denied.push(*write); + } + + denied + } + + match self { + Self::All => { + let denied = incompatible(&access); + ( + Self::Allow(Cow::Owned(access)), + Self::Disallow(Cow::Owned(denied)), + ) + } + Self::Allow(allowed) => { + if !allowed.is_superset(&access) { + panic!("view accesses components unavailable in this world: world allows only {}, view requires {}", allowed, access); + } + + let mut allowed = allowed.clone(); + allowed.to_mut().subtract(&access); + + (Self::Allow(Cow::Owned(access)), Self::Allow(allowed)) + } + Self::Disallow(denied) => { + if !denied.is_disjoint(&access) { + panic!("view accesses components unavailable in this world: world disallows {}, view requires {}", denied, access); + } + + let mut denied = denied.clone(); + append_incompatible(denied.to_mut(), &access); + + (Self::Allow(Cow::Owned(access)), Self::Disallow(denied)) + } + } + } +} + +#[derive(Debug)] +pub struct ComponentAccessError; + +#[derive(Clone)] +pub struct StorageAccessor<'a> { + storage: &'a Storage, + archetypes: Option<&'a BitSet>, +} + +impl<'a> StorageAccessor<'a> { + pub fn new(storage: &'a Storage, archetypes: Option<&'a BitSet>) -> Self { + Self { + storage, + archetypes, + } + } + + pub fn can_access_archetype(&self, ArchetypeIndex(archetype): ArchetypeIndex) -> bool { + match self.archetypes { + None => true, + Some(archetypes) => archetypes.contains(archetype), + } + } + + pub fn inner(&self) -> &'a Storage { self.storage } + + pub fn into_inner(self) -> &'a Storage { self.storage } +} + +impl<'a> Deref for StorageAccessor<'a> { + type Target = Storage; + fn deref(&self) -> &Self::Target { self.storage } +} + +/// Provides access to a subset of the entities of a `World`. +#[derive(Clone)] +pub struct SubWorld<'a> { + pub(crate) world: &'a World, + pub(crate) components: ComponentAccess<'a>, + pub(crate) archetypes: Option<&'a BitSet>, +} + +impl<'a> SubWorld<'a> { + /// Constructs a new SubWorld. + /// + /// # Safety + /// Queries assume that this type has been constructed correctly. Ensure that sub-worlds represent + /// disjoint portions of a world and that the world is not used while any of its sub-worlds are alive. + pub unsafe fn new_unchecked( + world: &'a World, + access: &'a Permissions, + archetypes: &'a ArchetypeAccess, + ) -> Self { + SubWorld { + world, + components: ComponentAccess::Allow(Cow::Borrowed(access)), + archetypes: if let ArchetypeAccess::Some(ref bitset) = archetypes { + Some(bitset) + } else { + None + }, + } + } + + /// Splits the world into two. The left world allows access only to the data declared by the view; + /// the right world allows access to all else. + pub fn split<'b, T: for<'v> View<'v>>(&'b mut self) -> (SubWorld<'b>, SubWorld<'b>) + where + 'a: 'b, + { + let permissions = T::requires_permissions(); + let (left, right) = self.components.split(permissions); + + ( + SubWorld { + world: self.world, + components: left, + archetypes: self.archetypes, + }, + SubWorld { + world: self.world, + components: right, + archetypes: self.archetypes, + }, + ) + } + + /// Splits the world into two. The left world allows access only to the data declared by the query's view; + /// the right world allows access to all else. + pub fn split_for_query<'q, V: for<'v> View<'v>, F: EntityFilter>( + &mut self, + _: &'q Query, + ) -> (SubWorld, SubWorld) { + self.split::() + } + + fn validate_archetype_access(&self, entity: Entity) -> bool { + if let Some(archetypes) = self.archetypes { + if let Some(location) = (*self.world).get_entity_location(entity) { + return (*archetypes).contains(*location.archetype()); + } + } + + true + } + + fn validate_reads(&self, entity: Entity) { + let valid = match &self.components { + ComponentAccess::All => true, + ComponentAccess::Allow(restrictions) => { + restrictions.reads().contains(&ComponentTypeId::of::()) + } + ComponentAccess::Disallow(restrictions) => { + !restrictions.reads().contains(&ComponentTypeId::of::()) + } + }; + + if !valid || !self.validate_archetype_access(entity) { + panic!("Attempted to read a component that this system does not have declared access to. \ + Consider adding a query which contains `{}` and this entity in its result set to the system, \ + or use `SystemBuilder::read_component` to declare global access.", + std::any::type_name::()); + } + } + + fn validate_reads_by_id(&self, entity: Entity, component: ComponentTypeId) { + let valid = match &self.components { + ComponentAccess::All => true, + ComponentAccess::Allow(restrictions) => restrictions.reads().contains(&component), + ComponentAccess::Disallow(restrictions) => !restrictions.reads().contains(&component), + }; + + if !valid || !self.validate_archetype_access(entity) { + panic!("Attempted to read a component that this system does not have declared access to. \ + Consider adding a query which contains the component and this entity in its result set to the system, \ + or use `SystemBuilder::read_component` to declare global access."); + } + } + + fn validate_writes(&self, entity: Entity) { + let valid = match &self.components { + ComponentAccess::All => true, + ComponentAccess::Allow(restrictions) => { + restrictions.writes().contains(&ComponentTypeId::of::()) + } + ComponentAccess::Disallow(restrictions) => { + !restrictions.writes().contains(&ComponentTypeId::of::()) + } + }; + + if !valid || !self.validate_archetype_access(entity) { + panic!("Attempted to write to a component that this system does not have declared access to. \ + Consider adding a query which contains `{}` and this entity in its result set to the system, \ + or use `SystemBuilder::write_component` to declare global access.", + std::any::type_name::()); + } + } +} + +impl<'a> EntityStore for SubWorld<'a> { + #[inline] + fn has_component(&self, entity: Entity) -> bool { + self.validate_reads::(entity); + self.world.has_component::(entity) + } + + #[inline] + fn has_component_by_id(&self, entity: Entity, component: ComponentTypeId) -> bool { + self.validate_reads_by_id(entity, component); + self.world.has_component_by_id(entity, component) + } + + #[inline] + fn get_component(&self, entity: Entity) -> Option> { + self.validate_reads::(entity); + self.world.get_component::(entity) + } + + #[inline] + unsafe fn get_component_mut_unchecked( + &self, + entity: Entity, + ) -> Option> { + self.validate_writes::(entity); + self.world.get_component_mut_unchecked::(entity) + } + + #[inline] + fn get_tag(&self, entity: Entity) -> Option<&T> { self.world.get_tag(entity) } + + #[inline] + fn is_alive(&self, entity: Entity) -> bool { self.world.is_alive(entity) } + + fn get_component_storage View<'b>>( + &self, + ) -> Result { + if V::validate_access(&self.components) { + Ok(StorageAccessor { + storage: self.world.storage(), + archetypes: self.archetypes, + }) + } else { + Err(ComponentAccessError) + } + } +} + +impl<'a> From<&'a mut World> for SubWorld<'a> { + fn from(world: &'a mut World) -> Self { + Self { + world, + components: ComponentAccess::All, + archetypes: None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn writeread_left_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (left, _) = world.split::>(); + assert!(left.get_component::(entity).is_some()); + } + + #[test] + #[should_panic] + fn writeread_left_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (left, _) = world.split::>(); + let _ = left.get_component::(entity); + } + + #[test] + fn writeread_right_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, right) = world.split::>(); + assert!(right.get_component::(entity).is_some()); + } + + #[test] + #[should_panic] + fn writeread_right_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, right) = world.split::>(); + let _ = right.get_component::(entity); + } + + // -------- + + #[test] + fn readread_left_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (left, _) = world.split::>(); + assert!(left.get_component::(entity).is_some()); + } + + #[test] + #[should_panic] + fn readread_left_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (left, _) = world.split::>(); + let _ = left.get_component::(entity); + } + + #[test] + fn readread_right_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, right) = world.split::>(); + assert!(right.get_component::(entity).is_some()); + } + + #[test] + fn readread_right_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, right) = world.split::>(); + assert!(right.get_component::(entity).is_some()); + } + + // -------- + + #[test] + fn writewrite_left_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (mut left, _) = world.split::>(); + assert!(left.get_component_mut::(entity).is_some()); + } + + #[test] + #[should_panic] + fn writewrite_left_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (mut left, _) = world.split::>(); + let _ = left.get_component_mut::(entity); + } + + #[test] + fn writewrite_right_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, mut right) = world.split::>(); + assert!(right.get_component_mut::(entity).is_some()); + } + + #[test] + #[should_panic] + fn writewrite_right_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, mut right) = world.split::>(); + let _ = right.get_component_mut::(entity); + } + + // -------- + + #[test] + #[should_panic] + fn readwrite_left_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (mut left, _) = world.split::>(); + let _ = left.get_component_mut::(entity); + } + + #[test] + #[should_panic] + fn readwrite_left_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (mut left, _) = world.split::>(); + let _ = left.get_component_mut::(entity); + } + + #[test] + fn readwrite_right_included() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, mut right) = world.split::>(); + assert!(right.get_component_mut::(entity).is_some()); + } + + #[test] + #[should_panic] + fn readwrite_right_excluded() { + let mut world = World::new(); + let entity = world.insert((), vec![(1usize, false)])[0]; + + let (_, mut right) = world.split::>(); + let _ = right.get_component_mut::(entity); + } +} diff --git a/crates/bevy_legion/legion_core/src/world.rs b/crates/bevy_legion/legion_core/src/world.rs index afe5f685af..825bdacac9 100644 --- a/crates/bevy_legion/legion_core/src/world.rs +++ b/crates/bevy_legion/legion_core/src/world.rs @@ -25,7 +25,12 @@ use crate::storage::Tag; use crate::storage::TagMeta; use crate::storage::TagTypeId; use crate::storage::Tags; -use crate::tuple::TupleEq; +use crate::{ + query::Query, + query::View, + subworld::{ComponentAccess, ComponentAccessError, StorageAccessor, SubWorld}, + tuple::TupleEq, +}; use parking_lot::Mutex; use std::cell::UnsafeCell; use std::collections::HashMap; @@ -83,6 +88,73 @@ impl Universe { } } +/// A queryable collection of entities. +pub trait EntityStore { + /// Checks that the provided `Component` is present on a given entity. + /// + /// Returns true if it exists, otherwise false. + fn has_component(&self, entity: Entity) -> bool; + + /// Checks that the provided `ComponentTypeId` is present on a given entity. + /// + /// Returns true if it exists, otherwise false. + fn has_component_by_id(&self, entity: Entity, component: ComponentTypeId) -> bool; + + /// Borrows component data for the given entity. + /// + /// Returns `Some(data)` if the entity was found and contains the specified data. + /// Otherwise `None` is returned. + /// + /// # Panics + /// + /// This function may panic if the component was not declared as read by this system. + fn get_component(&self, entity: Entity) -> Option>; + + /// Borrows component data for the given entity. Does not perform static borrow checking. + /// + /// Returns `Some(data)` if the entity was found and contains the specified data. + /// Otherwise `None` is returned. + /// + /// # Safety + /// + /// Accessing a component which is already being concurrently accessed elsewhere is undefined behavior. + /// + /// # Panics + /// + /// This function may panic if any other code is currently borrowing `T` mutable or if the component was not declared + /// as written by this system. + unsafe fn get_component_mut_unchecked(&self, entity: Entity) + -> Option>; + + /// Mutably borrows entity data for the given entity. + /// + /// Returns `Some(data)` if the entity was found and contains the specified data. + /// Otherwise `None` is returned. + /// + /// # Panics + /// + /// This function may panic if the component was not declared as written by this system. + #[inline] + fn get_component_mut(&mut self, entity: Entity) -> Option> { + // safe because the &mut self ensures exclusivity + unsafe { self.get_component_mut_unchecked(entity) } + } + + /// Gets tag data for the given entity. + /// + /// Returns `Some(data)` if the entity was found and contains the specified data. + /// Otherwise `None` is returned. + fn get_tag(&self, entity: Entity) -> Option<&T>; + + /// Determines if the given `Entity` is alive within this `World`. + fn is_alive(&self, entity: Entity) -> bool; + + /// Gets the entity component storage. Validates that the world can provide access to everything needed by the view. + fn get_component_storage View<'a>>( + &self, + ) -> Result; +} + #[derive(Default, Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct WorldId(usize, usize); @@ -118,7 +190,12 @@ impl World { /// /// `Entity` IDs in such a world will only be unique within that world. See also /// `Universe::create_world`. - pub fn new() -> Self { Self::new_in_universe(WorldId::next(0), GuidEntityAllocator::default()) } + pub fn new() -> Self { + Self::new_in_universe( + WorldId::next(0), + GuidEntityAllocator::default() + ) + } fn new_in_universe(id: WorldId, allocator: GuidEntityAllocator) -> Self { Self { @@ -618,115 +695,11 @@ impl World { Ok(()) } - /// Borrows component data for the given entity. - /// - /// Returns `Some(data)` if the entity was found and contains the specified data. - /// Otherwise `None` is returned. - pub fn get_component(&self, entity: Entity) -> Option> { - if !self.is_alive(entity) { - return None; - } - - let location = self.entity_locations.get(entity)?; - let chunk = self.storage().chunk(location)?; - let (slice_borrow, slice) = unsafe { - chunk - .components(ComponentTypeId::of::())? - .data_slice::() - .deconstruct() - }; - let component = slice.get(*location.component())?; - - Some(Ref::new(slice_borrow, component)) - } - fn get_component_storage(&self, entity: Entity) -> Option<&ComponentStorage> { let location = self.entity_locations.get(entity)?; self.storage().chunk(location) } - /// Checks that the provided `ComponentTypeId` is present on a given entity. - /// - /// Returns true if it exists, otherwise false. - pub fn has_component_by_id(&self, entity: Entity, component: ComponentTypeId) -> bool { - if !self.is_alive(entity) { - return false; - } - - if let Some(chunkset) = self.get_component_storage(entity) { - return chunkset.components(component).is_some(); - } - - false - } - - /// Checks that the provided `Component` is present on a given entity. - /// - /// Returns true if it exists, otherwise false. - #[inline] - pub fn has_component(&self, entity: Entity) -> bool { - self.has_component_by_id(entity, ComponentTypeId::of::()) - } - - /// Mutably borrows entity data for the given entity. - /// - /// Returns `Some(data)` if the entity was found and contains the specified data. - /// Otherwise `None` is returned. - /// - /// # Safety - /// - /// Accessing a component which is already being concurrently accessed elsewhere is undefined behavior. - /// - /// # Panics - /// - /// This function may panic if any other code is currently borrowing `T` (such as in a query). - pub unsafe fn get_component_mut_unchecked( - &self, - entity: Entity, - ) -> Option> { - if !self.is_alive(entity) { - return None; - } - - let location = self.entity_locations.get(entity)?; - let chunk = self.storage().chunk(location)?; - let (slice_borrow, slice) = chunk - .components(ComponentTypeId::of::())? - .data_slice_mut::() - .deconstruct(); - let component = slice.get_mut(*location.component())?; - - Some(RefMut::new(slice_borrow, component)) - } - - /// Mutably borrows entity data for the given entity. - /// - /// Returns `Some(data)` if the entity was found and contains the specified data. - /// Otherwise `None` is returned. - pub fn get_component_mut(&mut self, entity: Entity) -> Option> { - // safe because the &mut self ensures exclusivity - unsafe { self.get_component_mut_unchecked(entity) } - } - - /// Gets tag data for the given entity. - /// - /// Returns `Some(data)` if the entity was found and contains the specified data. - /// Otherwise `None` is returned. - pub fn get_tag(&self, entity: Entity) -> Option<&T> { - if !self.is_alive(entity) { - return None; - } - - let location = self.entity_locations.get(entity)?; - let archetype = self.storage().archetype(location.archetype())?; - let tags = archetype.tags().get(TagTypeId::of::())?; - - unsafe { tags.data_slice::().get(*location.set()) } - } - - /// Determines if the given `Entity` is alive within this `World`. - pub fn is_alive(&self, entity: Entity) -> bool { self.entity_allocator.is_alive(entity) } - /// Returns the entity's component types, if the entity exists. pub fn entity_component_types( &self, @@ -1113,6 +1086,116 @@ impl World { self.create_chunk_set(archetype, tags) } } + + /// Splits the world into two. The left world allows access only to the data declared by the view; + /// the right world allows access to all else. + pub fn split View<'v>>(&mut self) -> (SubWorld, SubWorld) { + let permissions = T::requires_permissions(); + let (left, right) = ComponentAccess::All.split(permissions); + + ( + SubWorld { + world: self, + components: left, + archetypes: None, + }, + SubWorld { + world: self, + components: right, + archetypes: None, + }, + ) + } + + /// Splits the world into two. The left world allows access only to the data declared by the query's view; + /// the right world allows access to all else. + pub fn split_for_query<'q, V: for<'v> View<'v>, F: EntityFilter>( + &mut self, + _: &'q Query, + ) -> (SubWorld, SubWorld) { + self.split::() + } +} + +impl EntityStore for World { + #[inline] + fn has_component(&self, entity: Entity) -> bool { + self.has_component_by_id(entity, ComponentTypeId::of::()) + } + + fn has_component_by_id(&self, entity: Entity, component: ComponentTypeId) -> bool { + if !self.is_alive(entity) { + return false; + } + + if let Some(chunkset) = self.get_component_storage(entity) { + return chunkset.components(component).is_some(); + } + + false + } + + #[inline] + fn get_component(&self, entity: Entity) -> Option> { + if !self.is_alive(entity) { + return None; + } + + let location = self.entity_locations.get(entity)?; + let chunk = self.storage().chunk(location)?; + let (slice_borrow, slice) = unsafe { + chunk + .components(ComponentTypeId::of::())? + .data_slice::() + .deconstruct() + }; + let component = slice.get(*location.component())?; + + Some(Ref::new(slice_borrow, component)) + } + + #[inline] + unsafe fn get_component_mut_unchecked( + &self, + entity: Entity, + ) -> Option> { + if !self.is_alive(entity) { + return None; + } + + let location = self.entity_locations.get(entity)?; + let chunk = self.storage().chunk(location)?; + let (slice_borrow, slice) = chunk + .components(ComponentTypeId::of::())? + .data_slice_mut::() + .deconstruct(); + let component = slice.get_mut(*location.component())?; + + Some(RefMut::new(slice_borrow, component)) + } + + #[inline] + fn get_tag(&self, entity: Entity) -> Option<&T> { + if !self.is_alive(entity) { + return None; + } + + let location = self.entity_locations.get(entity)?; + let archetype = self.storage().archetype(location.archetype())?; + let tags = archetype.tags().get(TagTypeId::of::())?; + + unsafe { tags.data_slice::().get(*location.set()) } + } + + #[inline] + fn is_alive(&self, entity: Entity) -> bool { self.entity_allocator.is_alive(entity) } + + #[inline] + fn get_component_storage View<'b>>( + &self, + ) -> Result { + Ok(StorageAccessor::new(self.storage(), None)) + } } impl Default for World { diff --git a/crates/bevy_legion/legion_fn_system_macro/src/lib.rs b/crates/bevy_legion/legion_fn_system_macro/src/lib.rs index dfde2ef4b5..b5c0effafa 100644 --- a/crates/bevy_legion/legion_fn_system_macro/src/lib.rs +++ b/crates/bevy_legion/legion_fn_system_macro/src/lib.rs @@ -44,44 +44,27 @@ pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream { let resource_tuple = tuple(resource); let resource_var_tuple = tuple(resource_var); - let resource_access = if resource_count == 0 { - quote! { Access::default() } + let resource_permissions = if resource_count == 0 { + quote! { Permissions::new() } } else { - quote! {{ - let mut resource_access: Access = Access::default(); - resource_access - .reads - .extend(<#resource_tuple as ResourceSet>::read_types().iter()); - resource_access - .writes - .extend(<#resource_tuple as ResourceSet>::write_types().iter()); - resource_access - }} + quote! { + <#resource_tuple as ResourceSet>::requires_permissions() + } }; for query_count in 0..=max_queries { let view = &views[0..query_count]; let query_var = &query_vars[0..query_count]; - let view_tuple = tuple(view); let query_var_tuple = tuple(query_var); let subworld = &subworld[0..query_count.min(1)]; let subworld_var = &subworld_var[0..query_count.min(1)]; - let component_access = if query_count == 0 { - quote! { Access::default() } - } else { - quote! {{ - let mut component_access: Access = Access::default(); - component_access - .reads - .extend(<#view_tuple as View>::read_types().iter()); - component_access - .writes - .extend(<#view_tuple as View>::write_types().iter()); - component_access - }} - }; + let component_permissions = quote! {{ + let mut permissions = Permissions::new(); + #(permissions.add(#view::requires_permissions());)* + permissions + }}; for command_buffer_index in 0..2 { let command_buffer = &command_buffer[0..command_buffer_index]; @@ -93,18 +76,17 @@ pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream { #(#view: for<'b> View<'b> + DefaultFilter + ViewElement),* > IntoSystem<(#(#command_buffer)*), (#(#resource,)*), (#(#view,)*)> for Func where - Func: FnMut(#(#resource,)*#(&mut #command_buffer,)* #(&mut #subworld,)* #(&mut SystemQuery<#view, <#view as DefaultFilter>::Filter>),*) + Send + Sync + 'static, + Func: FnMut(#(#resource,)* #(&mut #command_buffer,)* #(&mut #subworld,)* #(&mut Query<#view, <#view as DefaultFilter>::Filter>),*) + Send + Sync + 'static, #(<#view as DefaultFilter>::Filter: Sync),* { fn system_id(mut self, id: SystemId) -> Box { - let resource_access: Access = #resource_access; - let component_access: Access = #component_access; - + let resource_permissions: Permissions = #resource_permissions; + let component_permissions: Permissions = #component_permissions; let run_fn = FuncSystemFnWrapper( move |_command_buffer, _world, _resources: #resource_tuple, - _queries: &mut (#(SystemQuery<#view, <#view as DefaultFilter>::Filter>),*) + _queries: &mut (#(Query<#view, <#view as DefaultFilter>::Filter>),*) | { let #resource_var_tuple = _resources; let #query_var_tuple = _queries; @@ -117,11 +99,13 @@ pub fn impl_fn_query_systems(_input: TokenStream) -> TokenStream { name: id, queries: AtomicRefCell::new((#(<#view>::query()),*)), access: SystemAccess { - resources: resource_access, - components: component_access, - tags: Access::default(), + resources: resource_permissions, + components: component_permissions, + tags: Permissions::default(), }, - archetypes: ArchetypeAccess::Some(BitSet::default()), + // TODO: by setting to ALL, we're missing out on legion's ability to parallelize archetypes + // archetypes: ArchetypeAccess::Some(BitSet::default()), + archetypes: ArchetypeAccess::All, _resources: PhantomData::<#resource_tuple>, command_buffer: FxHashMap::default(), run_fn: AtomicRefCell::new(run_fn), diff --git a/crates/bevy_legion/legion_systems/Cargo.toml b/crates/bevy_legion/legion_systems/Cargo.toml index 363e05c3d7..14e09f6355 100644 --- a/crates/bevy_legion/legion_systems/Cargo.toml +++ b/crates/bevy_legion/legion_systems/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "legion-systems" -version = "0.2.1" +version = "0.2.4" description = "High performance entity component system (ECS) library" authors = ["Thomas Gillen "] repository = "https://github.com/TomGillen/legion" @@ -19,7 +19,7 @@ par-schedule = ["rayon", "crossbeam-queue"] more-system-fns = [] [dependencies] -legion-core = { path = "../legion_core", version = "0.2.1", default-features = false } +legion-core = { path = "../legion_core", version = "0.2.4", default-features = false } legion_fn_system_macro = { path = "../legion_fn_system_macro" } downcast-rs = "1.0" @@ -32,7 +32,6 @@ bit-set = "0.5" paste = "0.1" tracing = "0.1" fxhash = "0.2" -uuid = "0.8" [dev-dependencies] tracing-subscriber = "0.2" \ No newline at end of file diff --git a/crates/bevy_legion/legion_systems/readme.md b/crates/bevy_legion/legion_systems/readme.md new file mode 100644 index 0000000000..3240c0b38c --- /dev/null +++ b/crates/bevy_legion/legion_systems/readme.md @@ -0,0 +1,53 @@ +# Legion Systems + +This crate provides systems and a scheduler for the [legion](https://crates.io/crates/legion) ECS library. + +Systems represent a unit of program logic which creates, deletes and manipulates the entity in a `World`. Systems can declare queries which define how they access entity data, and can declare access to shared `resources`. Systems can be compiled into a `Schedule`, which is executed each frame. + +# Defining Systems + +Systems are constructed via the `SystemBuilder`. The function body of the system is given as a closure. + +By default, a system is provided with a command buffer and a sub-world. The sub-world allows access to entity data, but entities cannot be immediately created or deleted. Instead, entity creation command can be queued into the command buffer. Command buffers are flushed at the end of the schedule by default. + +``` +let system = SystemBuilder::new("my system") + .build(|command_buffer, world, _, _| { + // create a new entity + command_buffer.insert(Tag, vec![(Pos, Vel)]); + }); +``` + +Queries can be added to a system to allow the system to access entity data, and shared data can be accessed via resources: + +``` +let system = SystemBuilder::new("my system") + .with_query(<(Read, Write)>::query()) + .reads_resource::, world: &mut SubWorld, - query: &mut Query<(Read, Write)>, - query2: &mut Query>, + query: &mut SimpleQuery<(Read, Write)>, + query2: &mut SimpleQuery>, ) { println!("{:?}", *a); for (x, mut y) in query.iter_mut(world) { diff --git a/crates/bevy_legion/legion_systems/src/system_fn_types.rs b/crates/bevy_legion/legion_systems/src/system_fn_types.rs index b2f77c9f60..9f95ebc5a9 100644 --- a/crates/bevy_legion/legion_systems/src/system_fn_types.rs +++ b/crates/bevy_legion/legion_systems/src/system_fn_types.rs @@ -1,14 +1,14 @@ use crate::{ resource::{self, PreparedRead, PreparedWrite, ResourceSet, ResourceTypeId, Resources}, - schedule::{ArchetypeAccess, Runnable}, - QuerySet, SubWorld, SystemAccess, SystemId, + schedule::Runnable, + QuerySet, SystemAccess, SystemId, }; use fxhash::FxHashMap; use legion_core::{ borrow::{AtomicRefCell, RefMut}, command::CommandBuffer, storage::ComponentTypeId, - world::{World, WorldId}, + world::{World, WorldId}, permission::Permissions, subworld::{SubWorld, ArchetypeAccess}, }; use std::{ hash::{Hash, Hasher}, @@ -100,8 +100,11 @@ impl<'a, T: resource::Resource> ResourceSet for Res<'a, T> { .unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::())); Res::new(resource.deref() as *const T) } - fn read_types() -> Vec { vec![ResourceTypeId::of::()] } - fn write_types() -> Vec { Vec::new() } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push_read(ResourceTypeId::of::()); + permissions + } } #[derive(Debug)] @@ -194,8 +197,11 @@ impl<'a, T: resource::Resource> ResourceSet for ResMut<'a, T> { .unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::())); ResMut::new(resource.deref_mut() as *mut T) } - fn read_types() -> Vec { vec![ResourceTypeId::of::()] } - fn write_types() -> Vec { Vec::new() } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push(ResourceTypeId::of::()); + permissions + } } impl ResourceSet for PreparedRead { @@ -207,8 +213,11 @@ impl ResourceSet for PreparedRead { .unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::())); PreparedRead::new(resource.deref() as *const T) } - fn read_types() -> Vec { vec![ResourceTypeId::of::()] } - fn write_types() -> Vec { Vec::new() } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push_read(ResourceTypeId::of::()); + permissions + } } impl ResourceSet for PreparedWrite { @@ -220,8 +229,11 @@ impl ResourceSet for PreparedWrite { .unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::())); PreparedWrite::new(resource.deref_mut() as *mut T) } - fn read_types() -> Vec { Vec::new() } - fn write_types() -> Vec { vec![ResourceTypeId::of::()] } + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push(ResourceTypeId::of::()); + permissions + } } /// The concrete type which contains the system closure provided by the user. This struct should @@ -240,7 +252,7 @@ where Q: QuerySet, F: FuncSystemFn< Resources = ::PreparedResources, - Queries = ::Queries, + Queries = Q, >, { pub name: SystemId, @@ -263,18 +275,21 @@ where Q: QuerySet, F: FuncSystemFn< Resources = ::PreparedResources, - Queries = ::Queries, + Queries = Q, >, { fn name(&self) -> &SystemId { &self.name } fn reads(&self) -> (&[ResourceTypeId], &[ComponentTypeId]) { - (&self.access.resources.reads, &self.access.components.reads) + ( + self.access.resources.reads(), + self.access.components.reads(), + ) } fn writes(&self) -> (&[ResourceTypeId], &[ComponentTypeId]) { ( - &self.access.resources.writes, - &self.access.components.writes, + self.access.resources.writes(), + self.access.components.writes(), ) } @@ -297,20 +312,22 @@ where debug!("Initializing"); let resources = R::fetch_unchecked(resources); let mut queries = self.queries.get_mut(); - let mut prepared_queries = queries.prepare(); - let mut world_shim = SubWorld::new(world, &self.access.components, &self.archetypes); + //let mut prepared_queries = queries.prepare(); + let mut world_shim = + SubWorld::new_unchecked(world, &self.access.components, &self.archetypes); let cmd = self .command_buffer .entry(world.id()) .or_insert_with(|| AtomicRefCell::new(CommandBuffer::new(world))); - info!("Running"); + info!(permissions = ?self.access, archetypes = ?self.archetypes, "Running"); let mut borrow = self.run_fn.get_mut(); borrow.deref_mut().run( &mut cmd.get_mut(), &mut world_shim, resources, - &mut prepared_queries, + //&mut prepared_queries, + queries.deref_mut(), ); } } diff --git a/crates/bevy_legion/tests/query_api.rs b/crates/bevy_legion/tests/query_api.rs index 51d70cd9f6..0b7e5ba41e 100644 --- a/crates/bevy_legion/tests/query_api.rs +++ b/crates/bevy_legion/tests/query_api.rs @@ -606,3 +606,27 @@ fn query_iter_tag() { assert_eq!(&Model(*c), m); } } + +#[test] +fn query_get_all_components() { + let _ = tracing_subscriber::fmt::try_init(); + + let universe = Universe::new(); + let mut world = universe.create_world(); + + world.insert( + (Static, Model(0)), + vec![(Pos(1., 2., 3.), Rot(0.1, 0.2, 0.3))], + ); + world.insert((Static, Model(1)), vec![(Pos(1., 2., 3.),)]); + + let query_pos = Read::::query(); + let query_rot = Read::::query(); + assert_eq!(2, query_pos.components::(&world).len()); + assert_eq!(1, query_rot.components::(&world).len()); + + let query_pos = Write::::query(); + let query_rot = Write::::query(); + assert_eq!(2, query_pos.components_mut::(&mut world).len()); + assert_eq!(1, query_rot.components_mut::(&mut world).len()); +} diff --git a/crates/bevy_legion/tests/world_api.rs b/crates/bevy_legion/tests/world_api.rs index 6011202ec3..38926fd8d3 100644 --- a/crates/bevy_legion/tests/world_api.rs +++ b/crates/bevy_legion/tests/world_api.rs @@ -1,4 +1,6 @@ use legion::prelude::*; +use legion::storage::ComponentTypeId; +use legion::storage::TagTypeId; use std::collections::HashSet; #[derive(Clone, Copy, Debug, PartialEq)] @@ -247,39 +249,40 @@ fn delete_first() { } #[test] -// fn merge() { -// let _ = tracing_subscriber::fmt::try_init(); +fn move_from() { + let _ = tracing_subscriber::fmt::try_init(); -// let universe = Universe::new(); -// let mut world_1 = universe.create_world(); -// let mut world_2 = universe.create_world(); + let universe = Universe::new(); + let mut world_1 = universe.create_world(); + let mut world_2 = universe.create_world(); -// let shared = (Static, Model(5)); -// let components = vec![ -// (Pos(1., 2., 3.), Rot(0.1, 0.2, 0.3)), -// (Pos(4., 5., 6.), Rot(0.4, 0.5, 0.6)), -// ]; + let shared = (Static, Model(5)); + let components = vec![ + (Pos(1., 2., 3.), Rot(0.1, 0.2, 0.3)), + (Pos(4., 5., 6.), Rot(0.4, 0.5, 0.6)), + ]; -// let mut world_1_entities: Vec = Vec::new(); -// for e in world_1.insert(shared, components.clone()) { -// world_1_entities.push(*e); -// } + let mut world_1_entities: Vec = Vec::new(); + for e in world_1.insert(shared, components.clone()) { + world_1_entities.push(*e); + } -// let mut world_2_entities: Vec = Vec::new(); -// for e in world_2.insert(shared, components.clone()) { -// world_2_entities.push(*e); -// } + let mut world_2_entities: Vec = Vec::new(); + for e in world_2.insert(shared, components.clone()) { + world_2_entities.push(*e); + } -// world_1.merge(world_2); + world_1.move_from(world_2); -// for (i, e) in world_2_entities.iter().enumerate() { -// assert!(world_1.is_alive(*e)); + for (i, e) in world_2_entities.iter().enumerate() { + assert!(world_1.is_alive(*e)); + + let (pos, rot) = components.get(i).unwrap(); + assert_eq!(pos, &world_1.get_component(*e).unwrap() as &Pos); + assert_eq!(rot, &world_1.get_component(*e).unwrap() as &Rot); + } +} -// let (pos, rot) = components.get(i).unwrap(); -// assert_eq!(pos, &world_1.get_component(*e).unwrap() as &Pos); -// assert_eq!(rot, &world_1.get_component(*e).unwrap() as &Rot); -// } -// } #[test] fn mutate_add_component() { let _ = tracing_subscriber::fmt::try_init(); @@ -547,3 +550,42 @@ fn iter_entities() { // Verify that no extra entities are included assert!(entities.is_empty()); } + +// Test that World::entity_component_types returns the correct ComponentTypeId +#[test] +fn entity_component_types() { + let _ = tracing_subscriber::fmt::try_init(); + + let universe = Universe::new(); + let mut world = universe.create_world(); + + let shared = (Model(5),); + let components = vec![(Pos(1., 2., 3.),)]; + + let entity = world.insert(shared, components)[0]; + + let component_types = world.entity_component_types(entity).unwrap(); + let component_type_ids: Vec = + component_types.iter().map(|(id, _)| *id).collect(); + assert_eq!(1, component_type_ids.len()); + assert!(component_type_ids.contains(&ComponentTypeId::of::())); +} + +// Test that World::entity_tag_types returns the correct TagTypeId +#[test] +fn entity_tag_types() { + let _ = tracing_subscriber::fmt::try_init(); + + let universe = Universe::new(); + let mut world = universe.create_world(); + + let shared = (Model(5),); + let components = vec![(Pos(1., 2., 3.),)]; + + let entity = world.insert(shared, components)[0]; + + let tag_types = world.entity_tag_types(entity).unwrap(); + let tag_type_ids: Vec = tag_types.iter().map(|(id, _)| *id).collect(); + assert_eq!(1, tag_type_ids.len()); + assert!(tag_type_ids.contains(&TagTypeId::of::())); +} diff --git a/crates/bevy_render/src/camera/active_cameras.rs b/crates/bevy_render/src/camera/active_cameras.rs index 06e03a6b9a..479421a4ce 100644 --- a/crates/bevy_render/src/camera/active_cameras.rs +++ b/crates/bevy_render/src/camera/active_cameras.rs @@ -1,9 +1,5 @@ use crate::Camera; -use legion::{ - entity::Entity, - prelude::Read, - systems::{Query, ResMut, SubWorld}, -}; +use legion::prelude::*; use std::collections::HashMap; #[derive(Default)] diff --git a/crates/bevy_render/src/camera/visible_entities.rs b/crates/bevy_render/src/camera/visible_entities.rs index 09779d8700..3233f45b93 100644 --- a/crates/bevy_render/src/camera/visible_entities.rs +++ b/crates/bevy_render/src/camera/visible_entities.rs @@ -1,11 +1,7 @@ use crate::{draw::Draw, Camera}; use bevy_core::float_ord::FloatOrd; use bevy_transform::prelude::Transform; -use legion::{ - entity::Entity, - prelude::{Read, Write}, - systems::{Query, SubWorld}, -}; +use legion::prelude::*; #[derive(Debug)] pub struct VisibleEntity { @@ -26,17 +22,20 @@ impl VisibleEntities { pub fn visible_entities_system( world: &mut SubWorld, - camera_query: &mut Query<(Read, Read, Write)>, + camera_query: &mut Query<(Read, Write)>, entities_query: &mut Query>, + _transform_query: &mut Query>, _transform_entities_query: &mut Query<(Read, Read)>, // ensures we can optionally access Transforms ) { - for (_camera, camera_transform, mut visible_entities) in camera_query.iter_mut(world) { + let (mut camera_world, world) = world.split_for_query(camera_query); + for (camera_entity, (_camera, mut visible_entities)) in camera_query.iter_entities_mut(&mut camera_world) { visible_entities.value.clear(); + let camera_transform = world.get_component::(camera_entity).unwrap(); let camera_position = camera_transform.value.w_axis().truncate(); let mut no_transform_order = 0.0; let mut transparent_entities = Vec::new(); - for (entity, draw) in entities_query.iter_entities(world) { + for (entity, draw) in entities_query.iter_entities(&world) { if !draw.is_visible { continue; } diff --git a/crates/bevy_render/src/draw.rs b/crates/bevy_render/src/draw.rs index 8d8bc78945..31aef12cef 100644 --- a/crates/bevy_render/src/draw.rs +++ b/crates/bevy_render/src/draw.rs @@ -12,10 +12,7 @@ use crate::{ }; use bevy_asset::{Assets, Handle}; use bevy_property::Properties; -use legion::{ - prelude::{Res, ResourceSet, Write}, - systems::{resource::ResourceTypeId, ResMut, SubWorld, Query}, -}; +use legion::{systems::resource::ResourceTypeId, prelude::*, permission::Permissions}; use std::{ ops::{Deref, DerefMut, Range}, sync::Arc, @@ -165,20 +162,16 @@ impl<'a> ResourceSet for DrawContext<'a> { current_pipeline: None, } } - fn read_types() -> Vec { - vec![ - ResourceTypeId::of::>(), - ResourceTypeId::of::(), - ResourceTypeId::of::(), - ResourceTypeId::of::(), - ] - } - fn write_types() -> Vec { - vec![ - ResourceTypeId::of::>(), - ResourceTypeId::of::>(), - ResourceTypeId::of::(), - ] + fn requires_permissions() -> Permissions { + let mut permissions = Permissions::new(); + permissions.push_read(ResourceTypeId::of::>()); + permissions.push_read(ResourceTypeId::of::()); + permissions.push_read(ResourceTypeId::of::()); + permissions.push_read(ResourceTypeId::of::()); + permissions.push(ResourceTypeId::of::>()); + permissions.push(ResourceTypeId::of::>()); + permissions.push(ResourceTypeId::of::()); + permissions } } diff --git a/crates/bevy_render/src/pipeline/render_pipelines.rs b/crates/bevy_render/src/pipeline/render_pipelines.rs index be0f7497f5..54551eecf7 100644 --- a/crates/bevy_render/src/pipeline/render_pipelines.rs +++ b/crates/bevy_render/src/pipeline/render_pipelines.rs @@ -5,10 +5,7 @@ use crate::{ }; use bevy_asset::Handle; use bevy_property::Properties; -use legion::{ - prelude::Write, - systems::{Query, ResMut, SubWorld}, -}; +use legion::prelude::*; #[derive(Properties, Default, Clone)] pub struct RenderPipeline { pub pipeline: Handle, diff --git a/crates/bevy_render/src/shader/shader_defs.rs b/crates/bevy_render/src/shader/shader_defs.rs index e5e3c1d4ab..6cd8deb832 100644 --- a/crates/bevy_render/src/shader/shader_defs.rs +++ b/crates/bevy_render/src/shader/shader_defs.rs @@ -1,9 +1,6 @@ use crate::{texture::Texture, RenderPipelines}; use bevy_asset::{Assets, Handle}; -use legion::{ - prelude::{Read, Res, Write}, - systems::{Query, SubWorld}, -}; +use legion::prelude::*; pub use bevy_derive::ShaderDefs; diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 4d8700ba3c..11ad28af8f 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -5,7 +5,7 @@ use bevy_render::{ render_resource::{RenderResource, RenderResources}, texture::Texture, }; -pub use legion::prelude::*; +use legion::prelude::*; use glam::Vec2; #[repr(C)] diff --git a/crates/bevy_transform/src/hierarchy_maintenance_system.rs b/crates/bevy_transform/src/hierarchy_maintenance_system.rs index 5dc71b4018..21753fa519 100644 --- a/crates/bevy_transform/src/hierarchy_maintenance_system.rs +++ b/crates/bevy_transform/src/hierarchy_maintenance_system.rs @@ -25,11 +25,12 @@ pub fn build(_: &mut World) -> Vec> { .build(move |commands, world, _resource, queries| { // Entities with a missing `Parent` (ie. ones that have a `PreviousParent`), remove // them from the `Children` of the `PreviousParent`. - for (entity, previous_parent) in queries.0.iter_entities(world) { + let (mut children_world, mut world) = world.split::>(); + for (entity, previous_parent) in queries.0.iter_entities(&mut world) { log::trace!("Parent was removed from {}", entity); if let Some(previous_parent_entity) = previous_parent.0 { if let Some(mut previous_parent_children) = - world.get_component_mut::(previous_parent_entity) + children_world.get_component_mut::(previous_parent_entity) { log::trace!(" > Removing {} from it's prev parent's children", entity); previous_parent_children.0.retain(|e| *e != entity); @@ -42,7 +43,7 @@ pub fn build(_: &mut World) -> Vec> { HashMap::>::with_capacity(16); // Entities with a changed Parent (that also have a PreviousParent, even if None) - for (entity, (parent, mut previous_parent)) in queries.1.iter_entities_mut(world) { + for (entity, (parent, mut previous_parent)) in queries.1.iter_entities_mut(&mut world) { log::trace!("Parent changed for {}", entity); // If the `PreviousParent` is not None. @@ -55,7 +56,7 @@ pub fn build(_: &mut World) -> Vec> { // Remove from `PreviousParent.Children`. if let Some(mut previous_parent_children) = - world.get_component_mut::(previous_parent_entity) + children_world.get_component_mut::(previous_parent_entity) { log::trace!(" > Removing {} from prev parent's children", entity); (*previous_parent_children).0.retain(|e| *e != entity); @@ -68,7 +69,7 @@ pub fn build(_: &mut World) -> Vec> { // Add to the parent's `Children` (either the real component, or // `children_additions`). log::trace!("Adding {} to it's new parent {}", entity, parent.0); - if let Some(mut new_parent_children) = world.get_component_mut::(parent.0) + if let Some(mut new_parent_children) = children_world.get_component_mut::(parent.0) { // This is the parent log::trace!( diff --git a/crates/bevy_transform/src/transform_propagate_system.rs b/crates/bevy_transform/src/transform_propagate_system.rs index fda58cc628..10255aa1e1 100644 --- a/crates/bevy_transform/src/transform_propagate_system.rs +++ b/crates/bevy_transform/src/transform_propagate_system.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use crate::{ components::*, - ecs::{prelude::*, systems::SubWorld}, + ecs::{prelude::*, subworld::SubWorld}, }; pub fn build(_: &mut World) -> Box { @@ -22,7 +22,7 @@ pub fn build(_: &mut World) -> Box { fn propagate_recursive( parent_local_to_world: Transform, - world: &mut SubWorld, + world: &SubWorld, entity: Entity, commands: &mut CommandBuffer, ) { diff --git a/crates/bevy_ui/src/ui_update_system.rs b/crates/bevy_ui/src/ui_update_system.rs index 53e4f6ccb3..1bc03a58d8 100644 --- a/crates/bevy_ui/src/ui_update_system.rs +++ b/crates/bevy_ui/src/ui_update_system.rs @@ -3,7 +3,7 @@ use bevy_core::transform::run_on_hierarchy_subworld_mut; use bevy_transform::prelude::{Children, Parent, Translation}; use bevy_window::Windows; use glam::Vec2; -use legion::{prelude::*, systems::SubWorld}; +use legion::prelude::*; pub const UI_Z_STEP: f32 = 0.001; @@ -20,15 +20,16 @@ pub fn ui_update_system( _parent_query: &mut Query>, _children_query: &mut Query>, ) { + let (mut node_world, hierarchy_world) = world.split_for_query(node_query); let window_size = if let Some(window) = windows.get_primary() { Vec2::new(window.width as f32, window.height as f32) } else { return; }; let orphan_nodes = node_query - .iter_entities_mut(world) + .iter_entities_mut(&mut node_world) // TODO: replace this filter with a legion query filter (when SimpleQuery gets support for filters) - .filter(|(entity, _)| world.get_component::(*entity).is_none()) + .filter(|(entity, _)| hierarchy_world.get_component::(*entity).is_none()) .map(|(e, _)| e) .collect::>(); let mut window_rect = Rect { diff --git a/examples/game/breakout/main.rs b/examples/game/breakout/main.rs index 9847f2a72f..5320d1a70b 100644 --- a/examples/game/breakout/main.rs +++ b/examples/game/breakout/main.rs @@ -198,19 +198,22 @@ fn ball_collision_system( mut scoreboard: ResMut, command_buffer: &mut CommandBuffer, world: &mut SubWorld, - ball_query: &mut Query<(Write, Read, Read)>, + ball_query: &mut Query>, paddle_query: &mut Query<(Read, Read, Read)>, brick_query: &mut Query<(Read, Read, Read)>, wall_query: &mut Query<(Read, Read, Read)>, ) { - for (mut ball, translation, sprite) in ball_query.iter_mut(world) { + let (mut ball_world, world) = world.split_for_query(ball_query); + for (entity, mut ball) in ball_query.iter_entities_mut(&mut ball_world) { + let translation = world.get_component::(entity).unwrap(); + let sprite = world.get_component::(entity).unwrap(); let ball_position = translation.0; let ball_size = sprite.size; let velocity = &mut ball.velocity; let mut collision = None; // check collision with walls - for (_wall, translation, sprite) in wall_query.iter(world) { + for (_wall, translation, sprite) in wall_query.iter(&world) { if collision.is_some() { break; } @@ -219,7 +222,7 @@ fn ball_collision_system( } // check collision with paddle(s) - for (_paddle, translation, sprite) in paddle_query.iter(world) { + for (_paddle, translation, sprite) in paddle_query.iter(&world) { if collision.is_some() { break; } @@ -228,7 +231,7 @@ fn ball_collision_system( } // check collision with bricks - for (brick_entity, (_brick, translation, sprite)) in brick_query.iter_entities(world) { + for (brick_entity, (_brick, translation, sprite)) in brick_query.iter_entities(&world) { if collision.is_some() { break; } diff --git a/src/prelude.rs b/src/prelude.rs index 400e925ef2..14c22bee20 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,12 +1,12 @@ pub use crate::{ app::{ - schedule_runner::ScheduleRunnerPlugin, stage, App, AppBuilder, AppPlugin, DynamicAppPlugin, - ComponentSet, EventReader, Events, FromResources, System, + schedule_runner::ScheduleRunnerPlugin, stage, App, AppBuilder, AppPlugin, ComponentSet, + DynamicAppPlugin, EventReader, Events, FromResources, System, }, asset::{AddAsset, AssetEvent, AssetServer, Assets, Handle}, core::{ time::{Time, Timer}, - transform::{CommandBufferBuilderSource, WorldBuilder, WorldBuilderSource, FaceToward}, + transform::{CommandBufferBuilderSource, FaceToward, WorldBuilder, WorldBuilderSource}, }, diagnostic::DiagnosticsPlugin, input::{keyboard::KeyCode, mouse::MouseButton, Input}, @@ -29,7 +29,7 @@ pub use crate::{ render_resource::RenderResources, shader::{Shader, ShaderDefs, ShaderStage, ShaderStages}, texture::Texture, - Camera, Color, ColorSource, OrthographicProjection, PerspectiveProjection, VisibleEntities + Camera, Color, ColorSource, OrthographicProjection, PerspectiveProjection, VisibleEntities, }, scene::{Scene, SceneSpawner}, sprite::{ @@ -44,17 +44,10 @@ pub use crate::{ AddDefaultPlugins, }; pub use legion::{ - borrow::{Ref as Com, RefMut as ComMut}, - command::CommandBuffer, - entity::Entity, - event::Event as LegionEvent, filter::filter_fns::*, - query::{IntoQuery, Read, Tagged, TryRead, TryWrite, Write}, - systems::{ - bit_set::BitSet, - resource::{ResourceSet, Resources}, - schedule::{Executor, Runnable, Schedulable, Schedule}, - IntoSystem, Query, Res, ResMut, SubWorld, SystemBuilder, + prelude::{ + BitSet, CommandBuffer, Entity, EntityStore, Event as LegionEvent, Executor, IntoQuery, + IntoSystem, Query, Read, Res, ResMut, ResourceSet, Resources, Runnable, Schedulable, + Schedule, SubWorld, SystemBuilder, Tagged, TryRead, TryWrite, Universe, World, Write, }, - world::{Universe, World}, };