Repository: Ralith/hecs
Pull request #367 (2024-03-22 02:50:33)

view on world
solves #328


Patch:
diff --git a/src/lib.rs b/src/lib.rs
index 53c6646..dd07786 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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;
diff --git a/src/query.rs b/src/query.rs
index 1b470b8..2f7d93f 100644
--- a/src/query.rs
+++ b/src/query.rs
@@ -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;
     }
 
@@ -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());
         }
     }
 }
@@ -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| {
@@ -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 => (),
@@ -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::*;
diff --git a/src/world.rs b/src/world.rs
index 8118ab6..d6e7bc6 100644
--- a/src/world.rs
+++ b/src/world.rs
@@ -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
@@ -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


Test patch:
diff --git a/tests/tests.rs b/tests/tests.rs
index bece118..9183a74 100644
--- a/tests/tests.rs
+++ b/tests/tests.rs
@@ -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() {