Skip to content

Commit

Permalink
Add slice access
Browse files Browse the repository at this point in the history
  • Loading branch information
willglynn committed Aug 29, 2019
1 parent ef5b2f6 commit c1d8a96
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 12 deletions.
47 changes: 40 additions & 7 deletions docs/tutorials/src/05_storages.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,34 @@ has 4 layers).
Here a list of the storages with a short description and a link
to the corresponding heading.

|Storage Type |Description |Optimized for |
|:-------------------:|--------------------------|------------------------------|
| [`BTreeStorage`] | Works with a `BTreeMap` | no particular case |
| [`DenseVecStorage`] | Uses a redirection table | fairly often used components |
| [`HashMapStorage`] | Uses a `HashMap` | rare components |
| [`NullStorage`] | Can flag entities | doesn't depend on rarity |
| [`VecStorage`] | Uses a sparse `Vec` | commonly used components |
|Storage Type |Description |Optimized for |
|:----------------------:|----------------------------------------------------|------------------------------|
| [`BTreeStorage`] | Works with a `BTreeMap` | no particular case |
| [`DenseVecStorage`] | Uses a redirection table | fairly often used components |
| [`HashMapStorage`] | Uses a `HashMap` | rare components |
| [`NullStorage`] | Can flag entities | doesn't depend on rarity |
| [`VecStorage`] | Uses a sparse `Vec`, empty slots are uninitialized | commonly used components |
| [`DefaultVecStorage`] | Uses a sparse `Vec`, empty slots contain `Default` | commonly used components |

[`BTreeStorage`]: #btreestorage
[`DenseVecStorage`]: #densevecstorage
[`HashMapStorage`]: #hashmapstorage
[`NullStorage`]: #nullstorage
[`VecStorage`]: #vecstorage
[`DefaultVecStorage`]: #defaultvecstorage

## Slices

Certain storages provide access to component slices:

|Storage Type | Slice type | Density | Indices |
|:----------------------:|---------------------|---------|---------------|
| [`DenseVecStorage`] | `&[T]` | Dense | Arbitrary |
| [`DefaultVecStorage`] | `&[T]` | Sparse | Entity `id()` |

This is intended as an advanced technique. Component slices provide
maximally efficient reads and writes, but they are incompatible with
many of the usual abstractions which makes them more difficult to use.

## `BTreeStorage`

Expand All @@ -57,6 +72,11 @@ one which provides a mapping from the entity id to the index for the data vec
(it's a redirection table). This is useful when your component is bigger
than a `usize` because it consumes less RAM.

`DefaultVecStorage<T>` provides `as_slice()` and `as_mut_slice()` accessors
which return `&[T]`. The indices in this slice do not correspond to entity
IDs, nor do they correspond to indices in any other storage, nor do they
correspond to indices in this storage at a different point in time.

## `HashMapStorage`

This should be used for components which are associated with very few entities,
Expand All @@ -81,3 +101,16 @@ just leaves uninitialized gaps where we don't have any component.
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).

## `DefaultVecStorage`

This storage works exactly like `VecStorage`, but instead of leaving gaps
uninitialized, it fills them with the component's default value. This
requires the component to `impl Default`, and it results in more memory
writes than `VecStorage`.

`DefaultVecStorage` provides `as_slice()` and `as_mut_slice()` accessors
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.
107 changes: 107 additions & 0 deletions examples/slices.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
extern crate specs;

use specs::prelude::*;

// A component contains data which is associated with an entity.

#[derive(Debug)]
struct Vel(f32);

impl Component for Vel {
type Storage = DefaultVecStorage<Self>;
}

impl Default for Vel {
fn default() -> Self {
Self(0.0)
}
}

#[derive(Debug)]
struct Pos(f32);

impl Component for Pos {
type Storage = DefaultVecStorage<Self>;
}

impl Default for Pos {
fn default() -> Self {
Self(0.0)
}
}

struct SysA;

impl<'a> System<'a> for SysA {
// These are the resources required for execution.
// You can also define a struct and `#[derive(SystemData)]`,
// see the `full` example.
type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>);

fn run(&mut self, (mut pos, vel): Self::SystemData) {
// Both the `Pos` and `Vel` components use `DefaultVecStorage`, which supports
// `as_slice()` and `as_mut_slice()`. This lets us access components without
// indirection.
let mut pos_slice = pos.as_mut_slice();
let vel_slice = vel.as_slice();

// Note that an entity which has position but not velocity will still have
// an entry in both slices. `DefaultVecStorage` is sparse, and here is where
// that matters: the storage has space for many entities, but not all of them
// contain meaningful values. These slices may be a mix of present and absent
// (`Default`) data.
//
// We could check the `mask()` before reading the velocity and updating the
// position, and this is what `.join()` normally does. However, because:
//
// 1. `Vel` uses `DefaultVecStorage`,
// 2. `Vel`'s default is 0.0, and
// 3. `Pos` += 0.0 is a no-op,
//
// we can unconditionally add `Vel` to `Pos` without reading the `mask()`!
// This results in a tight inner loop of known size, which is especially
// suitable for SIMD, OpenCL, CUDA, and other accelerator technologies.
//
// Finally, note that `DefaultVecStorage` and `VecStorage` slice indices
// always agree. If an entity is at location `i` in one `VecStorage`, it
// will be at location `i` in every other `VecStorage`. (By contrast,
// `DenseVecStorage` uses unpredictable indices and cannot be used in
// this way.) We need only worry about handling slices of different
// lengths.
let len = pos_slice.len().min(vel_slice.len());
for i in 0..len {
pos_slice[i].0 += vel_slice[i].0;
}
}
}

fn main() {
// The `World` is our
// container for components
// and other resources.

let mut world = World::new();

// This builds a dispatcher.
// The third parameter of `add` specifies
// logical dependencies on other systems.
// Since we only have one, we don't depend on anything.
// See the `full` example for dependencies.
let mut dispatcher = DispatcherBuilder::new().with(SysA, "sys_a", &[]).build();

// setup() must be called before creating any entity, it will register
// all Components and Resources that Systems depend on
dispatcher.setup(&mut world);

// An entity may or may not contain some component.

world.create_entity().with(Vel(2.0)).with(Pos(0.0)).build();
world.create_entity().with(Vel(4.0)).with(Pos(1.6)).build();
world.create_entity().with(Vel(1.5)).with(Pos(5.4)).build();

// This entity does not have `Vel`, so it won't be dispatched.
world.create_entity().with(Pos(2.0)).build();

// This dispatches all the systems in parallel (but blocking).
dispatcher.dispatch(&world);
}
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ pub use crate::{
changeset::ChangeSet,
join::Join,
storage::{
DenseVecStorage, FlaggedStorage, HashMapStorage, NullStorage, ReadStorage, Storage,
Tracked, VecStorage, WriteStorage,
DenseVecStorage, DefaultVecStorage, FlaggedStorage, HashMapStorage, NullStorage,
ReadStorage, Storage, Tracked, VecStorage, WriteStorage,
},
world::{Builder, Component, Entities, Entity, EntityBuilder, LazyUpdate, WorldExt},
};
4 changes: 2 additions & 2 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pub use shred::AsyncDispatcher;
pub use crate::{
changeset::ChangeSet,
storage::{
ComponentEvent, DenseVecStorage, FlaggedStorage, HashMapStorage, NullStorage, ReadStorage,
Storage, Tracked, VecStorage, WriteStorage,
ComponentEvent, DenseVecStorage, DefaultVecStorage, FlaggedStorage, HashMapStorage,
NullStorage, ReadStorage, Storage, Tracked, VecStorage, WriteStorage,
},
world::{Builder, Component, Entities, Entity, EntityBuilder, LazyUpdate, WorldExt},
};
34 changes: 33 additions & 1 deletion src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ pub use self::{
ImmutableParallelRestriction, MutableParallelRestriction, RestrictedStorage,
SequentialRestriction,
},
storages::{BTreeStorage, DenseVecStorage, HashMapStorage, NullStorage, VecStorage},
storages::{BTreeStorage, DenseVecStorage, DefaultVecStorage, HashMapStorage, NullStorage, VecStorage},
track::{ComponentEvent, Tracked},
};

use self::storages::SliceAccess;

use std::{
self,
marker::PhantomData,
Expand Down Expand Up @@ -259,6 +261,36 @@ where
}
}

impl<'e, T, D> Storage<'e, T, D>
where
T: Component,
D: Deref<Target = MaskedStorage<T>>,
T::Storage: SliceAccess<T>
{
/// Returns the component data as a slice.
///
/// The indices of this slice may not correspond to anything in particular.
/// Check the underlying storage documentation for details.
pub fn as_slice(&self) -> &[<T::Storage as SliceAccess<T>>::Element] {
self.data.inner.as_slice()
}
}

impl<'e, T, D> Storage<'e, T, D>
where
T: Component,
D: DerefMut<Target = MaskedStorage<T>>,
T::Storage: SliceAccess<T>
{
/// Returns the component data as a slice.
///
/// The indices of this slice may not correspond to anything in particular.
/// Check the underlying storage documentation for details.
pub fn as_mut_slice(&mut self) -> &mut [<T::Storage as SliceAccess<T>>::Element] {
self.data.inner.as_mut_slice()
}
}

impl<'e, T, D> Storage<'e, T, D>
where
T: Component,
Expand Down
110 changes: 110 additions & 0 deletions src/storage/storages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ use crate::{
world::Index,
};

/// Some storages can provide slices to access the underlying data.
///
/// The underlying data may be of type `T`, or it may be of a type
/// which wraps `T`. The associated type `Element` identifies what
/// the slices will contain.
pub trait SliceAccess<T> {
type Element;

fn as_slice(&self) -> &[Self::Element];
fn as_mut_slice(&mut self) -> &mut [Self::Element];
}

/// BTreeMap-based storage.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
Expand Down Expand Up @@ -83,6 +95,12 @@ unsafe impl<T> DistinctStorage for HashMapStorage<T> {}
///
/// Note that this only stores the data (`T`) densely; indices
/// to the data are stored in a sparse `Vec`.
///
/// `as_slice()` and `as_mut_slice()` indices are local to this
/// `DenseVecStorage` at this particular moment. These indices
/// cannot be compared with indices from any other storage, and
/// a particular entity's position within this slice may change
/// over time.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct DenseVecStorage<T> {
Expand All @@ -91,6 +109,28 @@ pub struct DenseVecStorage<T> {
data_id: Vec<Index>,
}

impl<T> SliceAccess<T> for DenseVecStorage<T> {
type Element = T;

/// Returns a slice of all the components in this storage.
///
/// Indices inside the slice do not correspond to anything in particular, and
/// especially do not correspond with entity IDs.
#[inline]
fn as_slice(&self) -> &[Self::Element] {
self.data.as_slice()
}

/// Returns a mutable slice of all the components in this storage.
///
/// Indices inside the slice do not correspond to anything in particular, and
/// especially do not correspond with entity IDs.
#[inline]
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
self.data.as_mut_slice()
}
}

impl<T> UnprotectedStorage<T> for DenseVecStorage<T> {
unsafe fn clean<B>(&mut self, _has: B)
where
Expand Down Expand Up @@ -227,3 +267,73 @@ impl<T> UnprotectedStorage<T> for VecStorage<T> {
}

unsafe impl<T> DistinctStorage for VecStorage<T> {}

/// Vector storage, like `VecStorage`, but allows safe access to the
/// interior slices because unused slots are always initialized.
///
/// 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.
#[derive(Derivative)]
#[derivative(Default(bound = ""))]
pub struct DefaultVecStorage<T>(Vec<T>);

impl<T> UnprotectedStorage<T> for DefaultVecStorage<T> where T: Default {
unsafe fn clean<B>(&mut self, _has: B)
where
B: BitSetLike,
{
self.0.clear();
}

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

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

unsafe fn insert(&mut self, id: Index, v: T) {
let id = id as usize;

if self.0.len() <= id {
// fill all the empty slots with default values
self.0.resize_with(id, Default::default);
// store the desired value
self.0.push(v)
} else {
// store the desired value directly
self.0[id] = v;
}
}

unsafe fn remove(&mut self, id: Index) -> T {
// make a new default value
let mut v = T::default();
// swap it into the vec
std::ptr::swap(self.0.get_unchecked_mut(id as usize), &mut v);
// return the old value
return v;
}
}

unsafe impl<T> DistinctStorage for DefaultVecStorage<T> {}

impl<T> SliceAccess<T> for DefaultVecStorage<T> {
type Element = T;

/// Returns a slice of all the components in this storage.
#[inline]
fn as_slice(&self) -> &[Self::Element] {
self.0.as_slice()
}

/// Returns a mutable slice of all the components in this storage.
#[inline]
fn as_mut_slice(&mut self) -> &mut [Self::Element] {
self.0.as_mut_slice()
}
}
Loading

0 comments on commit c1d8a96

Please sign in to comment.