From 1e322d9f763d99fcdb976036811e92b2099c4e07 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Wed, 4 May 2022 19:16:10 +0000 Subject: [PATCH] `bevy_ptr` standalone crate (#4653) # 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. --- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/bundle.rs | 2 +- crates/bevy_ecs/src/component.rs | 2 +- crates/bevy_ecs/src/lib.rs | 3 ++- crates/bevy_ecs/src/query/fetch.rs | 2 +- crates/bevy_ecs/src/query/filter.rs | 2 +- crates/bevy_ecs/src/storage/blob_vec.rs | 2 +- crates/bevy_ecs/src/storage/sparse_set.rs | 2 +- crates/bevy_ecs/src/storage/table.rs | 2 +- crates/bevy_ecs/src/system/system_param.rs | 2 +- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 +- crates/bevy_internal/Cargo.toml | 1 + crates/bevy_internal/src/lib.rs | 5 ++++ crates/bevy_ptr/Cargo.toml | 11 ++++++++ crates/bevy_ptr/README.md | 7 ++++++ .../src/ptr.rs => bevy_ptr/src/lib.rs} | 25 ++++++++++++++++++- tools/publish.sh | 1 + 18 files changed, 62 insertions(+), 12 deletions(-) create mode 100644 crates/bevy_ptr/Cargo.toml create mode 100644 crates/bevy_ptr/README.md rename crates/{bevy_ecs/src/ptr.rs => bevy_ptr/src/lib.rs} (86%) diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index c7a76ac02bdf7..7262c85c9f491 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -14,6 +14,7 @@ trace = [] default = ["bevy_reflect"] [dependencies] +bevy_ptr = { path = "../bevy_ptr", version = "0.8.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", optional = true } bevy_tasks = { path = "../bevy_tasks", version = "0.8.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.8.0-dev" } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 1fa9fa9653112..1ff332ec62ca6 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -8,10 +8,10 @@ use crate::{ archetype::{AddBundle, Archetype, ArchetypeId, Archetypes, ComponentStatus}, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - ptr::OwningPtr, storage::{SparseSetIndex, SparseSets, Storages, Table}, }; use bevy_ecs_macros::all_tuples; +use bevy_ptr::OwningPtr; use std::{any::TypeId, collections::HashMap}; /// An ordered collection of [`Component`]s. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index fac3bfb9dbeff..92767386742c7 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,11 +1,11 @@ //! Types for declaring and storing [`Component`]s. use crate::{ - ptr::OwningPtr, storage::{SparseSetIndex, Storages}, system::Resource, }; pub use bevy_ecs_macros::Component; +use bevy_ptr::OwningPtr; use std::{ alloc::Layout, any::{Any, TypeId}, diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 6cbfabdaeac20..15a5ac64a8d22 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -6,7 +6,6 @@ pub mod change_detection; pub mod component; pub mod entity; pub mod event; -pub mod ptr; pub mod query; #[cfg(feature = "bevy_reflect")] pub mod reflect; @@ -15,6 +14,8 @@ pub mod storage; pub mod system; pub mod world; +pub use bevy_ptr as ptr; + /// Most commonly used re-exported types. pub mod prelude { #[doc(hidden)] diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 9a6ad81fb02e9..5a89bc5280f63 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -3,13 +3,13 @@ use crate::{ change_detection::Ticks, component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, - ptr::{ThinSlicePtr, UnsafeCellDeref}, query::{debug_checked_unreachable, Access, FilteredAccess}, storage::{ComponentSparseSet, Table, Tables}, world::{Mut, World}, }; use bevy_ecs_macros::all_tuples; pub use bevy_ecs_macros::WorldQuery; +use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; use std::{cell::UnsafeCell, marker::PhantomData}; /// Types that can be queried from a [`World`]. diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index ab0aa8dd03ada..276b43459e64d 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -2,7 +2,6 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, entity::Entity, - ptr::{ThinSlicePtr, UnsafeCellDeref}, query::{ debug_checked_unreachable, Access, Fetch, FetchState, FilteredAccess, QueryFetch, ROQueryFetch, WorldQuery, WorldQueryGats, @@ -11,6 +10,7 @@ use crate::{ world::World, }; use bevy_ecs_macros::all_tuples; +use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref}; use std::{cell::UnsafeCell, marker::PhantomData}; use super::ReadOnlyFetch; diff --git a/crates/bevy_ecs/src/storage/blob_vec.rs b/crates/bevy_ecs/src/storage/blob_vec.rs index f46ab409e4cfb..c2902553e7124 100644 --- a/crates/bevy_ecs/src/storage/blob_vec.rs +++ b/crates/bevy_ecs/src/storage/blob_vec.rs @@ -4,7 +4,7 @@ use std::{ ptr::NonNull, }; -use crate::ptr::{OwningPtr, Ptr, PtrMut}; +use bevy_ptr::{OwningPtr, Ptr, PtrMut}; /// A flat, type-erased data storage type /// diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 3a2c8d3675ea4..192cfd2aae9bc 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -1,9 +1,9 @@ use crate::{ component::{ComponentId, ComponentInfo, ComponentTicks}, entity::Entity, - ptr::{OwningPtr, Ptr}, storage::BlobVec, }; +use bevy_ptr::{OwningPtr, Ptr}; use std::{cell::UnsafeCell, marker::PhantomData}; #[derive(Debug)] diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index 21884dd3ab729..755644998decf 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -1,9 +1,9 @@ use crate::{ component::{ComponentId, ComponentInfo, ComponentTicks, Components}, entity::Entity, - ptr::{OwningPtr, Ptr, PtrMut}, storage::{BlobVec, SparseSet}, }; +use bevy_ptr::{OwningPtr, Ptr, PtrMut}; use bevy_utils::HashMap; use std::{ cell::UnsafeCell, diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index e86b0d12abb68..55cb02176a5ab 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -5,7 +5,6 @@ use crate::{ change_detection::Ticks, component::{Component, ComponentId, ComponentTicks, Components}, entity::{Entities, Entity}, - ptr::UnsafeCellDeref, query::{ Access, FilteredAccess, FilteredAccessSet, QueryFetch, QueryState, ReadOnlyFetch, WorldQuery, @@ -15,6 +14,7 @@ use crate::{ }; pub use bevy_ecs_macros::SystemParam; use bevy_ecs_macros::{all_tuples, impl_param_set}; +use bevy_ptr::UnsafeCellDeref; use std::{ fmt::Debug, marker::PhantomData, diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 9a53fe8992b6c..c9b6006381a7d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,10 +4,10 @@ use crate::{ change_detection::Ticks, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - ptr::{OwningPtr, Ptr, UnsafeCellDeref}, storage::{SparseSet, Storages}, world::{Mut, World}, }; +use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; use std::{any::TypeId, cell::UnsafeCell}; /// A read-only reference to a particular [`Entity`] and all of its components diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index bd71cefddd8b6..bdcd809bd9e23 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -13,11 +13,11 @@ use crate::{ change_detection::Ticks, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{AllocAtWithoutReplacement, Entities, Entity}, - ptr::{OwningPtr, UnsafeCellDeref}, query::{QueryState, WorldQuery}, storage::{Column, SparseSet, Storages}, system::Resource, }; +use bevy_ptr::{OwningPtr, UnsafeCellDeref}; use bevy_utils::tracing::debug; use std::{ any::TypeId, diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 360c957e984bc..35be208d1ba6d 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -75,6 +75,7 @@ bevy_hierarchy = { path = "../bevy_hierarchy", version = "0.8.0-dev" } bevy_input = { path = "../bevy_input", version = "0.8.0-dev" } bevy_log = { path = "../bevy_log", version = "0.8.0-dev" } bevy_math = { path = "../bevy_math", version = "0.8.0-dev" } +bevy_ptr = { path = "../bevy_ptr", version = "0.8.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.8.0-dev", features = ["bevy"] } bevy_scene = { path = "../bevy_scene", version = "0.8.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.8.0-dev" } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index a6992073137cd..95d1cb35e234c 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -47,6 +47,11 @@ pub mod math { pub use bevy_math::*; } +pub mod ptr { + //! Utilities for working with untyped pointers in a more safe way. + pub use bevy_ptr::*; +} + pub mod reflect { // TODO: remove these renames once TypeRegistryArc is no longer required //! Type reflection used for dynamically interacting with rust types. diff --git a/crates/bevy_ptr/Cargo.toml b/crates/bevy_ptr/Cargo.toml new file mode 100644 index 0000000000000..0b3d02daab8e5 --- /dev/null +++ b/crates/bevy_ptr/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bevy_ptr" +version = "0.8.0-dev" +edition = "2021" +description = "Utilities for working with untyped pointers in a more safe way" +homepage = "https://bevyengine.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy"] + +[dependencies] diff --git a/crates/bevy_ptr/README.md b/crates/bevy_ptr/README.md new file mode 100644 index 0000000000000..a08c3043ec391 --- /dev/null +++ b/crates/bevy_ptr/README.md @@ -0,0 +1,7 @@ +# `bevy_ptr` + +The `bevy_ptr` crate provides low-level abstractions for working with pointers in a more safe way than using rust's raw pointers. + +Rust has lifetimed and typed references (`&'a T`), unlifetimed and typed references (`*const T`), but no lifetimed but untyped references. +`bevy_ptr` adds them, called `Ptr<'a>`, `PtrMut<'a>` and `OwningPtr<'a>`. +These types are lifetime-checked so can never lead to problems like use-after-frees and must always point to valid data. diff --git a/crates/bevy_ecs/src/ptr.rs b/crates/bevy_ptr/src/lib.rs similarity index 86% rename from crates/bevy_ecs/src/ptr.rs rename to crates/bevy_ptr/src/lib.rs index 2f81e4632b376..cb3cc2abb3c39 100644 --- a/crates/bevy_ecs/src/ptr.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -1,3 +1,4 @@ +#![doc = include_str!("../README.md")] use std::{cell::UnsafeCell, marker::PhantomData, mem::MaybeUninit, ptr::NonNull}; /// Type-erased borrow of some unknown type chosen when constructing this type. @@ -249,13 +250,35 @@ impl<'a, T> From<&'a [T]> for ThinSlicePtr<'a, T> { } } -pub(crate) trait UnsafeCellDeref<'a, T> { +mod private { + use std::cell::UnsafeCell; + + pub trait SealedUnsafeCell {} + impl<'a, T> SealedUnsafeCell for &'a UnsafeCell {} +} + +/// Extension trait for helper methods on [`UnsafeCell`] +pub trait UnsafeCellDeref<'a, T>: private::SealedUnsafeCell { + /// # Safety + /// - The returned value must be unique and not alias any mutable or immutable references to the contents of the [`UnsafeCell`]. + /// - At all times, you must avoid data races. If multiple threads have access to the same [`UnsafeCell`], then any writes must have a proper happens-before relation to all other accesses or use atomics ([`UnsafeCell`] docs for reference). unsafe fn deref_mut(self) -> &'a mut T; + + /// # Safety + /// - For the lifetime `'a` of the returned value you must not construct a mutable reference to the contents of the [`UnsafeCell`]. + /// - At all times, you must avoid data races. If multiple threads have access to the same [`UnsafeCell`], then any writes must have a proper happens-before relation to all other accesses or use atomics ([`UnsafeCell`] docs for reference). unsafe fn deref(self) -> &'a T; + + /// Returns a copy of the contained value. + /// + /// # Safety + /// - The [`UnsafeCell`] must not currently have a mutable reference to its content. + /// - At all times, you must avoid data races. If multiple threads have access to the same [`UnsafeCell`], then any writes must have a proper happens-before relation to all other accesses or use atomics ([`UnsafeCell`] docs for reference). unsafe fn read(self) -> T where T: Copy; } + impl<'a, T> UnsafeCellDeref<'a, T> for &'a UnsafeCell { #[inline] unsafe fn deref_mut(self) -> &'a mut T { diff --git a/tools/publish.sh b/tools/publish.sh index 67b17c3ab0492..124e63d0dc726 100644 --- a/tools/publish.sh +++ b/tools/publish.sh @@ -1,6 +1,7 @@ # if crate A depends on crate B, B must come before A in this list crates=( bevy_utils + bevy_ptr bevy_macro_utils bevy_derive bevy_math