mirror of
https://github.com/bevyengine/bevy
synced 2025-01-08 03:08:55 +00:00
1e322d9f76
# Objective The pointer types introduced in #3001 are useful not just in `bevy_ecs`, but also in crates like `bevy_reflect` (#4475) or even outside of bevy. ## Solution Extract `Ptr<'a>`, `PtrMut<'a>`, `OwnedPtr<'a>`, `ThinSlicePtr<'a, T>` and `UnsafeCellDeref` from `bevy_ecs::ptr` into `bevy_ptr`. **Note:** `bevy_ecs` still reexports the `bevy_ptr` as `bevy_ecs::ptr` so that crates like `bevy_transform` can use the `Bundle` derive without needing to depend on `bevy_ptr` themselves.
411 lines
12 KiB
Rust
411 lines
12 KiB
Rust
//! Types for declaring and storing [`Component`]s.
|
|
|
|
use crate::{
|
|
storage::{SparseSetIndex, Storages},
|
|
system::Resource,
|
|
};
|
|
pub use bevy_ecs_macros::Component;
|
|
use bevy_ptr::OwningPtr;
|
|
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.drop_as::<T>()
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|