From 20103792b93f78803e0c4bac060b4406902e81c0 Mon Sep 17 00:00:00 2001 From: Will Glynn Date: Thu, 29 Aug 2019 12:45:54 -0500 Subject: [PATCH] Switch VecStorage to MaybeUninit and add slice access --- .travis.yml | 4 +-- README.md | 2 +- docs/tutorials/src/05_storages.md | 11 +++++++- src/storage/storages.rs | 42 ++++++++++++++++++++++--------- src/storage/tests.rs | 26 +++++++++++++++++++ 5 files changed, 69 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc5b79967..01a183f5b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ language: rust rust: - nightly -- 1.34.0 +- 1.36.0 - stable cache: @@ -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"; diff --git a/README.md b/README.md index 147308fc6..872ea1baf 100644 --- a/README.md +++ b/README.md @@ -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] diff --git a/docs/tutorials/src/05_storages.md b/docs/tutorials/src/05_storages.md index a16439bd7..a0aaf94f5 100644 --- a/docs/tutorials/src/05_storages.md +++ b/docs/tutorials/src/05_storages.md @@ -53,6 +53,7 @@ Certain storages provide access to component slices: |Storage Type | Slice type | Density | Indices | |:----------------------:|---------------------|---------|---------------| | [`DenseVecStorage`] | `&[T]` | Dense | Arbitrary | +| [`VecStorage`] | `&[MaybeUninit]` | Sparse | Entity `id()` | | [`DefaultVecStorage`] | `&[T]` | Sparse | Entity `id()` | This is intended as an advanced technique. Component slices provide @@ -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` provides `as_slice()` and `as_mut_slice()` accessors which +return `&[MaybeUninit]`. 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 @@ -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. diff --git a/src/storage/storages.rs b/src/storage/storages.rs index 95985c44f..206af5dc0 100644 --- a/src/storage/storages.rs +++ b/src/storage/storages.rs @@ -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; @@ -219,35 +220,53 @@ unsafe impl DistinctStorage for NullStorage {} /// 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(Vec); +pub struct VecStorage(Vec>); + +impl SliceAccess for VecStorage { + type Element = MaybeUninit; + + #[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 UnprotectedStorage for VecStorage { unsafe fn clean(&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(); @@ -256,12 +275,11 @@ impl UnprotectedStorage for VecStorage { } // 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)) } } @@ -274,8 +292,8 @@ unsafe impl DistinctStorage for VecStorage {} /// 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(Vec); diff --git a/src/storage/tests.rs b/src/storage/tests.rs index b6ee6493f..b59a1fcd1 100644 --- a/src/storage/tests.rs +++ b/src/storage/tests.rs @@ -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(world: &mut World) -> WriteStorage where @@ -448,6 +449,27 @@ mod test { } } + fn test_maybeuninit_slice + Debug + Eq>() + where + T::Storage: Default + SliceAccess>, + { + let mut w = World::new(); + let mut s: Storage = 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::(); @@ -480,6 +502,10 @@ mod test { fn vec_test_anti() { test_anti::(); } + #[test] + fn vec_test_maybeuninit_slice() { + test_maybeuninit_slice::(); + } #[test] fn vec_arc() {