Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add ChangeSet that can be collected into from iterators, and joined over for easy application to components #344

Merged
merged 1 commit into from
Feb 11, 2018
Merged
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
11 changes: 8 additions & 3 deletions specs-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern crate syn;

use proc_macro::TokenStream;
use quote::Tokens;
use syn::{Path, DeriveInput};
use syn::{DeriveInput, Path};
use syn::synom::Synom;

/// Custom derive macro for the `Component` trait.
Expand Down Expand Up @@ -47,9 +47,14 @@ fn impl_component(ast: &DeriveInput) -> Tokens {
let name = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

let storage = ast.attrs.iter()
let storage = ast.attrs
.iter()
.find(|attr| attr.path.segments[0].ident == "storage")
.map(|attr| syn::parse2::<StorageAttribute>(attr.tts.clone()).unwrap().storage)
.map(|attr| {
syn::parse2::<StorageAttribute>(attr.tts.clone())
.unwrap()
.storage
})
.unwrap_or_else(|| parse_quote!(DenseVecStorage));

quote! {
Expand Down
184 changes: 184 additions & 0 deletions src/changeset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use hibitset::BitSet;
use std::iter::FromIterator;
use std::ops::AddAssign;

use join::Join;
use storage::{DenseVecStorage, UnprotectedStorage};
use world::{Entity, Index};

/// Change set that can be collected from an iterator, and joined on for easy application to
/// components.
///
/// ### Example
///
/// ```rust
/// # extern crate specs;
/// # use specs::prelude::*;
///
/// pub struct Health(i32);
///
/// impl Component for Health {
/// type Storage = DenseVecStorage<Self>;
/// }
///
/// # fn main() {
/// # let mut world = World::new();
/// # world.register::<Health>();
///
/// let a = world.create_entity().with(Health(100)).build();
/// let b = world.create_entity().with(Health(200)).build();
///
/// let changeset = [(a, 32), (b, 12), (b, 13)]
/// .iter()
/// .cloned()
/// .collect::<ChangeSet<i32>>();
/// for (health, modifier) in (&mut world.write::<Health>(), &changeset).join() {
/// health.0 -= modifier;
/// }
/// # }
/// ```
pub struct ChangeSet<T> {
mask: BitSet,
inner: DenseVecStorage<T>,
}

impl<T> ChangeSet<T> {
/// Create a new change set
pub fn new() -> Self {
ChangeSet {
mask: BitSet::default(),
inner: DenseVecStorage::default(),
}
}

/// Add a value to the change set. If the entity already have a value in the change set, the
/// incoming value will be added to that.
pub fn add(&mut self, entity: Entity, value: T)
where
T: AddAssign,
{
if self.mask.contains(entity.id()) {
unsafe {
*self.inner.get_mut(entity.id()) += value;
}
} else {
unsafe {
self.inner.insert(entity.id(), value);
}
self.mask.add(entity.id());
}
}

/// Clear the changeset
pub fn clear(&mut self) {
for id in &self.mask {
unsafe {
self.inner.remove(id);
}
}
self.mask.clear();
}
}

impl<T> FromIterator<(Entity, T)> for ChangeSet<T>
where
T: AddAssign,
{
fn from_iter<I: IntoIterator<Item = (Entity, T)>>(iter: I) -> Self {
let mut changeset = ChangeSet::new();
for (entity, d) in iter {
changeset.add(entity, d);
}
changeset
}
}

impl<T> Extend<(Entity, T)> for ChangeSet<T>
where
T: AddAssign,
{
fn extend<I: IntoIterator<Item = (Entity, T)>>(&mut self, iter: I) {
for (entity, d) in iter {
self.add(entity, d);
}
}
}

impl<'a, T> Join for &'a mut ChangeSet<T> {
type Type = &'a mut T;
type Value = &'a mut DenseVecStorage<T>;
type Mask = &'a BitSet;

fn open(self) -> (Self::Mask, Self::Value) {
(&self.mask, &mut self.inner)
}

unsafe fn get(v: &mut Self::Value, id: Index) -> Self::Type {
let value: *mut Self::Value = v as *mut Self::Value;
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is necessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

It is taken from the Join on &mut Storage, it's required for the compiler to understand that v is mutable.

Copy link
Member Author

Choose a reason for hiding this comment

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

It fails to validate the lifetime without it.

Copy link
Member Author

Choose a reason for hiding this comment

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

The basic issue is that it fails to match &mut on the function signature to &'a for the Join implementation, and it's not possible to set &'a on the function signature because it would not match the trait then.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, right.

(*value).get_mut(id)
}
}

impl<'a, T> Join for &'a ChangeSet<T> {
type Type = &'a T;
type Value = &'a DenseVecStorage<T>;
type Mask = &'a BitSet;

fn open(self) -> (Self::Mask, Self::Value) {
(&self.mask, &self.inner)
}

unsafe fn get(value: &mut Self::Value, id: Index) -> Self::Type {
value.get(id)
}
}

impl<T> Join for ChangeSet<T> {
type Type = T;
type Value = DenseVecStorage<T>;
type Mask = BitSet;

fn open(self) -> (Self::Mask, Self::Value) {
(self.mask, self.inner)
}

unsafe fn get(value: &mut Self::Value, id: Index) -> Self::Type {
value.remove(id)
}
}

#[cfg(test)]
mod tests {
use super::ChangeSet;
use join::Join;
use storage::DenseVecStorage;
use world::{Component, World};

pub struct Health(i32);

impl Component for Health {
type Storage = DenseVecStorage<Self>;
}

#[test]
fn test() {
let mut world = World::new();
world.register::<Health>();

let a = world.create_entity().with(Health(100)).build();
let b = world.create_entity().with(Health(200)).build();
let c = world.create_entity().with(Health(300)).build();

let changeset = [(a, 32), (b, 12), (b, 13)]
.iter()
.cloned()
.collect::<ChangeSet<i32>>();
for (health, modifier) in (&mut world.write::<Health>(), &changeset).join() {
health.0 -= modifier;
}
let healths = world.read::<Health>();
assert_eq!(68, healths.get(a).unwrap().0);
assert_eq!(175, healths.get(b).unwrap().0);
assert_eq!(300, healths.get(c).unwrap().0);
}
}
3 changes: 1 addition & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ pub enum Error {
/// Wrong generation error.
WrongGeneration(WrongGeneration),

#[doc(hidden)]
__NonExhaustive,
#[doc(hidden)] __NonExhaustive,
}

impl Display for Error {
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,3 +228,5 @@ pub mod prelude;
pub mod storage;
/// Entities, resources, components, and general world management.
pub mod world;
/// Change set
pub mod changeset;
1 change: 1 addition & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub use shrev::ReaderId;
#[cfg(not(target_os = "emscripten"))]
pub use shred::AsyncDispatcher;

pub use changeset::ChangeSet;
pub use storage::{DenseVecStorage, FlaggedStorage, InsertedFlag, ModifiedFlag, ReadStorage,
RemovedFlag, Storage, Tracked, VecStorage, WriteStorage};
pub use world::{Component, Entities, Entity, EntityBuilder, LazyUpdate, World};
6 changes: 4 additions & 2 deletions src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut, Not};

use hibitset::{BitSet, BitSetNot, BitSetLike};
use hibitset::{BitSet, BitSetLike, BitSetNot};
use shred::Fetch;

use self::drain::Drain;
Expand Down Expand Up @@ -149,7 +149,9 @@ impl<T: Component> MaskedStorage<T> {
/// Drop an element by a given index.
pub fn drop(&mut self, id: Index) {
if self.mask.remove(id) {
unsafe { self.inner.drop(id); }
unsafe {
self.inner.drop(id);
}
}
}
}
Expand Down
6 changes: 2 additions & 4 deletions src/storage/restrict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,9 @@ where
#[inline]
fn assert_same_storage(&self, storage: &T::Storage) {
assert_eq!(
self.pointer,
storage as *const T::Storage,
self.pointer, storage as *const T::Storage,
"Attempt to get an unchecked entry from a storage: {:?} {:?}",
self.pointer,
storage as *const T::Storage
self.pointer, storage as *const T::Storage
);
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/storage/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -527,8 +527,7 @@ mod test {
let c2 = { restricted.get_mut_unchecked(&entry).0 };

assert_eq!(
c1,
c2,
c1, c2,
"Mutable and immutable gets returned different components."
);
assert!(
Expand Down Expand Up @@ -767,7 +766,7 @@ mod test {

let mut modified = BitSet::new();
let mut modified_id = s1.track_modified();

let mut removed = BitSet::new();
let mut removed_id = s1.track_removed();

Expand Down