Path: blob/main/crates/bevy_ecs/src/storage/resource.rs
6604 views
use crate::{1change_detection::{MaybeLocation, MutUntyped, TicksMut},2component::{CheckChangeTicks, ComponentId, ComponentTicks, Components, Tick, TickCells},3storage::{blob_vec::BlobVec, SparseSet},4};5use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};6use bevy_utils::prelude::DebugName;7use core::{cell::UnsafeCell, mem::ManuallyDrop, panic::Location};89#[cfg(feature = "std")]10use std::thread::ThreadId;1112/// The type-erased backing storage and metadata for a single resource within a [`World`].13///14/// If `SEND` is false, values of this type will panic if dropped from a different thread.15///16/// [`World`]: crate::world::World17pub struct ResourceData<const SEND: bool> {18data: ManuallyDrop<BlobVec>,19added_ticks: UnsafeCell<Tick>,20changed_ticks: UnsafeCell<Tick>,21#[cfg_attr(22not(feature = "std"),23expect(dead_code, reason = "currently only used with the std feature")24)]25type_name: DebugName,26#[cfg(feature = "std")]27origin_thread_id: Option<ThreadId>,28changed_by: MaybeLocation<UnsafeCell<&'static Location<'static>>>,29}3031impl<const SEND: bool> Drop for ResourceData<SEND> {32fn drop(&mut self) {33// For Non Send resources we need to validate that correct thread34// is dropping the resource. This validation is not needed in case35// of SEND resources. Or if there is no data.36if !SEND && self.is_present() {37// If this thread is already panicking, panicking again will cause38// the entire process to abort. In this case we choose to avoid39// dropping or checking this altogether and just leak the column.40#[cfg(feature = "std")]41if std::thread::panicking() {42return;43}44self.validate_access();45}46// SAFETY: Drop is only called once upon dropping the ResourceData47// and is inaccessible after this as the parent ResourceData has48// been dropped. The validate_access call above will check that the49// data is dropped on the thread it was inserted from.50unsafe {51ManuallyDrop::drop(&mut self.data);52}53}54}5556impl<const SEND: bool> ResourceData<SEND> {57/// The only row in the underlying `BlobVec`.58const ROW: usize = 0;5960/// Validates the access to `!Send` resources is only done on the thread they were created from.61///62/// # Panics63/// If `SEND` is false, this will panic if called from a different thread than the one it was inserted from.64#[inline]65fn validate_access(&self) {66if !SEND {67#[cfg(feature = "std")]68if self.origin_thread_id != Some(std::thread::current().id()) {69// Panic in tests, as testing for aborting is nearly impossible70panic!(71"Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.",72self.type_name,73self.origin_thread_id,74std::thread::current().id()75);76}7778// TODO: Handle no_std non-send.79// Currently, no_std is single-threaded only, so this is safe to ignore.80// To support no_std multithreading, an alternative will be required.81// Remove the #[expect] attribute above when this is addressed.82}83}8485/// Returns true if the resource is populated.86#[inline]87pub fn is_present(&self) -> bool {88!self.data.is_empty()89}9091/// Returns a reference to the resource, if it exists.92///93/// # Panics94/// If `SEND` is false, this will panic if a value is present and is not accessed from the95/// original thread it was inserted from.96#[inline]97pub fn get_data(&self) -> Option<Ptr<'_>> {98self.is_present().then(|| {99self.validate_access();100// SAFETY: We've already checked if a value is present, and there should only be one.101unsafe { self.data.get_unchecked(Self::ROW) }102})103}104105/// Returns a reference to the resource's change ticks, if it exists.106#[inline]107pub fn get_ticks(&self) -> Option<ComponentTicks> {108// SAFETY: This is being fetched through a read-only reference to Self, so no other mutable references109// to the ticks can exist.110unsafe {111self.is_present().then(|| ComponentTicks {112added: self.added_ticks.read(),113changed: self.changed_ticks.read(),114})115}116}117118/// Returns references to the resource and its change ticks, if it exists.119///120/// # Panics121/// If `SEND` is false, this will panic if a value is present and is not accessed from the122/// original thread it was inserted in.123#[inline]124pub(crate) fn get_with_ticks(125&self,126) -> Option<(127Ptr<'_>,128TickCells<'_>,129MaybeLocation<&UnsafeCell<&'static Location<'static>>>,130)> {131self.is_present().then(|| {132self.validate_access();133(134// SAFETY: We've already checked if a value is present, and there should only be one.135unsafe { self.data.get_unchecked(Self::ROW) },136TickCells {137added: &self.added_ticks,138changed: &self.changed_ticks,139},140self.changed_by.as_ref(),141)142})143}144145/// Returns a mutable reference to the resource, if it exists.146///147/// # Panics148/// If `SEND` is false, this will panic if a value is present and is not accessed from the149/// original thread it was inserted in.150pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option<MutUntyped<'_>> {151let (ptr, ticks, caller) = self.get_with_ticks()?;152Some(MutUntyped {153// SAFETY: We have exclusive access to the underlying storage.154value: unsafe { ptr.assert_unique() },155// SAFETY: We have exclusive access to the underlying storage.156ticks: unsafe { TicksMut::from_tick_cells(ticks, last_run, this_run) },157// SAFETY: We have exclusive access to the underlying storage.158changed_by: unsafe { caller.map(|caller| caller.deref_mut()) },159})160}161162/// Inserts a value into the resource. If a value is already present163/// it will be replaced.164///165/// # Panics166/// If `SEND` is false, this will panic if a value is present and is not replaced from167/// the original thread it was inserted in.168///169/// # Safety170/// - `value` must be valid for the underlying type for the resource.171#[inline]172pub(crate) unsafe fn insert(173&mut self,174value: OwningPtr<'_>,175change_tick: Tick,176caller: MaybeLocation,177) {178if self.is_present() {179self.validate_access();180// SAFETY: The caller ensures that the provided value is valid for the underlying type and181// is properly initialized. We've ensured that a value is already present and previously182// initialized.183unsafe {184self.data.replace_unchecked(Self::ROW, value);185}186} else {187#[cfg(feature = "std")]188if !SEND {189self.origin_thread_id = Some(std::thread::current().id());190}191self.data.push(value);192*self.added_ticks.deref_mut() = change_tick;193}194*self.changed_ticks.deref_mut() = change_tick;195196self.changed_by197.as_ref()198.map(|changed_by| changed_by.deref_mut())199.assign(caller);200}201202/// Inserts a value into the resource with a pre-existing change tick. If a203/// value is already present it will be replaced.204///205/// # Panics206/// If `SEND` is false, this will panic if a value is present and is not replaced from207/// the original thread it was inserted in.208///209/// # Safety210/// - `value` must be valid for the underlying type for the resource.211#[inline]212pub(crate) unsafe fn insert_with_ticks(213&mut self,214value: OwningPtr<'_>,215change_ticks: ComponentTicks,216caller: MaybeLocation,217) {218if self.is_present() {219self.validate_access();220// SAFETY: The caller ensures that the provided value is valid for the underlying type and221// is properly initialized. We've ensured that a value is already present and previously222// initialized.223unsafe {224self.data.replace_unchecked(Self::ROW, value);225}226} else {227#[cfg(feature = "std")]228if !SEND {229self.origin_thread_id = Some(std::thread::current().id());230}231self.data.push(value);232}233*self.added_ticks.deref_mut() = change_ticks.added;234*self.changed_ticks.deref_mut() = change_ticks.changed;235self.changed_by236.as_ref()237.map(|changed_by| changed_by.deref_mut())238.assign(caller);239}240241/// Removes a value from the resource, if present.242///243/// # Panics244/// If `SEND` is false, this will panic if a value is present and is not removed from the245/// original thread it was inserted from.246#[inline]247#[must_use = "The returned pointer to the removed component should be used or dropped"]248pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks, MaybeLocation)> {249if !self.is_present() {250return None;251}252if !SEND {253self.validate_access();254}255// SAFETY: We've already validated that the row is present.256let res = unsafe { self.data.swap_remove_and_forget_unchecked(Self::ROW) };257258let caller = self259.changed_by260.as_ref()261// SAFETY: This function is being called through an exclusive mutable reference to Self262.map(|changed_by| unsafe { *changed_by.deref_mut() });263264// SAFETY: This function is being called through an exclusive mutable reference to Self, which265// makes it sound to read these ticks.266unsafe {267Some((268res,269ComponentTicks {270added: self.added_ticks.read(),271changed: self.changed_ticks.read(),272},273caller,274))275}276}277278/// Removes a value from the resource, if present, and drops it.279///280/// # Panics281/// If `SEND` is false, this will panic if a value is present and is not282/// accessed from the original thread it was inserted in.283#[inline]284pub(crate) fn remove_and_drop(&mut self) {285if self.is_present() {286self.validate_access();287self.data.clear();288}289}290291pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {292self.added_ticks.get_mut().check_tick(check);293self.changed_ticks.get_mut().check_tick(check);294}295}296297/// The backing store for all [`Resource`]s stored in the [`World`].298///299/// [`Resource`]: crate::resource::Resource300/// [`World`]: crate::world::World301#[derive(Default)]302pub struct Resources<const SEND: bool> {303resources: SparseSet<ComponentId, ResourceData<SEND>>,304}305306impl<const SEND: bool> Resources<SEND> {307/// The total number of resources stored in the [`World`]308///309/// [`World`]: crate::world::World310#[inline]311pub fn len(&self) -> usize {312self.resources.len()313}314315/// Iterate over all resources that have been initialized, i.e. given a [`ComponentId`]316pub fn iter(&self) -> impl Iterator<Item = (ComponentId, &ResourceData<SEND>)> {317self.resources.iter().map(|(id, data)| (*id, data))318}319320/// Returns true if there are no resources stored in the [`World`],321/// false otherwise.322///323/// [`World`]: crate::world::World324#[inline]325pub fn is_empty(&self) -> bool {326self.resources.is_empty()327}328329/// Gets read-only access to a resource, if it exists.330#[inline]331pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData<SEND>> {332self.resources.get(component_id)333}334335/// Clears all resources.336#[inline]337pub fn clear(&mut self) {338self.resources.clear();339}340341/// Gets mutable access to a resource, if it exists.342#[inline]343pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData<SEND>> {344self.resources.get_mut(component_id)345}346347/// Fetches or initializes a new resource and returns back its underlying column.348///349/// # Panics350/// Will panic if `component_id` is not valid for the provided `components`351/// If `SEND` is true, this will panic if `component_id`'s `ComponentInfo` is not registered as being `Send` + `Sync`.352pub(crate) fn initialize_with(353&mut self,354component_id: ComponentId,355components: &Components,356) -> &mut ResourceData<SEND> {357self.resources.get_or_insert_with(component_id, || {358let component_info = components.get_info(component_id).unwrap();359if SEND {360assert!(361component_info.is_send_and_sync(),362"Send + Sync resource {} initialized as non_send. It may have been inserted via World::insert_non_send_resource by accident. Try using World::insert_resource instead.",363component_info.name(),364);365}366// SAFETY: component_info.drop() is valid for the types that will be inserted.367let data = unsafe {368BlobVec::new(369component_info.layout(),370component_info.drop(),3711372)373};374ResourceData {375data: ManuallyDrop::new(data),376added_ticks: UnsafeCell::new(Tick::new(0)),377changed_ticks: UnsafeCell::new(Tick::new(0)),378type_name: component_info.name(),379#[cfg(feature = "std")]380origin_thread_id: None,381changed_by: MaybeLocation::caller().map(UnsafeCell::new),382}383})384}385386pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {387for info in self.resources.values_mut() {388info.check_change_ticks(check);389}390}391}392393394