Path: blob/main/crates/bevy_ecs/src/storage/non_send.rs
30636 views
use crate::{1change_detection::{CheckChangeTicks, ComponentTickCells, ComponentTicks, MaybeLocation, Tick},2component::{ComponentId, Components},3storage::{blob_array::BlobArray, SparseSet},4};5use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};6use bevy_utils::prelude::DebugName;7use core::{cell::UnsafeCell, panic::Location};89#[cfg(feature = "std")]10use std::thread::ThreadId;1112/// The type-erased backing storage and metadata for one instance of non send data within a [`World`].13///14/// Values of this type will panic if dropped from a different thread.15///16/// [`World`]: crate::world::World17pub struct NonSendData {18/// Capacity is 1, length is 1 if `is_present` and 0 otherwise.19data: BlobArray,20is_present: bool,21added_ticks: UnsafeCell<Tick>,22changed_ticks: UnsafeCell<Tick>,23#[cfg_attr(24not(feature = "std"),25expect(dead_code, reason = "currently only used with the std feature")26)]27type_name: DebugName,28#[cfg(feature = "std")]29origin_thread_id: Option<ThreadId>,30changed_by: MaybeLocation<UnsafeCell<&'static Location<'static>>>,31}3233impl Drop for NonSendData {34fn drop(&mut self) {35// We need to validate that correct thread is dropping the data.36// This validation is not needed if there is no data.37if self.is_present() {38// If this thread is already panicking, panicking again will cause39// the entire process to abort. In this case we choose to avoid40// dropping or checking this altogether and just leak the column.41#[cfg(feature = "std")]42if std::thread::panicking() {43return;44}45self.validate_access();46}47// SAFETY: Drop is only called once upon dropping the NonSendData48// and is inaccessible after this as the parent NonSendData has49// been dropped. The validate_access call above will check that the50// data is dropped on the thread it was inserted from.51unsafe {52self.data.drop(1, self.is_present().into());53}54}55}5657impl NonSendData {58/// The only row in the underlying `BlobArray`.59const ROW: usize = 0;6061/// Validates that the access to `NonSendData` is only done on the thread they were created from.62///63/// # Panics64/// This will panic if called from a different thread than the one it was inserted from.65#[inline]66fn validate_access(&self) {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 data {} 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}8384/// Returns true if the data is populated.85#[inline]86pub fn is_present(&self) -> bool {87self.is_present88}8990/// Returns a reference to the data, if it exists.91///92/// # Panics93/// This will panic if a value is present and is not accessed from the original thread it was inserted from.94#[inline]95pub fn get_data(&self) -> Option<Ptr<'_>> {96self.is_present().then(|| {97self.validate_access();98// SAFETY: We've already checked if a value is present, and there should only be one.99unsafe { self.data.get_unchecked(Self::ROW) }100})101}102103/// Returns a reference to the data's change ticks, if it exists.104#[inline]105pub fn get_ticks(&self) -> Option<ComponentTicks> {106// SAFETY: This is being fetched through a read-only reference to Self, so no other mutable references107// to the ticks can exist.108unsafe {109self.is_present().then(|| ComponentTicks {110added: self.added_ticks.read(),111changed: self.changed_ticks.read(),112})113}114}115116/// Returns references to the data and its change ticks, if it exists.117///118/// # Panics119/// This will panic if a value is present and is not accessed from the original thread it was inserted in.120#[inline]121pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, ComponentTickCells<'_>)> {122self.is_present().then(|| {123self.validate_access();124(125// SAFETY: We've already checked if a value is present, and there should only be one.126unsafe { self.data.get_unchecked(Self::ROW) },127ComponentTickCells {128added: &self.added_ticks,129changed: &self.changed_ticks,130changed_by: self.changed_by.as_ref(),131},132)133})134}135136/// Inserts a value into the non-send data. If a value is already present137/// it will be replaced.138///139/// # Panics140/// This will panic if a value is present and is not replaced from the original thread it was inserted in.141///142/// # Safety143/// - `value` must be valid for the underlying type for the data.144#[inline]145pub(crate) unsafe fn insert(146&mut self,147value: OwningPtr<'_>,148change_tick: Tick,149caller: MaybeLocation,150) {151if self.is_present() {152self.validate_access();153// SAFETY: The caller ensures that the provided value is valid for the underlying type and154// is properly initialized. We've ensured that a value is already present and previously155// initialized.156unsafe { self.data.replace_unchecked(Self::ROW, value) };157} else {158#[cfg(feature = "std")]159{160self.origin_thread_id = Some(std::thread::current().id());161}162// SAFETY:163// - There is only one element, and it's always allocated.164// - The caller guarantees must be valid for the underlying type and thus its165// layout must be identical.166// - The value was previously not present and thus must not have been initialized.167unsafe { self.data.initialize_unchecked(Self::ROW, value) };168*self.added_ticks.deref_mut() = change_tick;169self.is_present = true;170}171*self.changed_ticks.deref_mut() = change_tick;172173self.changed_by174.as_ref()175.map(|changed_by| changed_by.deref_mut())176.assign(caller);177}178179/// Removes a value from the data, if present.180///181/// # Panics182/// This will panic if a value is present and is not removed from the original thread it was inserted from.183#[inline]184#[must_use = "The returned pointer to the removed component should be used or dropped"]185pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks, MaybeLocation)> {186if !self.is_present() {187return None;188}189self.validate_access();190191self.is_present = false;192193// SAFETY:194// - There is always only one row in the `BlobArray` created during initialization.195// - This function has validated that the row is present with the check of `self.is_present`.196// - The caller is to take ownership of the value, returned as a `OwningPtr`.197let res = unsafe { self.data.get_unchecked_mut(Self::ROW).promote() };198199let caller = self200.changed_by201.as_ref()202// SAFETY: This function is being called through an exclusive mutable reference to Self203.map(|changed_by| unsafe { *changed_by.deref_mut() });204205// SAFETY: This function is being called through an exclusive mutable reference to Self, which206// makes it sound to read these ticks.207unsafe {208Some((209res,210ComponentTicks {211added: self.added_ticks.read(),212changed: self.changed_ticks.read(),213},214caller,215))216}217}218219/// Removes a value from the data, if present, and drops it.220///221/// # Panics222/// This will panic if a value is present and is not accessed from the original thread it was inserted in.223#[inline]224pub(crate) fn remove_and_drop(&mut self) {225if self.is_present() {226self.validate_access();227// SAFETY: There is only one element, and it's always allocated.228unsafe { self.data.drop_last_element(Self::ROW) };229self.is_present = false;230}231}232233pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {234self.added_ticks.get_mut().check_tick(check);235self.changed_ticks.get_mut().check_tick(check);236}237}238239/// The backing store for all non send data stored in the [`World`].240///241/// [`World`]: crate::world::World242#[derive(Default)]243pub struct NonSends {244non_sends: SparseSet<ComponentId, NonSendData>,245}246247impl NonSends {248/// The total amount of `!Send` data stored in the [`World`]249///250/// [`World`]: crate::world::World251#[inline]252pub fn len(&self) -> usize {253self.non_sends.len()254}255256/// Iterate over all non send data that have been initialized, i.e. given a [`ComponentId`]257pub fn iter(&self) -> impl Iterator<Item = (ComponentId, &NonSendData)> {258self.non_sends.iter().map(|(id, data)| (*id, data))259}260261/// Returns true if there is no `!Send` data stored in the [`World`],262/// false otherwise.263///264/// [`World`]: crate::world::World265#[inline]266pub fn is_empty(&self) -> bool {267self.non_sends.is_empty()268}269270/// Gets read-only access to some `!Send` data, if it exists.271#[inline]272pub fn get(&self, component_id: ComponentId) -> Option<&NonSendData> {273self.non_sends.get(component_id)274}275276/// Clears all non send data.277#[inline]278pub fn clear(&mut self) {279self.non_sends.clear();280}281282/// Gets mutable access to `!Send` data, if it exists.283#[inline]284pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut NonSendData> {285self.non_sends.get_mut(component_id)286}287288/// Fetches or initializes new `!Send` data and returns back its underlying column.289///290/// # Panics291/// Will panic if `component_id` is not valid for the provided `components`292pub(crate) fn initialize_with(293&mut self,294component_id: ComponentId,295components: &Components,296) -> &mut NonSendData {297self.non_sends.get_or_insert_with(component_id, || {298let component_info = components.get_info(component_id).unwrap();299// SAFETY:300// * component_info.drop() is valid for the types that will be inserted.301// * `ComponentInfo` ensures that `layout().size()` is a multiple of `layout().align()`302let data = unsafe {303BlobArray::with_capacity(component_info.layout(), component_info.drop(), 1)304};305NonSendData {306data,307is_present: false,308added_ticks: UnsafeCell::new(Tick::new(0)),309changed_ticks: UnsafeCell::new(Tick::new(0)),310type_name: component_info.name(),311#[cfg(feature = "std")]312origin_thread_id: None,313changed_by: MaybeLocation::caller().map(UnsafeCell::new),314}315})316}317318pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {319for info in self.non_sends.values_mut() {320info.check_change_ticks(check);321}322}323}324325326