legion: upgrade

This commit is contained in:
Carter Anderson 2020-06-23 01:11:52 -07:00
parent 1f12964026
commit 5787bcb2c5
36 changed files with 1982 additions and 1202 deletions

View file

@ -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<T>(

View file

@ -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 <thomas.gillen@googlemail.com>"]
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"

View file

@ -120,12 +120,12 @@ fn sequential(world: &mut World) {
fn parallel(world: &mut World) {
join(
|| unsafe {
for (mut b, a) in <(Write<B>, Read<A>)>::query().iter_unchecked(&world) {
for (mut b, a) in <(Write<B>, Read<A>)>::query().iter_unchecked(world) {
b.0 = a.0;
}
},
|| unsafe {
for (mut c, a) in <(Write<C>, Read<A>)>::query().iter_unchecked(&world) {
for (mut c, a) in <(Write<C>, Read<A>)>::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<B>, Read<A>)>::query().par_for_each_unchecked(&world, |(mut b, a)| {
<(Write<B>, Read<A>)>::query().par_for_each_unchecked(world, |(mut b, a)| {
b.0 = a.0;
});
},
|| unsafe {
<(Write<C>, Read<A>)>::query().par_for_each_unchecked(&world, |(mut c, a)| {
<(Write<C>, Read<A>)>::query().par_for_each_unchecked(world, |(mut c, a)| {
c.0 = a.0;
});
},

View file

@ -53,10 +53,10 @@ fn main() {
.write_resource::<ExampleResource1>()
.read_resource::<ExampleResource2>()
.with_query(<(Write<Pos>, Read<Vel>)>::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;

View file

@ -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 <thomas.gillen@googlemail.com>"]
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"

View file

@ -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.

View file

@ -711,3 +711,99 @@ impl<'a, T: 'a, I: Iterator<Item = &'a mut T> + 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<Shared<'a>>,
value: T,
}
impl<'a, T: 'a> RefMapSet<'a, T> {
#[inline(always)]
pub fn new(borrow: Vec<Shared<'a>>, value: T) -> Self { Self { borrow, value } }
#[inline(always)]
pub fn map_into<K: 'a, F: FnMut(&mut T) -> 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<Shared<'a>>, 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<T> for RefMapSet<'a, T> {
#[inline(always)]
fn as_ref(&self) -> &T { &self.value }
}
impl<'a, T: 'a> std::borrow::Borrow<T> 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<Exclusive<'a>>,
value: T,
}
impl<'a, T: 'a> RefMapMutSet<'a, T> {
#[inline(always)]
pub fn new(borrow: Vec<Exclusive<'a>>, value: T) -> Self { Self { borrow, value } }
#[inline(always)]
pub fn map_into<K: 'a, F: FnMut(&mut T) -> 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<Exclusive<'a>>, 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<T> for RefMapMutSet<'a, T> {
#[inline(always)]
fn as_ref(&self) -> &T { &self.value }
}
impl<'a, T: 'a> std::borrow::Borrow<T> for RefMapMutSet<'a, T> {
#[inline(always)]
fn borrow(&self) -> &T { &self.value }
}

View file

@ -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 }
}

View file

@ -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},
};
}

View file

@ -0,0 +1,382 @@
use smallvec::SmallVec;
use std::fmt::{Debug, Display};
#[derive(Clone)]
pub struct Permissions<T: PartialEq> {
items: SmallVec<[T; 4]>,
shared: usize, // index of first shared
write: usize, // index of first write exclusive
}
impl<T: PartialEq> Permissions<T> {
pub fn new() -> Self {
Self {
items: SmallVec::default(),
shared: 0,
write: 0,
}
}
fn find(&self, item: &T) -> Option<usize> { 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<T: PartialEq> Default for Permissions<T> {
fn default() -> Self { Self::new() }
}
impl<T: PartialEq + Debug> Debug for Permissions<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn list<V: Debug>(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::<T>(&self.reads()),
list::<T>(&self.writes())
)
}
}
impl<T: PartialEq + Display> Display for Permissions<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn list<V: Display>(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::<T>(&self.reads()),
list::<T>(&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]);
}
}

View file

@ -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<T: Component>() -> bool;
/// Determines if the view writes to the specified data type.
fn writes<T: Component>() -> bool;
/// Returns an array of the components read by this view
fn read_types() -> Vec<ComponentTypeId>;
/// Returns an array of the components written by this view
fn write_types() -> Vec<ComponentTypeId>;
/// Returns the set of permissions required by the view.
fn requires_permissions() -> Permissions<ComponentTypeId>;
}
/// A type which can construct a default entity filter.
@ -156,9 +158,15 @@ impl<'a, T: Component> View<'a> for Read<T> {
fn writes<D: Component>() -> bool { false }
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
fn validate_access(access: &ComponentAccess) -> bool {
access.allows_read(ComponentTypeId::of::<T>())
}
fn write_types() -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn requires_permissions() -> Permissions<ComponentTypeId> {
let mut permissions = Permissions::new();
permissions.push_read(ComponentTypeId::of::<T>());
permissions
}
}
impl<T: Component> ViewElement for Read<T> {
@ -209,9 +217,15 @@ impl<'a, T: Component> View<'a> for TryRead<T> {
fn writes<D: Component>() -> bool { false }
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
fn requires_permissions() -> Permissions<ComponentTypeId> {
let mut permissions = Permissions::new();
permissions.push_read(ComponentTypeId::of::<T>());
permissions
}
fn write_types() -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn validate_access(access: &ComponentAccess) -> bool {
access.allows_read(ComponentTypeId::of::<T>())
}
}
impl<T: Component> ViewElement for TryRead<T> {
@ -268,11 +282,15 @@ impl<'a, T: Component> View<'a> for Write<T> {
#[inline]
fn writes<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
#[inline]
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
fn requires_permissions() -> Permissions<ComponentTypeId> {
let mut permissions = Permissions::new();
permissions.push(ComponentTypeId::of::<T>());
permissions
}
#[inline]
fn write_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
fn validate_access(access: &ComponentAccess) -> bool {
access.allows_write(ComponentTypeId::of::<T>())
}
}
impl<T: Component> ViewElement for Write<T> {
@ -323,11 +341,15 @@ impl<'a, T: Component> View<'a> for TryWrite<T> {
#[inline]
fn writes<D: Component>() -> bool { TypeId::of::<T>() == TypeId::of::<D>() }
#[inline]
fn read_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
fn requires_permissions() -> Permissions<ComponentTypeId> {
let mut permissions = Permissions::new();
permissions.push(ComponentTypeId::of::<T>());
permissions
}
#[inline]
fn write_types() -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<T>()] }
fn validate_access(access: &ComponentAccess) -> bool {
access.allows_write(ComponentTypeId::of::<T>())
}
}
impl<T: Component> ViewElement for TryWrite<T> {
@ -386,11 +408,10 @@ impl<'a, T: Tag> View<'a> for Tagged<T> {
#[inline]
fn writes<D: Component>() -> bool { false }
#[inline]
fn read_types() -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn requires_permissions() -> Permissions<ComponentTypeId> { Permissions::new() }
#[inline]
fn write_types() -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn validate_access(_: &ComponentAccess) -> bool { true }
}
impl<T: Tag> ViewElement for Tagged<T> {
@ -449,6 +470,10 @@ macro_rules! impl_view_tuple {
true
}
fn validate_access(access: &ComponentAccess) -> bool {
$( $ty::validate_access(access) )&&*
}
fn reads<Data: Component>() -> bool {
$( $ty::reads::<Data>() )||*
}
@ -457,16 +482,10 @@ macro_rules! impl_view_tuple {
$( $ty::writes::<Data>() )||*
}
fn read_types() -> Vec<ComponentTypeId> {
let mut vec = vec![];
$( vec.extend($ty::read_types()); )*
vec
}
fn write_types() -> Vec<ComponentTypeId> {
let mut vec = vec![];
$( vec.extend($ty::write_types()); )*
vec
fn requires_permissions() -> Permissions<ComponentTypeId> {
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) -> <V as View<'a>>::Iter {
pub fn iter_mut(&mut self) -> <V as View<'a>>::Iter {
V::fetch(
self.archetype,
self.components,
@ -618,7 +637,7 @@ where
FChunk: Filter<ChunkFilterData<'data>>,
{
_view: PhantomData<V>,
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::<V>().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, <<V as View<'data>>::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, <<V as View<'data>>::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, <<V as View<'data>>::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(<<V as View<'data>>::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(<<V as View<'data>>::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(<<V as View<'data>>::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::<T>() {
panic!("data type not readable via this query");
}
let mut borrows = vec![];
let mut refs = vec![];
let storage = world.get_component_storage::<Read<T>>().unwrap().inner();
unsafe {
self.filter
.iter_archetype_indexes(storage)
.flat_map(|archetype_index| {
storage
.archetypes()
.get_unchecked(archetype_index.0)
.iter_data_slice::<T>()
})
.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::<T>() {
panic!("data type not writable via this query");
}
let mut borrows = vec![];
let mut refs = vec![];
let storage = world.get_component_storage::<Read<T>>().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::<T>()
})
.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
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'data>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'data>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'data>>>::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::<V>().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
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'data>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'data>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'data>>>::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
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'data>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'data>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'data>>>::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, <<V as View<'a>>::Iter as Iterator>::Item)) + Send + Sync,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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, <<V as View<'a>>::Iter as Iterator>::Item)) + Send + Sync,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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, <<V as View<'a>>::Iter as Iterator>::Item)) + Send + Sync,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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(<<V as View<'a>>::Iter as Iterator>::Item) + Send + Sync,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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(<<V as View<'a>>::Iter as Iterator>::Item) + Send + Sync,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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(<<V as View<'a>>::Iter as Iterator>::Item) + Send + Sync,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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,
<F::ArchetypeFilter as Filter<ArchetypeFilterData<'a>>>::Iter: FissileIterator,
<F::ChunksetFilter as Filter<ChunksetFilterData<'a>>>::Iter: FissileIterator,
<F::ChunkFilter as Filter<ChunkFilterData<'a>>>::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<V>,
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,

View file

@ -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<T: Component>() -> Self { Self(type_name::<T>()) }
}
#[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<T: Component>() -> Self { Self(type_name::<T>(), 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<T: Component>() -> Self { Self(type_name::<T>()) }
}
#[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<T: Component>() -> Self { Self(type_name::<T>(), 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<Item = RefMap<&[T]>> + 'a {
self.chunk_sets.iter().flat_map(move |set| {
set.chunks.iter().map(move |chunk| {
let c = chunk.components(ComponentTypeId::of::<T>()).unwrap();
unsafe { c.data_slice::<T>() }
})
})
}
pub(crate) unsafe fn iter_data_slice_unchecked_mut<'a, T: Component>(
&'a self,
) -> impl Iterator<Item = RefMapMut<&mut [T]>> + 'a {
self.chunk_sets.iter().flat_map(move |set| {
set.chunks.iter().map(move |chunk| {
let c = chunk.components(ComponentTypeId::of::<T>()).unwrap();
c.data_slice_mut::<T>()
})
})
}
}
fn align_up(addr: usize, align: usize) -> usize { (addr + (align - 1)) & align.wrapping_neg() }

View file

@ -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<ComponentTypeId>>),
Disallow(Cow<'a, Permissions<ComponentTypeId>>),
}
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<ComponentTypeId>) -> (Self, Self) {
fn append_incompatible(
denied: &mut Permissions<ComponentTypeId>,
to_deny: &Permissions<ComponentTypeId>,
) {
// 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<ComponentTypeId>,
) -> Permissions<ComponentTypeId> {
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<ComponentTypeId>,
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<V, F>,
) -> (SubWorld, SubWorld) {
self.split::<V>()
}
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<T: Component>(&self, entity: Entity) {
let valid = match &self.components {
ComponentAccess::All => true,
ComponentAccess::Allow(restrictions) => {
restrictions.reads().contains(&ComponentTypeId::of::<T>())
}
ComponentAccess::Disallow(restrictions) => {
!restrictions.reads().contains(&ComponentTypeId::of::<T>())
}
};
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::<T>());
}
}
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<T: Component>(&self, entity: Entity) {
let valid = match &self.components {
ComponentAccess::All => true,
ComponentAccess::Allow(restrictions) => {
restrictions.writes().contains(&ComponentTypeId::of::<T>())
}
ComponentAccess::Disallow(restrictions) => {
!restrictions.writes().contains(&ComponentTypeId::of::<T>())
}
};
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::<T>());
}
}
}
impl<'a> EntityStore for SubWorld<'a> {
#[inline]
fn has_component<T: Component>(&self, entity: Entity) -> bool {
self.validate_reads::<T>(entity);
self.world.has_component::<T>(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<T: Component>(&self, entity: Entity) -> Option<Ref<T>> {
self.validate_reads::<T>(entity);
self.world.get_component::<T>(entity)
}
#[inline]
unsafe fn get_component_mut_unchecked<T: Component>(
&self,
entity: Entity,
) -> Option<RefMut<T>> {
self.validate_writes::<T>(entity);
self.world.get_component_mut_unchecked::<T>(entity)
}
#[inline]
fn get_tag<T: 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<V: for<'b> View<'b>>(
&self,
) -> Result<StorageAccessor, ComponentAccessError> {
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::<Write<usize>>();
assert!(left.get_component::<usize>(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::<Write<usize>>();
let _ = left.get_component::<bool>(entity);
}
#[test]
fn writeread_right_included() {
let mut world = World::new();
let entity = world.insert((), vec![(1usize, false)])[0];
let (_, right) = world.split::<Write<usize>>();
assert!(right.get_component::<bool>(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::<Write<usize>>();
let _ = right.get_component::<usize>(entity);
}
// --------
#[test]
fn readread_left_included() {
let mut world = World::new();
let entity = world.insert((), vec![(1usize, false)])[0];
let (left, _) = world.split::<Read<usize>>();
assert!(left.get_component::<usize>(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::<Read<usize>>();
let _ = left.get_component::<bool>(entity);
}
#[test]
fn readread_right_included() {
let mut world = World::new();
let entity = world.insert((), vec![(1usize, false)])[0];
let (_, right) = world.split::<Read<usize>>();
assert!(right.get_component::<bool>(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::<Read<usize>>();
assert!(right.get_component::<usize>(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::<Write<usize>>();
assert!(left.get_component_mut::<usize>(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::<Write<usize>>();
let _ = left.get_component_mut::<bool>(entity);
}
#[test]
fn writewrite_right_included() {
let mut world = World::new();
let entity = world.insert((), vec![(1usize, false)])[0];
let (_, mut right) = world.split::<Write<usize>>();
assert!(right.get_component_mut::<bool>(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::<Write<usize>>();
let _ = right.get_component_mut::<usize>(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::<Read<usize>>();
let _ = left.get_component_mut::<usize>(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::<Read<usize>>();
let _ = left.get_component_mut::<bool>(entity);
}
#[test]
fn readwrite_right_included() {
let mut world = World::new();
let entity = world.insert((), vec![(1usize, false)])[0];
let (_, mut right) = world.split::<Read<usize>>();
assert!(right.get_component_mut::<bool>(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::<Read<usize>>();
let _ = right.get_component_mut::<usize>(entity);
}
}

View file

@ -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<T: 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<T: Component>(&self, entity: Entity) -> Option<Ref<T>>;
/// 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<T: Component>(&self, entity: Entity)
-> Option<RefMut<T>>;
/// 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<T: Component>(&mut self, entity: Entity) -> Option<RefMut<T>> {
// 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<T: 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<V: for<'a> View<'a>>(
&self,
) -> Result<StorageAccessor, ComponentAccessError>;
}
#[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<T: Component>(&self, entity: Entity) -> Option<Ref<T>> {
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::<T>())?
.data_slice::<T>()
.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<T: Component>(&self, entity: Entity) -> bool {
self.has_component_by_id(entity, ComponentTypeId::of::<T>())
}
/// 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<T: Component>(
&self,
entity: Entity,
) -> Option<RefMut<T>> {
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::<T>())?
.data_slice_mut::<T>()
.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<T: Component>(&mut self, entity: Entity) -> Option<RefMut<T>> {
// 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<T: 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::<T>())?;
unsafe { tags.data_slice::<T>().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<T: for<'v> 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<V, F>,
) -> (SubWorld, SubWorld) {
self.split::<V>()
}
}
impl EntityStore for World {
#[inline]
fn has_component<T: Component>(&self, entity: Entity) -> bool {
self.has_component_by_id(entity, ComponentTypeId::of::<T>())
}
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<T: Component>(&self, entity: Entity) -> Option<Ref<T>> {
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::<T>())?
.data_slice::<T>()
.deconstruct()
};
let component = slice.get(*location.component())?;
Some(Ref::new(slice_borrow, component))
}
#[inline]
unsafe fn get_component_mut_unchecked<T: Component>(
&self,
entity: Entity,
) -> Option<RefMut<T>> {
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::<T>())?
.data_slice_mut::<T>()
.deconstruct();
let component = slice.get_mut(*location.component())?;
Some(RefMut::new(slice_borrow, component))
}
#[inline]
fn get_tag<T: 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::<T>())?;
unsafe { tags.data_slice::<T>().get(*location.set()) }
}
#[inline]
fn is_alive(&self, entity: Entity) -> bool { self.entity_allocator.is_alive(entity) }
#[inline]
fn get_component_storage<V: for<'b> View<'b>>(
&self,
) -> Result<StorageAccessor, ComponentAccessError> {
Ok(StorageAccessor::new(self.storage(), None))
}
}
impl Default for World {

View file

@ -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<ResourceTypeId> = 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<ComponentTypeId> = 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<dyn Schedulable> {
let resource_access: Access<ResourceTypeId> = #resource_access;
let component_access: Access<ComponentTypeId> = #component_access;
let resource_permissions: Permissions<ResourceTypeId> = #resource_permissions;
let component_permissions: Permissions<ComponentTypeId> = #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),

View file

@ -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 <thomas.gillen@googlemail.com>"]
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"

View file

@ -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<Vel>, Write<Pos>)>::query())
.reads_resource::<Time>()
.build(|command_buffer, world, time, query| {
for (vel, pos) in query.iter_mut(world) {
*pos += *vel * time;
}
});
```
Multiple queries can resources can be requested. The third and fouth closure parameters will contain a tuple of all resources and queries, respectively.
Systems can be compiled into a `Schedule`. And the schedule executed. Schedules will automatically parallelize systems where possible, based upon the resource and entity accesses declared by each system. Side effects (such as writing to a component) will be visible in the order in which systems were given to the schedule. System command buffers are flushed at the end of the schedule by default.
```
// create shared resources
let mut resources = Resources::default();
resources.insert(Time::new());
// define our system schedule
let mut schedule = Schedule::builder()
.add_system(update_positions)
.add_system(handle_collisions)
.flush() // flush command buffers so later systems can see new entities
.add_system(render)
.build();
// each frame, execute the schedule
schedule.execute(&mut world, &mut resources);
```

View file

@ -13,15 +13,8 @@ pub use system_fn_types::{Res, ResMut};
pub mod prelude {
pub use crate::{
bit_set::BitSet,
// aliased preparedread and preparedwrite used by system_fn
resource::{ResourceSet, Resources},
schedule::{Executor, Runnable, Schedulable, Schedule},
IntoSystem,
Query,
Res,
ResMut,
SubWorld,
System,
SystemBuilder,
IntoSystem, SimpleQuery as Query, Res, ResMut, System, SystemBuilder,
};
}

View file

@ -2,7 +2,7 @@ use downcast_rs::{impl_downcast, Downcast};
use fxhash::FxHashMap;
use legion_core::borrow::{AtomicRefCell, Ref, RefMut};
use legion_core::downcast_typename::DowncastTypename;
use legion_core::query::{Read, ReadOnly, Write};
use legion_core::{permission::Permissions, query::{Read, ReadOnly, Write}};
use std::{
any::{type_name, Any},
marker::PhantomData,
@ -37,27 +37,16 @@ impl DowncastTypename for dyn Resource {
// type_name_of_val(self) == type_name::<T>()
}
}
#[cfg(not(feature = "ffi"))]
/// A type ID identifying a component type.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ResourceTypeId(&'static str);
#[cfg(not(feature = "ffi"))]
impl ResourceTypeId {
/// Gets the component type ID that represents type `T`.
pub fn of<T: Resource>() -> Self { Self(type_name::<T>()) }
}
#[cfg(feature = "ffi")]
/// A type ID identifying a component type.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ResourceTypeId(&'static str, u32);
#[cfg(feature = "ffi")]
impl ResourceTypeId {
/// Gets the component type ID that represents type `T`.
pub fn of<T: Resource>() -> Self { Self(type_name::<T>(), 0) }
}
/// Trait which is implemented for tuples of resources and singular resources. This abstracts
/// fetching resources to allow for ergonomic fetching.
@ -108,8 +97,7 @@ pub trait ResourceSet: Send + Sync {
unsafe { Self::fetch_unchecked(resources) }
}
fn read_types() -> Vec<ResourceTypeId>;
fn write_types() -> Vec<ResourceTypeId>;
fn requires_permissions() -> Permissions<ResourceTypeId>;
}
/// Blanket trait for resource types.
@ -368,8 +356,9 @@ impl ResourceSet for () {
type PreparedResources = ();
unsafe fn fetch_unchecked(_: &Resources) {}
fn read_types() -> Vec<ResourceTypeId> { Vec::new() }
fn write_types() -> Vec<ResourceTypeId> { Vec::new() }
fn requires_permissions() -> Permissions<ResourceTypeId> {
Permissions::new()
}
}
impl<T: Resource> ResourceSet for Read<T> {
@ -381,8 +370,11 @@ impl<T: Resource> ResourceSet for Read<T> {
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedRead::new(resource.deref() as *const T)
}
fn read_types() -> Vec<ResourceTypeId> { vec![ResourceTypeId::of::<T>()] }
fn write_types() -> Vec<ResourceTypeId> { Vec::new() }
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
permissions.push_read(ResourceTypeId::of::<T>());
permissions
}
}
impl<T: Resource> ResourceSet for Write<T> {
type PreparedResources = PreparedWrite<T>;
@ -393,8 +385,11 @@ impl<T: Resource> ResourceSet for Write<T> {
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedWrite::new(resource.deref_mut() as *mut T)
}
fn read_types() -> Vec<ResourceTypeId> { Vec::new() }
fn write_types() -> Vec<ResourceTypeId> { vec![ResourceTypeId::of::<T>()] }
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
permissions.push(ResourceTypeId::of::<T>());
permissions
}
}
macro_rules! impl_resource_tuple {
@ -407,15 +402,10 @@ macro_rules! impl_resource_tuple {
unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
($( $ty::fetch_unchecked(resources), )*)
}
fn read_types() -> Vec<ResourceTypeId> {
let mut vec = vec![];
$( vec.extend($ty::read_types()); )*
vec
}
fn write_types() -> Vec<ResourceTypeId> {
let mut vec = vec![];
$( vec.extend($ty::write_types()); )*
vec
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
$( permissions.add($ty::requires_permissions()); )*
permissions
}
}
};

View file

@ -2,11 +2,11 @@ use crate::{
resource::{ResourceTypeId, Resources},
system::SystemId,
};
use bit_set::BitSet;
use legion_core::{
borrow::RefMut,
command::CommandBuffer,
storage::ComponentTypeId,
subworld::ArchetypeAccess,
world::{World, WorldId},
};
use std::cell::UnsafeCell;
@ -36,26 +36,6 @@ use std::iter::repeat;
pub trait Schedulable: Runnable + Send + Sync {}
impl<T> Schedulable for T where T: Runnable + Send + Sync {}
/// Describes which archetypes a system declares access to.
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),
},
}
}
}
/// Trait describing a schedulable type. This is implemented by `System`
pub trait Runnable {
/// Gets the name of the system.
@ -199,7 +179,6 @@ impl Executor {
trace!(system_index = n, "Added write dependency");
dependencies.insert(*n);
}
resource_last_read.insert(*res, i);
}
for res in write_res {
trace!(resource = ?res, "Write resource");
@ -213,20 +192,34 @@ impl Executor {
trace!(system_index = n, "Added write dependency");
dependencies.insert(*n);
}
}
// update access tracking
for res in read_res {
resource_last_read.insert(*res, i);
}
for res in write_res {
resource_last_read.insert(*res, i);
resource_last_mutated.insert(*res, i);
}
static_dependency_counts.push(AtomicUsize::from(dependencies.len()));
trace!(dependants = ?dependencies, "Computed static dependants");
for dep in dependencies {
static_dependants[dep].push(i);
trace!(dependants = ?dependencies, dependency_counts = ?static_dependency_counts, "Computed static dependants");
for dep in &dependencies {
static_dependants[*dep].push(i);
}
// find component access dependencies
let mut comp_dependencies = FxHashSet::default();
for comp in read_comp {
trace!(component = ?comp, "Read component");
if let Some(n) = component_last_mutated.get(comp) {
trace!(system_index = n, "Added write dependency");
comp_dependencies.insert(*n);
}
}
for comp in write_comp {
// Writes have to be exclusive, so we are dependent on reads too
// writes have to be exclusive, so we are dependent on reads too
trace!(component = ?comp, "Write component");
if let Some(n) = component_last_read.get(comp) {
trace!(system_index = n, "Added read dependency");
@ -236,17 +229,20 @@ impl Executor {
trace!(system_index = n, "Added write dependency");
comp_dependencies.insert(*n);
}
}
// update access tracking
for comp in read_comp {
component_last_read.insert(*comp, i);
}
for comp in write_comp {
component_last_read.insert(*comp, i);
component_last_mutated.insert(*comp, i);
}
// Do reads after writes to ensure we don't overwrite last_read
for comp in read_comp {
trace!(component = ?comp, "Read component");
if let Some(n) = component_last_mutated.get(comp) {
trace!(system_index = n, "Added write dependency");
comp_dependencies.insert(*n);
}
component_last_read.insert(*comp, i);
// remove dependencies which are already static from dynamic dependencies
for static_dep in &dependencies {
comp_dependencies.remove(static_dep);
}
trace!(depentants = ?comp_dependencies, "Computed dynamic dependants");
@ -311,6 +307,7 @@ impl Executor {
pub fn run_systems(&mut self, world: &mut World, resources: &mut Resources) {
self.systems.iter_mut().for_each(|system| {
let system = unsafe { system.get_mut() };
system.prepare(world);
system.run(world, resources);
});
}
@ -329,7 +326,11 @@ impl Executor {
match self.systems.len() {
1 => {
// safety: we have exlusive access to all systems, world and resources here
unsafe { self.systems[0].get_mut().run(world, resources) };
unsafe {
let system = self.systems[0].get_mut();
system.prepare(world);
system.run(world, resources);
};
}
_ => {
let systems = &mut self.systems;
@ -372,14 +373,23 @@ impl Executor {
let awaiting = &self.awaiting;
trace!(?awaiting, "Initialized await counts");
// execute all systems with no outstanding dependencies
(0..systems.len())
.filter(|i| awaiting[*i].load(Ordering::SeqCst) == 0)
.into_par_iter()
.filter(|i| static_dependency_counts[*i].load(Ordering::SeqCst) == 0)
.for_each(|i| {
// safety: we are at the root of the execution tree, so we know each
// index is exclusive here
unsafe { self.run_recursive(i, world, resources) };
});
debug_assert!(
awaiting.iter().all(|x| x.load(Ordering::SeqCst) == 0),
"not all systems run: {:?}",
awaiting
);
}
}
},
@ -408,19 +418,9 @@ impl Executor {
self.systems[i].get_mut().run_unsafe(world, resources);
self.static_dependants[i].par_iter().for_each(|dep| {
match self.awaiting[*dep].compare_exchange(
1,
std::usize::MAX,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => {
// safety: each dependency is unique, so run_recursive is safe to call
self.run_recursive(*dep, world, resources);
}
Err(_) => {
self.awaiting[*dep].fetch_sub(1, Ordering::Relaxed);
}
if self.awaiting[*dep].fetch_sub(1, Ordering::Relaxed) == 1 {
// safety: each dependency is unique, so run_recursive is safe to call
self.run_recursive(*dep, world, resources);
}
});
}
@ -469,9 +469,11 @@ impl Builder {
}
/// Adds a thread local system to the schedule. This system will be executed on the main thread.
pub fn add_thread_local<S: Into<Box<dyn Runnable>>>(self, system: S) -> Self {
let mut system = system.into();
self.add_thread_local_fn(move |world, resources| system.run(world, resources))
pub fn add_thread_local<S: Into<Box<dyn Runnable>>>(mut self, system: S) -> Self {
self.finalize_executor();
let system = system.into();
self.steps.push(Step::ThreadLocalSystem(system));
self
}
/// Finalizes the builder into a `Schedule`.
@ -495,6 +497,8 @@ pub enum Step {
FlushCmdBuffers,
/// A thread local function.
ThreadLocalFn(Box<dyn FnMut(&mut World, &mut Resources)>),
/// A thread local system
ThreadLocalSystem(Box<dyn Runnable>),
}
/// A schedule of systems for execution.
@ -528,17 +532,32 @@ impl Schedule {
/// Executes all of the steps in the schedule.
pub fn execute(&mut self, world: &mut World, resources: &mut Resources) {
let mut waiting_flush: Vec<&mut Executor> = Vec::new();
enum ToFlush<'a> {
Executor(&'a mut Executor),
System(RefMut<'a, CommandBuffer>),
}
let mut waiting_flush: Vec<ToFlush> = Vec::new();
for step in &mut self.steps {
match step {
Step::Systems(executor) => {
executor.run_systems(world, resources);
waiting_flush.push(executor);
waiting_flush.push(ToFlush::Executor(executor));
}
Step::FlushCmdBuffers => {
waiting_flush.drain(..).for_each(|e| match e {
ToFlush::Executor(exec) => exec.flush_command_buffers(world),
ToFlush::System(mut cmd) => cmd.write(world),
});
}
Step::FlushCmdBuffers => waiting_flush
.drain(..)
.for_each(|e| e.flush_command_buffers(world)),
Step::ThreadLocalFn(function) => function(world, resources),
Step::ThreadLocalSystem(system) => {
system.prepare(world);
system.run(world, resources);
if let Some(cmd) = system.command_buffer_mut(world.id()) {
waiting_flush.push(ToFlush::System(cmd));
}
}
}
}
}
@ -620,10 +639,14 @@ mod tests {
});
let system_two = SystemBuilder::new("two")
.with_query(Write::<TestComp>::query())
.build(move |_, world, _, query| assert_eq!(0, query.iter_mut(world).count()));
.build(move |_, world, _, query| {
assert_eq!(0, query.iter_mut(world).count());
});
let system_three = SystemBuilder::new("three")
.with_query(Write::<TestComp>::query())
.build(move |_, world, _, query| assert_eq!(1, query.iter_mut(world).count()));
.build(move |_, world, _, query| {
assert_eq!(1, query.iter_mut(world).count());
});
let mut schedule = Schedule::builder()
.add_system(system_one)
@ -634,4 +657,34 @@ mod tests {
schedule.execute(&mut world, &mut resources);
}
#[test]
fn flush_thread_local() {
let universe = Universe::new();
let mut world = universe.create_world();
let mut resources = Resources::default();
#[derive(Clone, Copy, Debug, PartialEq)]
struct TestComp(f32, f32, f32);
let entity = Arc::new(Mutex::new(None));
{
let entity = entity.clone();
let system_one = SystemBuilder::new("one").build_thread_local(move |cmd, _, _, _| {
let mut entity = entity.lock().unwrap();
*entity = Some(cmd.insert((), vec![(TestComp(0.0, 0.0, 0.0),)])[0]);
});
let mut schedule = Schedule::builder().add_thread_local(system_one).build();
schedule.execute(&mut world, &mut resources);
}
let entity = entity.lock().unwrap();
assert!(entity.is_some());
assert!(world.get_entity_location(entity.unwrap()).is_some());
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,19 @@
use crate::{
resource::{ResourceSet, ResourceTypeId},
schedule::{ArchetypeAccess, Schedulable},
schedule::Schedulable,
system_fn_types::{FuncSystem, FuncSystemFnWrapper},
Access, SubWorld, SystemAccess, SystemId, SystemQuery,
SystemAccess, SystemId,
};
use bit_set::BitSet;
// use bit_set::BitSet;
use fxhash::FxHashMap;
use legion_core::{
borrow::AtomicRefCell,
command::CommandBuffer,
permission::Permissions,
query::Query,
query::{DefaultFilter, IntoQuery, View, ViewElement},
storage::ComponentTypeId,
subworld::{ArchetypeAccess, SubWorld},
};
use legion_fn_system_macro::impl_fn_query_systems;
use std::marker::PhantomData;
@ -24,16 +27,17 @@ pub trait IntoSystem<CommandBuffer, Resources, Queries> {
impl_fn_query_systems!();
#[allow(type_alias_bounds)]
pub type Query<V>
pub type SimpleQuery<V>
where
V: for<'a> View<'a> + DefaultFilter,
= SystemQuery<V, <V as DefaultFilter>::Filter>;
= legion_core::query::Query<V, <V as DefaultFilter>::Filter>;
#[cfg(test)]
mod tests {
use crate::{resource::Resources, system_fn_types::Res, IntoSystem, Query, SubWorld};
use crate::{resource::Resources, system_fn_types::Res, IntoSystem, SimpleQuery};
use legion_core::{
query::{Read, Write},
subworld::SubWorld,
world::World,
};
use std::fmt::Debug;
@ -54,7 +58,7 @@ mod tests {
resources.insert(A(0));
world.insert((), vec![(X(1), Y(1)), (X(2), Y(2))]);
fn query_system(world: &mut SubWorld, query: &mut Query<(Read<X>, Write<Y>)>) {
fn query_system(world: &mut SubWorld, query: &mut SimpleQuery<(Read<X>, Write<Y>)>) {
for (x, mut y) in query.iter_mut(world) {
y.0 = 2;
println!("{:?}", x);
@ -66,8 +70,8 @@ mod tests {
fn query_system2(
a: Res<A>,
world: &mut SubWorld,
query: &mut Query<(Read<X>, Write<Y>)>,
query2: &mut Query<Read<X>>,
query: &mut SimpleQuery<(Read<X>, Write<Y>)>,
query2: &mut SimpleQuery<Read<X>>,
) {
println!("{:?}", *a);
for (x, mut y) in query.iter_mut(world) {

View file

@ -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::<T>()));
Res::new(resource.deref() as *const T)
}
fn read_types() -> Vec<ResourceTypeId> { vec![ResourceTypeId::of::<T>()] }
fn write_types() -> Vec<ResourceTypeId> { Vec::new() }
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
permissions.push_read(ResourceTypeId::of::<T>());
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::<T>()));
ResMut::new(resource.deref_mut() as *mut T)
}
fn read_types() -> Vec<ResourceTypeId> { vec![ResourceTypeId::of::<T>()] }
fn write_types() -> Vec<ResourceTypeId> { Vec::new() }
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
permissions.push(ResourceTypeId::of::<T>());
permissions
}
}
impl<T: resource::Resource> ResourceSet for PreparedRead<T> {
@ -207,8 +213,11 @@ impl<T: resource::Resource> ResourceSet for PreparedRead<T> {
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedRead::new(resource.deref() as *const T)
}
fn read_types() -> Vec<ResourceTypeId> { vec![ResourceTypeId::of::<T>()] }
fn write_types() -> Vec<ResourceTypeId> { Vec::new() }
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
permissions.push_read(ResourceTypeId::of::<T>());
permissions
}
}
impl<T: resource::Resource> ResourceSet for PreparedWrite<T> {
@ -220,8 +229,11 @@ impl<T: resource::Resource> ResourceSet for PreparedWrite<T> {
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedWrite::new(resource.deref_mut() as *mut T)
}
fn read_types() -> Vec<ResourceTypeId> { Vec::new() }
fn write_types() -> Vec<ResourceTypeId> { vec![ResourceTypeId::of::<T>()] }
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
permissions.push(ResourceTypeId::of::<T>());
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 = <R as ResourceSet>::PreparedResources,
Queries = <Q as QuerySet>::Queries,
Queries = Q,
>,
{
pub name: SystemId,
@ -263,18 +275,21 @@ where
Q: QuerySet,
F: FuncSystemFn<
Resources = <R as ResourceSet>::PreparedResources,
Queries = <Q as QuerySet>::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(),
);
}
}

View file

@ -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::<Pos>::query();
let query_rot = Read::<Rot>::query();
assert_eq!(2, query_pos.components::<Pos, World>(&world).len());
assert_eq!(1, query_rot.components::<Rot, World>(&world).len());
let query_pos = Write::<Pos>::query();
let query_rot = Write::<Rot>::query();
assert_eq!(2, query_pos.components_mut::<Pos, World>(&mut world).len());
assert_eq!(1, query_rot.components_mut::<Rot, World>(&mut world).len());
}

View file

@ -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<Entity> = Vec::new();
// for e in world_1.insert(shared, components.clone()) {
// world_1_entities.push(*e);
// }
let mut world_1_entities: Vec<Entity> = Vec::new();
for e in world_1.insert(shared, components.clone()) {
world_1_entities.push(*e);
}
// let mut world_2_entities: Vec<Entity> = Vec::new();
// for e in world_2.insert(shared, components.clone()) {
// world_2_entities.push(*e);
// }
let mut world_2_entities: Vec<Entity> = 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<ComponentTypeId> =
component_types.iter().map(|(id, _)| *id).collect();
assert_eq!(1, component_type_ids.len());
assert!(component_type_ids.contains(&ComponentTypeId::of::<Pos>()));
}
// 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<TagTypeId> = tag_types.iter().map(|(id, _)| *id).collect();
assert_eq!(1, tag_type_ids.len());
assert!(tag_type_ids.contains(&TagTypeId::of::<Model>()));
}

View file

@ -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)]

View file

@ -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<Camera>, Read<Transform>, Write<VisibleEntities>)>,
camera_query: &mut Query<(Read<Camera>, Write<VisibleEntities>)>,
entities_query: &mut Query<Read<Draw>>,
_transform_query: &mut Query<Read<Transform>>,
_transform_entities_query: &mut Query<(Read<Draw>, Read<Transform>)>, // 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::<Transform>(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;
}

View file

@ -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<legion::systems::resource::ResourceTypeId> {
vec![
ResourceTypeId::of::<Box<dyn RenderResourceContext>>(),
ResourceTypeId::of::<VertexBufferDescriptors>(),
ResourceTypeId::of::<AssetRenderResourceBindings>(),
ResourceTypeId::of::<SharedBuffers>(),
]
}
fn write_types() -> Vec<legion::systems::resource::ResourceTypeId> {
vec![
ResourceTypeId::of::<Assets<PipelineDescriptor>>(),
ResourceTypeId::of::<Assets<Shader>>(),
ResourceTypeId::of::<PipelineCompiler>(),
]
fn requires_permissions() -> Permissions<ResourceTypeId> {
let mut permissions = Permissions::new();
permissions.push_read(ResourceTypeId::of::<Box<dyn RenderResourceContext>>());
permissions.push_read(ResourceTypeId::of::<VertexBufferDescriptors>());
permissions.push_read(ResourceTypeId::of::<AssetRenderResourceBindings>());
permissions.push_read(ResourceTypeId::of::<SharedBuffers>());
permissions.push(ResourceTypeId::of::<Assets<PipelineDescriptor>>());
permissions.push(ResourceTypeId::of::<Assets<Shader>>());
permissions.push(ResourceTypeId::of::<PipelineCompiler>());
permissions
}
}

View file

@ -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<PipelineDescriptor>,

View file

@ -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;

View file

@ -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)]

View file

@ -25,11 +25,12 @@ pub fn build(_: &mut World) -> Vec<Box<dyn Schedulable>> {
.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::<Write<Children>>();
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::<Children>(previous_parent_entity)
children_world.get_component_mut::<Children>(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<Box<dyn Schedulable>> {
HashMap::<Entity, SmallVec<[Entity; 8]>>::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<Box<dyn Schedulable>> {
// Remove from `PreviousParent.Children`.
if let Some(mut previous_parent_children) =
world.get_component_mut::<Children>(previous_parent_entity)
children_world.get_component_mut::<Children>(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<Box<dyn Schedulable>> {
// 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::<Children>(parent.0)
if let Some(mut new_parent_children) = children_world.get_component_mut::<Children>(parent.0)
{
// This is the parent
log::trace!(

View file

@ -1,7 +1,7 @@
#![allow(dead_code)]
use crate::{
components::*,
ecs::{prelude::*, systems::SubWorld},
ecs::{prelude::*, subworld::SubWorld},
};
pub fn build(_: &mut World) -> Box<dyn Schedulable> {
@ -22,7 +22,7 @@ pub fn build(_: &mut World) -> Box<dyn Schedulable> {
fn propagate_recursive(
parent_local_to_world: Transform,
world: &mut SubWorld,
world: &SubWorld,
entity: Entity,
commands: &mut CommandBuffer,
) {

View file

@ -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<Read<Parent>>,
_children_query: &mut Query<Read<Children>>,
) {
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::<Parent>(*entity).is_none())
.filter(|(entity, _)| hierarchy_world.get_component::<Parent>(*entity).is_none())
.map(|(e, _)| e)
.collect::<Vec<Entity>>();
let mut window_rect = Rect {

View file

@ -198,19 +198,22 @@ fn ball_collision_system(
mut scoreboard: ResMut<Scoreboard>,
command_buffer: &mut CommandBuffer,
world: &mut SubWorld,
ball_query: &mut Query<(Write<Ball>, Read<Translation>, Read<Sprite>)>,
ball_query: &mut Query<Write<Ball>>,
paddle_query: &mut Query<(Read<Paddle>, Read<Translation>, Read<Sprite>)>,
brick_query: &mut Query<(Read<Brick>, Read<Translation>, Read<Sprite>)>,
wall_query: &mut Query<(Read<Wall>, Read<Translation>, Read<Sprite>)>,
) {
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::<Translation>(entity).unwrap();
let sprite = world.get_component::<Sprite>(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;
}

View file

@ -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},
};