Skip to content

Commit

Permalink
Expose views directly from the World
Browse files Browse the repository at this point in the history
  • Loading branch information
sanbox-irl committed Mar 27, 2024
1 parent e2e9499 commit d5fdabd
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ pub use entity_builder::{BuiltEntity, BuiltEntityClone, EntityBuilder, EntityBui
pub use entity_ref::{ComponentRef, ComponentRefShared, EntityRef, Ref, RefMut};
pub use query::{
Access, Batch, BatchedIter, Or, PreparedQuery, PreparedQueryBorrow, PreparedQueryIter,
PreparedView, Query, QueryBorrow, QueryIter, QueryMut, QueryShared, Satisfies, View, With,
Without,
PreparedView, Query, QueryBorrow, QueryIter, QueryMut, QueryShared, Satisfies, View,
ViewBorrow, With, Without,
};
pub use query_one::QueryOne;
pub use take::TakenEntity;
Expand Down
159 changes: 141 additions & 18 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,15 +649,7 @@ impl<'w, Q: Query> QueryBorrow<'w, Q> {
if self.borrowed {
return;
}
for x in self.world.archetypes() {
if x.is_empty() {
continue;
}
// TODO: Release prior borrows on failure?
if let Some(state) = Q::Fetch::prepare(x) {
Q::Fetch::borrow(x, state);
}
}
start_borrow::<Q>(self.world.archetypes_inner());
self.borrowed = true;
}

Expand Down Expand Up @@ -728,14 +720,7 @@ unsafe impl<'w, Q: Query> Sync for QueryBorrow<'w, Q> where for<'a> Q::Item<'a>:
impl<'w, Q: Query> Drop for QueryBorrow<'w, Q> {
fn drop(&mut self) {
if self.borrowed {
for x in self.world.archetypes() {
if x.is_empty() {
continue;
}
if let Some(state) = Q::Fetch::prepare(x) {
Q::Fetch::release(x, state);
}
}
release_borrow::<Q>(self.world.archetypes_inner());
}
}
}
Expand Down Expand Up @@ -1347,7 +1332,7 @@ impl<'q, Q: Query> View<'q, Q> {
///
/// `'q` must be sufficient to guarantee that `Q` cannot violate borrow safety, either with
/// dynamic borrow checks or by representing exclusive access to the `World`.
unsafe fn new(meta: &'q [EntityMeta], archetypes: &'q [Archetype]) -> Self {
pub(crate) unsafe fn new(meta: &'q [EntityMeta], archetypes: &'q [Archetype]) -> Self {
let fetch = archetypes
.iter()
.map(|archetype| {
Expand Down Expand Up @@ -1656,6 +1641,118 @@ impl<'a, 'q, Q: Query> IntoIterator for &'a mut PreparedView<'q, Q> {
}
}

/// A borrow of a [`World`](crate::World) sufficient to random-access the results of the query `Q`.
///
/// Note that borrows are not released until this object is dropped.
///
/// This struct is a thin wrapper around [`View`]. See it for more documentation.
pub struct ViewBorrow<'w, Q: Query> {
view: View<'w, Q>,
}

impl<'w, Q: Query> ViewBorrow<'w, Q> {
pub(crate) fn new(world: &'w World) -> Self {
start_borrow::<Q>(world.archetypes_inner());
let view = unsafe { View::<Q>::new(world.entities_meta(), world.archetypes_inner()) };

Self { view }
}

/// Retrieve the query results corresponding to `entity`
///
/// Will yield `None` if the entity does not exist or does not match the query.
///
/// Does not require exclusive access to the map, but is defined only for queries yielding only shared references.
///
/// See [`View::get``].
pub fn get(&self, entity: Entity) -> Option<Q::Item<'_>>
where
Q: QueryShared,
{
self.view.get(entity)
}

/// Retrieve the query results corresponding to `entity`
///
/// Will yield `None` if the entity does not exist or does not match the query.
///
/// See [`View::get_mut``].
pub fn get_mut(&mut self, entity: Entity) -> Option<Q::Item<'_>> {
self.view.get_mut(entity)
}

/// Equivalent to `get(entity).is_some()`, but does not require `Q: QueryShared`
///
/// See [`View::contains``].
pub fn contains(&self, entity: Entity) -> bool {
self.view.contains(entity)
}

/// Like `get_mut`, but allows simultaneous access to multiple entities
///
/// See [`View::get_unchecked``].
///
/// # Safety
///
/// Must not be invoked while any unique borrow of the fetched components of `entity` is live.
pub unsafe fn get_unchecked(&self, entity: Entity) -> Option<Q::Item<'_>> {
self.view.get_unchecked(entity)
}

/// Like `get_mut`, but allows checked simultaneous access to multiple entities
///
/// For N > 3, the check for distinct entities will clone the array and take O(N log N) time.
///
/// See [`View::get_many_mut``].
///
/// # Examples
///
/// ```
/// # use hecs::World;
/// let mut world = World::new();
///
/// let a = world.spawn((1, 1.0));
/// let b = world.spawn((2, 4.0));
/// let c = world.spawn((3, 9.0));
///
/// let mut view = world.view_mut::<&mut i32>();
/// let [a, b, c] = view.get_mut_n([a, b, c]);
///
/// assert_eq!(*a.unwrap(), 1);
/// assert_eq!(*b.unwrap(), 2);
/// assert_eq!(*c.unwrap(), 3);
/// ```
pub fn get_many_mut<const N: usize>(
&mut self,
entities: [Entity; N],
) -> [Option<Q::Item<'_>>; N] {
self.view.get_many_mut(entities)
}

/// Iterate over all entities satisfying `Q`
///
/// See [`View::iter_mut``]
pub fn iter_mut(&mut self) -> ViewIter<'_, Q> {
self.view.iter_mut()
}
}

impl<'w, Q: Query> Drop for ViewBorrow<'w, Q> {
fn drop(&mut self) {
release_borrow::<Q>(self.view.archetypes)
}
}

impl<'a, 'q, Q: Query> IntoIterator for &'a mut ViewBorrow<'q, Q> {
type IntoIter = ViewIter<'a, Q>;
type Item = (Entity, Q::Item<'a>);

#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter_mut()
}
}

pub(crate) fn assert_distinct<const N: usize>(entities: &[Entity; N]) {
match N {
1 => (),
Expand All @@ -1674,6 +1771,32 @@ pub(crate) fn assert_distinct<const N: usize>(entities: &[Entity; N]) {
}
}
}

/// Start the borrow
fn start_borrow<Q: Query>(archetypes: &[Archetype]) {
for x in archetypes {
if x.is_empty() {
continue;
}
// TODO: Release prior borrows on failure?
if let Some(state) = Q::Fetch::prepare(x) {
Q::Fetch::borrow(x, state);
}
}
}

/// Releases the borrow
fn release_borrow<Q: Query>(archetypes: &[Archetype]) {
for x in archetypes {
if x.is_empty() {
continue;
}
if let Some(state) = Q::Fetch::prepare(x) {
Q::Fetch::release(x, state);
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
14 changes: 13 additions & 1 deletion src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::entities::{Entities, EntityMeta, Location, ReserveEntitiesIterator};
use crate::query::{assert_borrow, assert_distinct};
use crate::{
Bundle, ColumnBatch, ComponentRef, DynamicBundle, Entity, EntityRef, Fetch, MissingComponent,
NoSuchEntity, Query, QueryBorrow, QueryMut, QueryOne, TakenEntity,
NoSuchEntity, Query, QueryBorrow, QueryMut, QueryOne, TakenEntity, View, ViewBorrow,
};

/// An unordered collection of entities, each having any number of distinctly typed components
Expand Down Expand Up @@ -399,6 +399,18 @@ impl World {
QueryBorrow::new(self)
}

/// Provide random access to any entity for a given Query.
pub fn view<Q: Query>(&self) -> ViewBorrow<'_, Q> {
ViewBorrow::new(self)
}

/// Provide random access to any entity for a given Query on a uniquely
/// borrowed world. Like [`view`](Self::view), but faster because dynamic borrow checks can be skipped.
pub fn view_mut<Q: Query>(&mut self) -> View<'_, Q> {
assert_borrow::<Q>();
unsafe { View::<Q>::new(self.entities_meta(), self.archetypes_inner()) }
}

/// Query a uniquely borrowed world
///
/// Like [`query`](Self::query), but faster because dynamic borrow checks can be skipped. Note
Expand Down
77 changes: 77 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,83 @@ fn random_access_via_view_mut() {
assert!(!view.contains(f));
}

#[test]
fn view_borrow_on_world() {
let mut world = World::new();
let e0 = world.spawn((3, "hello"));
let e1 = world.spawn((6.0, "world"));
let e2 = world.spawn((12,));

{
let str_view = world.view::<&&str>();

assert_eq!(*str_view.get(e0).unwrap(), "hello");
assert_eq!(*str_view.get(e1).unwrap(), "world");
assert_eq!(str_view.get(e2), None);
}

{
let mut int_view = world.view::<&mut i32>();
assert_eq!(*int_view.get_mut(e0).unwrap(), 3);
assert_eq!(int_view.get_mut(e1), None);
assert_eq!(*int_view.get_mut(e2).unwrap(), 12);

// edit some value
*int_view.get_mut(e0).unwrap() = 100;
}

{
let mut int_str_view = world.view::<(&&str, &mut i32)>();
let (s, i) = int_str_view.get_mut(e0).unwrap();
assert_eq!(*s, "hello");
assert_eq!(*i, 100);
assert_eq!(int_str_view.get_mut(e1), None);
assert_eq!(int_str_view.get_mut(e2), None);
}
}

#[test]
fn view_mut_on_world() {
let mut world = World::new();
let e0 = world.spawn((3, "hello"));
let e1 = world.spawn((6.0, "world"));
let e2 = world.spawn((12,));

let str_view = world.view_mut::<&&str>();
assert_eq!(*str_view.get(e0).unwrap(), "hello");
assert_eq!(*str_view.get(e1).unwrap(), "world");
assert_eq!(str_view.get(e2), None);

let mut int_view = world.view_mut::<&mut i32>();
assert_eq!(*int_view.get_mut(e0).unwrap(), 3);
assert_eq!(int_view.get_mut(e1), None);
assert_eq!(*int_view.get_mut(e2).unwrap(), 12);

// edit some value
*int_view.get_mut(e0).unwrap() = 100;

let mut int_str_view = world.view_mut::<(&&str, &mut i32)>();
let (s, i) = int_str_view.get_mut(e0).unwrap();
assert_eq!(*s, "hello");
assert_eq!(*i, 100);
assert_eq!(int_str_view.get_mut(e1), None);
assert_eq!(int_str_view.get_mut(e2), None);
}

#[should_panic]
#[test]
fn view_mut_panic() {
let mut world = World::new();
let e = world.spawn(('a',));

// we should panic since we have two overlapping views:
let mut first_view = world.view::<&mut char>();
let mut second_view = world.view::<&mut char>();

first_view.get_mut(e).unwrap();
second_view.get_mut(e).unwrap();
}

#[test]
#[should_panic]
fn simultaneous_access_must_be_non_overlapping() {
Expand Down

0 comments on commit d5fdabd

Please sign in to comment.