use std::any::TypeId;
use std::fmt;
use std::marker::PhantomData;
use std::mem;
use std::slice;
use pragma_core::jobs::{Grain, ThreadPool};
use crate::access::Access;
use crate::archetype::Archetype;
use crate::component::Ticks;
use crate::fetch::{ColumnRef, QueryData, QueryFilter, ReadOnlyQueryData};
use crate::{EcsError, Entity, World};
pub struct Query<Q: QueryData, F: QueryFilter = ()> {
access: Access,
world: Option<u64>,
seen: usize,
matched: Vec<usize>,
last_run: u64,
mask: Vec<bool>,
marker: PhantomData<fn() -> (Q, F)>,
}
impl<Q: QueryData, F: QueryFilter> fmt::Debug for Query<Q, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Query")
.field("access", &self.access)
.field("matched_tables", &self.matched.len())
.finish_non_exhaustive()
}
}
impl<Q: QueryData, F: QueryFilter> Query<Q, F> {
pub fn new() -> Result<Self, EcsError> {
let mut access = Access::new();
Q::declare(&mut access)?;
F::declare(&mut access)?;
Ok(Self {
access,
world: None,
seen: 0,
matched: Vec::new(),
last_run: 0,
mask: Vec::new(),
marker: PhantomData,
})
}
#[must_use]
pub const fn access(&self) -> &Access {
&self.access
}
pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> Result<QueryIter<'w, 's, Q, F>, EcsError>
where
Q: ReadOnlyQueryData,
{
let last_run = self.begin(world, world.change_tick())?;
self.fill_world_mask(world, last_run)?;
Ok(QueryIter {
tables: world.archetypes(),
ids: self.matched.iter(),
masks: Masks::new(&self.mask),
rows: None,
})
}
pub fn iter_mut<'w, 's>(
&'s mut self,
world: &'w mut World,
) -> Result<QueryIterMut<'w, 's, Q, F>, EcsError> {
let tick = world.next_tick();
let last_run = self.begin(world, tick)?;
self.fill_world_mask(world, last_run)?;
Ok(QueryIterMut {
tables: TablesMut::new(world.archetypes_mut(), &self.matched),
masks: Masks::new(&self.mask),
tick,
rows: None,
})
}
pub fn par_for_each<'w, G>(
&mut self,
world: &'w World,
pool: &ThreadPool,
grain: Grain,
f: G,
) -> Result<(), EcsError>
where
Q: ReadOnlyQueryData,
G: Fn(Q::Item<'w>) + Sync,
{
let last_run = self.begin(world, world.change_tick())?;
self.fill_world_mask(world, last_run)?;
let tables = world.archetypes();
let matched = || self.matched.iter().filter_map(|&id| tables.get(id));
let plan = Plan::new(matched().map(Archetype::len), grain);
let mut masks = Masks::<F>::new(&self.mask);
let rows = matched().filter_map(|table| {
let mask = masks.take(table.len());
Some((table.len(), fetch_shared::<Q>(table)?, mask))
});
run::<Q, G>(rows, plan, pool, &f, world.len())
}
pub fn par_for_each_mut<'w, G>(
&mut self,
world: &'w mut World,
pool: &ThreadPool,
grain: Grain,
f: G,
) -> Result<(), EcsError>
where
G: Fn(Q::Item<'w>) + Sync,
{
let tick = world.next_tick();
let last_run = self.begin(world, tick)?;
self.fill_world_mask(world, last_run)?;
let entities = world.len();
let tables = world.archetypes();
let plan = Plan::new(
self.matched
.iter()
.filter_map(|&id| tables.get(id))
.map(Archetype::len),
grain,
);
let mut masks = Masks::<F>::new(&self.mask);
let rows = TablesMut::new(world.archetypes_mut(), &self.matched).filter_map(|table| {
let mask = masks.take(table.len());
Some((table.len(), fetch_unique::<Q>(table, tick)?, mask))
});
run::<Q, G>(rows, plan, pool, &f, entities)
}
fn begin(&mut self, world: &World, now: u64) -> Result<u64, EcsError> {
self.update(world)?;
Ok(mem::replace(&mut self.last_run, now))
}
fn fill_world_mask(&mut self, world: &World, last_run: u64) -> Result<(), EcsError> {
let tables = world.archetypes();
self.fill_mask(tables, last_run, world.len(), |id, ty| {
tables.get(id)?.ticks(ty)
})
}
pub(crate) fn update(&mut self, world: &World) -> Result<(), EcsError> {
if self.world != Some(world.id()) {
self.world = Some(world.id());
self.seen = 0;
self.matched.clear();
self.last_run = 0;
}
let tables = world.archetypes();
for (id, table) in tables.iter().enumerate().skip(self.seen) {
let types: &[TypeId] = table.types();
if Q::matches(types) && F::matches(types) {
self.matched
.try_reserve(1)
.map_err(|_| EcsError::OutOfMemory {
entities: world.len(),
})?;
self.matched.push(id);
}
self.seen = id.saturating_add(1);
}
Ok(())
}
pub(crate) fn fill_mask<'a>(
&mut self,
tables: &[Archetype],
last_run: u64,
entities: usize,
ticks: impl Fn(usize, TypeId) -> Option<Ticks<'a>>,
) -> Result<(), EcsError> {
self.mask.clear();
if F::DENSE {
return Ok(());
}
let rows = |id: &usize| tables.get(*id).map_or(0, Archetype::len);
let total = self.matched.iter().map(rows).fold(0, usize::saturating_add);
self.mask
.try_reserve(total)
.map_err(|_| EcsError::OutOfMemory { entities })?;
for id in &self.matched {
let start = self.mask.len();
self.mask.resize(start.saturating_add(rows(id)), true);
if let Some(segment) = self.mask.get_mut(start..) {
F::mask(&|ty| ticks(*id, ty), last_run, segment);
}
}
Ok(())
}
pub(crate) fn matched(&self) -> &[usize] {
&self.matched
}
pub(crate) fn mask(&self) -> &[bool] {
&self.mask
}
}
fn fetch<'w, Q: QueryData>(
types: &[TypeId],
columns: impl Iterator<Item = ColumnRef<'w>>,
entities: &'w [Entity],
tick: u64,
) -> Option<Q::Slice<'w>> {
let mut state = Q::State::default();
for (ty, column) in types.iter().zip(columns) {
Q::offer(&mut state, *ty, &mut Some(column));
}
Q::finish(state, entities, tick)
}
fn fetch_shared<Q: QueryData>(table: &Archetype) -> Option<Q::Slice<'_>> {
let columns = table
.columns()
.iter()
.map(|c| ColumnRef::Shared(c.as_any()));
fetch::<Q>(table.types(), columns, table.entities(), 0)
}
fn fetch_unique<Q: QueryData>(table: &mut Archetype, tick: u64) -> Option<Q::Slice<'_>> {
let (types, columns, entities) = table.parts_mut();
let columns = columns
.iter_mut()
.map(|c| ColumnRef::Unique(c.as_any_mut()));
fetch::<Q>(types, columns, entities, tick)
}
pub(crate) struct Masks<'m, F> {
rest: &'m [bool],
marker: PhantomData<fn() -> F>,
}
impl<'m, F: QueryFilter> Masks<'m, F> {
pub(crate) fn new(mask: &'m [bool]) -> Self {
Self {
rest: mask,
marker: PhantomData,
}
}
pub(crate) fn take(&mut self, rows: usize) -> Option<&'m [bool]> {
if F::DENSE {
return None;
}
let (mask, rest) = self.rest.split_at(rows.min(self.rest.len()));
self.rest = rest;
Some(mask)
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct Masked<'m, I> {
items: I,
mask: Option<slice::Iter<'m, bool>>,
}
impl<'m, I> Masked<'m, I> {
pub(crate) fn new(items: I, mask: Option<&'m [bool]>) -> Self {
Self {
items,
mask: mask.map(<[bool]>::iter),
}
}
}
impl<I: Iterator> Iterator for Masked<'_, I> {
type Item = I::Item;
fn fold<B, G: FnMut(B, Self::Item) -> B>(self, init: B, mut g: G) -> B {
match self.mask {
None => self.items.fold(init, g),
Some(mask) => self.items.zip(mask).fold(
init,
|acc, (item, keep)| {
if *keep { g(acc, item) } else { acc }
},
),
}
}
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
match &mut self.mask {
None => self.items.next(),
Some(mask) => loop {
let item = self.items.next()?;
if mask.next().copied().unwrap_or(false) {
return Some(item);
}
},
}
}
}
struct TablesMut<'w, 's> {
rest: &'w mut [Archetype],
offset: usize,
ids: slice::Iter<'s, usize>,
}
impl<'w, 's> TablesMut<'w, 's> {
fn new(tables: &'w mut [Archetype], ids: &'s [usize]) -> Self {
Self {
rest: tables,
offset: 0,
ids: ids.iter(),
}
}
}
impl<'w> Iterator for TablesMut<'w, '_> {
type Item = &'w mut Archetype;
fn next(&mut self) -> Option<Self::Item> {
let &id = self.ids.next()?;
let rest = mem::take(&mut self.rest);
let skip = id.checked_sub(self.offset)?.min(rest.len());
let (table, rest) = rest.split_at_mut(skip).1.split_first_mut()?;
self.rest = rest;
self.offset = id.saturating_add(1);
Some(table)
}
}
#[derive(Clone, Copy)]
pub(crate) struct Plan {
size: usize,
chunks: usize,
}
impl Plan {
pub(crate) fn new(lens: impl Iterator<Item = usize> + Clone, grain: Grain) -> Self {
let total = lens.clone().fold(0, usize::saturating_add);
let size = grain.chunk_size(total).max(1);
let chunks = lens
.map(|len| len.div_ceil(size))
.fold(0, usize::saturating_add);
Self { size, chunks }
}
}
type Chunk<'w, 'm, Q> = (<Q as QueryData>::Slice<'w>, Option<&'m [bool]>);
pub(crate) fn run<'w, 'm, Q, G>(
tables: impl Iterator<Item = (usize, Q::Slice<'w>, Option<&'m [bool]>)>,
plan: Plan,
pool: &ThreadPool,
f: &G,
entities: usize,
) -> Result<(), EcsError>
where
Q: QueryData,
G: Fn(Q::Item<'w>) + Sync,
{
let mut chunks: Vec<Option<Chunk<'w, 'm, Q>>> = Vec::new();
chunks
.try_reserve_exact(plan.chunks)
.map_err(|_| EcsError::OutOfMemory { entities })?;
for (mut len, mut rows, mut mask) in tables {
while len > plan.size {
let (head, tail) = Q::split_at(rows, plan.size);
let (head_mask, tail_mask) = match mask {
Some(mask) => {
let (head, tail) = mask.split_at(plan.size.min(mask.len()));
(Some(head), Some(tail))
}
None => (None, None),
};
chunks.push(Some((head, head_mask)));
rows = tail;
mask = tail_mask;
len = len.saturating_sub(plan.size);
}
if len > 0 {
chunks.push(Some((rows, mask)));
}
}
debug_assert_eq!(chunks.len(), plan.chunks);
pool.parallel_for(&mut chunks, Grain::Fixed(1), |chunk| {
if let Some((rows, mask)) = chunk.take() {
Masked::new(Q::iter(rows), mask).for_each(f);
}
})?;
Ok(())
}
pub struct QueryIter<'w, 's, Q: QueryData, F: QueryFilter = ()> {
tables: &'w [Archetype],
ids: slice::Iter<'s, usize>,
masks: Masks<'s, F>,
rows: Option<Masked<'s, Q::Iter<'w>>>,
}
impl<Q: QueryData, F: QueryFilter> fmt::Debug for QueryIter<'_, '_, Q, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("QueryIter")
.field("tables_left", &self.ids.len())
.finish_non_exhaustive()
}
}
impl<'w, 's, Q: QueryData, F: QueryFilter> QueryIter<'w, 's, Q, F> {
#[inline(always)]
fn table(&mut self) -> Option<Option<Masked<'s, Q::Iter<'w>>>> {
let &id = self.ids.next()?;
let table = self.tables.get(id)?;
let mask = self.masks.take(table.len());
Some(fetch_shared::<Q>(table).map(|rows| Masked::new(Q::iter(rows), mask)))
}
}
impl<'w, Q: QueryData, F: QueryFilter> Iterator for QueryIter<'w, '_, Q, F> {
type Item = Q::Item<'w>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(item) = self.rows.as_mut().and_then(Iterator::next) {
return Some(item);
}
self.rows = self.table()?;
}
}
fn fold<B, G: FnMut(B, Self::Item) -> B>(mut self, init: B, mut g: G) -> B {
let mut acc = self
.rows
.take()
.into_iter()
.fold(init, |acc, rows| rows.fold(acc, &mut g));
while let Some(rows) = self.table() {
acc = rows
.into_iter()
.fold(acc, |acc, rows| rows.fold(acc, &mut g));
}
acc
}
}
pub struct QueryIterMut<'w, 's, Q: QueryData, F: QueryFilter = ()> {
tables: TablesMut<'w, 's>,
masks: Masks<'s, F>,
tick: u64,
rows: Option<Masked<'s, Q::Iter<'w>>>,
}
impl<Q: QueryData, F: QueryFilter> fmt::Debug for QueryIterMut<'_, '_, Q, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("QueryIterMut")
.field("tables_left", &self.tables.ids.len())
.finish_non_exhaustive()
}
}
impl<'w, 's, Q: QueryData, F: QueryFilter> QueryIterMut<'w, 's, Q, F> {
#[inline(always)]
fn table(&mut self) -> Option<Option<Masked<'s, Q::Iter<'w>>>> {
let table = self.tables.next()?;
let mask = self.masks.take(table.len());
Some(fetch_unique::<Q>(table, self.tick).map(|rows| Masked::new(Q::iter(rows), mask)))
}
}
impl<'w, Q: QueryData, F: QueryFilter> Iterator for QueryIterMut<'w, '_, Q, F> {
type Item = Q::Item<'w>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(item) = self.rows.as_mut().and_then(Iterator::next) {
return Some(item);
}
self.rows = self.table()?;
}
}
fn fold<B, G: FnMut(B, Self::Item) -> B>(mut self, init: B, mut g: G) -> B {
let mut acc = self
.rows
.take()
.into_iter()
.fold(init, |acc, rows| rows.fold(acc, &mut g));
while let Some(rows) = self.table() {
acc = rows
.into_iter()
.fold(acc, |acc, rows| rows.fold(acc, &mut g));
}
acc
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Changed, With, Without};
#[derive(Debug, PartialEq)]
struct A(u32);
#[derive(Debug, PartialEq)]
struct B(u32);
struct Marker;
#[test]
fn aliasing_is_rejected_at_construction() {
let name = std::any::type_name::<A>();
let conflict = Err(EcsError::ConflictingAccess { component: name });
assert_eq!(Query::<(&A, &mut A)>::new().map(|_| ()), conflict);
assert_eq!(Query::<(&mut A, &mut A)>::new().map(|_| ()), conflict);
assert_eq!(Query::<(Option<&A>, &mut A)>::new().map(|_| ()), conflict);
assert_eq!(Query::<(&mut A, (&B, &A))>::new().map(|_| ()), conflict);
assert!(Query::<(&A, &A, Option<&A>)>::new().is_ok());
assert!(Query::<(&mut A, &mut B), With<A>>::new().is_ok());
assert!(Query::<&mut A, (Changed<A>, Changed<B>)>::new().is_ok());
}
#[test]
fn the_cache_follows_new_tables_and_other_worlds() {
let mut world = World::new();
let e = world.spawn().unwrap();
world.insert(e, A(1)).unwrap();
let mut query = Query::<&A, Without<Marker>>::new().unwrap();
assert_eq!(query.iter(&world).unwrap().count(), 1);
let f = world.spawn().unwrap();
world.insert(f, B(2)).unwrap();
world.insert(f, A(2)).unwrap();
let g = world.spawn().unwrap();
world.insert(g, A(3)).unwrap();
world.insert(g, Marker).unwrap();
let mut seen: Vec<u32> = query.iter(&world).unwrap().map(|a| a.0).collect();
seen.sort_unstable();
assert_eq!(seen, [1, 2]);
let mut other = World::new();
let h = other.spawn().unwrap();
other.insert(h, Marker).unwrap();
other.insert(h, B(0)).unwrap();
other.insert(h, A(4)).unwrap();
assert_eq!(query.iter(&other).unwrap().count(), 0);
let i = other.spawn().unwrap();
other.insert(i, A(5)).unwrap();
let only: Vec<u32> = query.iter(&other).unwrap().map(|a| a.0).collect();
assert_eq!(only, [5]);
assert!(format!("{query:?}").starts_with("Query { access: {"));
}
#[test]
fn mutable_iteration_skips_tables_and_writes_every_row() {
let mut world = World::new();
let mut expected = Vec::new();
for i in 0..20_u32 {
let e = world.spawn().unwrap();
world.insert(e, A(i)).unwrap();
if i % 3 == 0 {
world.insert(e, B(i)).unwrap();
}
if i % 4 == 0 {
world.insert(e, Marker).unwrap();
}
if i % 3 == 0 && i % 4 != 0 {
expected.push(i);
}
}
let mut query = Query::<(Entity, &mut A, &B), Without<Marker>>::new().unwrap();
let mut iter = query.iter_mut(&mut world).unwrap();
assert!(format!("{iter:?}").starts_with("QueryIterMut"));
let mut visited = Vec::new();
for (e, mut a, b) in &mut iter {
a.0 += 100;
visited.push((e, b.0));
}
visited.sort_by_key(|(_, b)| *b);
assert_eq!(
visited.iter().map(|(_, b)| *b).collect::<Vec<_>>(),
expected
);
for (e, b) in visited {
assert_eq!(world.get::<A>(e), Some(&A(b + 100)));
}
}
#[test]
fn a_query_sees_changes_since_its_previous_call_only() {
let mut world = World::new();
let ids: Vec<Entity> = (0..6).map(|_| world.spawn().unwrap()).collect();
for (i, e) in (0_u32..).zip(&ids) {
world.insert(*e, A(i)).unwrap();
if i % 2 == 0 {
world.insert(*e, B(i)).unwrap();
}
}
let mut changed = Query::<Entity, Changed<A>>::new().unwrap();
let mut double = Query::<(Entity, &mut A), Changed<A>>::new().unwrap();
assert_eq!(changed.iter(&world).unwrap().count(), 6);
let mut writer = Query::<&mut A>::new().unwrap();
for mut a in writer.iter_mut(&mut world).unwrap() {
if a.0 % 2 == 1 {
a.0 += 10;
}
}
let mut seen: Vec<Entity> = changed.iter(&world).unwrap().collect();
seen.sort();
assert_eq!(seen, [ids[1], ids[3], ids[5]]);
for (_, mut a) in double.iter_mut(&mut world).unwrap() {
a.0 += 1;
}
world.insert(ids[4], A(0)).unwrap();
let rows: Vec<Entity> = double
.iter_mut(&mut world)
.unwrap()
.map(|(e, _)| e)
.collect();
assert_eq!(rows, [ids[4]]);
assert_eq!(changed.iter(&world).unwrap().count(), 6);
assert_eq!(changed.iter(&world).unwrap().count(), 0, "no writes since");
}
#[test]
fn masks_split_with_the_chunks() {
let pool =
ThreadPool::new(pragma_core::jobs::PoolConfig::default().with_workers(2)).unwrap();
let mut world = World::new();
let ids: Vec<Entity> = (0..50).map(|_| world.spawn().unwrap()).collect();
for (i, e) in (0_u32..).zip(&ids) {
world.insert(*e, A(i)).unwrap();
if i % 5 == 0 {
world.insert(*e, Marker).unwrap();
}
}
let mut changed = Query::<&mut A, Changed<A>>::new().unwrap();
changed
.par_for_each_mut(&mut world, &pool, Grain::Fixed(4), |_| {})
.unwrap();
for e in ids.iter().step_by(7) {
world.get_mut::<A>(*e).unwrap().0 += 1000;
}
let hits = std::sync::atomic::AtomicU32::new(0);
let mut reader = Query::<&A, Changed<A>>::new().unwrap();
reader.iter(&world).unwrap().for_each(drop);
changed
.par_for_each_mut(&mut world, &pool, Grain::Fixed(4), |a| {
assert!(a.0 >= 1000);
hits.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
})
.unwrap();
assert_eq!(hits.into_inner(), 8);
reader
.par_for_each(&world, &pool, Grain::Fixed(3), |_| panic!("no change"))
.unwrap();
}
#[test]
fn chunks_never_span_tables() {
let plan = Plan::new([5_usize, 0, 3].into_iter(), Grain::Fixed(2));
assert_eq!((plan.size, plan.chunks), (2, 3 + 2));
let empty = Plan::new(std::iter::empty(), Grain::Auto);
assert_eq!(empty.chunks, 0);
}
}