add more SAFETY comments and lint for missing ones in bevy_ecs (#4835)

# Objective

`SAFETY` comments are meant to be placed before `unsafe` blocks and should contain the reasoning of why in this case the usage of unsafe is okay. This is useful when reading the code because it makes it clear which assumptions are required for safety, and makes it easier to spot possible unsoundness holes. It also forces the code writer to think of something to write and maybe look at the safety contracts of any called unsafe methods again to double-check their correct usage.

There's a clippy lint called `undocumented_unsafe_blocks` which warns when using a block without such a comment. 

## Solution

- since clippy expects `SAFETY` instead of `SAFE`, rename those
- add `SAFETY` comments in more places
- for the last remaining 3 places, add an `#[allow()]` and `// TODO` since I wasn't comfortable enough with the code to justify their safety
- add ` #![warn(clippy::undocumented_unsafe_blocks)]` to `bevy_ecs`


### Note for reviewers

The first commit only renames `SAFETY` to `SAFE` so it doesn't need a thorough review.
cb042a416e..55cef2d6fa is the diff for all other changes.

### Safety comments where I'm not too familiar with the code

774012ece5/crates/bevy_ecs/src/entity/mod.rs (L540-L546)

774012ece5/crates/bevy_ecs/src/world/entity_ref.rs (L249-L252)

### Locations left undocumented with a `TODO` comment

5dde944a30/crates/bevy_ecs/src/schedule/executor_parallel.rs (L196-L199)

5dde944a30/crates/bevy_ecs/src/world/entity_ref.rs (L287-L289)

5dde944a30/crates/bevy_ecs/src/world/entity_ref.rs (L413-L415)

Co-authored-by: Jakob Hellermann <hellermann@sipgate.de>
This commit is contained in:
Jakob Hellermann 2022-07-04 14:44:24 +00:00
parent 4d05eb19be
commit d38a8dfdd7
31 changed files with 361 additions and 240 deletions

View File

@ -144,7 +144,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let struct_name = &ast.ident; let struct_name = &ast.ident;
TokenStream::from(quote! { TokenStream::from(quote! {
/// SAFE: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order /// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause { unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause {
fn component_ids( fn component_ids(
components: &mut #ecs_path::component::Components, components: &mut #ecs_path::component::Components,
@ -192,7 +192,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
let index = Index::from(i); let index = Index::from(i);
param_fn_muts.push(quote! { param_fn_muts.push(quote! {
pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item { pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item {
// SAFE: systems run without conflicts with other systems. // SAFETY: systems run without conflicts with other systems.
// Conflicting params in ParamSet are not accessible at the same time // Conflicting params in ParamSet are not accessible at the same time
// ParamSets are guaranteed to not conflict with other SystemParams // ParamSets are guaranteed to not conflict with other SystemParams
unsafe { unsafe {
@ -213,13 +213,13 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
type Fetch = ParamSetState<(#(#param::Fetch,)*)>; type Fetch = ParamSetState<(#(#param::Fetch,)*)>;
} }
// SAFE: All parameters are constrained to ReadOnlyFetch, so World is only read // SAFETY: All parameters are constrained to ReadOnlyFetch, so World is only read
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)> unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)>
where #(#param_fetch: ReadOnlySystemParamFetch,)* where #(#param_fetch: ReadOnlySystemParamFetch,)*
{ } { }
// SAFE: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts // SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts
// with any prior access, a panic will occur. // with any prior access, a panic will occur.
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)> unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)>

View File

@ -420,13 +420,13 @@ impl Archetypes {
#[inline] #[inline]
pub fn empty(&self) -> &Archetype { pub fn empty(&self) -> &Archetype {
// SAFE: empty archetype always exists // SAFETY: empty archetype always exists
unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) } unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) }
} }
#[inline] #[inline]
pub(crate) fn empty_mut(&mut self) -> &mut Archetype { pub(crate) fn empty_mut(&mut self) -> &mut Archetype {
// SAFE: empty archetype always exists // SAFETY: empty archetype always exists
unsafe { unsafe {
self.archetypes self.archetypes
.get_unchecked_mut(ArchetypeId::EMPTY.index()) .get_unchecked_mut(ArchetypeId::EMPTY.index())
@ -435,13 +435,13 @@ impl Archetypes {
#[inline] #[inline]
pub fn resource(&self) -> &Archetype { pub fn resource(&self) -> &Archetype {
// SAFE: resource archetype always exists // SAFETY: resource archetype always exists
unsafe { self.archetypes.get_unchecked(ArchetypeId::RESOURCE.index()) } unsafe { self.archetypes.get_unchecked(ArchetypeId::RESOURCE.index()) }
} }
#[inline] #[inline]
pub(crate) fn resource_mut(&mut self) -> &mut Archetype { pub(crate) fn resource_mut(&mut self) -> &mut Archetype {
// SAFE: resource archetype always exists // SAFETY: resource archetype always exists
unsafe { unsafe {
self.archetypes self.archetypes
.get_unchecked_mut(ArchetypeId::RESOURCE.index()) .get_unchecked_mut(ArchetypeId::RESOURCE.index())

View File

@ -99,6 +99,10 @@ pub unsafe trait Bundle: Send + Sync + 'static {
macro_rules! tuple_impl { macro_rules! tuple_impl {
($($name: ident),*) => { ($($name: ident),*) => {
// SAFETY:
// - `Bundle::component_ids` returns the `ComponentId`s for each component type in the
// bundle, in the exact order that `Bundle::get_components` is called.
// - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`.
unsafe impl<$($name: Component),*> Bundle for ($($name,)*) { unsafe impl<$($name: Component),*> Bundle for ($($name,)*) {
#[allow(unused_variables)] #[allow(unused_variables)]
fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec<ComponentId> { fn component_ids(components: &mut Components, storages: &mut Storages) -> Vec<ComponentId> {
@ -325,7 +329,7 @@ impl BundleInfo {
bundle_status.push(ComponentStatus::Mutated); bundle_status.push(ComponentStatus::Mutated);
} else { } else {
bundle_status.push(ComponentStatus::Added); bundle_status.push(ComponentStatus::Added);
// SAFE: component_id exists // SAFETY: component_id exists
let component_info = unsafe { components.get_info_unchecked(component_id) }; let component_info = unsafe { components.get_info_unchecked(component_id) };
match component_info.storage_type() { match component_info.storage_type() {
StorageType::Table => new_table_components.push(component_id), StorageType::Table => new_table_components.push(component_id),
@ -354,7 +358,7 @@ impl BundleInfo {
new_table_components.extend(current_archetype.table_components()); new_table_components.extend(current_archetype.table_components());
// sort to ignore order while hashing // sort to ignore order while hashing
new_table_components.sort(); new_table_components.sort();
// SAFE: all component ids in `new_table_components` exist // SAFETY: all component ids in `new_table_components` exist
table_id = unsafe { table_id = unsafe {
storages storages
.tables .tables
@ -492,7 +496,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
} else if new_archetype.id() == swapped_location.archetype_id { } else if new_archetype.id() == swapped_location.archetype_id {
new_archetype new_archetype
} else { } else {
// SAFE: the only two borrowed archetypes are above and we just did collision checks // SAFETY: the only two borrowed archetypes are above and we just did collision checks
&mut *self &mut *self
.archetypes_ptr .archetypes_ptr
.add(swapped_location.archetype_id.index()) .add(swapped_location.archetype_id.index())
@ -567,7 +571,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
#[inline] #[inline]
pub unsafe fn spawn<T: Bundle>(&mut self, bundle: T) -> Entity { pub unsafe fn spawn<T: Bundle>(&mut self, bundle: T) -> Entity {
let entity = self.entities.alloc(); let entity = self.entities.alloc();
// SAFE: entity is allocated (but non-existent), `T` matches this BundleInfo's type // SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type
self.spawn_non_existent(entity, bundle); self.spawn_non_existent(entity, bundle);
entity entity
} }
@ -599,14 +603,14 @@ impl Bundles {
let id = self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| { let id = self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| {
let component_ids = T::component_ids(components, storages); let component_ids = T::component_ids(components, storages);
let id = BundleId(bundle_infos.len()); let id = BundleId(bundle_infos.len());
// SAFE: T::component_id ensures info was created // SAFETY: T::component_id ensures info was created
let bundle_info = unsafe { let bundle_info = unsafe {
initialize_bundle(std::any::type_name::<T>(), component_ids, id, components) initialize_bundle(std::any::type_name::<T>(), component_ids, id, components)
}; };
bundle_infos.push(bundle_info); bundle_infos.push(bundle_info);
id id
}); });
// SAFE: index either exists, or was initialized // SAFETY: index either exists, or was initialized
unsafe { self.bundle_infos.get_unchecked(id.0) } unsafe { self.bundle_infos.get_unchecked(id.0) }
} }
} }
@ -623,7 +627,7 @@ unsafe fn initialize_bundle(
let mut storage_types = Vec::new(); let mut storage_types = Vec::new();
for &component_id in &component_ids { for &component_id in &component_ids {
// SAFE: component_id exists and is therefore valid // SAFETY: component_id exists and is therefore valid
let component_info = components.get_info_unchecked(component_id); let component_info = components.get_info_unchecked(component_id);
storage_types.push(component_info.storage_type()); storage_types.push(component_info.storage_type());
} }

View File

@ -474,7 +474,7 @@ impl Components {
#[inline] #[inline]
pub fn init_resource<T: Resource>(&mut self) -> ComponentId { pub fn init_resource<T: Resource>(&mut self) -> ComponentId {
// SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`]
unsafe { unsafe {
self.get_or_insert_resource_with(TypeId::of::<T>(), || { self.get_or_insert_resource_with(TypeId::of::<T>(), || {
ComponentDescriptor::new_resource::<T>() ComponentDescriptor::new_resource::<T>()
@ -484,7 +484,7 @@ impl Components {
#[inline] #[inline]
pub fn init_non_send<T: Any>(&mut self) -> ComponentId { pub fn init_non_send<T: Any>(&mut self) -> ComponentId {
// SAFE: The [`ComponentDescriptor`] matches the [`TypeId`] // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`]
unsafe { unsafe {
self.get_or_insert_resource_with(TypeId::of::<T>(), || { self.get_or_insert_resource_with(TypeId::of::<T>(), || {
ComponentDescriptor::new_non_send::<T>(StorageType::default()) ComponentDescriptor::new_non_send::<T>(StorageType::default())

View File

@ -591,6 +591,8 @@ impl Entities {
// Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return None // Flushes all reserved entities to an "invalid" state. Attempting to retrieve them will return None
// unless they are later populated with a valid archetype. // unless they are later populated with a valid archetype.
pub fn flush_as_invalid(&mut self) { pub fn flush_as_invalid(&mut self) {
// SAFETY: as per `flush` safety docs, the archetype id can be set to [`ArchetypeId::INVALID`] if
// the [`Entity`] has not been assigned to an [`Archetype`][crate::archetype::Archetype], which is the case here
unsafe { unsafe {
self.flush(|_entity, location| { self.flush(|_entity, location| {
location.archetype_id = ArchetypeId::INVALID; location.archetype_id = ArchetypeId::INVALID;
@ -658,6 +660,7 @@ mod tests {
fn reserve_entity_len() { fn reserve_entity_len() {
let mut e = Entities::default(); let mut e = Entities::default();
e.reserve_entity(); e.reserve_entity();
// SAFETY: entity_location is left invalid
unsafe { e.flush(|_, _| {}) }; unsafe { e.flush(|_, _| {}) };
assert_eq!(e.len(), 1); assert_eq!(e.len(), 1);
} }
@ -669,6 +672,7 @@ mod tests {
assert!(entities.contains(e)); assert!(entities.contains(e));
assert!(entities.get(e).is_none()); assert!(entities.get(e).is_none());
// SAFETY: entity_location is left invalid
unsafe { unsafe {
entities.flush(|_entity, _location| { entities.flush(|_entity, _location| {
// do nothing ... leaving entity location invalid // do nothing ... leaving entity location invalid

View File

@ -1,3 +1,4 @@
#![warn(clippy::undocumented_unsafe_blocks)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
#[cfg(target_pointer_width = "16")] #[cfg(target_pointer_width = "16")]

View File

@ -480,7 +480,7 @@ macro_rules! impl_query_filter_tuple {
} }
} }
// SAFE: filters are read only // SAFETY: filters are read only
unsafe impl<$($filter: ReadOnlyWorldQuery),*> ReadOnlyWorldQuery for Or<($($filter,)*)> {} unsafe impl<$($filter: ReadOnlyWorldQuery),*> ReadOnlyWorldQuery for Or<($($filter,)*)> {}
}; };
} }

View File

@ -52,6 +52,9 @@ where
#[inline(always)] #[inline(always)]
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
// SAFETY:
// `tables` and `archetypes` belong to the same world that the cursor was initialized for.
// `query_state` is the state that was passed to `QueryIterationCursor::init`.
unsafe { unsafe {
self.cursor self.cursor
.next(self.tables, self.archetypes, self.query_state) .next(self.tables, self.archetypes, self.query_state)
@ -150,33 +153,42 @@ where
#[inline(always)] #[inline(always)]
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
unsafe { for entity in self.entity_iter.by_ref() {
for entity in self.entity_iter.by_ref() { let location = match self.entities.get(*entity.borrow()) {
let location = match self.entities.get(*entity.borrow()) { Some(location) => location,
Some(location) => location, None => continue,
None => continue, };
};
if !self if !self
.query_state .query_state
.matched_archetypes .matched_archetypes
.contains(location.archetype_id.index()) .contains(location.archetype_id.index())
{ {
continue; continue;
} }
let archetype = &self.archetypes[location.archetype_id]; let archetype = &self.archetypes[location.archetype_id];
// SAFETY: `archetype` is from the world that `fetch/filter` were created for,
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
unsafe {
self.fetch self.fetch
.set_archetype(&self.query_state.fetch_state, archetype, self.tables); .set_archetype(&self.query_state.fetch_state, archetype, self.tables);
}
// SAFETY: `table` is from the world that `fetch/filter` were created for,
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
unsafe {
self.filter self.filter
.set_archetype(&self.query_state.filter_state, archetype, self.tables); .set_archetype(&self.query_state.filter_state, archetype, self.tables);
if self.filter.archetype_filter_fetch(location.index) {
return Some(self.fetch.archetype_fetch(location.index));
}
} }
None // SAFETY: set_archetype was called prior.
// `location.index` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
if unsafe { self.filter.archetype_filter_fetch(location.index) } {
// SAFETY: set_archetype was called prior, `location.index` is an archetype index in range of the current archetype
return Some(unsafe { self.fetch.archetype_fetch(location.index) });
}
} }
None
} }
fn size_hint(&self) -> (usize, Option<usize>) { fn size_hint(&self) -> (usize, Option<usize>) {
@ -289,7 +301,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery, const K: usize> QueryCombinationIter<
for<'a> QueryFetch<'a, Q>: Clone, for<'a> QueryFetch<'a, Q>: Clone,
for<'a> QueryFetch<'a, F>: Clone, for<'a> QueryFetch<'a, F>: Clone,
{ {
// safety: we are limiting the returned reference to self, // SAFETY: we are limiting the returned reference to self,
// making sure this method cannot be called multiple times without getting rid // making sure this method cannot be called multiple times without getting rid
// of any previously returned unique references first, thus preventing aliasing. // of any previously returned unique references first, thus preventing aliasing.
unsafe { unsafe {
@ -374,7 +386,9 @@ struct QueryIterationCursor<'w, 's, Q: WorldQuery, QF: Fetch<'w, State = Q::Stat
archetype_id_iter: std::slice::Iter<'s, ArchetypeId>, archetype_id_iter: std::slice::Iter<'s, ArchetypeId>,
fetch: QF, fetch: QF,
filter: QueryFetch<'w, F>, filter: QueryFetch<'w, F>,
// length of the table table or length of the archetype, depending on whether both `Q`'s and `F`'s fetches are dense
current_len: usize, current_len: usize,
// either table row or archetype index, depending on whether both `Q`'s and `F`'s fetches are dense
current_index: usize, current_index: usize,
phantom: PhantomData<(&'w (), Q)>, phantom: PhantomData<(&'w (), Q)>,
} }
@ -461,6 +475,10 @@ where
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
// QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual // QueryIterationCursor, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
/// # Safety
/// `tables` and `archetypes` must belong to the same world that the [`QueryIterationCursor`]
/// was initialized for.
/// `query_state` must be the same [`QueryState`] that was passed to `init` or `init_empty`.
#[inline(always)] #[inline(always)]
unsafe fn next( unsafe fn next(
&mut self, &mut self,
@ -470,9 +488,12 @@ where
) -> Option<QF::Item> { ) -> Option<QF::Item> {
if Self::IS_DENSE { if Self::IS_DENSE {
loop { loop {
// we are on the beginning of the query, or finished processing a table, so skip to the next
if self.current_index == self.current_len { if self.current_index == self.current_len {
let table_id = self.table_id_iter.next()?; let table_id = self.table_id_iter.next()?;
let table = &tables[*table_id]; let table = &tables[*table_id];
// SAFETY: `table` is from the world that `fetch/filter` were created for,
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
self.fetch.set_table(&query_state.fetch_state, table); self.fetch.set_table(&query_state.fetch_state, table);
self.filter.set_table(&query_state.filter_state, table); self.filter.set_table(&query_state.filter_state, table);
self.current_len = table.len(); self.current_len = table.len();
@ -480,11 +501,15 @@ where
continue; continue;
} }
// SAFETY: set_table was called prior.
// `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed.
if !self.filter.table_filter_fetch(self.current_index) { if !self.filter.table_filter_fetch(self.current_index) {
self.current_index += 1; self.current_index += 1;
continue; continue;
} }
// SAFETY: set_table was called prior.
// `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed.
let item = self.fetch.table_fetch(self.current_index); let item = self.fetch.table_fetch(self.current_index);
self.current_index += 1; self.current_index += 1;
@ -495,6 +520,8 @@ where
if self.current_index == self.current_len { if self.current_index == self.current_len {
let archetype_id = self.archetype_id_iter.next()?; let archetype_id = self.archetype_id_iter.next()?;
let archetype = &archetypes[*archetype_id]; let archetype = &archetypes[*archetype_id];
// SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for,
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
self.fetch self.fetch
.set_archetype(&query_state.fetch_state, archetype, tables); .set_archetype(&query_state.fetch_state, archetype, tables);
self.filter self.filter
@ -504,11 +531,15 @@ where
continue; continue;
} }
// SAFETY: set_archetype was called prior.
// `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
if !self.filter.archetype_filter_fetch(self.current_index) { if !self.filter.archetype_filter_fetch(self.current_index) {
self.current_index += 1; self.current_index += 1;
continue; continue;
} }
// SAFETY: set_archetype was called prior, `current_index` is an archetype index in range of the current archetype
// `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
let item = self.fetch.archetype_fetch(self.current_index); let item = self.fetch.archetype_fetch(self.current_index);
self.current_index += 1; self.current_index += 1;
return Some(item); return Some(item);

View File

@ -81,7 +81,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
/// Checks if the query is empty for the given [`World`], where the last change and current tick are given. /// Checks if the query is empty for the given [`World`], where the last change and current tick are given.
#[inline] #[inline]
pub fn is_empty(&self, world: &World, last_change_tick: u32, change_tick: u32) -> bool { pub fn is_empty(&self, world: &World, last_change_tick: u32, change_tick: u32) -> bool {
// SAFE: NopFetch does not access any members while &self ensures no one has exclusive access // SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access
unsafe { unsafe {
self.iter_unchecked_manual::<NopFetch<Q::State>>(world, last_change_tick, change_tick) self.iter_unchecked_manual::<NopFetch<Q::State>>(world, last_change_tick, change_tick)
.next() .next()
@ -211,7 +211,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> { ) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> {
self.update_archetypes(world); self.update_archetypes(world);
// SAFE: update_archetypes validates the `World` matches // SAFETY: update_archetypes validates the `World` matches
unsafe { unsafe {
self.get_many_read_only_manual( self.get_many_read_only_manual(
world, world,
@ -287,7 +287,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
) -> Result<[QueryItem<'w, Q>; N], QueryEntityError> { ) -> Result<[QueryItem<'w, Q>; N], QueryEntityError> {
self.update_archetypes(world); self.update_archetypes(world);
// SAFE: method requires exclusive world access // SAFETY: method requires exclusive world access
// and world has been validated via update_archetypes // and world has been validated via update_archetypes
unsafe { unsafe {
self.get_many_unchecked_manual( self.get_many_unchecked_manual(
@ -397,7 +397,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
last_change_tick: u32, last_change_tick: u32,
change_tick: u32, change_tick: u32,
) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> { ) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> {
// SAFE: fetch is read-only // SAFETY: fetch is read-only
// and world must be validated // and world must be validated
let array_of_results = entities.map(|entity| { let array_of_results = entities.map(|entity| {
self.get_unchecked_manual::<ROQueryFetch<'w, Q>>( self.get_unchecked_manual::<ROQueryFetch<'w, Q>>(
@ -527,7 +527,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
&'s mut self, &'s mut self,
world: &'w World, world: &'w World,
) -> QueryCombinationIter<'w, 's, Q, F, K> { ) -> QueryCombinationIter<'w, 's, Q, F, K> {
// SAFE: query is read only // SAFETY: query is read only
unsafe { unsafe {
self.update_archetypes(world); self.update_archetypes(world);
self.iter_combinations_unchecked_manual( self.iter_combinations_unchecked_manual(
@ -550,7 +550,7 @@ impl<Q: WorldQuery, F: WorldQuery> QueryState<Q, F> {
&'s mut self, &'s mut self,
world: &'w mut World, world: &'w mut World,
) -> QueryCombinationIter<'w, 's, Q, F, K> { ) -> QueryCombinationIter<'w, 's, Q, F, K> {
// SAFE: query has unique world access // SAFETY: query has unique world access
unsafe { unsafe {
self.update_archetypes(world); self.update_archetypes(world);
self.iter_combinations_unchecked_manual( self.iter_combinations_unchecked_manual(
@ -1272,6 +1272,8 @@ mod tests {
// It's best to test get_many_unchecked_manual directly, // It's best to test get_many_unchecked_manual directly,
// as it is shared and unsafe // as it is shared and unsafe
// We don't care about aliased mutabilty for the read-only equivalent // We don't care about aliased mutabilty for the read-only equivalent
// SAFETY: mutable access is not checked, but we own the world and don't use the query results
assert!(unsafe { assert!(unsafe {
query_state query_state
.get_many_unchecked_manual::<10>( .get_many_unchecked_manual::<10>(
@ -1284,6 +1286,7 @@ mod tests {
}); });
assert_eq!( assert_eq!(
// SAFETY: mutable access is not checked, but we own the world and don't use the query results
unsafe { unsafe {
query_state query_state
.get_many_unchecked_manual( .get_many_unchecked_manual(
@ -1298,6 +1301,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
// SAFETY: mutable access is not checked, but we own the world and don't use the query results
unsafe { unsafe {
query_state query_state
.get_many_unchecked_manual( .get_many_unchecked_manual(
@ -1312,6 +1316,7 @@ mod tests {
); );
assert_eq!( assert_eq!(
// SAFETY: mutable access is not checked, but we own the world and don't use the query results
unsafe { unsafe {
query_state query_state
.get_many_unchecked_manual( .get_many_unchecked_manual(

View File

@ -69,7 +69,7 @@ impl ReflectComponent {
world: &'a mut World, world: &'a mut World,
entity: Entity, entity: Entity,
) -> Option<ReflectMut<'a>> { ) -> Option<ReflectMut<'a>> {
// SAFE: unique world access // SAFETY: unique world access
unsafe { (self.reflect_component_mut)(world, entity) } unsafe { (self.reflect_component_mut)(world, entity) }
} }
@ -137,14 +137,18 @@ impl<C: Component + Reflect + FromWorld> FromType<C> for ReflectComponent {
.get::<C>() .get::<C>()
.map(|c| c as &dyn Reflect) .map(|c| c as &dyn Reflect)
}, },
reflect_component_mut: |world, entity| unsafe { reflect_component_mut: |world, entity| {
world // SAFETY: reflect_component_mut is an unsafe function pointer used by `reflect_component_unchecked_mut` which promises to never
.get_entity(entity)? // produce aliasing mutable references, and reflect_component_mut, which has mutable world access
.get_unchecked_mut::<C>(world.last_change_tick(), world.read_change_tick()) unsafe {
.map(|c| ReflectMut { world
value: c.value as &mut dyn Reflect, .get_entity(entity)?
ticks: c.ticks, .get_unchecked_mut::<C>(world.last_change_tick(), world.read_change_tick())
}) .map(|c| ReflectMut {
value: c.value as &mut dyn Reflect,
ticks: c.ticks,
})
}
}, },
} }
} }
@ -191,7 +195,7 @@ impl ReflectResource {
/// Gets the value of this [`Resource`] type from the world as a mutable reflected reference. /// Gets the value of this [`Resource`] type from the world as a mutable reflected reference.
pub fn reflect_resource_mut<'a>(&self, world: &'a mut World) -> Option<ReflectMut<'a>> { pub fn reflect_resource_mut<'a>(&self, world: &'a mut World) -> Option<ReflectMut<'a>> {
// SAFE: unique world access // SAFETY: unique world access
unsafe { (self.reflect_resource_unchecked_mut)(world) } unsafe { (self.reflect_resource_unchecked_mut)(world) }
} }
@ -205,6 +209,7 @@ impl ReflectResource {
&self, &self,
world: &'a World, world: &'a World,
) -> Option<ReflectMut<'a>> { ) -> Option<ReflectMut<'a>> {
// SAFETY: caller promises to uphold uniqueness guarantees
(self.reflect_resource_unchecked_mut)(world) (self.reflect_resource_unchecked_mut)(world)
} }
@ -234,13 +239,17 @@ impl<C: Resource + Reflect + FromWorld> FromType<C> for ReflectResource {
world.remove_resource::<C>(); world.remove_resource::<C>();
}, },
reflect_resource: |world| world.get_resource::<C>().map(|res| res as &dyn Reflect), reflect_resource: |world| world.get_resource::<C>().map(|res| res as &dyn Reflect),
reflect_resource_unchecked_mut: |world| unsafe { reflect_resource_unchecked_mut: |world| {
world // SAFETY: all usages of `reflect_resource_unchecked_mut` guarantee that there is either a single mutable
.get_resource_unchecked_mut::<C>() // reference or multiple immutable ones alive at any given point
.map(|res| ReflectMut { unsafe {
value: res.value as &mut dyn Reflect, world
ticks: res.ticks, .get_resource_unchecked_mut::<C>()
}) .map(|res| ReflectMut {
value: res.value as &mut dyn Reflect,
ticks: res.ticks,
})
}
}, },
copy_resource: |source_world, destination_world| { copy_resource: |source_world, destination_world| {
let source_resource = source_world.resource::<C>(); let source_resource = source_world.resource::<C>();

View File

@ -190,6 +190,7 @@ impl ParallelExecutor {
.unwrap_or_else(|error| unreachable!("{}", error)); .unwrap_or_else(|error| unreachable!("{}", error));
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
let system_guard = system_span.enter(); let system_guard = system_span.enter();
// SAFETY: the executor prevents two systems with conflicting access from running simultaneously.
unsafe { system.run_unsafe((), world) }; unsafe { system.run_unsafe((), world) };
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
drop(system_guard); drop(system_guard);

View File

@ -1,6 +1,7 @@
use std::{ use std::{
alloc::{handle_alloc_error, Layout}, alloc::{handle_alloc_error, Layout},
cell::UnsafeCell, cell::UnsafeCell,
num::NonZeroUsize,
ptr::NonNull, ptr::NonNull,
}; };
@ -14,7 +15,9 @@ pub(super) struct BlobVec {
capacity: usize, capacity: usize,
/// Number of elements, not bytes /// Number of elements, not bytes
len: usize, len: usize,
// the `data` ptr's layout is always `array_layout(item_layout, capacity)`
data: NonNull<u8>, data: NonNull<u8>,
// the `swap_scratch` ptr's layout is always `item_layout`
swap_scratch: NonNull<u8>, swap_scratch: NonNull<u8>,
// None if the underlying type doesn't need to be dropped // None if the underlying type doesn't need to be dropped
drop: Option<unsafe fn(OwningPtr<'_>)>, drop: Option<unsafe fn(OwningPtr<'_>)>,
@ -93,33 +96,45 @@ impl BlobVec {
pub fn reserve_exact(&mut self, additional: usize) { pub fn reserve_exact(&mut self, additional: usize) {
let available_space = self.capacity - self.len; let available_space = self.capacity - self.len;
if available_space < additional { if available_space < additional && self.item_layout.size() > 0 {
self.grow_exact(additional - available_space); // SAFETY: `available_space < additional`, so `additional - available_space > 0`
let increment = unsafe { NonZeroUsize::new_unchecked(additional - available_space) };
// SAFETY: not called for ZSTs
unsafe { self.grow_exact(increment) };
} }
} }
// FIXME: this should probably be an unsafe fn as it shouldn't be called if the layout // SAFETY: must not be called for a ZST item layout
// is for a ZST #[warn(unsafe_op_in_unsafe_fn)] // to allow unsafe blocks in unsafe fn
fn grow_exact(&mut self, increment: usize) { unsafe fn grow_exact(&mut self, increment: NonZeroUsize) {
debug_assert!(self.item_layout.size() != 0); debug_assert!(self.item_layout.size() != 0);
let new_capacity = self.capacity + increment; let new_capacity = self.capacity + increment.get();
let new_layout = let new_layout =
array_layout(&self.item_layout, new_capacity).expect("array layout should be valid"); array_layout(&self.item_layout, new_capacity).expect("array layout should be valid");
unsafe { let new_data = if self.capacity == 0 {
let new_data = if self.capacity == 0 { // SAFETY:
std::alloc::alloc(new_layout) // - layout has non-zero size as per safety requirement
} else { unsafe { std::alloc::alloc(new_layout) }
} else {
// SAFETY:
// - ptr was be allocated via this allocator
// - the layout of the ptr was `array_layout(self.item_layout, self.capacity)`
// - `item_layout.size() > 0` and `new_capacity > 0`, so the layout size is non-zero
// - "new_size, when rounded up to the nearest multiple of layout.align(), must not overflow (i.e., the rounded value must be less than usize::MAX)",
// since the item size is always a multiple of its align, the rounding cannot happen
// here and the overflow is handled in `array_layout`
unsafe {
std::alloc::realloc( std::alloc::realloc(
self.get_ptr_mut().as_ptr(), self.get_ptr_mut().as_ptr(),
array_layout(&self.item_layout, self.capacity) array_layout(&self.item_layout, self.capacity)
.expect("array layout should be valid"), .expect("array layout should be valid"),
new_layout.size(), new_layout.size(),
) )
}; }
};
self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout)); self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout));
}
self.capacity = new_capacity; self.capacity = new_capacity;
} }
@ -274,14 +289,14 @@ impl BlobVec {
/// Gets a [`Ptr`] to the start of the vec /// Gets a [`Ptr`] to the start of the vec
#[inline] #[inline]
pub fn get_ptr(&self) -> Ptr<'_> { pub fn get_ptr(&self) -> Ptr<'_> {
// SAFE: the inner data will remain valid for as long as 'self. // SAFETY: the inner data will remain valid for as long as 'self.
unsafe { Ptr::new(self.data) } unsafe { Ptr::new(self.data) }
} }
/// Gets a [`PtrMut`] to the start of the vec /// Gets a [`PtrMut`] to the start of the vec
#[inline] #[inline]
pub fn get_ptr_mut(&mut self) -> PtrMut<'_> { pub fn get_ptr_mut(&mut self) -> PtrMut<'_> {
// SAFE: the inner data will remain valid for as long as 'self. // SAFETY: the inner data will remain valid for as long as 'self.
unsafe { PtrMut::new(self.data) } unsafe { PtrMut::new(self.data) }
} }
@ -290,7 +305,7 @@ impl BlobVec {
/// # Safety /// # Safety
/// The type `T` must be the type of the items in this [`BlobVec`]. /// The type `T` must be the type of the items in this [`BlobVec`].
pub unsafe fn get_slice<T>(&self) -> &[UnsafeCell<T>] { pub unsafe fn get_slice<T>(&self) -> &[UnsafeCell<T>] {
// SAFE: the inner data will remain valid for as long as 'self. // SAFETY: the inner data will remain valid for as long as 'self.
std::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell<T>, self.len) std::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell<T>, self.len)
} }
@ -302,6 +317,7 @@ impl BlobVec {
if let Some(drop) = self.drop { if let Some(drop) = self.drop {
let layout_size = self.item_layout.size(); let layout_size = self.item_layout.size();
for i in 0..len { for i in 0..len {
// SAFETY: `i * layout_size` is inbounds for the allocation, and the item is left unreachable so it can be safely promoted to an `OwningPtr`
unsafe { unsafe {
// NOTE: this doesn't use self.get_unchecked(i) because the debug_assert on index // NOTE: this doesn't use self.get_unchecked(i) because the debug_assert on index
// will panic here due to self.len being set to 0 // will panic here due to self.len being set to 0
@ -317,6 +333,7 @@ impl Drop for BlobVec {
fn drop(&mut self) { fn drop(&mut self) {
self.clear(); self.clear();
if self.item_layout.size() > 0 { if self.item_layout.size() > 0 {
// SAFETY: the `swap_scratch` pointer is always allocated using `self.item_layout`
unsafe { unsafe {
std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout); std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout);
} }
@ -324,6 +341,7 @@ impl Drop for BlobVec {
let array_layout = let array_layout =
array_layout(&self.item_layout, self.capacity).expect("array layout should be valid"); array_layout(&self.item_layout, self.capacity).expect("array layout should be valid");
if array_layout.size() > 0 { if array_layout.size() > 0 {
// SAFETY: data ptr layout is correct, swap_scratch ptr layout is correct
unsafe { unsafe {
std::alloc::dealloc(self.get_ptr_mut().as_ptr(), array_layout); std::alloc::dealloc(self.get_ptr_mut().as_ptr(), array_layout);
} }
@ -427,8 +445,9 @@ mod tests {
#[test] #[test]
fn resize_test() { fn resize_test() {
let item_layout = Layout::new::<usize>(); let item_layout = Layout::new::<usize>();
// usize doesn't need dropping // SAFETY: `drop` fn is `None`, usize doesn't need dropping
let mut blob_vec = unsafe { BlobVec::new(item_layout, None, 64) }; let mut blob_vec = unsafe { BlobVec::new(item_layout, None, 64) };
// SAFETY: `i` is a usize, i.e. the type corresponding to `item_layout`
unsafe { unsafe {
for i in 0..1_000 { for i in 0..1_000 {
push(&mut blob_vec, i as usize); push(&mut blob_vec, i as usize);
@ -458,8 +477,12 @@ mod tests {
{ {
let item_layout = Layout::new::<Foo>(); let item_layout = Layout::new::<Foo>();
let drop = drop_ptr::<Foo>; let drop = drop_ptr::<Foo>;
// SAFETY: drop is able to drop a value of its `item_layout`
let mut blob_vec = unsafe { BlobVec::new(item_layout, Some(drop), 2) }; let mut blob_vec = unsafe { BlobVec::new(item_layout, Some(drop), 2) };
assert_eq!(blob_vec.capacity(), 2); assert_eq!(blob_vec.capacity(), 2);
// SAFETY: the following code only deals with values of type `Foo`, which satisfies the safety requirement of `push`, `get_mut` and `swap_remove` that the
// values have a layout compatible to the blob vec's `item_layout`.
// Every index is in range.
unsafe { unsafe {
let foo1 = Foo { let foo1 = Foo {
a: 42, a: 42,
@ -518,6 +541,7 @@ mod tests {
fn blob_vec_drop_empty_capacity() { fn blob_vec_drop_empty_capacity() {
let item_layout = Layout::new::<Foo>(); let item_layout = Layout::new::<Foo>();
let drop = drop_ptr::<Foo>; let drop = drop_ptr::<Foo>;
// SAFETY: drop is able to drop a value of its `item_layout`
let _ = unsafe { BlobVec::new(item_layout, Some(drop), 0) }; let _ = unsafe { BlobVec::new(item_layout, Some(drop), 0) };
} }
} }

View File

@ -159,7 +159,7 @@ impl ComponentSparseSet {
let dense_index = *dense_index as usize; let dense_index = *dense_index as usize;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index]);
// SAFE: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { self.dense.get_data_unchecked(dense_index) } unsafe { self.dense.get_data_unchecked(dense_index) }
}) })
} }
@ -169,7 +169,7 @@ impl ComponentSparseSet {
let dense_index = *self.sparse.get(entity.id())? as usize; let dense_index = *self.sparse.get(entity.id())? as usize;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index]);
// SAFE: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { unsafe {
Some(( Some((
self.dense.get_data_unchecked(dense_index), self.dense.get_data_unchecked(dense_index),
@ -183,7 +183,7 @@ impl ComponentSparseSet {
let dense_index = *self.sparse.get(entity.id())? as usize; let dense_index = *self.sparse.get(entity.id())? as usize;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index]);
// SAFE: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) } unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) }
} }
@ -197,7 +197,7 @@ impl ComponentSparseSet {
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index]);
self.entities.swap_remove(dense_index); self.entities.swap_remove(dense_index);
let is_last = dense_index == self.dense.len() - 1; let is_last = dense_index == self.dense.len() - 1;
// SAFE: dense_index was just removed from `sparse`, which ensures that it is valid // SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid
let (value, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) }; let (value, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
if !is_last { if !is_last {
let swapped_entity = self.entities[dense_index]; let swapped_entity = self.entities[dense_index];
@ -218,7 +218,7 @@ impl ComponentSparseSet {
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index]);
self.entities.swap_remove(dense_index); self.entities.swap_remove(dense_index);
let is_last = dense_index == self.dense.len() - 1; let is_last = dense_index == self.dense.len() - 1;
// SAFE: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { self.dense.swap_remove_unchecked(dense_index) } unsafe { self.dense.swap_remove_unchecked(dense_index) }
if !is_last { if !is_last {
let swapped_entity = self.entities[dense_index]; let swapped_entity = self.entities[dense_index];
@ -280,7 +280,7 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
pub fn insert(&mut self, index: I, value: V) { pub fn insert(&mut self, index: I, value: V) {
if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() {
// SAFE: dense indices stored in self.sparse always exist // SAFETY: dense indices stored in self.sparse always exist
unsafe { unsafe {
*self.dense.get_unchecked_mut(dense_index) = value; *self.dense.get_unchecked_mut(dense_index) = value;
} }
@ -293,7 +293,7 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V { pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V {
if let Some(dense_index) = self.sparse.get(index.clone()).cloned() { if let Some(dense_index) = self.sparse.get(index.clone()).cloned() {
// SAFE: dense indices stored in self.sparse always exist // SAFETY: dense indices stored in self.sparse always exist
unsafe { self.dense.get_unchecked_mut(dense_index) } unsafe { self.dense.get_unchecked_mut(dense_index) }
} else { } else {
let value = func(); let value = func();
@ -301,7 +301,7 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
self.sparse.insert(index.clone(), dense_index); self.sparse.insert(index.clone(), dense_index);
self.indices.push(index); self.indices.push(index);
self.dense.push(value); self.dense.push(value);
// SAFE: dense index was just populated above // SAFETY: dense index was just populated above
unsafe { self.dense.get_unchecked_mut(dense_index) } unsafe { self.dense.get_unchecked_mut(dense_index) }
} }
} }
@ -323,7 +323,7 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
pub fn get(&self, index: I) -> Option<&V> { pub fn get(&self, index: I) -> Option<&V> {
self.sparse.get(index).map(|dense_index| { self.sparse.get(index).map(|dense_index| {
// SAFE: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { self.dense.get_unchecked(*dense_index) } unsafe { self.dense.get_unchecked(*dense_index) }
}) })
} }
@ -331,7 +331,7 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
pub fn get_mut(&mut self, index: I) -> Option<&mut V> { pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
let dense = &mut self.dense; let dense = &mut self.dense;
self.sparse.get(index).map(move |dense_index| { self.sparse.get(index).map(move |dense_index| {
// SAFE: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { dense.get_unchecked_mut(*dense_index) } unsafe { dense.get_unchecked_mut(*dense_index) }
}) })
} }

View File

@ -42,7 +42,7 @@ impl Column {
#[inline] #[inline]
pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self { pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self {
Column { Column {
// SAFE: component_info.drop() is valid for the types that will be inserted. // SAFETY: component_info.drop() is valid for the types that will be inserted.
data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) }, data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) },
ticks: Vec::with_capacity(capacity), ticks: Vec::with_capacity(capacity),
} }
@ -559,7 +559,7 @@ mod tests {
table.add_column(components.get_info(component_id).unwrap()); table.add_column(components.get_info(component_id).unwrap());
let entities = (0..200).map(Entity::from_raw).collect::<Vec<_>>(); let entities = (0..200).map(Entity::from_raw).collect::<Vec<_>>();
for entity in &entities { for entity in &entities {
// SAFE: we allocate and immediately set data afterwards // SAFETY: we allocate and immediately set data afterwards
unsafe { unsafe {
let row = table.allocate(*entity); let row = table.allocate(*entity);
let value: W<usize> = W(row); let value: W<usize> = W(row);

View File

@ -21,10 +21,10 @@ pub struct CommandQueue {
metas: Vec<CommandMeta>, metas: Vec<CommandMeta>,
} }
// SAFE: All commands [`Command`] implement [`Send`] // SAFETY: All commands [`Command`] implement [`Send`]
unsafe impl Send for CommandQueue {} unsafe impl Send for CommandQueue {}
// SAFE: `&CommandQueue` never gives access to the inner commands. // SAFETY: `&CommandQueue` never gives access to the inner commands.
unsafe impl Sync for CommandQueue {} unsafe impl Sync for CommandQueue {}
impl CommandQueue { impl CommandQueue {
@ -34,7 +34,7 @@ impl CommandQueue {
where where
C: Command, C: Command,
{ {
/// SAFE: This function is only every called when the `command` bytes is the associated /// SAFETY: This function is only every called when the `command` bytes is the associated
/// [`Commands`] `T` type. Also this only reads the data via `read_unaligned` so unaligned /// [`Commands`] `T` type. Also this only reads the data via `read_unaligned` so unaligned
/// accesses are safe. /// accesses are safe.
unsafe fn write_command<T: Command>(command: *mut MaybeUninit<u8>, world: &mut World) { unsafe fn write_command<T: Command>(command: *mut MaybeUninit<u8>, world: &mut World) {
@ -57,7 +57,7 @@ impl CommandQueue {
if size > 0 { if size > 0 {
self.bytes.reserve(size); self.bytes.reserve(size);
// SAFE: The internal `bytes` vector has enough storage for the // SAFETY: The internal `bytes` vector has enough storage for the
// command (see the call the `reserve` above), the vector has // command (see the call the `reserve` above), the vector has
// its length set appropriately and can contain any kind of bytes. // its length set appropriately and can contain any kind of bytes.
// In case we're writing a ZST and the `Vec` hasn't allocated yet // In case we're writing a ZST and the `Vec` hasn't allocated yet
@ -83,13 +83,13 @@ impl CommandQueue {
// flush the previously queued entities // flush the previously queued entities
world.flush(); world.flush();
// SAFE: In the iteration below, `meta.func` will safely consume and drop each pushed command. // SAFETY: In the iteration below, `meta.func` will safely consume and drop each pushed command.
// This operation is so that we can reuse the bytes `Vec<u8>`'s internal storage and prevent // This operation is so that we can reuse the bytes `Vec<u8>`'s internal storage and prevent
// unnecessary allocations. // unnecessary allocations.
unsafe { self.bytes.set_len(0) }; unsafe { self.bytes.set_len(0) };
for meta in self.metas.drain(..) { for meta in self.metas.drain(..) {
// SAFE: The implementation of `write_command` is safe for the according Command type. // SAFETY: The implementation of `write_command` is safe for the according Command type.
// It's ok to read from `bytes.as_mut_ptr()` because we just wrote to it in `push`. // It's ok to read from `bytes.as_mut_ptr()` because we just wrote to it in `push`.
// The bytes are safely cast to their original type, safely read, and then dropped. // The bytes are safely cast to their original type, safely read, and then dropped.
unsafe { unsafe {

View File

@ -68,7 +68,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for ParallelCommandsState {
} }
} }
// SAFE: no component or resource access to report // SAFETY: no component or resource access to report
unsafe impl SystemParamState for ParallelCommandsState { unsafe impl SystemParamState for ParallelCommandsState {
fn init(_: &mut World, _: &mut crate::system::SystemMeta) -> Self { fn init(_: &mut World, _: &mut crate::system::SystemMeta) -> Self {
Self::default() Self::default()

View File

@ -166,7 +166,7 @@ impl<Param: SystemParam> SystemState<Param> {
Param::Fetch: ReadOnlySystemParamFetch, Param::Fetch: ReadOnlySystemParamFetch,
{ {
self.validate_world_and_update_archetypes(world); self.validate_world_and_update_archetypes(world);
// SAFE: Param is read-only and doesn't allow mutable access to World. It also matches the World this SystemState was created with. // SAFETY: Param is read-only and doesn't allow mutable access to World. It also matches the World this SystemState was created with.
unsafe { self.get_unchecked_manual(world) } unsafe { self.get_unchecked_manual(world) }
} }
@ -177,7 +177,7 @@ impl<Param: SystemParam> SystemState<Param> {
world: &'w mut World, world: &'w mut World,
) -> <Param::Fetch as SystemParamFetch<'w, 's>>::Item { ) -> <Param::Fetch as SystemParamFetch<'w, 's>>::Item {
self.validate_world_and_update_archetypes(world); self.validate_world_and_update_archetypes(world);
// SAFE: World is uniquely borrowed and matches the World this SystemState was created with. // SAFETY: World is uniquely borrowed and matches the World this SystemState was created with.
unsafe { self.get_unchecked_manual(world) } unsafe { self.get_unchecked_manual(world) }
} }

View File

@ -293,7 +293,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn iter(&self) -> QueryIter<'_, 's, Q, ROQueryFetch<'_, Q>, F> { pub fn iter(&self) -> QueryIter<'_, 's, Q, ROQueryFetch<'_, Q>, F> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state self.state
@ -323,7 +323,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn iter_mut(&mut self) -> QueryIter<'_, '_, Q, QueryFetch<'_, Q>, F> { pub fn iter_mut(&mut self) -> QueryIter<'_, '_, Q, QueryFetch<'_, Q>, F> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state self.state
@ -340,7 +340,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// - if K > N: empty set (no K-sized combinations exist) /// - if K > N: empty set (no K-sized combinations exist)
#[inline] #[inline]
pub fn iter_combinations<const K: usize>(&self) -> QueryCombinationIter<'_, '_, Q, F, K> { pub fn iter_combinations<const K: usize>(&self) -> QueryCombinationIter<'_, '_, Q, F, K> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state.iter_combinations_unchecked_manual( self.state.iter_combinations_unchecked_manual(
@ -377,7 +377,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
pub fn iter_combinations_mut<const K: usize>( pub fn iter_combinations_mut<const K: usize>(
&mut self, &mut self,
) -> QueryCombinationIter<'_, '_, Q, F, K> { ) -> QueryCombinationIter<'_, '_, Q, F, K> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state.iter_combinations_unchecked_manual( self.state.iter_combinations_unchecked_manual(
@ -446,7 +446,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// this call does not result in multiple mutable references to the same component /// this call does not result in multiple mutable references to the same component
#[inline] #[inline]
pub unsafe fn iter_unsafe(&'s self) -> QueryIter<'w, 's, Q, QueryFetch<'w, Q>, F> { pub unsafe fn iter_unsafe(&'s self) -> QueryIter<'w, 's, Q, QueryFetch<'w, Q>, F> {
// SEMI-SAFE: system runs without conflicts with other systems. // SEMI-SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
self.state self.state
.iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick) .iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick)
@ -462,7 +462,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
pub unsafe fn iter_combinations_unsafe<const K: usize>( pub unsafe fn iter_combinations_unsafe<const K: usize>(
&self, &self,
) -> QueryCombinationIter<'_, '_, Q, F, K> { ) -> QueryCombinationIter<'_, '_, Q, F, K> {
// SEMI-SAFE: system runs without conflicts with other systems. // SEMI-SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
self.state.iter_combinations_unchecked_manual( self.state.iter_combinations_unchecked_manual(
self.world, self.world,
@ -519,7 +519,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn for_each<'this>(&'this self, f: impl FnMut(ROQueryItem<'this, Q>)) { pub fn for_each<'this>(&'this self, f: impl FnMut(ROQueryItem<'this, Q>)) {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state.for_each_unchecked_manual::<ROQueryFetch<Q>, _>( self.state.for_each_unchecked_manual::<ROQueryFetch<Q>, _>(
@ -554,7 +554,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn for_each_mut<'a, FN: FnMut(QueryItem<'a, Q>)>(&'a mut self, f: FN) { pub fn for_each_mut<'a, FN: FnMut(QueryItem<'a, Q>)>(&'a mut self, f: FN) {
// SAFE: system runs without conflicts with other systems. same-system queries have runtime // SAFETY: system runs without conflicts with other systems. same-system queries have runtime
// borrow checks when they conflict // borrow checks when they conflict
unsafe { unsafe {
self.state.for_each_unchecked_manual::<QueryFetch<Q>, FN>( self.state.for_each_unchecked_manual::<QueryFetch<Q>, FN>(
@ -597,7 +597,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
batch_size: usize, batch_size: usize,
f: impl Fn(ROQueryItem<'this, Q>) + Send + Sync + Clone, f: impl Fn(ROQueryItem<'this, Q>) + Send + Sync + Clone,
) { ) {
// SAFE: system runs without conflicts with other systems. same-system queries have runtime // SAFETY: system runs without conflicts with other systems. same-system queries have runtime
// borrow checks when they conflict // borrow checks when they conflict
unsafe { unsafe {
self.state self.state
@ -625,7 +625,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
batch_size: usize, batch_size: usize,
f: FN, f: FN,
) { ) {
// SAFE: system runs without conflicts with other systems. same-system queries have runtime // SAFETY: system runs without conflicts with other systems. same-system queries have runtime
// borrow checks when they conflict // borrow checks when they conflict
unsafe { unsafe {
self.state self.state
@ -675,7 +675,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
) where ) where
EntityList::Item: Borrow<Entity>, EntityList::Item: Borrow<Entity>,
{ {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state.many_for_each_unchecked_manual( self.state.many_for_each_unchecked_manual(
@ -721,7 +721,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn get(&self, entity: Entity) -> Result<ROQueryItem<'_, Q>, QueryEntityError> { pub fn get(&self, entity: Entity) -> Result<ROQueryItem<'_, Q>, QueryEntityError> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state.get_unchecked_manual::<ROQueryFetch<Q>>( self.state.get_unchecked_manual::<ROQueryFetch<Q>>(
@ -746,7 +746,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
&self, &self,
entities: [Entity; N], entities: [Entity; N],
) -> Result<[ROQueryItem<'_, Q>; N], QueryEntityError> { ) -> Result<[ROQueryItem<'_, Q>; N], QueryEntityError> {
// SAFE: it is the scheduler's responsibility to ensure that `Query` is never handed out on the wrong `World`. // SAFETY: it is the scheduler's responsibility to ensure that `Query` is never handed out on the wrong `World`.
unsafe { unsafe {
self.state.get_many_read_only_manual( self.state.get_many_read_only_manual(
self.world, self.world,
@ -823,7 +823,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn get_mut(&mut self, entity: Entity) -> Result<QueryItem<'_, Q>, QueryEntityError> { pub fn get_mut(&mut self, entity: Entity) -> Result<QueryItem<'_, Q>, QueryEntityError> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state.get_unchecked_manual::<QueryFetch<Q>>( self.state.get_unchecked_manual::<QueryFetch<Q>>(
@ -846,7 +846,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
&mut self, &mut self,
entities: [Entity; N], entities: [Entity; N],
) -> Result<[QueryItem<'_, Q>; N], QueryEntityError> { ) -> Result<[QueryItem<'_, Q>; N], QueryEntityError> {
// SAFE: scheduler ensures safe Query world access // SAFETY: scheduler ensures safe Query world access
unsafe { unsafe {
self.state.get_many_unchecked_manual( self.state.get_many_unchecked_manual(
self.world, self.world,
@ -917,7 +917,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
&'s self, &'s self,
entity: Entity, entity: Entity,
) -> Result<QueryItem<'w, Q>, QueryEntityError> { ) -> Result<QueryItem<'w, Q>, QueryEntityError> {
// SEMI-SAFE: system runs without conflicts with other systems. // SEMI-SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
self.state.get_unchecked_manual::<QueryFetch<Q>>( self.state.get_unchecked_manual::<QueryFetch<Q>>(
self.world, self.world,
@ -1011,7 +1011,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
&mut self, &mut self,
entity: Entity, entity: Entity,
) -> Result<Mut<'_, T>, QueryComponentError> { ) -> Result<Mut<'_, T>, QueryComponentError> {
// SAFE: unique access to query (preventing aliased access) // SAFETY: unique access to query (preventing aliased access)
unsafe { self.get_component_unchecked_mut(entity) } unsafe { self.get_component_unchecked_mut(entity) }
} }
@ -1117,6 +1117,9 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn get_single(&self) -> Result<ROQueryItem<'_, Q>, QuerySingleError> { pub fn get_single(&self) -> Result<ROQueryItem<'_, Q>, QuerySingleError> {
// SAFETY:
// the query ensures that the components it accesses are not mutably accessible somewhere else
// and the query is read only.
unsafe { unsafe {
self.state.get_single_unchecked_manual::<ROQueryFetch<Q>>( self.state.get_single_unchecked_manual::<ROQueryFetch<Q>>(
self.world, self.world,
@ -1179,6 +1182,9 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn get_single_mut(&mut self) -> Result<QueryItem<'_, Q>, QuerySingleError> { pub fn get_single_mut(&mut self) -> Result<QueryItem<'_, Q>, QuerySingleError> {
// SAFETY:
// the query ensures mutable access to the components it accesses, and the query
// is uniquely borrowed
unsafe { unsafe {
self.state.get_single_unchecked_manual::<QueryFetch<Q>>( self.state.get_single_unchecked_manual::<QueryFetch<Q>>(
self.world, self.world,
@ -1238,7 +1244,7 @@ impl<'w, 's, Q: WorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn contains(&self, entity: Entity) -> bool { pub fn contains(&self, entity: Entity) -> bool {
// SAFE: NopFetch does not access any members while &self ensures no one has exclusive access // SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access
unsafe { unsafe {
self.state self.state
.get_unchecked_manual::<NopFetch<Q::State>>( .get_unchecked_manual::<NopFetch<Q::State>>(
@ -1340,7 +1346,7 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn get_inner(&'s self, entity: Entity) -> Result<ROQueryItem<'w, Q>, QueryEntityError> { pub fn get_inner(&'s self, entity: Entity) -> Result<ROQueryItem<'w, Q>, QueryEntityError> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state.get_unchecked_manual::<ROQueryFetch<'w, Q>>( self.state.get_unchecked_manual::<ROQueryFetch<'w, Q>>(
@ -1377,7 +1383,7 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: WorldQuery> Query<'w, 's, Q, F> {
/// ``` /// ```
#[inline] #[inline]
pub fn iter_inner(&'s self) -> QueryIter<'w, 's, Q, ROQueryFetch<'w, Q>, F> { pub fn iter_inner(&'s self) -> QueryIter<'w, 's, Q, ROQueryFetch<'w, Q>, F> {
// SAFE: system runs without conflicts with other systems. // SAFETY: system runs without conflicts with other systems.
// same-system queries have runtime borrow checks when they conflict // same-system queries have runtime borrow checks when they conflict
unsafe { unsafe {
self.state self.state

View File

@ -46,7 +46,7 @@ pub trait System: Send + Sync + 'static {
/// Runs the system with the given input in the world. /// Runs the system with the given input in the world.
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out { fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
self.update_archetype_component_access(world); self.update_archetype_component_access(world);
// SAFE: world and resources are exclusively borrowed // SAFETY: world and resources are exclusively borrowed
unsafe { self.run_unsafe(input, world) } unsafe { self.run_unsafe(input, world) }
} }
fn apply_buffers(&mut self, world: &mut World); fn apply_buffers(&mut self, world: &mut World);

View File

@ -134,10 +134,10 @@ impl<'w, 's, Q: WorldQuery + 'static, F: WorldQuery + 'static> SystemParam for Q
type Fetch = QueryState<Q, F>; type Fetch = QueryState<Q, F>;
} }
// SAFE: QueryState is constrained to read-only fetches, so it only reads World. // SAFETY: QueryState is constrained to read-only fetches, so it only reads World.
unsafe impl<Q: ReadOnlyWorldQuery, F: WorldQuery> ReadOnlySystemParamFetch for QueryState<Q, F> {} unsafe impl<Q: ReadOnlyWorldQuery, F: WorldQuery> ReadOnlySystemParamFetch for QueryState<Q, F> {}
// SAFE: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If // SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If
// this QueryState conflicts with any prior access, a panic will occur. // this QueryState conflicts with any prior access, a panic will occur.
unsafe impl<Q: WorldQuery + 'static, F: WorldQuery + 'static> SystemParamState unsafe impl<Q: WorldQuery + 'static, F: WorldQuery + 'static> SystemParamState
for QueryState<Q, F> for QueryState<Q, F>
@ -239,7 +239,7 @@ pub struct Res<'w, T: Resource> {
change_tick: u32, change_tick: u32,
} }
// SAFE: Res only reads a single World resource // SAFETY: Res only reads a single World resource
unsafe impl<T: Resource> ReadOnlySystemParamFetch for ResState<T> {} unsafe impl<T: Resource> ReadOnlySystemParamFetch for ResState<T> {}
impl<'w, T: Resource> Debug for Res<'w, T> impl<'w, T: Resource> Debug for Res<'w, T>
@ -305,7 +305,7 @@ impl<'a, T: Resource> SystemParam for Res<'a, T> {
type Fetch = ResState<T>; type Fetch = ResState<T>;
} }
// SAFE: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res
// conflicts with any prior access, a panic will occur. // conflicts with any prior access, a panic will occur.
unsafe impl<T: Resource> SystemParamState for ResState<T> { unsafe impl<T: Resource> SystemParamState for ResState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
@ -370,9 +370,11 @@ impl<'a, T: Resource> SystemParam for Option<Res<'a, T>> {
type Fetch = OptionResState<T>; type Fetch = OptionResState<T>;
} }
// SAFE: Only reads a single World resource // SAFETY: Only reads a single World resource
unsafe impl<T: Resource> ReadOnlySystemParamFetch for OptionResState<T> {} unsafe impl<T: Resource> ReadOnlySystemParamFetch for OptionResState<T> {}
// SAFETY: this impl defers to `ResState`, which initializes
// and validates the correct world access
unsafe impl<T: Resource> SystemParamState for OptionResState<T> { unsafe impl<T: Resource> SystemParamState for OptionResState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
Self(ResState::init(world, system_meta)) Self(ResState::init(world, system_meta))
@ -411,7 +413,7 @@ impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
type Fetch = ResMutState<T>; type Fetch = ResMutState<T>;
} }
// SAFE: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res // SAFETY: Res ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this Res
// conflicts with any prior access, a panic will occur. // conflicts with any prior access, a panic will occur.
unsafe impl<T: Resource> SystemParamState for ResMutState<T> { unsafe impl<T: Resource> SystemParamState for ResMutState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
@ -481,6 +483,8 @@ impl<'a, T: Resource> SystemParam for Option<ResMut<'a, T>> {
type Fetch = OptionResMutState<T>; type Fetch = OptionResMutState<T>;
} }
// SAFETY: this impl defers to `ResMutState`, which initializes
// and validates the correct world access
unsafe impl<T: Resource> SystemParamState for OptionResMutState<T> { unsafe impl<T: Resource> SystemParamState for OptionResMutState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
Self(ResMutState::init(world, system_meta)) Self(ResMutState::init(world, system_meta))
@ -514,10 +518,10 @@ impl<'w, 's> SystemParam for Commands<'w, 's> {
type Fetch = CommandQueue; type Fetch = CommandQueue;
} }
// SAFE: Commands only accesses internal state // SAFETY: Commands only accesses internal state
unsafe impl ReadOnlySystemParamFetch for CommandQueue {} unsafe impl ReadOnlySystemParamFetch for CommandQueue {}
// SAFE: only local state is accessed // SAFETY: only local state is accessed
unsafe impl SystemParamState for CommandQueue { unsafe impl SystemParamState for CommandQueue {
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Default::default() Default::default()
@ -542,7 +546,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for CommandQueue {
} }
} }
/// SAFE: only reads world /// SAFETY: only reads world
unsafe impl ReadOnlySystemParamFetch for WorldState {} unsafe impl ReadOnlySystemParamFetch for WorldState {}
/// The [`SystemParamState`] of [`&World`](crate::world::World). /// The [`SystemParamState`] of [`&World`](crate::world::World).
@ -553,6 +557,7 @@ impl<'w> SystemParam for &'w World {
type Fetch = WorldState; type Fetch = WorldState;
} }
// SAFETY: `read_all` access is set and conflicts result in a panic
unsafe impl SystemParamState for WorldState { unsafe impl SystemParamState for WorldState {
fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(_world: &mut World, system_meta: &mut SystemMeta) -> Self {
let mut access = Access::default(); let mut access = Access::default();
@ -637,7 +642,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState {
/// ``` /// ```
pub struct Local<'a, T: Resource>(&'a mut T); pub struct Local<'a, T: Resource>(&'a mut T);
// SAFE: Local only accesses internal state // SAFETY: Local only accesses internal state
unsafe impl<T: Resource> ReadOnlySystemParamFetch for LocalState<T> {} unsafe impl<T: Resource> ReadOnlySystemParamFetch for LocalState<T> {}
impl<'a, T: Resource> Debug for Local<'a, T> impl<'a, T: Resource> Debug for Local<'a, T>
@ -673,7 +678,7 @@ impl<'a, T: Resource + FromWorld> SystemParam for Local<'a, T> {
type Fetch = LocalState<T>; type Fetch = LocalState<T>;
} }
// SAFE: only local state is accessed // SAFETY: only local state is accessed
unsafe impl<T: Resource + FromWorld> SystemParamState for LocalState<T> { unsafe impl<T: Resource + FromWorld> SystemParamState for LocalState<T> {
fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Self(T::from_world(world)) Self(T::from_world(world))
@ -741,7 +746,7 @@ impl<'a, T: Component> RemovedComponents<'a, T> {
} }
} }
// SAFE: Only reads World components // SAFETY: Only reads World components
unsafe impl<T: Component> ReadOnlySystemParamFetch for RemovedComponentsState<T> {} unsafe impl<T: Component> ReadOnlySystemParamFetch for RemovedComponentsState<T> {}
/// The [`SystemParamState`] of [`RemovedComponents<T>`]. /// The [`SystemParamState`] of [`RemovedComponents<T>`].
@ -755,7 +760,7 @@ impl<'a, T: Component> SystemParam for RemovedComponents<'a, T> {
type Fetch = RemovedComponentsState<T>; type Fetch = RemovedComponentsState<T>;
} }
// SAFE: no component access. removed component entity collections can be read in parallel and are // SAFETY: no component access. removed component entity collections can be read in parallel and are
// never mutably borrowed during system execution // never mutably borrowed during system execution
unsafe impl<T: Component> SystemParamState for RemovedComponentsState<T> { unsafe impl<T: Component> SystemParamState for RemovedComponentsState<T> {
fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, _system_meta: &mut SystemMeta) -> Self {
@ -803,7 +808,7 @@ pub struct NonSend<'w, T: 'static> {
change_tick: u32, change_tick: u32,
} }
// SAFE: Only reads a single World non-send resource // SAFETY: Only reads a single World non-send resource
unsafe impl<T> ReadOnlySystemParamFetch for NonSendState<T> {} unsafe impl<T> ReadOnlySystemParamFetch for NonSendState<T> {}
impl<'w, T> Debug for NonSend<'w, T> impl<'w, T> Debug for NonSend<'w, T>
@ -857,7 +862,7 @@ impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
type Fetch = NonSendState<T>; type Fetch = NonSendState<T>;
} }
// SAFE: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // SAFETY: NonSendComponentId and ArchetypeComponentId access is applied to SystemMeta. If this
// NonSend conflicts with any prior access, a panic will occur. // NonSend conflicts with any prior access, a panic will occur.
unsafe impl<T: 'static> SystemParamState for NonSendState<T> { unsafe impl<T: 'static> SystemParamState for NonSendState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
@ -926,9 +931,11 @@ impl<'w, T: 'static> SystemParam for Option<NonSend<'w, T>> {
type Fetch = OptionNonSendState<T>; type Fetch = OptionNonSendState<T>;
} }
// SAFE: Only reads a single non-send resource // SAFETY: Only reads a single non-send resource
unsafe impl<T: 'static> ReadOnlySystemParamFetch for OptionNonSendState<T> {} unsafe impl<T: 'static> ReadOnlySystemParamFetch for OptionNonSendState<T> {}
// SAFETY: this impl defers to `NonSendState`, which initializes
// and validates the correct world access
unsafe impl<T: 'static> SystemParamState for OptionNonSendState<T> { unsafe impl<T: 'static> SystemParamState for OptionNonSendState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
Self(NonSendState::init(world, system_meta)) Self(NonSendState::init(world, system_meta))
@ -968,7 +975,7 @@ impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
type Fetch = NonSendMutState<T>; type Fetch = NonSendMutState<T>;
} }
// SAFE: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this // SAFETY: NonSendMut ComponentId and ArchetypeComponentId access is applied to SystemMeta. If this
// NonSendMut conflicts with any prior access, a panic will occur. // NonSendMut conflicts with any prior access, a panic will occur.
unsafe impl<T: 'static> SystemParamState for NonSendMutState<T> { unsafe impl<T: 'static> SystemParamState for NonSendMutState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
@ -1041,6 +1048,8 @@ impl<'a, T: 'static> SystemParam for Option<NonSendMut<'a, T>> {
type Fetch = OptionNonSendMutState<T>; type Fetch = OptionNonSendMutState<T>;
} }
// SAFETY: this impl defers to `NonSendMutState`, which initializes
// and validates the correct world access
unsafe impl<T: 'static> SystemParamState for OptionNonSendMutState<T> { unsafe impl<T: 'static> SystemParamState for OptionNonSendMutState<T> {
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
Self(NonSendMutState::init(world, system_meta)) Self(NonSendMutState::init(world, system_meta))
@ -1075,14 +1084,14 @@ impl<'a> SystemParam for &'a Archetypes {
type Fetch = ArchetypesState; type Fetch = ArchetypesState;
} }
// SAFE: Only reads World archetypes // SAFETY: Only reads World archetypes
unsafe impl ReadOnlySystemParamFetch for ArchetypesState {} unsafe impl ReadOnlySystemParamFetch for ArchetypesState {}
/// The [`SystemParamState`] of [`Archetypes`]. /// The [`SystemParamState`] of [`Archetypes`].
#[doc(hidden)] #[doc(hidden)]
pub struct ArchetypesState; pub struct ArchetypesState;
// SAFE: no component value access // SAFETY: no component value access
unsafe impl SystemParamState for ArchetypesState { unsafe impl SystemParamState for ArchetypesState {
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Self Self
@ -1107,14 +1116,14 @@ impl<'a> SystemParam for &'a Components {
type Fetch = ComponentsState; type Fetch = ComponentsState;
} }
// SAFE: Only reads World components // SAFETY: Only reads World components
unsafe impl ReadOnlySystemParamFetch for ComponentsState {} unsafe impl ReadOnlySystemParamFetch for ComponentsState {}
/// The [`SystemParamState`] of [`Components`]. /// The [`SystemParamState`] of [`Components`].
#[doc(hidden)] #[doc(hidden)]
pub struct ComponentsState; pub struct ComponentsState;
// SAFE: no component value access // SAFETY: no component value access
unsafe impl SystemParamState for ComponentsState { unsafe impl SystemParamState for ComponentsState {
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Self Self
@ -1139,14 +1148,14 @@ impl<'a> SystemParam for &'a Entities {
type Fetch = EntitiesState; type Fetch = EntitiesState;
} }
// SAFE: Only reads World entities // SAFETY: Only reads World entities
unsafe impl ReadOnlySystemParamFetch for EntitiesState {} unsafe impl ReadOnlySystemParamFetch for EntitiesState {}
/// The [`SystemParamState`] of [`Entities`]. /// The [`SystemParamState`] of [`Entities`].
#[doc(hidden)] #[doc(hidden)]
pub struct EntitiesState; pub struct EntitiesState;
// SAFE: no component value access // SAFETY: no component value access
unsafe impl SystemParamState for EntitiesState { unsafe impl SystemParamState for EntitiesState {
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Self Self
@ -1171,14 +1180,14 @@ impl<'a> SystemParam for &'a Bundles {
type Fetch = BundlesState; type Fetch = BundlesState;
} }
// SAFE: Only reads World bundles // SAFETY: Only reads World bundles
unsafe impl ReadOnlySystemParamFetch for BundlesState {} unsafe impl ReadOnlySystemParamFetch for BundlesState {}
/// The [`SystemParamState`] of [`Bundles`]. /// The [`SystemParamState`] of [`Bundles`].
#[doc(hidden)] #[doc(hidden)]
pub struct BundlesState; pub struct BundlesState;
// SAFE: no component value access // SAFETY: no component value access
unsafe impl SystemParamState for BundlesState { unsafe impl SystemParamState for BundlesState {
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Self Self
@ -1228,7 +1237,7 @@ impl SystemChangeTick {
} }
} }
// SAFE: Only reads internal system state // SAFETY: Only reads internal system state
unsafe impl ReadOnlySystemParamFetch for SystemChangeTickState {} unsafe impl ReadOnlySystemParamFetch for SystemChangeTickState {}
impl SystemParam for SystemChangeTick { impl SystemParam for SystemChangeTick {
@ -1239,6 +1248,7 @@ impl SystemParam for SystemChangeTick {
#[doc(hidden)] #[doc(hidden)]
pub struct SystemChangeTickState {} pub struct SystemChangeTickState {}
// SAFETY: `SystemParamTickState` doesn't require any world access
unsafe impl SystemParamState for SystemChangeTickState { unsafe impl SystemParamState for SystemChangeTickState {
fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self { fn init(_world: &mut World, _system_meta: &mut SystemMeta) -> Self {
Self {} Self {}
@ -1267,7 +1277,7 @@ macro_rules! impl_system_param_tuple {
type Fetch = ($($param::Fetch,)*); type Fetch = ($($param::Fetch,)*);
} }
// SAFE: tuple consists only of ReadOnlySystemParamFetches // SAFETY: tuple consists only of ReadOnlySystemParamFetches
unsafe impl<$($param: ReadOnlySystemParamFetch),*> ReadOnlySystemParamFetch for ($($param,)*) {} unsafe impl<$($param: ReadOnlySystemParamFetch),*> ReadOnlySystemParamFetch for ($($param,)*) {}
#[allow(unused_variables)] #[allow(unused_variables)]
@ -1289,7 +1299,8 @@ macro_rules! impl_system_param_tuple {
} }
} }
/// SAFE: implementors of each `SystemParamState` in the tuple have validated their impls // SAFETY: implementors of each `SystemParamState` in the tuple have validated their impls
#[allow(clippy::undocumented_unsafe_blocks)] // false positive by clippy
#[allow(non_snake_case)] #[allow(non_snake_case)]
unsafe impl<$($param: SystemParamState),*> SystemParamState for ($($param,)*) { unsafe impl<$($param: SystemParamState),*> SystemParamState for ($($param,)*) {
#[inline] #[inline]
@ -1403,7 +1414,7 @@ impl<'w, 's, P: SystemParam> StaticSystemParam<'w, 's, P> {
#[doc(hidden)] #[doc(hidden)]
pub struct StaticSystemParamState<S, P>(S, PhantomData<fn() -> P>); pub struct StaticSystemParamState<S, P>(S, PhantomData<fn() -> P>);
// Safe: This doesn't add any more reads, and the delegated fetch confirms it // SAFETY: This doesn't add any more reads, and the delegated fetch confirms it
unsafe impl<S: ReadOnlySystemParamFetch, P> ReadOnlySystemParamFetch unsafe impl<S: ReadOnlySystemParamFetch, P> ReadOnlySystemParamFetch
for StaticSystemParamState<S, P> for StaticSystemParamState<S, P>
{ {
@ -1428,11 +1439,12 @@ where
world: &'world World, world: &'world World,
change_tick: u32, change_tick: u32,
) -> Self::Item { ) -> Self::Item {
// Safe: We properly delegate SystemParamState // SAFETY: We properly delegate SystemParamState
StaticSystemParam(S::get_param(&mut state.0, system_meta, world, change_tick)) StaticSystemParam(S::get_param(&mut state.0, system_meta, world, change_tick))
} }
} }
// SAFETY: all methods are just delegated to `S`'s `SystemParamState` implementation
unsafe impl<S: SystemParamState, P: SystemParam + 'static> SystemParamState unsafe impl<S: SystemParamState, P: SystemParam + 'static> SystemParamState
for StaticSystemParamState<S, P> for StaticSystemParamState<S, P>
{ {

View File

@ -65,7 +65,7 @@ impl<'w> EntityRef<'w> {
#[inline] #[inline]
pub fn get<T: Component>(&self) -> Option<&'w T> { pub fn get<T: Component>(&self) -> Option<&'w T> {
// SAFE: entity location is valid and returned component is of type T // SAFETY: entity location is valid and returned component is of type T
unsafe { unsafe {
get_component_with_type(self.world, TypeId::of::<T>(), self.entity, self.location) get_component_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|value| value.deref::<T>()) .map(|value| value.deref::<T>())
@ -76,7 +76,7 @@ impl<'w> EntityRef<'w> {
/// detection in custom runtimes. /// detection in custom runtimes.
#[inline] #[inline]
pub fn get_change_ticks<T: Component>(&self) -> Option<&'w ComponentTicks> { pub fn get_change_ticks<T: Component>(&self) -> Option<&'w ComponentTicks> {
// SAFE: entity location is valid // SAFETY: entity location is valid
unsafe { unsafe {
get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location) get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|ticks| ticks.deref()) .map(|ticks| ticks.deref())
@ -123,7 +123,7 @@ impl<'w> EntityRef<'w> {
#[inline] #[inline]
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> { pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'w>> {
self.world.components().get_info(component_id)?; self.world.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above // SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe { get_component(self.world, component_id, self.entity, self.location) } unsafe { get_component(self.world, component_id, self.entity, self.location) }
} }
} }
@ -184,7 +184,7 @@ impl<'w> EntityMut<'w> {
#[inline] #[inline]
pub fn get<T: Component>(&self) -> Option<&'_ T> { pub fn get<T: Component>(&self) -> Option<&'_ T> {
// SAFE: lifetimes enforce correct usage of returned borrow // SAFETY: lifetimes enforce correct usage of returned borrow
unsafe { unsafe {
get_component_with_type(self.world, TypeId::of::<T>(), self.entity, self.location) get_component_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|value| value.deref::<T>()) .map(|value| value.deref::<T>())
@ -193,7 +193,7 @@ impl<'w> EntityMut<'w> {
#[inline] #[inline]
pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> { pub fn get_mut<T: Component>(&mut self) -> Option<Mut<'_, T>> {
// SAFE: world access is unique, and lifetimes enforce correct usage of returned borrow // SAFETY: world access is unique, and lifetimes enforce correct usage of returned borrow
unsafe { self.get_unchecked_mut::<T>() } unsafe { self.get_unchecked_mut::<T>() }
} }
@ -201,7 +201,7 @@ impl<'w> EntityMut<'w> {
/// detection in custom runtimes. /// detection in custom runtimes.
#[inline] #[inline]
pub fn get_change_ticks<T: Component>(&self) -> Option<&ComponentTicks> { pub fn get_change_ticks<T: Component>(&self) -> Option<&ComponentTicks> {
// SAFE: entity location is valid // SAFETY: entity location is valid
unsafe { unsafe {
get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location) get_ticks_with_type(self.world, TypeId::of::<T>(), self.entity, self.location)
.map(|ticks| ticks.deref()) .map(|ticks| ticks.deref())
@ -245,7 +245,7 @@ impl<'w> EntityMut<'w> {
self.location.archetype_id, self.location.archetype_id,
change_tick, change_tick,
); );
// SAFE: location matches current entity. `T` matches `bundle_info` // SAFETY: location matches current entity. `T` matches `bundle_info`
unsafe { unsafe {
self.location = bundle_inserter.insert(self.entity, self.location.index, bundle); self.location = bundle_inserter.insert(self.entity, self.location.index, bundle);
} }
@ -263,6 +263,8 @@ impl<'w> EntityMut<'w> {
let bundle_info = self.world.bundles.init_info::<T>(components, storages); let bundle_info = self.world.bundles.init_info::<T>(components, storages);
let old_location = self.location; let old_location = self.location;
// SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid,
// components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T`
let new_archetype_id = unsafe { let new_archetype_id = unsafe {
remove_bundle_from_archetype( remove_bundle_from_archetype(
archetypes, archetypes,
@ -281,12 +283,12 @@ impl<'w> EntityMut<'w> {
let old_archetype = &mut archetypes[old_location.archetype_id]; let old_archetype = &mut archetypes[old_location.archetype_id];
let mut bundle_components = bundle_info.component_ids.iter().cloned(); let mut bundle_components = bundle_info.component_ids.iter().cloned();
let entity = self.entity; let entity = self.entity;
// SAFE: bundle components are iterated in order, which guarantees that the component type // SAFETY: bundle components are iterated in order, which guarantees that the component type
// matches // matches
let result = unsafe { let result = unsafe {
T::from_components(storages, |storages| { T::from_components(storages, |storages| {
let component_id = bundle_components.next().unwrap(); let component_id = bundle_components.next().unwrap();
// SAFE: entity location is valid and table row is removed below // SAFETY: entity location is valid and table row is removed below
take_component( take_component(
components, components,
storages, storages,
@ -299,6 +301,7 @@ impl<'w> EntityMut<'w> {
}) })
}; };
#[allow(clippy::undocumented_unsafe_blocks)] // TODO: document why this is safe
unsafe { unsafe {
Self::move_entity_from_remove::<false>( Self::move_entity_from_remove::<false>(
entity, entity,
@ -350,14 +353,14 @@ impl<'w> EntityMut<'w> {
.tables .tables
.get_2_mut(old_table_id, new_archetype.table_id()); .get_2_mut(old_table_id, new_archetype.table_id());
// SAFE: old_table_row exists // SAFETY: old_table_row exists
let move_result = if DROP { let move_result = if DROP {
old_table.move_to_and_drop_missing_unchecked(old_table_row, new_table) old_table.move_to_and_drop_missing_unchecked(old_table_row, new_table)
} else { } else {
old_table.move_to_and_forget_missing_unchecked(old_table_row, new_table) old_table.move_to_and_forget_missing_unchecked(old_table_row, new_table)
}; };
// SAFE: move_result.new_row is a valid position in new_archetype's table // SAFETY: move_result.new_row is a valid position in new_archetype's table
let new_location = new_archetype.allocate(entity, move_result.new_row); let new_location = new_archetype.allocate(entity, move_result.new_row);
// if an entity was moved into this entity's table spot, update its table row // if an entity was moved into this entity's table spot, update its table row
@ -385,6 +388,9 @@ impl<'w> EntityMut<'w> {
let bundle_info = self.world.bundles.init_info::<T>(components, storages); let bundle_info = self.world.bundles.init_info::<T>(components, storages);
let old_location = self.location; let old_location = self.location;
// SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid,
// components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T`
let new_archetype_id = unsafe { let new_archetype_id = unsafe {
remove_bundle_from_archetype( remove_bundle_from_archetype(
archetypes, archetypes,
@ -421,6 +427,7 @@ impl<'w> EntityMut<'w> {
} }
} }
#[allow(clippy::undocumented_unsafe_blocks)] // TODO: document why this is safe
unsafe { unsafe {
Self::move_entity_from_remove::<true>( Self::move_entity_from_remove::<true>(
entity, entity,
@ -470,7 +477,7 @@ impl<'w> EntityMut<'w> {
let sparse_set = world.storages.sparse_sets.get_mut(*component_id).unwrap(); let sparse_set = world.storages.sparse_sets.get_mut(*component_id).unwrap();
sparse_set.remove(self.entity); sparse_set.remove(self.entity);
} }
// SAFE: table rows stored in archetypes always exist // SAFETY: table rows stored in archetypes always exist
moved_entity = unsafe { moved_entity = unsafe {
world.storages.tables[archetype.table_id()].swap_remove_unchecked(table_row) world.storages.tables[archetype.table_id()].swap_remove_unchecked(table_row)
}; };
@ -517,7 +524,7 @@ impl<'w> EntityMut<'w> {
#[inline] #[inline]
pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'_>> { pub fn get_by_id(&self, component_id: ComponentId) -> Option<Ptr<'_>> {
self.world.components().get_info(component_id)?; self.world.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above // SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe { get_component(self.world, component_id, self.entity, self.location) } unsafe { get_component(self.world, component_id, self.entity, self.location) }
} }
@ -532,7 +539,7 @@ impl<'w> EntityMut<'w> {
#[inline] #[inline]
pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> { pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
self.world.components().get_info(component_id)?; self.world.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above // SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe { get_mut_by_id(self.world, self.entity, self.location, component_id) } unsafe { get_mut_by_id(self.world, self.entity, self.location, component_id) }
} }
} }
@ -552,14 +559,14 @@ pub(crate) unsafe fn get_component(
location: EntityLocation, location: EntityLocation,
) -> Option<Ptr<'_>> { ) -> Option<Ptr<'_>> {
let archetype = &world.archetypes[location.archetype_id]; let archetype = &world.archetypes[location.archetype_id];
// SAFE: component_id exists and is therefore valid // SAFETY: component_id exists and is therefore valid
let component_info = world.components.get_info_unchecked(component_id); let component_info = world.components.get_info_unchecked(component_id);
match component_info.storage_type() { match component_info.storage_type() {
StorageType::Table => { StorageType::Table => {
let table = &world.storages.tables[archetype.table_id()]; let table = &world.storages.tables[archetype.table_id()];
let components = table.get_column(component_id)?; let components = table.get_column(component_id)?;
let table_row = archetype.entity_table_row(location.index); let table_row = archetype.entity_table_row(location.index);
// SAFE: archetypes only store valid table_rows and the stored component type is T // SAFETY: archetypes only store valid table_rows and the stored component type is T
Some(components.get_data_unchecked(table_row)) Some(components.get_data_unchecked(table_row))
} }
StorageType::SparseSet => world StorageType::SparseSet => world
@ -589,7 +596,7 @@ unsafe fn get_component_and_ticks(
let table = &world.storages.tables[archetype.table_id()]; let table = &world.storages.tables[archetype.table_id()];
let components = table.get_column(component_id)?; let components = table.get_column(component_id)?;
let table_row = archetype.entity_table_row(location.index); let table_row = archetype.entity_table_row(location.index);
// SAFE: archetypes only store valid table_rows and the stored component type is T // SAFETY: archetypes only store valid table_rows and the stored component type is T
Some(( Some((
components.get_data_unchecked(table_row), components.get_data_unchecked(table_row),
components.get_ticks_unchecked(table_row), components.get_ticks_unchecked(table_row),
@ -617,7 +624,7 @@ unsafe fn get_ticks(
let table = &world.storages.tables[archetype.table_id()]; let table = &world.storages.tables[archetype.table_id()];
let components = table.get_column(component_id)?; let components = table.get_column(component_id)?;
let table_row = archetype.entity_table_row(location.index); let table_row = archetype.entity_table_row(location.index);
// SAFE: archetypes only store valid table_rows and the stored component type is T // SAFETY: archetypes only store valid table_rows and the stored component type is T
Some(components.get_ticks_unchecked(table_row)) Some(components.get_ticks_unchecked(table_row))
} }
StorageType::SparseSet => world StorageType::SparseSet => world
@ -655,10 +662,10 @@ unsafe fn take_component<'a>(
match component_info.storage_type() { match component_info.storage_type() {
StorageType::Table => { StorageType::Table => {
let table = &mut storages.tables[archetype.table_id()]; let table = &mut storages.tables[archetype.table_id()];
// SAFE: archetypes will always point to valid columns // SAFETY: archetypes will always point to valid columns
let components = table.get_column_mut(component_id).unwrap(); let components = table.get_column_mut(component_id).unwrap();
let table_row = archetype.entity_table_row(location.index); let table_row = archetype.entity_table_row(location.index);
// SAFE: archetypes only store valid table_rows and the stored component type is T // SAFETY: archetypes only store valid table_rows and the stored component type is T
components.get_data_unchecked_mut(table_row).promote() components.get_data_unchecked_mut(table_row).promote()
} }
StorageType::SparseSet => storages StorageType::SparseSet => storages
@ -768,7 +775,7 @@ unsafe fn remove_bundle_from_archetype(
let mut removed_sparse_set_components = Vec::new(); let mut removed_sparse_set_components = Vec::new();
for component_id in bundle_info.component_ids.iter().cloned() { for component_id in bundle_info.component_ids.iter().cloned() {
if current_archetype.contains(component_id) { if current_archetype.contains(component_id) {
// SAFE: bundle components were already initialized by bundles.get_info // SAFETY: bundle components were already initialized by bundles.get_info
let component_info = components.get_info_unchecked(component_id); let component_info = components.get_info_unchecked(component_id);
match component_info.storage_type() { match component_info.storage_type() {
StorageType::Table => removed_table_components.push(component_id), StorageType::Table => removed_table_components.push(component_id),
@ -800,7 +807,7 @@ unsafe fn remove_bundle_from_archetype(
next_table_id = if removed_table_components.is_empty() { next_table_id = if removed_table_components.is_empty() {
current_archetype.table_id() current_archetype.table_id()
} else { } else {
// SAFE: all components in next_table_components exist // SAFETY: all components in next_table_components exist
storages storages
.tables .tables
.get_id_or_insert(&next_table_components, components) .get_id_or_insert(&next_table_components, components)
@ -850,7 +857,7 @@ pub(crate) unsafe fn get_mut<T: Component>(
entity: Entity, entity: Entity,
location: EntityLocation, location: EntityLocation,
) -> Option<Mut<'_, T>> { ) -> Option<Mut<'_, T>> {
// SAFE: world access is unique, entity location is valid, and returned component is of type // SAFETY: world access is unique, entity location is valid, and returned component is of type
// T // T
let change_tick = world.change_tick(); let change_tick = world.change_tick();
let last_change_tick = world.last_change_tick(); let last_change_tick = world.last_change_tick();
@ -874,7 +881,7 @@ pub(crate) unsafe fn get_mut_by_id(
location: EntityLocation, location: EntityLocation,
component_id: ComponentId, component_id: ComponentId,
) -> Option<MutUntyped> { ) -> Option<MutUntyped> {
// SAFE: world access is unique, entity location and component_id required to be valid // SAFETY: world access is unique, entity location and component_id required to be valid
get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| { get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| {
MutUntyped { MutUntyped {
value: value.assert_unique(), value: value.assert_unique(),
@ -928,7 +935,7 @@ mod tests {
let entity = world.entity(entity); let entity = world.entity(entity);
let test_component = entity.get_by_id(component_id).unwrap(); let test_component = entity.get_by_id(component_id).unwrap();
// SAFE: points to a valid `TestComponent` // SAFETY: points to a valid `TestComponent`
let test_component = unsafe { test_component.deref::<TestComponent>() }; let test_component = unsafe { test_component.deref::<TestComponent>() };
assert_eq!(test_component.0, 42); assert_eq!(test_component.0, 42);
@ -947,14 +954,15 @@ mod tests {
let mut test_component = entity_mut.get_mut_by_id(component_id).unwrap(); let mut test_component = entity_mut.get_mut_by_id(component_id).unwrap();
{ {
test_component.set_changed(); test_component.set_changed();
// SAFE: `test_component` has unique access of the `EntityMut` and is not used afterwards
let test_component = let test_component =
// SAFETY: `test_component` has unique access of the `EntityMut` and is not used afterwards
unsafe { test_component.into_inner().deref_mut::<TestComponent>() }; unsafe { test_component.into_inner().deref_mut::<TestComponent>() };
test_component.0 = 43; test_component.0 = 43;
} }
let entity = world.entity(entity); let entity = world.entity(entity);
let test_component = entity.get_by_id(component_id).unwrap(); let test_component = entity.get_by_id(component_id).unwrap();
// SAFETY: `TestComponent` is the correct component type
let test_component = unsafe { test_component.deref::<TestComponent>() }; let test_component = unsafe { test_component.deref::<TestComponent>() };
assert_eq!(test_component.0, 43); assert_eq!(test_component.0, 43);

View File

@ -317,11 +317,11 @@ impl World {
self.flush(); self.flush();
match self.entities.alloc_at_without_replacement(entity) { match self.entities.alloc_at_without_replacement(entity) {
AllocAtWithoutReplacement::Exists(location) => { AllocAtWithoutReplacement::Exists(location) => {
// SAFE: `entity` exists and `location` is that entity's location // SAFETY: `entity` exists and `location` is that entity's location
Some(unsafe { EntityMut::new(self, entity, location) }) Some(unsafe { EntityMut::new(self, entity, location) })
} }
AllocAtWithoutReplacement::DidNotExist => { AllocAtWithoutReplacement::DidNotExist => {
// SAFE: entity was just allocated // SAFETY: entity was just allocated
Some(unsafe { self.spawn_at_internal(entity) }) Some(unsafe { self.spawn_at_internal(entity) })
} }
AllocAtWithoutReplacement::ExistsWithWrongGeneration => None, AllocAtWithoutReplacement::ExistsWithWrongGeneration => None,
@ -381,7 +381,7 @@ impl World {
#[inline] #[inline]
pub fn get_entity_mut(&mut self, entity: Entity) -> Option<EntityMut> { pub fn get_entity_mut(&mut self, entity: Entity) -> Option<EntityMut> {
let location = self.entities.get(entity)?; let location = self.entities.get(entity)?;
// SAFE: `entity` exists and `location` is that entity's location // SAFETY: `entity` exists and `location` is that entity's location
Some(unsafe { EntityMut::new(self, entity, location) }) Some(unsafe { EntityMut::new(self, entity, location) })
} }
@ -413,7 +413,7 @@ impl World {
pub fn spawn(&mut self) -> EntityMut { pub fn spawn(&mut self) -> EntityMut {
self.flush(); self.flush();
let entity = self.entities.alloc(); let entity = self.entities.alloc();
// SAFE: entity was just allocated // SAFETY: entity was just allocated
unsafe { self.spawn_at_internal(entity) } unsafe { self.spawn_at_internal(entity) }
} }
@ -423,10 +423,10 @@ impl World {
let archetype = self.archetypes.empty_mut(); let archetype = self.archetypes.empty_mut();
// PERF: consider avoiding allocating entities in the empty archetype unless needed // PERF: consider avoiding allocating entities in the empty archetype unless needed
let table_row = self.storages.tables[archetype.table_id()].allocate(entity); let table_row = self.storages.tables[archetype.table_id()].allocate(entity);
// SAFE: no components are allocated by archetype.allocate() because the archetype is // SAFETY: no components are allocated by archetype.allocate() because the archetype is
// empty // empty
let location = archetype.allocate(entity, table_row); let location = archetype.allocate(entity, table_row);
// SAFE: entity index was just allocated // SAFETY: entity index was just allocated
self.entities self.entities
.meta .meta
.get_unchecked_mut(entity.id() as usize) .get_unchecked_mut(entity.id() as usize)
@ -507,7 +507,7 @@ impl World {
/// ``` /// ```
#[inline] #[inline]
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> { pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
// SAFE: lifetimes enforce correct usage of returned borrow // SAFETY: lifetimes enforce correct usage of returned borrow
unsafe { get_mut(self, entity, self.get_entity(entity)?.location()) } unsafe { get_mut(self, entity, self.get_entity(entity)?.location()) }
} }
@ -688,7 +688,7 @@ impl World {
#[inline] #[inline]
pub fn insert_resource<R: Resource>(&mut self, value: R) { pub fn insert_resource<R: Resource>(&mut self, value: R) {
let component_id = self.components.init_resource::<R>(); let component_id = self.components.init_resource::<R>();
// SAFE: component_id just initialized and corresponds to resource of type T // SAFETY: component_id just initialized and corresponds to resource of type T
unsafe { self.insert_resource_with_id(component_id, value) }; unsafe { self.insert_resource_with_id(component_id, value) };
} }
@ -716,21 +716,21 @@ impl World {
pub fn insert_non_send_resource<R: 'static>(&mut self, value: R) { pub fn insert_non_send_resource<R: 'static>(&mut self, value: R) {
self.validate_non_send_access::<R>(); self.validate_non_send_access::<R>();
let component_id = self.components.init_non_send::<R>(); let component_id = self.components.init_non_send::<R>();
// SAFE: component_id just initialized and corresponds to resource of type R // SAFETY: component_id just initialized and corresponds to resource of type R
unsafe { self.insert_resource_with_id(component_id, value) }; unsafe { self.insert_resource_with_id(component_id, value) };
} }
/// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None]. /// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None].
#[inline] #[inline]
pub fn remove_resource<R: Resource>(&mut self) -> Option<R> { pub fn remove_resource<R: Resource>(&mut self) -> Option<R> {
// SAFE: R is Send + Sync // SAFETY: R is Send + Sync
unsafe { self.remove_resource_unchecked() } unsafe { self.remove_resource_unchecked() }
} }
#[inline] #[inline]
pub fn remove_non_send_resource<R: 'static>(&mut self) -> Option<R> { pub fn remove_non_send_resource<R: 'static>(&mut self) -> Option<R> {
self.validate_non_send_access::<R>(); self.validate_non_send_access::<R>();
// SAFE: we are on main thread // SAFETY: we are on main thread
unsafe { self.remove_resource_unchecked() } unsafe { self.remove_resource_unchecked() }
} }
@ -747,10 +747,10 @@ impl World {
if column.is_empty() { if column.is_empty() {
return None; return None;
} }
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of the // SAFETY: if a resource column exists, row 0 exists as well. caller takes ownership of the
// ptr value / drop is called when R is dropped // ptr value / drop is called when R is dropped
let (ptr, _) = unsafe { column.swap_remove_and_forget_unchecked(0) }; let (ptr, _) = unsafe { column.swap_remove_and_forget_unchecked(0) };
// SAFE: column is of type R // SAFETY: column is of type R
Some(unsafe { ptr.read::<R>() }) Some(unsafe { ptr.read::<R>() })
} }
@ -778,7 +778,7 @@ impl World {
} else { } else {
return false; return false;
}; };
// SAFE: resources table always have row 0 // SAFETY: resources table always have row 0
let ticks = unsafe { column.get_ticks_unchecked(0).deref() }; let ticks = unsafe { column.get_ticks_unchecked(0).deref() };
ticks.is_added(self.last_change_tick(), self.read_change_tick()) ticks.is_added(self.last_change_tick(), self.read_change_tick())
} }
@ -795,7 +795,7 @@ impl World {
} else { } else {
return false; return false;
}; };
// SAFE: resources table always have row 0 // SAFETY: resources table always have row 0
let ticks = unsafe { column.get_ticks_unchecked(0).deref() }; let ticks = unsafe { column.get_ticks_unchecked(0).deref() };
ticks.is_changed(self.last_change_tick(), self.read_change_tick()) ticks.is_changed(self.last_change_tick(), self.read_change_tick())
} }
@ -852,14 +852,14 @@ impl World {
#[inline] #[inline]
pub fn get_resource<R: Resource>(&self) -> Option<&R> { pub fn get_resource<R: Resource>(&self) -> Option<&R> {
let component_id = self.components.get_resource_id(TypeId::of::<R>())?; let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
// SAFE: unique world access // SAFETY: unique world access
unsafe { self.get_resource_with_id(component_id) } unsafe { self.get_resource_with_id(component_id) }
} }
/// Gets a mutable reference to the resource of the given type if it exists /// Gets a mutable reference to the resource of the given type if it exists
#[inline] #[inline]
pub fn get_resource_mut<R: Resource>(&mut self) -> Option<Mut<'_, R>> { pub fn get_resource_mut<R: Resource>(&mut self) -> Option<Mut<'_, R>> {
// SAFE: unique world access // SAFETY: unique world access
unsafe { self.get_resource_unchecked_mut() } unsafe { self.get_resource_unchecked_mut() }
} }
@ -882,7 +882,7 @@ impl World {
/// ///
/// # Safety /// # Safety
/// This will allow aliased mutable access to the given resource type. The caller must ensure /// This will allow aliased mutable access to the given resource type. The caller must ensure
/// that only one mutable access exists at a time. /// that there is either only one mutable access or multiple immutable accesses at a time.
#[inline] #[inline]
pub unsafe fn get_resource_unchecked_mut<R: Resource>(&self) -> Option<Mut<'_, R>> { pub unsafe fn get_resource_unchecked_mut<R: Resource>(&self) -> Option<Mut<'_, R>> {
let component_id = self.components.get_resource_id(TypeId::of::<R>())?; let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
@ -934,7 +934,7 @@ impl World {
#[inline] #[inline]
pub fn get_non_send_resource<R: 'static>(&self) -> Option<&R> { pub fn get_non_send_resource<R: 'static>(&self) -> Option<&R> {
let component_id = self.components.get_resource_id(TypeId::of::<R>())?; let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
// SAFE: component id matches type T // SAFETY: component id matches type T
unsafe { self.get_non_send_with_id(component_id) } unsafe { self.get_non_send_with_id(component_id) }
} }
@ -942,7 +942,7 @@ impl World {
/// Otherwise returns [None] /// Otherwise returns [None]
#[inline] #[inline]
pub fn get_non_send_resource_mut<R: 'static>(&mut self) -> Option<Mut<'_, R>> { pub fn get_non_send_resource_mut<R: 'static>(&mut self) -> Option<Mut<'_, R>> {
// SAFE: unique world access // SAFETY: unique world access
unsafe { self.get_non_send_resource_unchecked_mut() } unsafe { self.get_non_send_resource_unchecked_mut() }
} }
@ -951,7 +951,7 @@ impl World {
/// ///
/// # Safety /// # Safety
/// This will allow aliased mutable access to the given non-send resource type. The caller must /// This will allow aliased mutable access to the given non-send resource type. The caller must
/// ensure that only one mutable access exists at a time. /// ensure that there is either only one mutable access or multiple immutable accesses at a time.
#[inline] #[inline]
pub unsafe fn get_non_send_resource_unchecked_mut<R: 'static>(&self) -> Option<Mut<'_, R>> { pub unsafe fn get_non_send_resource_unchecked_mut<R: 'static>(&self) -> Option<Mut<'_, R>> {
let component_id = self.components.get_resource_id(TypeId::of::<R>())?; let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
@ -1033,7 +1033,7 @@ impl World {
SpawnOrInsert::Insert(ref mut inserter, archetype) SpawnOrInsert::Insert(ref mut inserter, archetype)
if location.archetype_id == archetype => if location.archetype_id == archetype =>
{ {
// SAFE: `entity` is valid, `location` matches entity, bundle matches inserter // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
unsafe { inserter.insert(entity, location.index, bundle) }; unsafe { inserter.insert(entity, location.index, bundle) };
} }
_ => { _ => {
@ -1045,7 +1045,7 @@ impl World {
location.archetype_id, location.archetype_id,
change_tick, change_tick,
); );
// SAFE: `entity` is valid, `location` matches entity, bundle matches inserter // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
unsafe { inserter.insert(entity, location.index, bundle) }; unsafe { inserter.insert(entity, location.index, bundle) };
spawn_or_insert = spawn_or_insert =
SpawnOrInsert::Insert(inserter, location.archetype_id); SpawnOrInsert::Insert(inserter, location.archetype_id);
@ -1054,7 +1054,7 @@ impl World {
} }
AllocAtWithoutReplacement::DidNotExist => { AllocAtWithoutReplacement::DidNotExist => {
if let SpawnOrInsert::Spawn(ref mut spawner) = spawn_or_insert { if let SpawnOrInsert::Spawn(ref mut spawner) = spawn_or_insert {
// SAFE: `entity` is allocated (but non existent), bundle matches inserter // SAFETY: `entity` is allocated (but non existent), bundle matches inserter
unsafe { spawner.spawn_non_existent(entity, bundle) }; unsafe { spawner.spawn_non_existent(entity, bundle) };
} else { } else {
let mut spawner = bundle_info.get_bundle_spawner( let mut spawner = bundle_info.get_bundle_spawner(
@ -1064,7 +1064,7 @@ impl World {
&mut self.storages, &mut self.storages,
change_tick, change_tick,
); );
// SAFE: `entity` is valid, `location` matches entity, bundle matches inserter // SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
unsafe { spawner.spawn_non_existent(entity, bundle) }; unsafe { spawner.spawn_non_existent(entity, bundle) };
spawn_or_insert = SpawnOrInsert::Spawn(spawner); spawn_or_insert = SpawnOrInsert::Spawn(spawner);
} }
@ -1123,11 +1123,11 @@ impl World {
"resource does not exist: {}", "resource does not exist: {}",
std::any::type_name::<R>() std::any::type_name::<R>()
); );
// SAFE: if a resource column exists, row 0 exists as well. caller takes ownership of // SAFETY: if a resource column exists, row 0 exists as well. caller takes ownership of
// the ptr value / drop is called when R is dropped // the ptr value / drop is called when R is dropped
unsafe { column.swap_remove_and_forget_unchecked(0) } unsafe { column.swap_remove_and_forget_unchecked(0) }
}; };
// SAFE: pointer is of type R // SAFETY: pointer is of type R
// Read the value onto the stack to avoid potential mut aliasing. // Read the value onto the stack to avoid potential mut aliasing.
let mut value = unsafe { ptr.read::<R>() }; let mut value = unsafe { ptr.read::<R>() };
let value_mut = Mut { let value_mut = Mut {
@ -1148,8 +1148,8 @@ impl World {
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>())); .unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
OwningPtr::make(value, |ptr| { OwningPtr::make(value, |ptr| {
// SAFETY: pointer is of type R
unsafe { unsafe {
// SAFE: pointer is of type R
column.push(ptr, ticks); column.push(ptr, ticks);
} }
}); });
@ -1216,12 +1216,12 @@ impl World {
let change_tick = self.change_tick(); let change_tick = self.change_tick();
let column = self.initialize_resource_internal(component_id); let column = self.initialize_resource_internal(component_id);
if column.is_empty() { if column.is_empty() {
// SAFE: column is of type R and has been allocated above // SAFETY: column is of type R and has been allocated above
OwningPtr::make(value, |ptr| { OwningPtr::make(value, |ptr| {
column.push(ptr, ComponentTicks::new(change_tick)); column.push(ptr, ComponentTicks::new(change_tick));
}); });
} else { } else {
// SAFE: column is of type R and has already been allocated // SAFETY: column is of type R and has already been allocated
*column.get_data_unchecked_mut(0).deref_mut::<R>() = value; *column.get_data_unchecked_mut(0).deref_mut::<R>() = value;
column.get_ticks_unchecked_mut(0).set_changed(change_tick); column.get_ticks_unchecked_mut(0).set_changed(change_tick);
} }
@ -1246,10 +1246,10 @@ impl World {
"insert_resource_by_id called with component id which doesn't exist in this world" "insert_resource_by_id called with component id which doesn't exist in this world"
) )
}); });
// SAFE: component_id is valid, checked by the lines above // SAFETY: component_id is valid, checked by the lines above
let column = self.initialize_resource_internal(component_id); let column = self.initialize_resource_internal(component_id);
if column.is_empty() { if column.is_empty() {
// SAFE: column is of type R and has been allocated above // SAFETY: column is of type R and has been allocated above
column.push(value, ComponentTicks::new(change_tick)); column.push(value, ComponentTicks::new(change_tick));
} else { } else {
let ptr = column.get_data_unchecked_mut(0); let ptr = column.get_data_unchecked_mut(0);
@ -1266,7 +1266,7 @@ impl World {
/// `component_id` must be valid for this world /// `component_id` must be valid for this world
#[inline] #[inline]
unsafe fn initialize_resource_internal(&mut self, component_id: ComponentId) -> &mut Column { unsafe fn initialize_resource_internal(&mut self, component_id: ComponentId) -> &mut Column {
// SAFE: resource archetype always exists // SAFETY: resource archetype always exists
let resource_archetype = self let resource_archetype = self
.archetypes .archetypes
.archetypes .archetypes
@ -1294,14 +1294,14 @@ impl World {
pub(crate) fn initialize_resource<R: Resource>(&mut self) -> ComponentId { pub(crate) fn initialize_resource<R: Resource>(&mut self) -> ComponentId {
let component_id = self.components.init_resource::<R>(); let component_id = self.components.init_resource::<R>();
// SAFE: resource initialized above // SAFETY: resource initialized above
unsafe { self.initialize_resource_internal(component_id) }; unsafe { self.initialize_resource_internal(component_id) };
component_id component_id
} }
pub(crate) fn initialize_non_send_resource<R: 'static>(&mut self) -> ComponentId { pub(crate) fn initialize_non_send_resource<R: 'static>(&mut self) -> ComponentId {
let component_id = self.components.init_non_send::<R>(); let component_id = self.components.init_non_send::<R>();
// SAFE: resource initialized above // SAFETY: resource initialized above
unsafe { self.initialize_resource_internal(component_id) }; unsafe { self.initialize_resource_internal(component_id) };
component_id component_id
} }
@ -1343,12 +1343,12 @@ impl World {
/// such as inserting a [Component]. /// such as inserting a [Component].
pub(crate) fn flush(&mut self) { pub(crate) fn flush(&mut self) {
let empty_archetype = self.archetypes.empty_mut(); let empty_archetype = self.archetypes.empty_mut();
let table = &mut self.storages.tables[empty_archetype.table_id()];
// PERF: consider pre-allocating space for flushed entities
// SAFETY: entity is set to a valid location
unsafe { unsafe {
let table = &mut self.storages.tables[empty_archetype.table_id()];
// PERF: consider pre-allocating space for flushed entities
// SAFE: entity is set to a valid location
self.entities.flush(|entity, location| { self.entities.flush(|entity, location| {
// SAFE: no components are allocated by archetype.allocate() because the archetype // SAFETY: no components are allocated by archetype.allocate() because the archetype
// is empty // is empty
*location = empty_archetype.allocate(entity, table.allocate(entity)); *location = empty_archetype.allocate(entity, table.allocate(entity));
}); });
@ -1428,9 +1428,10 @@ impl World {
let column = self.get_populated_resource_column(component_id)?; let column = self.get_populated_resource_column(component_id)?;
// SAFE: get_data_ptr requires that the mutability rules are not violated, and the caller promises // SAFETY: get_data_ptr requires that the mutability rules are not violated, and the caller promises
// to only modify the resource while the mutable borrow of the world is valid // to only modify the resource while the mutable borrow of the world is valid
let ticks = Ticks { let ticks = Ticks {
// SAFETY:
// - index is in-bounds because the column is initialized and non-empty // - index is in-bounds because the column is initialized and non-empty
// - no other reference to the ticks of the same row can exist at the same time // - no other reference to the ticks of the same row can exist at the same time
component_ticks: unsafe { &mut *column.get_ticks_unchecked(0).get() }, component_ticks: unsafe { &mut *column.get_ticks_unchecked(0).get() },
@ -1439,6 +1440,7 @@ impl World {
}; };
Some(MutUntyped { Some(MutUntyped {
// SAFETY: world access is unique, so no other reference can exist at the same time
value: unsafe { column.get_data_ptr().assert_unique() }, value: unsafe { column.get_data_ptr().assert_unique() },
ticks, ticks,
}) })
@ -1460,7 +1462,7 @@ impl World {
if column.is_empty() { if column.is_empty() {
return None; return None;
} }
// SAFE: if a resource column exists, row 0 exists as well // SAFETY: if a resource column exists, row 0 exists as well
unsafe { column.swap_remove_unchecked(0) }; unsafe { column.swap_remove_unchecked(0) };
Some(()) Some(())
@ -1474,7 +1476,7 @@ impl World {
#[inline] #[inline]
pub fn get_by_id(&self, entity: Entity, component_id: ComponentId) -> Option<Ptr<'_>> { pub fn get_by_id(&self, entity: Entity, component_id: ComponentId) -> Option<Ptr<'_>> {
self.components().get_info(component_id)?; self.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above // SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe { unsafe {
get_component( get_component(
self, self,
@ -1497,7 +1499,7 @@ impl World {
component_id: ComponentId, component_id: ComponentId,
) -> Option<MutUntyped<'_>> { ) -> Option<MutUntyped<'_>> {
self.components().get_info(component_id)?; self.components().get_info(component_id)?;
// SAFE: entity_location is valid, component_id is valid as checked by the line above // SAFETY: entity_location is valid, component_id is valid as checked by the line above
unsafe { unsafe {
get_mut_by_id( get_mut_by_id(
self, self,
@ -1526,7 +1528,9 @@ impl fmt::Debug for World {
// TODO: remove allow on lint - https://github.com/bevyengine/bevy/issues/3666 // TODO: remove allow on lint - https://github.com/bevyengine/bevy/issues/3666
#[allow(clippy::non_send_fields_in_send_ty)] #[allow(clippy::non_send_fields_in_send_ty)]
// SAFETY: all methods on the world ensure that non-send resources are only accessible on the main thread
unsafe impl Send for World {} unsafe impl Send for World {}
// SAFETY: all methods on the world ensure that non-send resources are only accessible on the main thread
unsafe impl Sync for World {} unsafe impl Sync for World {}
/// Creates an instance of the type this trait is implemented for /// Creates an instance of the type this trait is implemented for
@ -1711,6 +1715,7 @@ mod tests {
.unwrap(); .unwrap();
let resource = world.get_resource_by_id(component_id).unwrap(); let resource = world.get_resource_by_id(component_id).unwrap();
// SAFETY: `TestResource` is the correct resource type
let resource = unsafe { resource.deref::<TestResource>() }; let resource = unsafe { resource.deref::<TestResource>() };
assert_eq!(resource.0, 42); assert_eq!(resource.0, 42);
@ -1728,11 +1733,13 @@ mod tests {
{ {
let mut resource = world.get_resource_mut_by_id(component_id).unwrap(); let mut resource = world.get_resource_mut_by_id(component_id).unwrap();
resource.set_changed(); resource.set_changed();
// SAFETY: `TestResource` is the correct resource type
let resource = unsafe { resource.into_inner().deref_mut::<TestResource>() }; let resource = unsafe { resource.into_inner().deref_mut::<TestResource>() };
resource.0 = 43; resource.0 = 43;
} }
let resource = world.get_resource_by_id(component_id).unwrap(); let resource = world.get_resource_by_id(component_id).unwrap();
// SAFETY: `TestResource` is the correct resource type
let resource = unsafe { resource.deref::<TestResource>() }; let resource = unsafe { resource.deref::<TestResource>() };
assert_eq!(resource.0, 43); assert_eq!(resource.0, 43);
@ -1744,7 +1751,7 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
// SAFE: the drop function is valid for the layout and the data will be safe to access from any thread // SAFETY: the drop function is valid for the layout and the data will be safe to access from any thread
let descriptor = unsafe { let descriptor = unsafe {
ComponentDescriptor::new_with_layout( ComponentDescriptor::new_with_layout(
"Custom Test Component".to_string(), "Custom Test Component".to_string(),
@ -1761,11 +1768,14 @@ mod tests {
let component_id = world.init_component_with_descriptor(descriptor); let component_id = world.init_component_with_descriptor(descriptor);
let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
OwningPtr::make(value, |ptr| unsafe { OwningPtr::make(value, |ptr| {
// SAFE: value is valid for the component layout // SAFETY: value is valid for the component layout
world.insert_resource_by_id(component_id, ptr); unsafe {
world.insert_resource_by_id(component_id, ptr);
}
}); });
// SAFETY: [u8; 8] is the correct type for the resource
let data = unsafe { let data = unsafe {
world world
.get_resource_by_id(component_id) .get_resource_by_id(component_id)
@ -1785,9 +1795,11 @@ mod tests {
let invalid_component_id = ComponentId::new(usize::MAX); let invalid_component_id = ComponentId::new(usize::MAX);
let mut world = World::new(); let mut world = World::new();
OwningPtr::make((), |ptr| unsafe { OwningPtr::make((), |ptr| {
// SAFE: ptr must be valid for the component_id `invalid_component_id` which is invalid, but checked by `insert_resource_by_id` // SAFETY: ptr must be valid for the component_id `invalid_component_id` which is invalid, but checked by `insert_resource_by_id`
world.insert_resource_by_id(invalid_component_id, ptr); unsafe {
world.insert_resource_by_id(invalid_component_id, ptr);
}
}); });
} }

View File

@ -67,7 +67,7 @@ where
fn next(&mut self) -> Option<Entity> { fn next(&mut self) -> Option<Entity> {
let bundle = self.inner.next()?; let bundle = self.inner.next()?;
// SAFE: bundle matches spawner type // SAFETY: bundle matches spawner type
unsafe { Some(self.spawner.spawn(bundle)) } unsafe { Some(self.spawner.spawn(bundle)) }
} }

View File

@ -186,7 +186,7 @@ impl<'w> WorldCell<'w> {
let resource_archetype = self.world.archetypes.resource(); let resource_archetype = self.world.archetypes.resource();
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
Some(WorldBorrow::new( Some(WorldBorrow::new(
// SAFE: ComponentId matches TypeId // SAFETY: ComponentId matches TypeId
unsafe { self.world.get_resource_with_id(component_id)? }, unsafe { self.world.get_resource_with_id(component_id)? },
archetype_component_id, archetype_component_id,
self.access.clone(), self.access.clone(),
@ -218,7 +218,7 @@ impl<'w> WorldCell<'w> {
let resource_archetype = self.world.archetypes.resource(); let resource_archetype = self.world.archetypes.resource();
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
Some(WorldBorrowMut::new( Some(WorldBorrowMut::new(
// SAFE: ComponentId matches TypeId and access is checked by WorldBorrowMut // SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut
unsafe { unsafe {
self.world self.world
.get_resource_unchecked_mut_with_id(component_id)? .get_resource_unchecked_mut_with_id(component_id)?
@ -253,7 +253,7 @@ impl<'w> WorldCell<'w> {
let resource_archetype = self.world.archetypes.resource(); let resource_archetype = self.world.archetypes.resource();
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
Some(WorldBorrow::new( Some(WorldBorrow::new(
// SAFE: ComponentId matches TypeId // SAFETY: ComponentId matches TypeId
unsafe { self.world.get_non_send_with_id(component_id)? }, unsafe { self.world.get_non_send_with_id(component_id)? },
archetype_component_id, archetype_component_id,
self.access.clone(), self.access.clone(),
@ -285,7 +285,7 @@ impl<'w> WorldCell<'w> {
let resource_archetype = self.world.archetypes.resource(); let resource_archetype = self.world.archetypes.resource();
let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?; let archetype_component_id = resource_archetype.get_archetype_component_id(component_id)?;
Some(WorldBorrowMut::new( Some(WorldBorrowMut::new(
// SAFE: ComponentId matches TypeId and access is checked by WorldBorrowMut // SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut
unsafe { unsafe {
self.world self.world
.get_non_send_unchecked_mut_with_id(component_id)? .get_non_send_unchecked_mut_with_id(component_id)?

View File

@ -345,7 +345,7 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
let mut builder = WorldChildBuilder { let mut builder = WorldChildBuilder {
current_entity: None, current_entity: None,
parent_entities: vec![entity], parent_entities: vec![entity],
// SAFE: self.update_location() is called below. It is impossible to make EntityMut // SAFETY: self.update_location() is called below. It is impossible to make EntityMut
// function calls on `self` within the scope defined here // function calls on `self` within the scope defined here
world: unsafe { self.world_mut() }, world: unsafe { self.world_mut() },
}; };
@ -359,7 +359,7 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
fn push_children(&mut self, children: &[Entity]) -> &mut Self { fn push_children(&mut self, children: &[Entity]) -> &mut Self {
let parent = self.id(); let parent = self.id();
{ {
// SAFE: parent entity is not modified and its location is updated manually // SAFETY: parent entity is not modified and its location is updated manually
let world = unsafe { self.world_mut() }; let world = unsafe { self.world_mut() };
for child in children.iter() { for child in children.iter() {
world world
@ -381,7 +381,7 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self { fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self {
let parent = self.id(); let parent = self.id();
{ {
// SAFE: parent entity is not modified and its location is updated manually // SAFETY: parent entity is not modified and its location is updated manually
let world = unsafe { self.world_mut() }; let world = unsafe { self.world_mut() };
for child in children.iter() { for child in children.iter() {
world world
@ -403,7 +403,7 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
fn remove_children(&mut self, children: &[Entity]) -> &mut Self { fn remove_children(&mut self, children: &[Entity]) -> &mut Self {
let parent = self.id(); let parent = self.id();
// SAFE: This doesn't change the parent's location // SAFETY: This doesn't change the parent's location
let world = unsafe { self.world_mut() }; let world = unsafe { self.world_mut() };
for child in children.iter() { for child in children.iter() {
let mut child = world.entity_mut(*child); let mut child = world.entity_mut(*child);

View File

@ -114,7 +114,7 @@ impl<'w> DespawnRecursiveExt for EntityMut<'w> {
) )
.entered(); .entered();
// SAFE: EntityMut is consumed so even though the location is no longer // SAFETY: EntityMut is consumed so even though the location is no longer
// valid, it cannot be accessed again with the invalid location. // valid, it cannot be accessed again with the invalid location.
unsafe { unsafe {
despawn_with_children_recursive(self.world_mut(), entity); despawn_with_children_recursive(self.world_mut(), entity);
@ -131,7 +131,7 @@ impl<'w> DespawnRecursiveExt for EntityMut<'w> {
) )
.entered(); .entered();
// SAFE: The location is updated. // SAFETY: The location is updated.
unsafe { unsafe {
despawn_children(self.world_mut(), entity); despawn_children(self.world_mut(), entity);
self.update_location(); self.update_location();

View File

@ -161,6 +161,7 @@ impl<'a> OwningPtr<'a> {
#[inline] #[inline]
pub fn make<T, F: FnOnce(OwningPtr<'_>) -> R, R>(val: T, f: F) -> R { pub fn make<T, F: FnOnce(OwningPtr<'_>) -> R, R>(val: T, f: F) -> R {
let mut temp = MaybeUninit::new(val); let mut temp = MaybeUninit::new(val);
// SAFETY: `temp.as_mut_ptr()` is a reference to a local value on the stack, so it cannot be null
let ptr = unsafe { NonNull::new_unchecked(temp.as_mut_ptr().cast::<u8>()) }; let ptr = unsafe { NonNull::new_unchecked(temp.as_mut_ptr().cast::<u8>()) };
f(Self(ptr, PhantomData)) f(Self(ptr, PhantomData))
} }
@ -233,6 +234,7 @@ impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> {
#[inline] #[inline]
fn from(slice: &'a [T]) -> Self { fn from(slice: &'a [T]) -> Self {
Self { Self {
// SAFETY: a reference can never be null
ptr: unsafe { NonNull::new_unchecked(slice.as_ptr() as *mut T) }, ptr: unsafe { NonNull::new_unchecked(slice.as_ptr() as *mut T) },
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
len: slice.len(), len: slice.len(),

View File

@ -190,7 +190,7 @@ impl System for FixedTimestep {
} }
unsafe fn run_unsafe(&mut self, _input: (), world: &World) -> ShouldRun { unsafe fn run_unsafe(&mut self, _input: (), world: &World) -> ShouldRun {
// SAFE: this system inherits the internal system's component access and archetype component // SAFETY: this system inherits the internal system's component access and archetype component
// access, which means the caller has ensured running the internal system is safe // access, which means the caller has ensured running the internal system is safe
self.internal_system.run_unsafe((), world) self.internal_system.run_unsafe((), world)
} }

View File

@ -22,7 +22,7 @@ pub struct FlexSurface {
taffy: Taffy, taffy: Taffy,
} }
// SAFE: as long as MeasureFunc is Send + Sync. https://github.com/DioxusLabs/taffy/issues/146 // SAFETY: as long as MeasureFunc is Send + Sync. https://github.com/DioxusLabs/taffy/issues/146
// TODO: remove allow on lint - https://github.com/bevyengine/bevy/issues/3666 // TODO: remove allow on lint - https://github.com/bevyengine/bevy/issues/3666
#[allow(clippy::non_send_fields_in_send_ty)] #[allow(clippy::non_send_fields_in_send_ty)]
unsafe impl Send for FlexSurface {} unsafe impl Send for FlexSurface {}

View File

@ -29,5 +29,7 @@ fn noop_raw_waker() -> RawWaker {
} }
fn noop_waker() -> Waker { fn noop_waker() -> Waker {
// SAFETY: the `RawWakerVTable` is just a big noop and doesn't violate any of the rules in `RawWakerVTable`s documentation
// (which talks about retaining and releasing any "resources", of which there are none in this case)
unsafe { Waker::from_raw(noop_raw_waker()) } unsafe { Waker::from_raw(noop_raw_waker()) }
} }

View File

@ -24,7 +24,7 @@ impl RawWindowHandleWrapper {
} }
} }
// SAFE: RawWindowHandle is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only // SAFETY: RawWindowHandle is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only
// exposed via an unsafe method that forces the user to make a call for a given platform. (ex: some platforms don't // exposed via an unsafe method that forces the user to make a call for a given platform. (ex: some platforms don't
// support doing window operations off of the main thread). // support doing window operations off of the main thread).
// A recommendation for this pattern (and more context) is available here: // A recommendation for this pattern (and more context) is available here:
@ -41,7 +41,7 @@ unsafe impl Sync for RawWindowHandleWrapper {}
/// In many cases, this should only be constructed on the main thread. /// In many cases, this should only be constructed on the main thread.
pub struct ThreadLockedRawWindowHandleWrapper(RawWindowHandle); pub struct ThreadLockedRawWindowHandleWrapper(RawWindowHandle);
// SAFE: the caller has validated that this is a valid context to get RawWindowHandle // SAFETY: the caller has validated that this is a valid context to get RawWindowHandle
// as otherwise an instance of this type could not have been constructed // as otherwise an instance of this type could not have been constructed
// NOTE: we cannot simply impl HasRawWindowHandle for RawWindowHandleWrapper, // NOTE: we cannot simply impl HasRawWindowHandle for RawWindowHandleWrapper,
// as the `raw_window_handle` method is safe. We cannot guarantee that all calls // as the `raw_window_handle` method is safe. We cannot guarantee that all calls