bevy/crates/bevy_ecs/src/component.rs
TheRawMeatball 73c78c3667 Use lifetimed, type erased pointers in bevy_ecs (#3001)
# Objective

`bevy_ecs` has large amounts of unsafe code which is hard to get right and makes it difficult to audit for soundness.

## Solution

Introduce lifetimed, type-erased pointers: `Ptr<'a>` `PtrMut<'a>` `OwningPtr<'a>'` and `ThinSlicePtr<'a, T>` which are newtypes around a raw pointer with a lifetime and conceptually representing strong invariants about the pointee and validity of the pointer.

The process of converting bevy_ecs to use these has already caught multiple cases of unsound behavior.

## Changelog

TL;DR for release notes: `bevy_ecs` now uses lifetimed, type-erased pointers internally, significantly improving safety and legibility without sacrificing performance. This should have approximately no end user impact, unless you were meddling with the (unfortunately public) internals of `bevy_ecs`.

- `Fetch`, `FilterFetch` and `ReadOnlyFetch` trait no longer have a `'state` lifetime
    - this was unneeded
- `ReadOnly/Fetch` associated types on `WorldQuery` are now on a new `WorldQueryGats<'world>` trait
    - was required to work around lack of Generic Associated Types (we wish to express `type Fetch<'a>: Fetch<'a>`)
- `derive(WorldQuery)` no longer requires `'w` lifetime on struct
    - this was unneeded, and improves the end user experience
- `EntityMut::get_unchecked_mut` returns `&'_ mut T` not `&'w mut T`
    - allows easier use of unsafe API with less footguns, and can be worked around via lifetime transmutery as a user
- `Bundle::from_components` now takes a `ctx` parameter to pass to the `FnMut` closure
    - required because closure return types can't borrow from captures
- `Fetch::init` takes `&'world World`, `Fetch::set_archetype` takes `&'world Archetype` and `&'world Tables`, `Fetch::set_table` takes `&'world Table`
    - allows types implementing `Fetch` to store borrows into world
- `WorldQuery` trait now has a `shrink` fn to shorten the lifetime in `Fetch::<'a>::Item`
    - this works around lack of subtyping of assoc types, rust doesnt allow you to turn `<T as Fetch<'static>>::Item'` into `<T as Fetch<'a>>::Item'`
    - `QueryCombinationsIter` requires this
- Most types implementing `Fetch` now have a lifetime `'w`
    - allows the fetches to store borrows of world data instead of using raw pointers

## Migration guide

- `EntityMut::get_unchecked_mut` returns a more restricted lifetime, there is no general way to migrate this as it depends on your code
- `Bundle::from_components` implementations must pass the `ctx` arg to `func`
- `Bundle::from_components` callers have to use a fn arg instead of closure captures for borrowing from world
- Remove lifetime args on `derive(WorldQuery)` structs as it is nonsensical
- `<Q as WorldQuery>::ReadOnly/Fetch` should be changed to either `RO/QueryFetch<'world>` or `<Q as WorldQueryGats<'world>>::ReadOnly/Fetch`
- `<F as Fetch<'w, 's>>` should be changed to `<F as Fetch<'w>>`
- Change the fn sigs of `Fetch::init/set_archetype/set_table` to match respective trait fn sigs
- Implement the required `fn shrink` on any `WorldQuery` implementations
- Move assoc types `Fetch` and `ReadOnlyFetch` on `WorldQuery` impls to `WorldQueryGats` impls
- Pass an appropriate `'world` lifetime to whatever fetch struct you are for some reason using

### Type inference regression

in some cases rustc may give spurrious errors when attempting to infer the `F` parameter on a query/querystate this can be fixed by manually specifying the type, i.e. `QueryState:🆕:<_, ()>(world)`. The error is rather confusing:

```rust=
error[E0271]: type mismatch resolving `<() as Fetch<'_>>::Item == bool`
    --> crates/bevy_pbr/src/render/light.rs:1413:30
     |
1413 |             main_view_query: QueryState::new(world),
     |                              ^^^^^^^^^^^^^^^ expected `bool`, found `()`
     |
     = note: required because of the requirements on the impl of `for<'x> FilterFetch<'x>` for `<() as WorldQueryGats<'x>>::Fetch`
note: required by a bound in `bevy_ecs::query::QueryState::<Q, F>::new`
    --> crates/bevy_ecs/src/query/state.rs:49:32
     |
49   |     for<'x> QueryFetch<'x, F>: FilterFetch<'x>,
     |                                ^^^^^^^^^^^^^^^ required by this bound in `bevy_ecs::query::QueryState::<Q, F>::new`
```

---

Made with help from @BoxyUwU and @alice-i-cecile 

Co-authored-by: Boxy <supbscripter@gmail.com>
2022-04-27 23:44:06 +00:00

412 lines
12 KiB
Rust

//! Types for declaring and storing [`Component`]s.
use crate::{
ptr::OwningPtr,
storage::{SparseSetIndex, Storages},
system::Resource,
};
pub use bevy_ecs_macros::Component;
use std::{
alloc::Layout,
any::{Any, TypeId},
};
/// A component is data associated with an [`Entity`](crate::entity::Entity). Each entity can have
/// multiple different types of components, but only one of them per type.
///
/// Any type that is `Send + Sync + 'static` can implement `Component` using `#[derive(Component)]`.
///
/// In order to use foreign types as components, wrap them using a newtype pattern.
/// ```
/// # use bevy_ecs::component::Component;
/// use std::time::Duration;
/// #[derive(Component)]
/// struct Cooldown(Duration);
/// ```
/// Components are added with new entities using [`Commands::spawn`](crate::system::Commands::spawn),
/// or to existing entities with [`EntityCommands::insert`](crate::system::EntityCommands::insert),
/// or their [`World`](crate::world::World) equivalents.
///
/// Components can be accessed in systems by using a [`Query`](crate::system::Query)
/// as one of the arguments.
///
/// Components can be grouped together into a [`Bundle`](crate::bundle::Bundle).
pub trait Component: Send + Sync + 'static {
type Storage: ComponentStorage;
}
pub struct TableStorage;
pub struct SparseStorage;
pub trait ComponentStorage: sealed::Sealed {
// because the trait is sealed, those items are private API.
const STORAGE_TYPE: StorageType;
}
impl ComponentStorage for TableStorage {
const STORAGE_TYPE: StorageType = StorageType::Table;
}
impl ComponentStorage for SparseStorage {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
}
mod sealed {
pub trait Sealed {}
impl Sealed for super::TableStorage {}
impl Sealed for super::SparseStorage {}
}
/// The storage used for a specific component type.
///
/// # Examples
/// The [`StorageType`] for a component is configured via the derive attribute
///
/// ```
/// # use bevy_ecs::{prelude::*, component::*};
/// #[derive(Component)]
/// #[component(storage = "SparseSet")]
/// struct A;
/// ```
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum StorageType {
/// Provides fast and cache-friendly iteration, but slower addition and removal of components.
/// This is the default storage type.
Table,
/// Provides fast addition and removal of components, but slower iteration.
SparseSet,
}
impl Default for StorageType {
fn default() -> Self {
StorageType::Table
}
}
#[derive(Debug)]
pub struct ComponentInfo {
id: ComponentId,
descriptor: ComponentDescriptor,
}
impl ComponentInfo {
#[inline]
pub fn id(&self) -> ComponentId {
self.id
}
#[inline]
pub fn name(&self) -> &str {
&self.descriptor.name
}
#[inline]
pub fn type_id(&self) -> Option<TypeId> {
self.descriptor.type_id
}
#[inline]
pub fn layout(&self) -> Layout {
self.descriptor.layout
}
#[inline]
pub fn drop(&self) -> unsafe fn(OwningPtr<'_>) {
self.descriptor.drop
}
#[inline]
pub fn storage_type(&self) -> StorageType {
self.descriptor.storage_type
}
#[inline]
pub fn is_send_and_sync(&self) -> bool {
self.descriptor.is_send_and_sync
}
fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self {
ComponentInfo { id, descriptor }
}
}
#[derive(Debug, Copy, Clone, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct ComponentId(usize);
impl ComponentId {
#[inline]
pub const fn new(index: usize) -> ComponentId {
ComponentId(index)
}
#[inline]
pub fn index(self) -> usize {
self.0
}
}
impl SparseSetIndex for ComponentId {
#[inline]
fn sparse_set_index(&self) -> usize {
self.index()
}
fn get_sparse_set_index(value: usize) -> Self {
Self(value)
}
}
pub struct ComponentDescriptor {
name: String,
// SAFETY: This must remain private. It must match the statically known StorageType of the
// associated rust component type if one exists.
storage_type: StorageType,
// SAFETY: This must remain private. It must only be set to "true" if this component is
// actually Send + Sync
is_send_and_sync: bool,
type_id: Option<TypeId>,
layout: Layout,
// SAFETY: this function must be safe to call with pointers pointing to items of the type
// this descriptor describes.
drop: for<'a> unsafe fn(OwningPtr<'a>),
}
// We need to ignore the `drop` field in our `Debug` impl
impl std::fmt::Debug for ComponentDescriptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ComponentDescriptor")
.field("name", &self.name)
.field("storage_type", &self.storage_type)
.field("is_send_and_sync", &self.is_send_and_sync)
.field("type_id", &self.type_id)
.field("layout", &self.layout)
.finish()
}
}
impl ComponentDescriptor {
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
x.inner().cast::<T>().as_ptr().drop_in_place()
}
pub fn new<T: Component>() -> Self {
Self {
name: std::any::type_name::<T>().to_string(),
storage_type: T::Storage::STORAGE_TYPE,
is_send_and_sync: true,
type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(),
drop: Self::drop_ptr::<T>,
}
}
/// Create a new `ComponentDescriptor` for a resource.
///
/// The [`StorageType`] for resources is always [`TableStorage`].
pub fn new_resource<T: Resource>() -> Self {
Self {
name: std::any::type_name::<T>().to_string(),
// PERF: `SparseStorage` may actually be a more
// reasonable choice as `storage_type` for resources.
storage_type: StorageType::Table,
is_send_and_sync: true,
type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(),
drop: Self::drop_ptr::<T>,
}
}
fn new_non_send<T: Any>(storage_type: StorageType) -> Self {
Self {
name: std::any::type_name::<T>().to_string(),
storage_type,
is_send_and_sync: false,
type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(),
drop: Self::drop_ptr::<T>,
}
}
#[inline]
pub fn storage_type(&self) -> StorageType {
self.storage_type
}
#[inline]
pub fn type_id(&self) -> Option<TypeId> {
self.type_id
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
}
#[derive(Debug, Default)]
pub struct Components {
components: Vec<ComponentInfo>,
indices: std::collections::HashMap<TypeId, usize, fxhash::FxBuildHasher>,
resource_indices: std::collections::HashMap<TypeId, usize, fxhash::FxBuildHasher>,
}
impl Components {
#[inline]
pub fn init_component<T: Component>(&mut self, storages: &mut Storages) -> ComponentId {
let type_id = TypeId::of::<T>();
let components = &mut self.components;
let index = self.indices.entry(type_id).or_insert_with(|| {
let index = components.len();
let descriptor = ComponentDescriptor::new::<T>();
let info = ComponentInfo::new(ComponentId(index), descriptor);
if T::Storage::STORAGE_TYPE == StorageType::SparseSet {
storages.sparse_sets.get_or_insert(&info);
}
components.push(info);
index
});
ComponentId(*index)
}
#[inline]
pub fn len(&self) -> usize {
self.components.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.components.len() == 0
}
#[inline]
pub fn get_info(&self, id: ComponentId) -> Option<&ComponentInfo> {
self.components.get(id.0)
}
/// # Safety
///
/// `id` must be a valid [`ComponentId`]
#[inline]
pub unsafe fn get_info_unchecked(&self, id: ComponentId) -> &ComponentInfo {
debug_assert!(id.index() < self.components.len());
self.components.get_unchecked(id.0)
}
#[inline]
pub fn get_id(&self, type_id: TypeId) -> Option<ComponentId> {
self.indices.get(&type_id).map(|index| ComponentId(*index))
}
#[inline]
pub fn get_resource_id(&self, type_id: TypeId) -> Option<ComponentId> {
self.resource_indices
.get(&type_id)
.map(|index| ComponentId(*index))
}
#[inline]
pub fn init_resource<T: Resource>(&mut self) -> ComponentId {
// SAFE: The [`ComponentDescriptor`] matches the [`TypeId`]
unsafe {
self.get_or_insert_resource_with(TypeId::of::<T>(), || {
ComponentDescriptor::new_resource::<T>()
})
}
}
#[inline]
pub fn init_non_send<T: Any>(&mut self) -> ComponentId {
// SAFE: The [`ComponentDescriptor`] matches the [`TypeId`]
unsafe {
self.get_or_insert_resource_with(TypeId::of::<T>(), || {
ComponentDescriptor::new_non_send::<T>(StorageType::default())
})
}
}
/// # Safety
///
/// The [`ComponentDescriptor`] must match the [`TypeId`]
#[inline]
unsafe fn get_or_insert_resource_with(
&mut self,
type_id: TypeId,
func: impl FnOnce() -> ComponentDescriptor,
) -> ComponentId {
let components = &mut self.components;
let index = self.resource_indices.entry(type_id).or_insert_with(|| {
let descriptor = func();
let index = components.len();
components.push(ComponentInfo::new(ComponentId(index), descriptor));
index
});
ComponentId(*index)
}
}
#[derive(Copy, Clone, Debug)]
pub struct ComponentTicks {
pub(crate) added: u32,
pub(crate) changed: u32,
}
impl ComponentTicks {
#[inline]
pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool {
// The comparison is relative to `change_tick` so that we can detect changes over the whole
// `u32` range. Comparing directly the ticks would limit to half that due to overflow
// handling.
let component_delta = change_tick.wrapping_sub(self.added);
let system_delta = change_tick.wrapping_sub(last_change_tick);
component_delta < system_delta
}
#[inline]
pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool {
let component_delta = change_tick.wrapping_sub(self.changed);
let system_delta = change_tick.wrapping_sub(last_change_tick);
component_delta < system_delta
}
pub(crate) fn new(change_tick: u32) -> Self {
Self {
added: change_tick,
changed: change_tick,
}
}
pub(crate) fn check_ticks(&mut self, change_tick: u32) {
check_tick(&mut self.added, change_tick);
check_tick(&mut self.changed, change_tick);
}
/// Manually sets the change tick.
/// Usually, this is done automatically via the [`DerefMut`](std::ops::DerefMut) implementation
/// on [`Mut`](crate::world::Mut) or [`ResMut`](crate::system::ResMut) etc.
///
/// # Example
/// ```rust,no_run
/// # use bevy_ecs::{world::World, component::ComponentTicks};
/// let world: World = unimplemented!();
/// let component_ticks: ComponentTicks = unimplemented!();
///
/// component_ticks.set_changed(world.read_change_tick());
/// ```
#[inline]
pub fn set_changed(&mut self, change_tick: u32) {
self.changed = change_tick;
}
}
fn check_tick(last_change_tick: &mut u32, change_tick: u32) {
let tick_delta = change_tick.wrapping_sub(*last_change_tick);
const MAX_DELTA: u32 = (u32::MAX / 4) * 3;
// Clamp to max delta
if tick_delta > MAX_DELTA {
*last_change_tick = change_tick.wrapping_sub(MAX_DELTA);
}
}