Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use crate::alloc::alloc::{alloc, dealloc, Layout};
use crate::alloc::boxed::Box;
use crate::alloc::{vec, vec::Vec};
use crate::dynamic_query::DynamicQueryTypes;
use core::any::{type_name, TypeId};
use core::hash::{BuildHasher, BuildHasherDefault, Hasher};
use core::ops::Deref;
Expand Down Expand Up @@ -182,6 +183,10 @@ impl Archetype {
&self.types
}

pub(crate) fn entity_slice(&self) -> &[u32] {
&self.entities[..self.len() as usize]
}

/// Enumerate the types of the components of entities stored in this archetype.
///
/// Convenient for dispatching logic which needs to be performed on sets of type ids. For
Expand Down Expand Up @@ -370,6 +375,32 @@ impl Archetype {
Q::Fetch::access(self)
}

pub(crate) fn access_dynamic(&self, query: &DynamicQueryTypes) -> Option<Access> {
let mut access = None;
for &read_component in query.read_types {
if self.has_dynamic(read_component) {
access = access.max(Some(Access::Read));
} else {
return None;
}
}
for &write_component in query.write_types {
if self.has_dynamic(write_component) {
access = access.max(Some(Access::Write));
} else {
return None;
}
}
access
}

pub(crate) fn component_layout(&self, component_type: TypeId) -> Option<Layout> {
self.types()
.iter()
.find(|typ| typ.id == component_type)
.map(|info| info.layout)
}

/// Add components from another archetype with identical components
///
/// # Safety
Expand Down
130 changes: 130 additions & 0 deletions src/dynamic_query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use core::{alloc::Layout, any::TypeId, ptr::NonNull};

use crate::{entities::EntityMeta, Archetype, Entity};

/// The component types accessed by a dynamic query.
#[derive(Debug, Copy, Clone)]
pub struct DynamicQueryTypes<'a> {
pub(crate) read_types: &'a [TypeId],
pub(crate) write_types: &'a [TypeId],
}

impl<'a> DynamicQueryTypes<'a> {
/// Creates a dynamic query that reads component types in `read_types`
/// and writes component types in `write_types`.
pub fn new(read_types: &'a [TypeId], write_types: &'a [TypeId]) -> Self {
Self {
read_types,
write_types,
}
}

/// Gets the component types to be read by this query.
pub fn read_types(&self) -> &[TypeId] {
self.read_types
}

/// Gets the component types to be accessed mutably by this query.
pub fn write_types(&self) -> &[TypeId] {
self.write_types
}
}

/// A pointer to a slice of component data from a [`DynamicQuery`].
pub struct ComponentSlice {
typ: TypeId,
ptr: NonNull<u8>,
len: usize,
component_layout: Layout,
}

impl ComponentSlice {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a safe way to get mutable access to the write_types of the query.

/// Returns the number of components in this slice.
pub fn len(&self) -> usize {
self.len
}

/// Returns the layout of a single component in this slice.
pub fn component_layout(&self) -> Layout {
self.component_layout
}

/// Returns the number of bytes in the component slice.
pub fn len_in_bytes(&self) -> usize {
self.len() * self.component_layout().size()
}

/// Returns a pointer to the raw component data.
pub fn ptr(&self) -> NonNull<u8> {
self.ptr
}

/// Casts this component slice to a slice of type `T`.
///
/// # Panics
/// Panics if `T` is not the type of the components in this slice.
pub fn as_slice<T: 'static>(&self) -> &[T] {

@Ralith Ralith Sep 27, 2021

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unsound because &[T] may outlive the query (and indeed the entire World). ComponentSlice should have a lifetime parameter bounded by the lifetime of the query, which should be propagated to this return value.

assert_eq!(TypeId::of::<T>(), self.typ, "component type does not match");
unsafe { std::slice::from_raw_parts(self.ptr.as_ptr() as *const T, self.len) }
}
}

/// The result of a dynamic query.
pub struct DynamicQuery<'q> {
types: DynamicQueryTypes<'q>,
archetypes: &'q [Archetype],
entity_meta: &'q [EntityMeta],
}

impl<'q> DynamicQuery<'q> {
pub(crate) fn new(
types: DynamicQueryTypes<'q>,
archetypes: &'q [Archetype],
entity_meta: &'q [EntityMeta],
) -> Self {
Self {
types,
archetypes,
entity_meta,
}
}

/// Returns an iterator over entities yielded by this query.
pub fn iter_entities<'a>(&'a self) -> impl Iterator<Item = Entity> + 'a {
self.iter_matching_archetypes()
.flat_map(|archetype| archetype.entity_slice().iter().copied())
.map(move |id| Entity {
generation: unsafe { self.entity_meta.get_unchecked(id as usize).generation },
id,
})
}

/// Returns an iterator over pointers to components of the given type
/// yielded by this query.
pub fn iter_component_slices<'a>(
&'a self,
component_type: TypeId,
) -> impl Iterator<Item = ComponentSlice> + 'a {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unsound because no dynamic borrow-checking is performed, and the use of &self here and in World::query_dynamic allows for other outstanding (potentially mutable) borrows. Either something analogous to Fetch::borrow is needed to perform dynamic borrow-checking, or a lot of stuff should be switched to &mut self to statically prevent a dynamic query from coexisting with any other component borrow.

self.iter_matching_archetypes()
.map(move |archetype| unsafe {
let ptr = archetype
.get_dynamic(component_type, 0, 0)
.expect("component not in query");
let len = archetype.len() as usize;
let component_layout = archetype.component_layout(component_type).unwrap();
ComponentSlice {
typ: component_type,
ptr,
len,
component_layout,
}
})
}

fn iter_matching_archetypes<'a>(&'a self) -> impl Iterator<Item = &'q Archetype> + 'a {
self.archetypes
.iter()
.filter(move |archetype| archetype.access_dynamic(&self.types).is_some())
.filter(|archetype| archetype.len() > 0)
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ mod archetype;
mod batch;
mod borrow;
mod bundle;
mod dynamic_query;
mod entities;
mod entity_builder;
mod entity_ref;
Expand All @@ -72,6 +73,7 @@ mod world;
pub use archetype::Archetype;
pub use batch::{BatchIncomplete, BatchWriter, ColumnBatch, ColumnBatchBuilder, ColumnBatchType};
pub use bundle::{Bundle, DynamicBundle, MissingComponent};
pub use dynamic_query::{DynamicQuery, DynamicQueryTypes};
pub use entities::{Entity, NoSuchEntity};
pub use entity_builder::{BuiltEntity, Cloneable, EntityBuilder, ReusableBuiltEntity};
pub use entity_ref::{EntityRef, Ref, RefMut};
Expand Down
6 changes: 6 additions & 0 deletions src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
// copied, modified, or distributed except according to those terms.

use crate::alloc::{vec, vec::Vec};
use crate::{DynamicQuery, DynamicQueryTypes};
use core::any::TypeId;
use core::borrow::Borrow;
use core::convert::TryFrom;
Expand Down Expand Up @@ -398,6 +399,11 @@ impl World {

pub(crate) fn archetypes_inner(&self) -> &[Archetype] {
&self.archetypes.archetypes
}

/// Perform a dynamic query.
pub fn query_dynamic<'q>(&'q self, types: DynamicQueryTypes<'q>) -> DynamicQuery<'q> {
DynamicQuery::new(types, &self.archetypes.archetypes, &self.entities.meta)
}

/// Prepare a query against a single entity, using dynamic borrow checking
Expand Down
51 changes: 51 additions & 0 deletions tests/dynamic_query.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use std::any::TypeId;

use hecs::{DynamicQueryTypes, Entity, World};

#[test]
fn dynamic_query() {
let mut world = World::new();

let entity1 = world.spawn((123, "abc", 4.0));
let entity2 = world.spawn((500, "aaa", 4.5));
let entity3 = world.spawn((124, "abd", 6.0, vec![10]));
let entity4 = world.spawn(("one",));

let read_types = [TypeId::of::<i32>(), TypeId::of::<&'static str>()];
let write_types = [TypeId::of::<f64>()];
let types = DynamicQueryTypes::new(&read_types, &write_types);

let query = world.query_dynamic(types);
let entities: Vec<Entity> = query.iter_entities().collect();
assert_eq!(entities.len(), 3);
assert!(entities.contains(&entity1));
assert!(entities.contains(&entity2));
assert!(entities.contains(&entity3));
assert!(!entities.contains(&entity4));

let i32s: Vec<i32> = query
.iter_component_slices(TypeId::of::<i32>())
.flat_map(|components| components.as_slice().to_vec())
.collect();
let strings: Vec<&'static str> = query
.iter_component_slices(TypeId::of::<&'static str>())
.flat_map(|components| components.as_slice().to_vec())
.collect();

for (i, entity) in entities.into_iter().enumerate() {
let i32 = i32s[i];
let string = strings[i];
let (expected_i32, expected_string) = if entity == entity1 {
(123, "abc")
} else if entity == entity2 {
(500, "aaa")
} else if entity == entity3 {
(124, "abd")
} else {
unreachable!()
};

assert_eq!(i32, expected_i32);
assert_eq!(string, expected_string);
}
}