Skip to content

Commit

Permalink
Merge #93
Browse files Browse the repository at this point in the history
93: FlaggedStorage r=torkleyy
A basic implementation for a `TrackedStorage` (#89).

This version of the `TrackedStorage` does not implement a way to compare the past version and the modifies version of the components. Usage requires unwrapping the storage to the base `TrackedStorage` which implements `Join` for flagged components.

### Alternative design choices:
**Implement `TrackedStorage` similar to how `MaskedStorage` works.**

_Pros:_
- Would make it possible to have the `Join` of the storage only work on the flagged components without calling any additional methods.
- Might open up a path for a generic storage wrapper like `UnprotectedStorage` but instead as a container for how the `Join` gets the components necessary for the loop (this could be interesting since it would allow for different methods other than bitsets, but would also be pretty hard to get right).

_Cons:_
- Difficult to get all the components instead of just the flagged ones as there is no current way to directly get access to the container storage.
- Might be unintuitive at a glance as you would expect to get all the components when joining the storage.

**Compare modifications for more accurate flags**

_Pros:_
- Removes a large concern for if the user iterates over a mutable tracked storage.

_Cons:_
- Performance benefits of the tracked storage decreases.
- Difficult to implement. Requires a way to directly compare the new "modified" version with the past version.
  • Loading branch information
bors[bot] committed Jun 6, 2017
2 parents 2535555 + 302e9e3 commit 9d50cba
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ pub mod prelude {

pub use data::{Entities, Fetch, FetchMut, ReadStorage, WriteStorage};
pub use join::Join;
pub use storages::{DenseVecStorage, HashMapStorage, VecStorage};
pub use storages::{DenseVecStorage, HashMapStorage, VecStorage, FlaggedStorage};
}

pub use storage::storages;
Expand Down
14 changes: 13 additions & 1 deletion src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use mopa::Any;
use shred::{Fetch, FetchMut, ResourceId, Resources, SystemData};

use join::Join;
use world::{Component, Entity, Entities};
use world::{Component, Entity, EntityIndex, Entities};
use Index;

#[cfg(feature="serialize")]
Expand Down Expand Up @@ -145,6 +145,18 @@ pub struct Entry<'a, 'e, T, D> {
phantom: PhantomData<&'a ()>,
}

impl<'a, 'e, T, D> EntityIndex for Entry<'a, 'e, T, D> {
fn index(&self) -> Index {
self.id
}
}

impl<'a, 'b, 'e, T, D> EntityIndex for &'b Entry<'a, 'e, T, D> {
fn index(&self) -> Index {
(*self).index()
}
}

/// A storage type that iterates entities that have
/// a particular component type, but does not return the
/// component.
Expand Down
140 changes: 139 additions & 1 deletion src/storage/storages.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
//! Different types of storages you can use for your components.

use std::collections::BTreeMap;
use std::marker::PhantomData;

use fnv::FnvHashMap;
use hibitset::BitSet;

use storage::UnprotectedStorage;
use Index;
use world::EntityIndex;
use {Join, Index};

/// HashMap-based storage. Best suited for rare components.
pub struct HashMapStorage<T>(FnvHashMap<Index, T>);
Expand Down Expand Up @@ -194,3 +197,138 @@ impl<T: Default> UnprotectedStorage<T> for NullStorage<T> {
Default::default()
}
}

/// Wrapper storage that stores modifications to components in a bitset.
///
/// **Note: Never use `.iter()` on a mutable component storage that uses this.**
///
///# Example Usage:
///
/// ```rust
/// extern crate specs;
/// use specs::prelude::*;
///
/// pub struct Comp(u32);
/// impl Component for Comp {
/// // `FlaggedStorage` acts as a wrapper around another storage.
/// // You can put any store inside of here (e.g. HashMapStorage, VecStorage, etc.)
/// type Storage = FlaggedStorage<Comp, VecStorage<Comp>>;
/// }
///
/// pub struct CompSystem;
/// impl<'a> System<'a> for CompSystem {
/// type SystemData = WriteStorage<'a, Comp>;
/// fn run(&mut self, mut comps: WriteStorage<'a, Comp>) {
/// // Iterates over all components like normal.
/// for comp in (&comps).join() {
/// // ...
/// }
///
/// // **Never do this**
/// // This will flag all components as modified regardless of whether the inner loop
/// // did modify their data.
/// for comp in (&mut comps).join() {
/// // ...
/// }
///
/// // Instead do something like:
/// for mut entry in (&comps.check()).join() {
/// if true { // check whether this component should be modified.
/// let mut comp = comps.get_mut_unchecked(&mut entry);
/// // ...
/// }
/// }
///
/// // To iterate over the flagged/modified components:
/// for flagged_comp in ((&comps).open().1).join() {
/// // ...
/// }
///
/// // Clears the tracked storage every frame with this system.
/// (&mut comps).open().1.clear_flags();
/// }
/// }
///# fn main() { }
/// ```
pub struct FlaggedStorage<C, T> {
mask: BitSet,
storage: T,
phantom: PhantomData<C>,
}

impl<C, T: UnprotectedStorage<C>> UnprotectedStorage<C> for FlaggedStorage<C, T> {
fn new() -> Self {
FlaggedStorage {
mask: BitSet::new(),
storage: T::new(),
phantom: PhantomData,
}
}
unsafe fn clean<F>(&mut self, has: F) where F: Fn(Index) -> bool {
self.mask.clear();
self.storage.clean(has);
}
unsafe fn get(&self, id: Index) -> &C {
self.storage.get(id)
}
unsafe fn get_mut(&mut self, id: Index) -> &mut C {
// calling `.iter()` on an unconstrained mutable storage will flag everything
self.mask.add(id);
self.storage.get_mut(id)
}
unsafe fn insert(&mut self, id: Index, comp: C) {
self.mask.add(id);
self.storage.insert(id, comp);
}
unsafe fn remove(&mut self, id: Index) -> C {
self.mask.remove(id);
self.storage.remove(id)
}
}

impl<C, T: UnprotectedStorage<C>> FlaggedStorage<C, T> {
/// Whether the component related to the entity was flagged or not.
pub fn flagged<E: EntityIndex>(&self, entity: E) -> bool {
self.mask.contains(entity.index())
}
/// All components will be cleared of being flagged.
pub fn clear_flags(&mut self) {
self.mask.clear();
}
/// Flags a single component as not flagged.
pub fn unflag<E: EntityIndex>(&mut self, entity: E) {
self.mask.remove(entity.index());
}
/// Flags a single component as flagged.
pub fn flag<E: EntityIndex>(&mut self, entity: E) {
self.mask.add(entity.index());
}
}

impl<'a, C, T: UnprotectedStorage<C>> Join for &'a FlaggedStorage<C, T> {
type Type = &'a C;
type Value = &'a T;
type Mask = &'a BitSet;
fn open(self) -> (Self::Mask, Self::Value) {
(&self.mask, &self.storage)
}
unsafe fn get(v: &mut Self::Value, id: Index) -> &'a C {
v.get(id)
}
}

impl<'a, C, T: UnprotectedStorage<C>> Join for &'a mut FlaggedStorage<C, T> {
type Type = &'a mut C;
type Value = &'a mut T;
type Mask = &'a BitSet;
fn open(self) -> (Self::Mask, Self::Value) {
(&self.mask, &mut self.storage)
}
unsafe fn get(v: &mut Self::Value, id: Index) -> &'a mut C {
// similar issue here as the `Storage<T, A, D>` implementation
use std::mem;
let value: &'a mut Self::Value = mem::transmute(v);
value.get_mut(id)
}
}

66 changes: 66 additions & 0 deletions src/storage/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ mod test {
impl Component for Cvec {
type Storage = VecStorage<Cvec>;
}

#[derive(PartialEq, Eq, Debug)]
struct FlaggedCvec(u32);
impl From<u32> for FlaggedCvec {
fn from(v: u32) -> FlaggedCvec {
FlaggedCvec(v)
}
}
impl AsMut<u32> for FlaggedCvec {
fn as_mut(&mut self) -> &mut u32 {
&mut self.0
}
}
impl Component for FlaggedCvec {
type Storage = FlaggedStorage<FlaggedCvec, VecStorage<FlaggedCvec>>;
}

#[derive(PartialEq, Eq, Debug)]
struct Cmap(u32);
Expand Down Expand Up @@ -428,6 +444,56 @@ mod test {
// not the original.
}
}

#[test]
fn flagged() {
use join::Join;
let mut w = World::new();
w.register_with_id::<FlaggedCvec>(1);
w.register_with_id::<FlaggedCvec>(2);
let mut s1: Storage<FlaggedCvec, _> = w.write_with_id(1);
let mut s2: Storage<FlaggedCvec, _> = w.write_with_id(2);

for i in 0..15 {
// Test insertion flagging
s1.insert(Entity::new(i, Generation::new(1)), i.into());
assert!(s1.open().1.flagged(Entity::new(i, Generation::new(1))));

if i % 2 == 0 {
s2.insert(Entity::new(i, Generation::new(1)), i.into());
assert!(s2.open().1.flagged(Entity::new(i, Generation::new(1))));
}
}

(&mut s1).open().1.clear_flags();

// Cleared flags
for c1 in ((&s1).check()).join() {
assert!(!s1.open().1.flagged(&c1));
}

// Modify components to flag.
for (c1, c2) in (&mut s1, &s2).join() {
println!("{:?} {:?}", c1, c2);
c1.0 += c2.0;
}

for c1 in (s1.check()).join() {
// Should only be modified if the entity had both components
// Which means only half of them should have it.
if s1.open().1.flagged(&c1) {
println!("Flagged: {:?}", c1.index());
// Only every other component was flagged.
assert!(c1.index() % 2 == 0);
}
}

// Iterate over all flagged entities.
for (entity, _) in (&*w.entities(), s1.open().1).join() {
// All entities in here should be flagged.
assert!(s1.open().1.flagged(&entity));
}
}
}

#[cfg(feature="serialize")]
Expand Down
19 changes: 19 additions & 0 deletions src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ impl<'a> Iterator for CreateIterAtomic<'a> {
}
}

/// Any type that contains an entity's index.
///
/// e.g. Entry, Entity, etc.
pub trait EntityIndex {
fn index(&self) -> Index;
}

impl EntityIndex for Entity {
fn index(&self) -> Index {
self.id()
}
}

impl<'a> EntityIndex for &'a Entity {
fn index(&self) -> Index {
(*self).index()
}
}

/// `Entity` type, as seen by the user.
#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct Entity(Index, Generation);
Expand Down

0 comments on commit 9d50cba

Please sign in to comment.