Skip to content

Commit

Permalink
Switch VecStorage to MaybeUninit and add slice access
Browse files Browse the repository at this point in the history
  • Loading branch information
willglynn committed Aug 29, 2019
1 parent c1d8a96 commit 2010379
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 16 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ language: rust

rust:
- nightly
- 1.34.0
- 1.36.0
- stable

cache:
Expand All @@ -22,7 +22,7 @@ script:
cargo build --all-features --verbose;
cargo test --all-features --verbose --no-run;
cargo bench --verbose --no-run --all-features;
elif [ "$TRAVIS_RUST_VERSION" == "1.34.0" ]; then
elif [ "$TRAVIS_RUST_VERSION" == "1.36.0" ]; then
cargo check --tests --no-default-features;
cargo check --tests --no-default-features --features "parallel";
cargo check --tests --no-default-features --features "serde";
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Unlike most other ECS libraries out there, it provides
other and you can use barriers to force several stages in system execution
* high performance for real-world applications

Minimum Rust version: 1.34
Minimum Rust version: 1.36

## [Link to the book][book]

Expand Down
11 changes: 10 additions & 1 deletion docs/tutorials/src/05_storages.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Certain storages provide access to component slices:
|Storage Type | Slice type | Density | Indices |
|:----------------------:|---------------------|---------|---------------|
| [`DenseVecStorage`] | `&[T]` | Dense | Arbitrary |
| [`VecStorage`] | `&[MaybeUninit<T>]` | Sparse | Entity `id()` |
| [`DefaultVecStorage`] | `&[T]` | Sparse | Entity `id()` |

This is intended as an advanced technique. Component slices provide
Expand Down Expand Up @@ -102,6 +103,13 @@ Therefore it would be a waste of memory to use this storage for
rare components, but it's best suited for commonly used components
(like transform values).

`VecStorage<T>` provides `as_slice()` and `as_mut_slice()` accessors which
return `&[MaybeUninit<T>]`. Consult the `Storage::mask()` to determine
which indices are populated. Slice indices cannot be converted to `Entity`
values because they lack a generation counter, but they do correspond to
`Entity::id()`s, so indices can be used to collate between multiple
`VecStorage`s.

## `DefaultVecStorage`

This storage works exactly like `VecStorage`, but instead of leaving gaps
Expand All @@ -113,4 +121,5 @@ writes than `VecStorage`.
which return `&[T]`. `Storage::mask()` can be used to determine which
indices are in active use, but all indices are fully initialized, so the
`mask()` is not necessary for safety. `DefaultVecStorage` indices all
correspond with each other and with `Entity::id()`s.
correspond with each other, with `VecStorage` indices, and with
`Entity::id()`s.
42 changes: 30 additions & 12 deletions src/storage/storages.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Different types of storages you can use for your components.
use std::collections::BTreeMap;
use std::mem::MaybeUninit;

use derivative::Derivative;
use hashbrown::HashMap;
Expand Down Expand Up @@ -219,35 +220,53 @@ unsafe impl<T> DistinctStorage for NullStorage<T> {}

/// Vector storage. Uses a simple `Vec`. Supposed to have maximum
/// performance for the components mostly present in entities.
///
/// `as_slice()` and `as_mut_slice()` indices correspond to
/// entity IDs. These can be compared to other `VecStorage`s, to
/// other `DefaultVecStorage`s, and to `Entity::id()`s for live
/// entities.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct VecStorage<T>(Vec<T>);
pub struct VecStorage<T>(Vec<MaybeUninit<T>>);

impl<T> SliceAccess<T> for VecStorage<T> {
type Element = MaybeUninit<T>;

#[inline]
fn as_slice(&self) -> &[Self::Element] {
self.0.as_slice()
}

#[inline]
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
self.0.as_mut_slice()
}
}

impl<T> UnprotectedStorage<T> for VecStorage<T> {
unsafe fn clean<B>(&mut self, has: B)
where
B: BitSetLike,
where
B: BitSetLike,
{
use std::ptr;
for (i, v) in self.0.iter_mut().enumerate() {
if has.contains(i as u32) {
ptr::drop_in_place(v);
// drop in place
ptr::drop_in_place(&mut *v.as_mut_ptr());
}
}
self.0.set_len(0);
}

unsafe fn get(&self, id: Index) -> &T {
self.0.get_unchecked(id as usize)
&*self.0.get_unchecked(id as usize).as_ptr()
}

unsafe fn get_mut(&mut self, id: Index) -> &mut T {
self.0.get_unchecked_mut(id as usize)
&mut *self.0.get_unchecked_mut(id as usize).as_mut_ptr()
}

unsafe fn insert(&mut self, id: Index, v: T) {
use std::ptr;

let id = id as usize;
if self.0.len() <= id {
let delta = id + 1 - self.0.len();
Expand All @@ -256,12 +275,11 @@ impl<T> UnprotectedStorage<T> for VecStorage<T> {
}
// Write the value without reading or dropping
// the (currently uninitialized) memory.
ptr::write(self.0.get_unchecked_mut(id), v);
*self.0.get_unchecked_mut(id as usize) = MaybeUninit::new(v);
}

unsafe fn remove(&mut self, id: Index) -> T {
use std::ptr;

ptr::read(self.get(id))
}
}
Expand All @@ -274,8 +292,8 @@ unsafe impl<T> DistinctStorage for VecStorage<T> {}
/// Requires the component to implement `Default`.
///
/// `as_slice()` and `as_mut_slice()` indices correspond to entity IDs.
/// These can be compared to other `DefaultVecStorage`s, and to
/// `Entity::id()`s for live entities.
/// These can be compared to other `DefaultVecStorage`s, to other
/// `VecStorage`s, and to `Entity::id()`s for live entities.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct DefaultVecStorage<T>(Vec<T>);
Expand Down
26 changes: 26 additions & 0 deletions src/storage/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::any::Any;
use super::*;
use crate::world::{Component, Entity, Generation, Index, WorldExt};
use shred::World;
use std::mem::MaybeUninit;

fn create<T: Component>(world: &mut World) -> WriteStorage<T>
where
Expand Down Expand Up @@ -448,6 +449,27 @@ mod test {
}
}

fn test_maybeuninit_slice<T: Component + From<u32> + Debug + Eq>()
where
T::Storage: Default + SliceAccess<T, Element=MaybeUninit<T>>,
{
let mut w = World::new();
let mut s: Storage<T, _> = create(&mut w);

for i in 0..1_000 {
if let Err(err) = s.insert(Entity::new(i, Generation::new(1)), (i + 2718).into()) {
panic!("Failed to insert component into entity! {:?}", err);
}
}

let slice = s.as_slice();
assert_eq!(slice.len(), 1_000);
for (i, v) in slice.iter().enumerate() {
let v = unsafe { &*v.as_ptr() };
assert_eq!(v, &(i as u32 + 2718).into());
}
}

#[test]
fn vec_test_add() {
test_add::<Cvec>();
Expand Down Expand Up @@ -480,6 +502,10 @@ mod test {
fn vec_test_anti() {
test_anti::<Cvec>();
}
#[test]
fn vec_test_maybeuninit_slice() {
test_maybeuninit_slice::<Cvec>();
}

#[test]
fn vec_arc() {
Expand Down

0 comments on commit 2010379

Please sign in to comment.