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

New flagged storage for parallel joins #737

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -71,8 +71,9 @@ name = "cluster_bomb"
[[example]]
name = "bitset"

[[example]]
name = "track"
# TODO: restricted storage is unsound without streaming iterator or some changes made to it (i.e. to not allow access to component data on other entities in the mutable case)
Copy link
Contributor

Choose a reason for hiding this comment

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

My addition of GAT support of the storage traits should allow for this, I think.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, Join::Type would probably need to be made into a GAT as well. Are there any GAT based iterator crates that could serve as a replacement for the Iterator trait from std? I'm not seeing any 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

It's pretty trivial to create one.

trait StreamingIterator {
    type Item<'a>;

    fn next(&mut self) -> Self::Item<'_>;
}

I've not seen any 'canonical' streaming iterator crates that use GATs yet. Perhaps when std gets such a trait (here's hoping!) with for integration we can use that instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It will need some of the common adapters/methods: map, for_each, filter, filter_map, collect. Which seems fairly doable but is a bit more than the just the minimal version.

# [[example]]
# name = "track"

[[example]]
name = "ordered_track"
2 changes: 1 addition & 1 deletion src/changeset.rs
Original file line number Diff line number Diff line change
@@ -126,7 +126,7 @@ impl<'a, T> Join for &'a mut ChangeSet<T> {
// exists yet.
unsafe fn get(v: &mut Self::Value, id: Index) -> Self::Type {
let value: *mut Self::Value = v as *mut Self::Value;
(*value).get_mut(id)
(*value).get_access_mut(id)
}
}

22 changes: 18 additions & 4 deletions src/join/mod.rs
Original file line number Diff line number Diff line change
@@ -224,14 +224,24 @@ pub trait Join {
/// then illegal memory access can occur.
unsafe fn open(self) -> (Self::Mask, Self::Value);

// TODO: copy safety notes for callers to the impls of this method.
// TODO: also evaluate all impls
/// Get a joined component value by a given index.
///
/// # Safety
///
/// * A call to `get` must be preceded by a check if `id` is part of
/// `Self::Mask`
/// * The implementation of this method may use unsafe code, but has no
/// invariants to meet
/// `Self::Mask`.
/// * The caller must ensure the lifetimes of mutable references returned from a call
/// of this method end before subsequent calls with the same `id`.
/// * Conversly, the implementation of the method must never create a mutable reference (even if it isn't
/// returned) that was returned by a previous call with a distinct `id`. Since there is no
/// requirement that the caller (if there was `JoinIter` would half to be a streaming
/// iterator).
/// are no guarantees that the caller will release
/// * The implementation of this method may use `unsafe` to extend the lifetime of returned references but
/// must ensure that any references within Self::Type cannot outlive the references
/// they were derived from in Self::Value.
unsafe fn get(value: &mut Self::Value, id: Index) -> Self::Type;

/// If this `Join` typically returns all indices in the mask, then iterating
@@ -322,6 +332,10 @@ impl<J: Join> JoinIter<J> {
}

impl<J: Join> JoinIter<J> {
// TODO: these are unsound because they can be used to get two mutable references to the same
// component (in safe code)

/*
/// Allows getting joined values for specific entity.
///
/// ## Example
@@ -394,7 +408,7 @@ impl<J: Join> JoinIter<J> {
} else {
None
}
}
}*/
}

impl<J: Join> std::iter::Iterator for JoinIter<J> {
13 changes: 8 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#![warn(missing_docs)]
#![cfg_attr(feature = "nightly", feature(generic_associated_types, associated_type_defaults))]
#![cfg_attr(
feature = "nightly",
feature(generic_associated_types, associated_type_defaults)
)]

//! # SPECS Parallel ECS
//!
@@ -207,9 +210,9 @@ pub mod world;

pub use hibitset::BitSet;
pub use shred::{
Accessor, AccessorCow, BatchAccessor, BatchController, BatchUncheckedWorld,
Dispatcher, DispatcherBuilder, Read, ReadExpect, RunNow,
RunningTime, StaticAccessor, System, SystemData, World, Write, WriteExpect,
Accessor, AccessorCow, BatchAccessor, BatchController, BatchUncheckedWorld, Dispatcher,
DispatcherBuilder, Read, ReadExpect, RunNow, RunningTime, StaticAccessor, System, SystemData,
World, Write, WriteExpect,
};
pub use shrev::ReaderId;

@@ -232,4 +235,4 @@ pub use crate::{
};

#[cfg(feature = "nightly")]
pub use crate::storage::DerefFlaggedStorage;
pub use crate::storage::UnsplitFlaggedStorage;
12 changes: 10 additions & 2 deletions src/storage/deref_flagged.rs
Original file line number Diff line number Diff line change
@@ -55,6 +55,7 @@ where
}

impl<C: Component, T: UnprotectedStorage<C>> UnprotectedStorage<C> for DerefFlaggedStorage<C, T> {
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = FlaggedAccessMut<'a, <T as UnprotectedStorage<C>>::AccessMut<'a>, C>;

unsafe fn clean<B>(&mut self, has: B)
@@ -68,13 +69,20 @@ impl<C: Component, T: UnprotectedStorage<C>> UnprotectedStorage<C> for DerefFlag
self.storage.get(id)
}

unsafe fn get_mut(&mut self, id: Index) -> Self::AccessMut<'_> {
unsafe fn get_mut(&mut self, id: Index) -> &mut C {
if self.emit_event() {
self.channel.single_write(ComponentEvent::Modified(id));
}
self.storage.get_mut(id),
}

unsafe fn get_access_mut(&mut self, id: Index) -> Self::AccessMut<'_> {
let emit = self.emit_event();
FlaggedAccessMut {
channel: &mut self.channel,
emit,
id,
access: self.storage.get_mut(id),
access: self.storage.get_access_mut(id),
phantom: PhantomData,
}
}
12 changes: 8 additions & 4 deletions src/storage/entry.rs
Original file line number Diff line number Diff line change
@@ -194,20 +194,24 @@ where
pub fn get_mut(&mut self) -> AccessMutReturn<'_, T> {
// SAFETY: This is safe since `OccupiedEntry` is only constructed
// after checking the mask.
unsafe { self.storage.data.inner.get_mut(self.id) }
unsafe { self.storage.data.inner.get_access_mut(self.id) }
}

/// Converts the `OccupiedEntry` into a mutable reference bounded by
/// the storage's lifetime.
pub fn into_mut(self) -> AccessMutReturn<'a, T> {
// SAFETY: This is safe since `OccupiedEntry` is only constructed
// after checking the mask.
unsafe { self.storage.data.inner.get_mut(self.id) }
unsafe { self.storage.data.inner.get_access_mut(self.id) }
}

/// Inserts a value into the storage and returns the old one.
pub fn insert(&mut self, mut component: T) -> T {
std::mem::swap(&mut component, self.get_mut().deref_mut());
// SAFETY: This is safe since `OccupiedEntry` is only constructed
// after checking the mask.
std::mem::swap(&mut component, unsafe {
self.storage.data.inner.get_mut(self.id)
});
component
}

@@ -235,7 +239,7 @@ where
// SAFETY: This is safe since we added `self.id` to the mask.
unsafe {
self.storage.data.inner.insert(self.id, component);
self.storage.data.inner.get_mut(self.id)
self.storage.data.inner.get_access_mut(self.id)
}
}
}
19 changes: 15 additions & 4 deletions src/storage/flagged.rs
Original file line number Diff line number Diff line change
@@ -15,6 +15,9 @@ use shrev::EventChannel;
/// **Note:** Joining over all components of a `FlaggedStorage`
/// mutably will flag all components.
///
/// **Note:** restricted storages are currently removed since they need some changes to be sound so
/// the below advice won't currently work. Use `UnsplitFlaggedStorage` instead.
///
/// What you want to instead is to use `restrict_mut()` to first
/// get the entities which contain the component and then conditionally
/// modify the component after a call to `get_mut_unchecked()` or `get_mut()`.
@@ -201,6 +204,7 @@ where

impl<C: Component, T: UnprotectedStorage<C>> UnprotectedStorage<C> for FlaggedStorage<C, T> {
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = <T as UnprotectedStorage<C>>::AccessMut<'a>;

unsafe fn clean<B>(&mut self, has: B)
@@ -214,20 +218,27 @@ impl<C: Component, T: UnprotectedStorage<C>> UnprotectedStorage<C> for FlaggedSt
self.storage.get(id)
}

#[cfg(feature = "nightly")]
unsafe fn get_mut(&mut self, id: Index) -> <T as UnprotectedStorage<C>>::AccessMut<'_> {
unsafe fn get_mut(&mut self, id: Index) -> &mut C {
if self.emit_event() {
self.channel.single_write(ComponentEvent::Modified(id));
}
self.storage.get_mut(id)
}

#[cfg(feature = "nightly")]
unsafe fn get_access_mut(&mut self, id: Index) -> <T as UnprotectedStorage<C>>::AccessMut<'_> {
if self.emit_event() {
self.channel.single_write(ComponentEvent::Modified(id));
}
self.storage.get_access_mut(id)
}

#[cfg(not(feature = "nightly"))]
unsafe fn get_mut(&mut self, id: Index) -> &mut C {
unsafe fn get_access_mut(&mut self, id: Index) -> &mut C {
if self.emit_event() {
self.channel.single_write(ComponentEvent::Modified(id));
}
self.storage.get_mut(id)
self.storage.get_access_mut(id)
}

unsafe fn insert(&mut self, id: Index, comp: C) {
15 changes: 10 additions & 5 deletions src/storage/generic.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#[cfg(feature = "nightly")]
use std::ops::DerefMut;
use crate::{
storage::{InsertResult, ReadStorage, WriteStorage, AccessMutReturn},
storage::{AccessMutReturn, InsertResult, ReadStorage, WriteStorage},
world::{Component, Entity},
};

#[cfg(feature = "nightly")]
use crate::storage::UnprotectedStorage;

@@ -88,7 +87,8 @@ pub trait GenericWriteStorage {
type Component: Component;
/// The wrapper through with mutable access of a component is performed.
#[cfg(feature = "nightly")]
type AccessMut<'a>: DerefMut<Target=Self::Component> where Self: 'a;
#[rustfmt::skip]
type AccessMut<'a> where Self: 'a;

/// Get mutable access to an `Entity`s component
fn get_mut(&mut self, entity: Entity) -> Option<AccessMutReturn<'_, Self::Component>>;
@@ -97,7 +97,10 @@ pub trait GenericWriteStorage {
/// exist, it is automatically created using `Default::default()`.
///
/// Returns None if the entity is dead.
fn get_mut_or_default(&mut self, entity: Entity) -> Option<AccessMutReturn<'_, Self::Component>>
fn get_mut_or_default(
&mut self,
entity: Entity,
) -> Option<AccessMutReturn<'_, Self::Component>>
where
Self::Component: Default;

@@ -117,6 +120,7 @@ where
{
type Component = T;
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'b> where Self: 'b = <<T as Component>::Storage as UnprotectedStorage<T>>::AccessMut<'b>;

fn get_mut(&mut self, entity: Entity) -> Option<AccessMutReturn<'_, T>> {
@@ -155,6 +159,7 @@ where
{
type Component = T;
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'c> where Self: 'c = <<T as Component>::Storage as UnprotectedStorage<T>>::AccessMut<'c>;

fn get_mut(&mut self, entity: Entity) -> Option<AccessMutReturn<'_, T>> {
98 changes: 82 additions & 16 deletions src/storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,40 @@
//! Component storage types, implementations for component joins, etc.
// TODO: This is unsound without a streaming JoinIter
//
// #[cfg(feature = "nightly")]
// pub use self::deref_flagged::{DerefFlaggedStorage, FlaggedAccessMut};
// #[cfg(feature = "nightly")]
// mod deref_flagged;
//

// TODO: Some parts of this involving mutable components are
// unsound without a streaming JoinIter.
//
// mod restrict;
// pub use self::restrict::{
// ImmutableParallelRestriction, MutableParallelRestriction, PairedStorage, RestrictedStorage,
// SequentialRestriction,
// };

#[cfg(feature = "nightly")]
pub use self::split_flagged::{
EventCollector, EventPool, EventSink, FlaggedAccessMut, SplitFlaggedAccessMut,
SplitFlaggedStorage, UnsplitFlaggedStorage,
};
#[cfg(feature = "nightly")]
mod split_flagged;

pub use self::{
data::{ReadStorage, WriteStorage},
entry::{Entries, OccupiedEntry, StorageEntry, VacantEntry},
flagged::FlaggedStorage,
generic::{GenericReadStorage, GenericWriteStorage},
restrict::{
ImmutableParallelRestriction, MutableParallelRestriction, RestrictedStorage,
SequentialRestriction, PairedStorage
},
storages::{
BTreeStorage, DefaultVecStorage, DenseVecStorage, HashMapStorage, NullStorage, VecStorage,
},
track::{ComponentEvent, Tracked},
};
#[cfg(feature = "nightly")]
pub use self::deref_flagged::{DerefFlaggedStorage, FlaggedAccessMut};

use self::storages::SliceAccess;

@@ -42,10 +61,7 @@ mod data;
mod drain;
mod entry;
mod flagged;
#[cfg(feature = "nightly")]
mod deref_flagged;
mod generic;
mod restrict;
mod storages;
#[cfg(test)]
mod tests;
@@ -328,10 +344,10 @@ where
}

/// Tries to mutate the data associated with an `Entity`.
pub fn get_mut(&mut self, e: Entity) -> Option<AccessMutReturn<'_, T> > {
pub fn get_mut(&mut self, e: Entity) -> Option<AccessMutReturn<'_, T>> {
if self.data.mask.contains(e.id()) && self.entities.is_alive(e) {
// SAFETY: We checked the mask, so all invariants are met.
Some(unsafe { self.data.inner.get_mut(e.id()) })
Some(unsafe { self.data.inner.get_access_mut(e.id()) })
} else {
None
}
@@ -348,7 +364,7 @@ where
let id = e.id();
if self.data.mask.contains(id) {
// SAFETY: We checked the mask, so all invariants are met.
std::mem::swap(&mut v, unsafe { self.data.inner.get_mut(id).deref_mut() });
std::mem::swap(&mut v, unsafe { self.data.inner.get_mut(id) });
Ok(Some(v))
} else {
self.data.mask.add(id);
@@ -464,8 +480,25 @@ where
// This is horribly unsafe. Unfortunately, Rust doesn't provide a way
// to abstract mutable/immutable state at the moment, so we have to hack
// our way through it.

// # Safety
//
// See Join::get safety comments
//
// The caller is ensures any references returned are dropped before
// any future calls to this method with the same Index value.
//
// Additionally, UnprotectedStorage::get_mut is required to return distinct
// mutable references for distinct Index values and to not create references
// internally that would alias with other mutable references produced
// by calls with distinct Index values. Note, this isn't the same as `DistinctStorage`
// which is stricter in that it requires internally mutable operations never alias for
// distinct Index value (so it can be called in parallel).
// TODO: evaluate IdvStorage for this property (this is in a separate crate).
//
// Thus, this will not create aliased mutable references.
let value: *mut Self::Value = v as *mut Self::Value;
(*value).get_mut(i)
(*value).get_access_mut(i)
}
}

@@ -507,7 +540,8 @@ where
pub trait UnprotectedStorage<T>: TryDefault {
/// The wrapper through with mutable access of a component is performed.
#[cfg(feature = "nightly")]
type AccessMut<'a>: DerefMut<Target=T> where Self: 'a;
#[rustfmt::skip]
type AccessMut<'a> where Self: 'a;

/// Clean the storage given a bitset with bits set for valid indices.
/// Allows us to safely drop the storage.
@@ -544,22 +578,54 @@ pub trait UnprotectedStorage<T>: TryDefault {
///
/// A mask should keep track of those states, and an `id` being contained
/// in the tracking mask is sufficient to call this method.
///
/// Implementations must not create references (even internally) that alias
/// references returned by previous calls with distinct `id` values.
unsafe fn get_mut(&mut self, id: Index) -> &mut T;

/// Tries mutating the data associated with an `Index`.
/// This is unsafe because the external set used
/// to protect this storage is absent.
///
/// Unlike, [UnprotectedStorage::get_mut] this method allows the storage
/// to wrap the mutable reference (when the `nightly` feature is enabled) to allow
/// any logic such as emitting modification events to only occur if the component
/// is mutated.
///
/// # Safety
///
/// May only be called after a call to `insert` with `id` and
/// no following call to `remove` with `id`.
///
/// A mask should keep track of those states, and an `id` being contained
/// in the tracking mask is sufficient to call this method.
///
/// Implementations must not create references (even internally) that alias
/// references returned by previous calls with distinct `id` values.
#[cfg(feature = "nightly")]
unsafe fn get_mut(&mut self, id: Index) -> Self::AccessMut<'_>;
unsafe fn get_access_mut(&mut self, id: Index) -> Self::AccessMut<'_>;

/// Tries mutating the data associated with an `Index`.
/// This is unsafe because the external set used
/// to protect this storage is absent.
///
/// Unlike, [UnprotectedStorage::get_mut] this method allows the storage
/// to wrap the mutable reference (when the `nightly` feature is enabled) to allow
/// any logic such as emitting modification events to only occur if the component
/// is mutated.
///
/// # Safety
///
/// May only be called after a call to `insert` with `id` and
/// no following call to `remove` with `id`.
///
/// A mask should keep track of those states, and an `id` being contained
/// in the tracking mask is sufficient to call this method.
///
/// Implementations must not create references (even internally) that alias
/// references returned by previous calls with distinct `id` values.
#[cfg(not(feature = "nightly"))]
unsafe fn get_mut(&mut self, id: Index) -> &mut T;
unsafe fn get_access_mut(&mut self, id: Index) -> &mut T;

/// Inserts new data for a given `Index`.
///
55 changes: 51 additions & 4 deletions src/storage/restrict.rs
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ use crate::join::Join;
#[cfg(feature = "parallel")]
use crate::join::ParJoin;
use crate::{
storage::{MaskedStorage, Storage, UnprotectedStorage, AccessMutReturn},
storage::{AccessMutReturn, DistinctStorage, MaskedStorage, Storage, UnprotectedStorage},
world::{Component, EntitiesRes, Entity, Index},
};

@@ -86,6 +86,7 @@ where
C: Component,
S: BorrowMut<C::Storage> + 'rf,
B: Borrow<BitSet> + 'rf,
C::Storage: Sync + DistinctStorage,
{
}

@@ -147,6 +148,48 @@ where
}

unsafe fn get(value: &mut Self::Value, id: Index) -> Self::Type {
// TODO: this is unsound with a non streaming JoinIter
//
// Calls with distinct `id`s will create aliased mutable
// references that are returned all referencing the same Self::Value.
//
// Additionally, using `get_mut` with two separate `PairedStorages`s can
// produce aliased mutable references:
//
// use specs::prelude::*;
// use specs::storage::PairedStorage;
//
// struct Pos(f32);
//
// impl Component for Pos {
// type Storage = VecStorage<Self>;
// }
//
// fn main() {
// let mut world = World::new();
//
// world.register::<Pos>();
//
// world.create_entity().with(Pos(0.0)).build();
// world.create_entity().with(Pos(1.6)).build();
// world.create_entity().with(Pos(5.4)).build();
//
// let entities = world.entities();
// let mut pos = world.write_storage::<Pos>();
//
// let mut restrict_pos = pos.restrict_mut();
// let mut vec: Vec<PairedStorage<'_, '_, _, &mut _, _, _>> = (&mut restrict_pos).join().collect();
// let (a, vec) = vec.split_first_mut().unwrap();
// let (b, _) = vec.split_first_mut().unwrap();
// let entity = entities.entity(0);
// let alias_a: &mut Pos = a.get_mut(entity).unwrap();
// let alias_b: &mut Pos = b.get_mut(entity).unwrap();
// assert!(core::ptr::eq(alias_a, alias_b));
// }
//
// Also, since PairedStorage is Send, event if the underlying storage does not impl DistinctStorage
// sending a PairedStorage to another thread can be used to call the underlying get_mut
// simultaneously
let value: &'rf mut Self::Value = &mut *(value as *mut Self::Value);
PairedStorage {
index: id,
@@ -235,6 +278,10 @@ where
{
/// Gets the component related to the current entry without checking whether
/// the storage has it or not.
//
// TODO: this is a misleading name since it implies something does need to be checked but we are
// skipping it, when instead this is just getting the one entity's component that can be
// retrieved without a check because we know it exists.
pub fn get_unchecked(&self) -> &C {
unsafe { self.storage.borrow().get(self.index) }
}
@@ -248,8 +295,8 @@ where
{
/// Gets the component related to the current entry without checking whether
/// the storage has it or not.
pub fn get_mut_unchecked(&mut self) -> AccessMutReturn<'_, C> {
unsafe { self.storage.borrow_mut().get_mut(self.index) }
pub fn get_mut_unchecked(&mut self) -> AccessMutReturn<'_, C> {
unsafe { self.storage.borrow_mut().get_access_mut(self.index) }
}
}

@@ -291,7 +338,7 @@ where
/// threads.
pub fn get_mut(&mut self, entity: Entity) -> Option<AccessMutReturn<'_, C>> {
if self.bitset.borrow().contains(entity.id()) && self.entities.is_alive(entity) {
Some(unsafe { self.storage.borrow_mut().get_mut(entity.id()) })
Some(unsafe { self.storage.borrow_mut().get_access_mut(entity.id()) })
} else {
None
}
486 changes: 486 additions & 0 deletions src/storage/split_flagged.rs

Large diffs are not rendered by default.

30 changes: 30 additions & 0 deletions src/storage/storages.rs
Original file line number Diff line number Diff line change
@@ -33,6 +33,7 @@ impl<T> Default for BTreeStorage<T> {

impl<T> UnprotectedStorage<T> for BTreeStorage<T> {
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = &'a mut T;

unsafe fn clean<B>(&mut self, _has: B)
@@ -50,6 +51,10 @@ impl<T> UnprotectedStorage<T> for BTreeStorage<T> {
self.0.get_mut(&id).unwrap()
}

unsafe fn get_access_mut(&mut self, id: Index) -> &mut T {
self.get_mut(id)
}

unsafe fn insert(&mut self, id: Index, v: T) {
self.0.insert(id, v);
}
@@ -74,6 +79,7 @@ impl<T> Default for HashMapStorage<T> {

impl<T> UnprotectedStorage<T> for HashMapStorage<T> {
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = &'a mut T;

unsafe fn clean<B>(&mut self, _has: B)
@@ -91,6 +97,10 @@ impl<T> UnprotectedStorage<T> for HashMapStorage<T> {
self.0.get_mut(&id).unwrap()
}

unsafe fn get_access_mut(&mut self, id: Index) -> &mut T {
self.get_mut(id)
}

unsafe fn insert(&mut self, id: Index, v: T) {
self.0.insert(id, v);
}
@@ -154,6 +164,7 @@ impl<T> SliceAccess<T> for DenseVecStorage<T> {

impl<T> UnprotectedStorage<T> for DenseVecStorage<T> {
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = &'a mut T;

unsafe fn clean<B>(&mut self, _has: B)
@@ -173,6 +184,10 @@ impl<T> UnprotectedStorage<T> for DenseVecStorage<T> {
self.data.get_unchecked_mut(did as usize)
}

unsafe fn get_access_mut(&mut self, id: Index) -> &mut T {
self.get_mut(id)
}

unsafe fn insert(&mut self, id: Index, v: T) {
let id = id as usize;
if self.data_id.len() <= id {
@@ -211,6 +226,7 @@ where
T: Default,
{
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = &'a mut T;

unsafe fn clean<B>(&mut self, _has: B)
@@ -227,6 +243,10 @@ where
&mut self.0
}

unsafe fn get_access_mut(&mut self, id: Index) -> &mut T {
self.get_mut(id)
}

unsafe fn insert(&mut self, _: Index, _: T) {}

unsafe fn remove(&mut self, _: Index) -> T {
@@ -281,6 +301,7 @@ impl<T> SliceAccess<T> for VecStorage<T> {

impl<T> UnprotectedStorage<T> for VecStorage<T> {
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = &'a mut T;

unsafe fn clean<B>(&mut self, has: B)
@@ -305,6 +326,10 @@ impl<T> UnprotectedStorage<T> for VecStorage<T> {
&mut *self.0.get_unchecked_mut(id as usize).as_mut_ptr()
}

unsafe fn get_access_mut(&mut self, id: Index) -> &mut T {
self.get_mut(id)
}

unsafe fn insert(&mut self, id: Index, v: T) {
let id = id as usize;
if self.0.len() <= id {
@@ -346,6 +371,7 @@ where
T: Default,
{
#[cfg(feature = "nightly")]
#[rustfmt::skip]
type AccessMut<'a> where T: 'a = &'a mut T;

unsafe fn clean<B>(&mut self, _has: B)
@@ -363,6 +389,10 @@ where
self.0.get_unchecked_mut(id as usize)
}

unsafe fn get_access_mut(&mut self, id: Index) -> &mut T {
self.get_mut(id)
}

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